fonchain-fiee/pkg/utils/excel/jinja.go
2025-02-19 14:24:15 +08:00

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
}