431 lines
12 KiB
Go
431 lines
12 KiB
Go
package excel
|
|
|
|
import (
|
|
"archive/zip"
|
|
"encoding/xml"
|
|
"fmt"
|
|
uuid "github.com/satori/go.uuid"
|
|
"io"
|
|
"io/fs"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var MonthMap = make(map[string]string)
|
|
|
|
type DefineTimeFormat struct {
|
|
//常规时间格式(日期带横杠)
|
|
Normal_YMDhms string
|
|
Normal_YMD string
|
|
Normal_hms string
|
|
//带斜杠的时间格式
|
|
Slash_YMDhms string
|
|
Slash_YMD string
|
|
//无间隔符
|
|
NoSpacer_YMDhms string
|
|
NoSpacer_YMD string
|
|
}
|
|
|
|
var TimeFormat DefineTimeFormat
|
|
var Loc *time.Location
|
|
|
|
type Relationships struct {
|
|
XMLName xml.Name `xml:"Relationships"`
|
|
Text string `xml:",chardata"`
|
|
Xmlns string `xml:"xmlns,attr"`
|
|
Relationship []Relationship
|
|
}
|
|
|
|
type Relationship struct {
|
|
Text string `xml:",chardata"`
|
|
ID string `xml:"Id,attr"`
|
|
Type string `xml:"Type,attr"`
|
|
Target string `xml:"Target,attr"`
|
|
}
|
|
|
|
var xmlImageDocumentRel string = "/word/_rels/document.xml.rels"
|
|
var xmlSuffix string = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`
|
|
var xmlDocument string = "/word/document.xml"
|
|
var imagePath string = "/word/media"
|
|
var zipPath1 string = "/_rels"
|
|
var zipPath2 string = "/customXml"
|
|
var zipPath3 string = "/docProps"
|
|
var zipPath4 string = "/word"
|
|
var zipPath5 string = "/[Content_Types].xml"
|
|
var deleteXmlDocument string = "/word/numbering.xml"
|
|
|
|
// 将文字渲染进docx中{{image}}
|
|
//
|
|
// 将图片渲染进docx中{{image}}
|
|
//
|
|
// 渲染for循环
|
|
// var srcPath string = "./report/template2/report.docx"
|
|
// var desPath string = "./report/report.docx"
|
|
// mapImage := make(map[string][]string, 0) 宽高单位为px
|
|
// mapImage["name"] = append(mapImage["name"], "./uploads/1.jpg?width=100&height=100")
|
|
// mapImage["reasons"] = append(mapImage["reasons"], "./uploads/2.jpg?width=100&height=100")
|
|
// mapImage["reasons"] = append(mapImage["reasons"], "./uploads/3.jpg?width=100&height=100")
|
|
func JinJa2(srcPath string, desPath string, repl map[string]string, mapImage map[string][]string, mapListData map[string][]map[string]string) (err error) {
|
|
|
|
// 渲染图片
|
|
CopyFile(srcPath, desPath)
|
|
zipPath := strings.Replace(desPath, ".docx", ".zip", 1)
|
|
dirPath := strings.Replace(desPath, ".docx", "", 1)
|
|
// 重命名为zip
|
|
CopyFile(desPath, zipPath)
|
|
// 解压
|
|
Unzip(zipPath, dirPath)
|
|
os.Remove(zipPath)
|
|
//删除
|
|
|
|
DeleteXmlDocument(dirPath + deleteXmlDocument)
|
|
// 修改xml文件
|
|
UpdateXml(dirPath, repl, mapImage, mapListData)
|
|
// 压缩为zip
|
|
_, err = os.Stat(dirPath + zipPath2)
|
|
if err == nil {
|
|
Zip(zipPath, dirPath+zipPath1, dirPath+zipPath2, dirPath+zipPath3, dirPath+zipPath4, dirPath+zipPath5)
|
|
} else {
|
|
Zip(zipPath, dirPath+zipPath1, dirPath+zipPath3, dirPath+zipPath4, dirPath+zipPath5)
|
|
}
|
|
|
|
// 重命名为docx
|
|
CopyFile(zipPath, desPath)
|
|
os.RemoveAll(dirPath)
|
|
os.Remove(zipPath)
|
|
return nil
|
|
|
|
}
|
|
|
|
func CopyFile(src string, dst string) (err error) {
|
|
content, err := ioutil.ReadFile(src)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
ioutil.WriteFile(dst, content, os.ModePerm)
|
|
return nil
|
|
}
|
|
|
|
func Unzip(zipPath, dstDir string) error {
|
|
// open zip file
|
|
reader, err := zip.OpenReader(zipPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer reader.Close()
|
|
for _, file := range reader.File {
|
|
if err := unzipFile(file, dstDir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func unzipFile(file *zip.File, dstDir string) error {
|
|
filePath := path.Join(dstDir, file.Name)
|
|
if file.FileInfo().IsDir() {
|
|
if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
rc, err := file.Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rc.Close()
|
|
|
|
w, err := os.Create(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer w.Close()
|
|
|
|
_, err = io.Copy(w, rc)
|
|
return err
|
|
}
|
|
|
|
func Zip(zipPath string, paths ...string) error {
|
|
if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
archive, err := os.Create(zipPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer archive.Close()
|
|
|
|
zipWriter := zip.NewWriter(archive)
|
|
defer zipWriter.Close()
|
|
|
|
for _, srcPath := range paths {
|
|
srcPath = strings.TrimSuffix(srcPath, string(os.PathSeparator))
|
|
err = filepath.Walk(srcPath, func(path string, info fs.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
header, err := zip.FileInfoHeader(info)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
header.Method = zip.Deflate
|
|
header.Name, err = filepath.Rel(filepath.Dir(srcPath), path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
header.Name += string(os.PathSeparator)
|
|
}
|
|
headerWriter, err := zipWriter.CreateHeader(header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
_, err = io.Copy(headerWriter, f)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetPicTag(ids []string, names []string) (picTags string) {
|
|
for seq, v := range ids {
|
|
cx := 629285
|
|
cy := 368300
|
|
vvalue := strings.Replace(v, "rId", "", 1)
|
|
nameParams := strings.Split(names[seq], "?")
|
|
var picTag string = `<w:drawing>
|
|
<wp:inline distT="0" distB="0" distL="0" distR="0">
|
|
<wp:extent cx="629285" cy="368300"/>
|
|
<wp:effectExtent l="0" t="0" r="0" b="0"/>
|
|
<wp:docPr id="" name="" descr=""/>
|
|
<wp:cNvGraphicFramePr>
|
|
<a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/>
|
|
</wp:cNvGraphicFramePr>
|
|
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
|
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
|
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
|
<pic:nvPicPr>
|
|
<pic:cNvPr id="" name="" descr=""/>
|
|
<pic:cNvPicPr>
|
|
<a:picLocks noChangeAspect="1"/>
|
|
</pic:cNvPicPr>
|
|
</pic:nvPicPr>
|
|
<pic:blipFill>
|
|
<a:blip r:embed=""/>
|
|
<a:stretch>
|
|
<a:fillRect/>
|
|
</a:stretch>
|
|
</pic:blipFill>
|
|
<pic:spPr>
|
|
<a:xfrm>
|
|
<a:off x="0" y="0"/>
|
|
<a:ext cx="629285" cy="368300"/>
|
|
</a:xfrm>
|
|
<a:prstGeom prst="rect">
|
|
<a:avLst/>
|
|
</a:prstGeom>
|
|
</pic:spPr>
|
|
</pic:pic>
|
|
</a:graphicData>
|
|
</a:graphic>
|
|
</wp:inline>
|
|
</w:drawing>`
|
|
uuid, _ := uuid.NewV4()
|
|
vuuid := uuid.String()
|
|
picTag = strings.Replace(picTag, `<wp:docPr id="" name="" descr=""/>`, fmt.Sprintf(`<wp:docPr id="%v" name="%v" descr="%v"/>`, fmt.Sprintf("%v%v", vvalue, vuuid), nameParams[0], nameParams[0]), 1)
|
|
picTag = strings.Replace(picTag, `<pic:cNvPr id="" name="" descr=""/>`, fmt.Sprintf(`<pic:cNvPr id="%v" name="%v" descr="%v"/>`, fmt.Sprintf("%v%v", vvalue, vuuid), nameParams[0], nameParams[0]), 1)
|
|
picTag = strings.Replace(picTag, `<a:blip r:embed=""/>`, fmt.Sprintf(`<a:blip r:embed="%v"/>`, v), 1)
|
|
if len(nameParams) > 1 {
|
|
u, _ := url.Parse(nameParams[1])
|
|
m, _ := url.ParseQuery(u.Path)
|
|
cx, _ = strconv.Atoi(m["width"][0])
|
|
cy, _ = strconv.Atoi(m["height"][0])
|
|
cx = cx * 9525
|
|
cy = cy * 9525
|
|
picTag = strings.Replace(picTag, `<wp:extent cx="629285" cy="368300"/>`, fmt.Sprintf(`<wp:extent cx="%v" cy="%v"/>`, cx, cy), 1)
|
|
picTag = strings.Replace(picTag, `<a:ext cx="629285" cy="368300"/>`, fmt.Sprintf(`<a:ext cx="%v" cy="%v"/>`, cx, cy), 1)
|
|
}
|
|
picTags = picTags + picTag
|
|
}
|
|
return
|
|
}
|
|
|
|
func UpdateRelXml(path string, content string) {
|
|
os.Remove(path)
|
|
xmlfile1, _ := os.Create(path)
|
|
defer xmlfile1.Close()
|
|
xmlfile1.WriteString(content)
|
|
}
|
|
|
|
func UpdateDocumentXml(path string, content string) {
|
|
os.Remove(path)
|
|
xmlfile2, _ := os.Create(path) //打开文件
|
|
defer xmlfile2.Close()
|
|
xmlfile2.WriteString(content)
|
|
}
|
|
|
|
func UpdateXml(dirPath string, repl map[string]string, mapImage map[string][]string, mapListData map[string][]map[string]string) {
|
|
xmlDoc, err := ioutil.ReadFile(dirPath + xmlDocument)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
xmlDocContent := string(xmlDoc)
|
|
if len(repl) > 0 {
|
|
for k, v := range repl {
|
|
v = strings.ReplaceAll(v, "\r\n", "<w:br/>")
|
|
v = strings.ReplaceAll(v, "\n", "<w:br/>")
|
|
v = strings.ReplaceAll(v, "\r", "<w:br/>")
|
|
xmlDocContent = strings.ReplaceAll(xmlDocContent, fmt.Sprintf("{{%v}}", k), fmt.Sprintf("%v", v))
|
|
}
|
|
}
|
|
if len(mapImage) > 0 {
|
|
// 修改rel文件
|
|
xmlRel, err := ioutil.ReadFile(dirPath + xmlImageDocumentRel)
|
|
list := Relationships{}
|
|
xml.Unmarshal([]byte(xmlRel), &list)
|
|
for k1, v1 := range mapImage {
|
|
list_rId := []string{}
|
|
list_rName := []string{}
|
|
for _, v2 := range v1 {
|
|
// 将图片移动到docx中
|
|
lf := strings.Split(v2, "/")
|
|
lfParms := strings.Split(lf[len(lf)-1], "?")
|
|
v2s := strings.Split(v2, "?")
|
|
CopyFile(v2s[0], dirPath+imagePath+"/"+lfParms[0])
|
|
rId := fmt.Sprintf("rId%v", len(list.Relationship)+1)
|
|
list.Relationship = append(list.Relationship, Relationship{
|
|
ID: rId,
|
|
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
Target: fmt.Sprintf("media/%v", lfParms[0]),
|
|
})
|
|
list_rId = append(list_rId, rId)
|
|
list_rName = append(list_rName, lf[len(lf)-1])
|
|
}
|
|
xmlDocContent = strings.ReplaceAll(xmlDocContent, fmt.Sprintf("<w:t>{{%v}}</w:t>", k1), GetPicTag(list_rId, list_rName))
|
|
}
|
|
xmlContent, err := xml.Marshal(list)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
UpdateRelXml(dirPath+xmlImageDocumentRel, xmlSuffix+string(xmlContent))
|
|
}
|
|
if len(mapListData) > 0 {
|
|
xmlDocContent = RenderForTag(mapListData, xmlDocContent)
|
|
}
|
|
|
|
UpdateDocumentXml(dirPath+xmlDocument, xmlDocContent)
|
|
}
|
|
|
|
func RenderForTag(mapFor map[string][]map[string]string, xmlDocContent string) string {
|
|
compileIndex2 := regexp.MustCompile(fmt.Sprintf(`{%%endfor%%}`)).FindAllStringIndex(xmlDocContent, -1)
|
|
compileIndex3 := regexp.MustCompile(fmt.Sprintf("<w:tr>")).FindAllStringIndex(xmlDocContent, -1)
|
|
compileIndex4 := regexp.MustCompile(fmt.Sprintf("</w:tr>")).FindAllStringIndex(xmlDocContent, -1)
|
|
fmt.Println(compileIndex3, compileIndex4)
|
|
contentList := []string{}
|
|
contentReplaceList := []string{}
|
|
for k, v := range mapFor {
|
|
compileIndex := regexp.MustCompile(fmt.Sprintf(`{%%for v in %v%%}`, k)).FindAllStringIndex(xmlDocContent, -1)
|
|
compileIndex2Copy := [][]int{}
|
|
for _, vc2 := range compileIndex2 {
|
|
if vc2[0] > compileIndex[0][1] {
|
|
compileIndex2Copy = append(compileIndex2Copy, vc2)
|
|
break
|
|
}
|
|
}
|
|
if len(compileIndex3) > 0 && len(compileIndex4) > 0 {
|
|
startIndex := compileIndex3[0][0]
|
|
endIndex := compileIndex4[0][0]
|
|
start2Index := compileIndex3[0][0]
|
|
end2Index := compileIndex4[0][0]
|
|
for _, v2 := range compileIndex {
|
|
for s21, v21 := range compileIndex3 {
|
|
if v21[0] > v2[0] && s21 > 0 {
|
|
startIndex = compileIndex3[s21-1][0]
|
|
start2Index = compileIndex3[s21][0]
|
|
break
|
|
}
|
|
}
|
|
for s22, v22 := range compileIndex4 {
|
|
if v22[0] > compileIndex2Copy[0][0] && s22 > 0 {
|
|
endIndex = compileIndex4[s22][1]
|
|
end2Index = compileIndex4[s22-1][1]
|
|
break
|
|
}
|
|
}
|
|
tableContent := xmlDocContent[startIndex:endIndex]
|
|
// fmt.Println(tableContent)
|
|
// fmt.Println(table2Content)
|
|
table2ReplaceContent := ""
|
|
contentList = append(contentList, tableContent)
|
|
for _, v22 := range v {
|
|
table2Content := xmlDocContent[start2Index:end2Index]
|
|
for k221, v221 := range v22 {
|
|
table2Content = strings.ReplaceAll(table2Content, fmt.Sprintf("{{v.%v}}", k221), v221)
|
|
}
|
|
table2ReplaceContent = table2ReplaceContent + table2Content
|
|
}
|
|
fmt.Println(table2ReplaceContent)
|
|
contentReplaceList = append(contentReplaceList, table2ReplaceContent)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
for seq, _ := range contentList {
|
|
xmlDocContent = strings.Replace(xmlDocContent, contentList[seq], contentReplaceList[seq], 1)
|
|
}
|
|
return xmlDocContent
|
|
}
|
|
|
|
func Decimal(value float64) float64 {
|
|
value, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", value), 64)
|
|
return value
|
|
}
|
|
|
|
// Find 判断字符串是否存在数组或切片
|
|
func Find(slice []string, val string) (int, bool) {
|
|
for i, item := range slice {
|
|
if item == val {
|
|
return i, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
func StringTimeToFormat(str_time string) (str_time2 string) {
|
|
lista := strings.Split(str_time, "T")
|
|
listb := strings.Split(lista[1], ".")
|
|
str_time2 = lista[0] + " " + listb[0]
|
|
return
|
|
}
|
|
func DeleteXmlDocument(destDocument string) error {
|
|
_, err := os.Stat(destDocument)
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
} else {
|
|
os.Remove(destDocument)
|
|
}
|
|
return nil
|
|
}
|