├── .gitignore ├── README.md ├── docx ├── docx.go ├── escape.go └── xml_config.go ├── main.go ├── template.docx └── template_new.docx /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smarterlab-docx 2 | 3 | 1. Support docx template substitution 4 | 2. Support OOXML 5 | 3. Support ms-word2007 and above version 6 | 7 | 8 | ### DEMO ### 9 | 查看 main.go 10 | -------------------------------------------------------------------------------- /docx/docx.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "archive/zip" 5 | "bufio" 6 | "bytes" 7 | "encoding/xml" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | type ZipData interface { 18 | files() []*zip.File 19 | close() error 20 | } 21 | 22 | //Type for in memory zip files 23 | type ZipInMemory struct { 24 | data *zip.Reader 25 | } 26 | 27 | func (d ZipInMemory) files() []*zip.File { 28 | return d.data.File 29 | } 30 | 31 | //Since there is nothing to close for in memory, just nil the data and return nil 32 | func (d ZipInMemory) close() error { 33 | d.data = nil 34 | return nil 35 | } 36 | 37 | //Type for zip files read from disk 38 | type ZipFile struct { 39 | data *zip.ReadCloser 40 | } 41 | 42 | func (d ZipFile) files() []*zip.File { 43 | return d.data.File 44 | } 45 | 46 | func (d ZipFile) close() error { 47 | return d.data.Close() 48 | } 49 | 50 | type ReplaceDocx struct { 51 | ZipReader ZipData 52 | Content string 53 | } 54 | 55 | type Text struct { 56 | Words string `json:"word"` 57 | Color string `json:"color"` 58 | Size string `json:"size"` 59 | Isbold bool `json:"isbold"` 60 | IsCenter bool `json:"iscenter"` 61 | } 62 | 63 | type TableTHead struct { 64 | TData interface{} `json:"tdata"` 65 | TDW int `json:"tdw"` 66 | } 67 | 68 | //TableTD descripes every block of the table 69 | type TableTD struct { 70 | //TData refers block's element 71 | TData []interface{} `json:"tdata"` 72 | //TDBG refers block's background 73 | TDBG int `json:"tdbg"` 74 | TDW int `json:"tdw"` 75 | TDM int `json:"tdm"` // 0 - 无 1 - 开始 2 - 结束 76 | } 77 | 78 | //Table include table configuration. 79 | type Table struct { 80 | //Tbname is the name of the table 81 | Tbname string `json:"tbname"` 82 | //Text OR Image in the sanme line 83 | Inline bool `json:"inline"` 84 | //Table data except table head 85 | TableBody [][]*TableTD `json:"tablebody"` 86 | //Table head data 87 | TableHead []*TableTHead `json:"tablehead"` 88 | //Thcenter set table head center word 89 | Thcenter bool `json:"thcenter"` 90 | } 91 | 92 | func (r *ReplaceDocx) Editable() *Docx { 93 | return &Docx{ 94 | Files: r.ZipReader.files(), 95 | Content: r.Content, 96 | } 97 | } 98 | 99 | func (r *ReplaceDocx) Close() error { 100 | return r.ZipReader.close() 101 | } 102 | 103 | type Docx struct { 104 | Files []*zip.File 105 | Content string 106 | } 107 | 108 | func ReadDocxFromMemory(data io.ReaderAt, size int64) (*ReplaceDocx, error) { 109 | reader, err := zip.NewReader(data, size) 110 | if err != nil { 111 | return nil, err 112 | } 113 | zipData := ZipInMemory{data: reader} 114 | return ReadDocx(zipData) 115 | } 116 | 117 | func ReadDocxFile(path string) (*ReplaceDocx, error) { 118 | reader, err := zip.OpenReader(path) 119 | if err != nil { 120 | return nil, err 121 | } 122 | zipData := ZipFile{data: reader} 123 | return ReadDocx(zipData) 124 | } 125 | 126 | func ReadDocx(reader ZipData) (*ReplaceDocx, error) { 127 | content, err := readText(reader.files()) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | return &ReplaceDocx{ZipReader: reader, Content: content}, nil 133 | } 134 | 135 | func readText(files []*zip.File) (text string, err error) { 136 | var documentFile *zip.File 137 | documentFile, err = retrieveWordDoc(files) 138 | if err != nil { 139 | return text, err 140 | } 141 | var documentReader io.ReadCloser 142 | documentReader, err = documentFile.Open() 143 | if err != nil { 144 | return text, err 145 | } 146 | 147 | text, err = wordDocToString(documentReader) 148 | return 149 | } 150 | 151 | func wordDocToString(reader io.Reader) (string, error) { 152 | b, err := ioutil.ReadAll(reader) 153 | if err != nil { 154 | return "", err 155 | } 156 | return string(b), nil 157 | } 158 | 159 | func retrieveWordDoc(files []*zip.File) (file *zip.File, err error) { 160 | for _, f := range files { 161 | if f.Name == "word/document.xml" { 162 | file = f 163 | } 164 | } 165 | if file == nil { 166 | err = errors.New("document.xml file not found") 167 | } 168 | return 169 | } 170 | 171 | func (d *Docx) ReplaceRaw(oldString string, newString string, num int) { 172 | d.Content = strings.Replace(d.Content, fmt.Sprintf("{{%s}}", oldString), newString, num) 173 | } 174 | 175 | func (d *Docx) Replace(oldString string, newString string, num int) (err error) { 176 | oldString, err = encode(oldString) 177 | if err != nil { 178 | return err 179 | } 180 | newString, err = encode(newString) 181 | if err != nil { 182 | return err 183 | } 184 | d.Content = strings.Replace(d.Content, fmt.Sprintf("{{%s}}", oldString), newString, num) 185 | 186 | return nil 187 | } 188 | 189 | func (d *Docx) ReplaceXML(oldString string, newString string, num int) { 190 | d.Content = strings.Replace(d.Content, fmt.Sprintf("{{%s}}", oldString), newString, num) 191 | } 192 | 193 | //WriteTable ==表格的格式 194 | func (d *Docx) ReplaceTable(table *Table) error { 195 | XMLTable := bytes.Buffer{} 196 | inline := table.Inline 197 | tableBody := table.TableBody 198 | tableHead := table.TableHead 199 | var used bool 200 | used = false 201 | 202 | //handle TableHead :Split with TableBody 203 | if tableHead != nil { 204 | XMLTable.WriteString(XMLTableHead) 205 | XMLTable.WriteString(XMLTableGridBegin) 206 | for _, h := range tableHead { 207 | gcw := fmt.Sprintf(XMLTableGridCol, strconv.FormatInt(int64(h.TDW), 10)) 208 | XMLTable.WriteString(gcw) 209 | } 210 | XMLTable.WriteString(XMLTableGridEnd) 211 | 212 | XMLTable.WriteString(XMLTableHeadTR) 213 | for _, rowdata := range tableHead { 214 | thw := fmt.Sprintf(XMLHeadTableTDBegin, strconv.FormatInt(int64(rowdata.TDW), 10)) 215 | XMLTable.WriteString(thw) 216 | if table.Thcenter { 217 | XMLTable.WriteString(XMLHeadTableTDBegin2C) 218 | } else { 219 | XMLTable.WriteString(XMLHeadTableTDBegin2) 220 | } 221 | if text, ok := rowdata.TData.(*Text); ok { 222 | //not 223 | color := text.Color 224 | size := text.Size 225 | word := text.Words 226 | var data string 227 | if text.IsCenter { 228 | if text.Isbold { 229 | data = fmt.Sprintf(XMLHeadtableTDTextBC, color, size, size, word) 230 | } else { 231 | data = fmt.Sprintf(XMLHeadtableTDTextC, color, size, size, word) 232 | } 233 | } else { 234 | if text.Isbold { 235 | data = fmt.Sprintf(XMLHeadtableTDTextB, color, size, size, word) 236 | } else { 237 | data = fmt.Sprintf(XMLHeadtableTDText, color, size, size, word) 238 | } 239 | } 240 | XMLTable.WriteString(data) 241 | } 242 | XMLTable.WriteString(XMLIMGtail) 243 | XMLTable.WriteString(XMLHeadTableTDEnd) 244 | } 245 | XMLTable.WriteString(XMLTableEndTR) 246 | } else { 247 | XMLTable.WriteString(XMLTableNoHead) 248 | XMLTable.WriteString(XMLTableGridBegin) 249 | if len(tableBody) > 0 { 250 | for _, tb := range tableBody[0] { 251 | gcw := fmt.Sprintf(XMLTableGridCol, strconv.Itoa(tb.TDW)) 252 | XMLTable.WriteString(gcw) 253 | } 254 | XMLTable.WriteString(XMLTableGridEnd) 255 | } 256 | } 257 | //Generate formation 258 | for _, v := range tableBody { 259 | XMLTable.WriteString(XMLTableTR) 260 | for _, vv := range v { 261 | //td bg 262 | var td string 263 | if vv.TDM == 1 { 264 | td = fmt.Sprintf(XMLTableMergeSTD, strconv.FormatInt(int64(vv.TDW), 10), strconv.FormatInt(int64(vv.TDBG), 10)) 265 | } else if vv.TDM == 2 { 266 | td = fmt.Sprintf(XMLTableMergeCTD, strconv.FormatInt(int64(vv.TDW), 10), strconv.FormatInt(int64(vv.TDBG), 10)) 267 | } else { 268 | td = fmt.Sprintf(XMLTableTD, strconv.FormatInt(int64(vv.TDW), 10), strconv.FormatInt(int64(vv.TDBG), 10)) 269 | } 270 | XMLTable.WriteString(td) 271 | if vv.TDM < 2 { 272 | tds := 0 273 | for _, vvv := range vv.TData { 274 | table, ok := vvv.(*Table) 275 | if !inline && !ok { 276 | XMLTable.WriteString(XMLTableTD2) 277 | } 278 | if inline && !ok && tds == 0 { 279 | XMLTable.WriteString(XMLTableTD2) 280 | } 281 | //if td is a table 282 | if ok { 283 | //end with table 284 | used = true 285 | tablestr, err := replaceTableToBuffer(table) 286 | if err != nil { 287 | return err 288 | } 289 | XMLTable.WriteString(tablestr) 290 | // FIXME: magic operation 291 | XMLTable.WriteString(XMLMagicFooter) 292 | //image or text 293 | } else { 294 | if text, ko := vvv.(*Text); ko { 295 | if vv.TDM == 1 { 296 | text.IsCenter = true 297 | } 298 | if text.IsCenter { 299 | if text.Isbold { 300 | XMLTable.WriteString(XMLHeadtableTDTextBC) 301 | } else { 302 | XMLTable.WriteString(XMLHeadtableTDTextC) 303 | } 304 | } else { 305 | if text.Isbold { 306 | XMLTable.WriteString(XMLHeadtableTDTextB) 307 | } else { 308 | XMLTable.WriteString(XMLHeadtableTDText) 309 | } 310 | } 311 | } 312 | used = false 313 | if !inline { 314 | XMLTable.WriteString(XMLIMGtail) 315 | } 316 | } 317 | tds++ 318 | } 319 | } 320 | if inline && !used { 321 | XMLTable.WriteString(XMLIMGtail) 322 | } 323 | XMLTable.WriteString(XMLHeadTableTDEnd) 324 | } 325 | XMLTable.WriteString(XMLTableEndTR) 326 | } 327 | XMLTable.WriteString(XMLTableFooter) 328 | //serialization 329 | var rows []interface{} 330 | for _, row := range tableBody { 331 | for _, rowdata := range row { 332 | for _, rowEle := range rowdata.TData { 333 | if _, ok := rowEle.([][][]interface{}); !ok { 334 | if text, ok := rowEle.(*Text); ok { 335 | tColor := text.Color 336 | tSize := text.Size 337 | tWord := text.Words 338 | rows = append(rows, tColor, tSize, tSize, tWord) 339 | } 340 | } 341 | } 342 | } 343 | } 344 | 345 | //data fill in 346 | tabledata := fmt.Sprintf(XMLTable.String(), rows...) 347 | // fmt.Printf("table XML内容:%+v", tabledata) 348 | d.ReplaceXML(table.Tbname, tabledata, -1) 349 | return nil 350 | } 351 | 352 | func replaceTableToBuffer(table *Table) (string, error) { 353 | tableHead := table.TableHead 354 | tableBody := table.TableBody 355 | inline := table.Inline 356 | XMLTable := bytes.Buffer{} 357 | var Bused bool 358 | Bused = false 359 | //handle TableHead :Split with TableBody 360 | if tableHead != nil { 361 | //表格中的表格为无边框形式 362 | XMLTable.WriteString(XMLTableInTableHead) 363 | XMLTable.WriteString(XMLTableHeadTR) 364 | for _, rowdata := range tableHead { 365 | thw := fmt.Sprintf(XMLHeadTableTDBegin, strconv.FormatInt(int64(rowdata.TDW), 10)) 366 | XMLTable.WriteString(thw) 367 | if table.Thcenter { 368 | XMLTable.WriteString(XMLHeadTableTDBegin2C) 369 | } else { 370 | XMLTable.WriteString(XMLHeadTableTDBegin2) 371 | } 372 | if text, ok := rowdata.TData.(*Text); ok { 373 | color := text.Color 374 | size := text.Size 375 | word := text.Words 376 | var data string 377 | if text.IsCenter { 378 | if text.Isbold { 379 | data = fmt.Sprintf(XMLHeadtableTDTextBC, color, size, size, word) 380 | } else { 381 | data = fmt.Sprintf(XMLHeadtableTDTextC, color, size, size, word) 382 | } 383 | } else { 384 | if text.Isbold { 385 | data = fmt.Sprintf(XMLHeadtableTDTextB, color, size, size, word) 386 | } else { 387 | data = fmt.Sprintf(XMLHeadtableTDText, color, size, size, word) 388 | } 389 | } 390 | XMLTable.WriteString(data) 391 | } 392 | XMLTable.WriteString(XMLIMGtail) 393 | XMLTable.WriteString(XMLHeadTableTDEnd) 394 | } 395 | XMLTable.WriteString(XMLTableEndTR) 396 | } else { 397 | XMLTable.WriteString(XMLTableInTableNoHead) 398 | } 399 | 400 | //Generate formation 401 | for _, v := range tableBody { 402 | XMLTable.WriteString(XMLTableTR) 403 | 404 | for _, vv := range v { 405 | var ttd string 406 | if vv.TDM == 1 { 407 | ttd = fmt.Sprintf(XMLTableInTableMergeSTD, strconv.FormatInt(int64(vv.TDW), 10)) 408 | } else if vv.TDM == 2 { 409 | ttd = fmt.Sprintf(XMLTableInTableMergeCTD, strconv.FormatInt(int64(vv.TDW), 10)) 410 | } else { 411 | ttd = fmt.Sprintf(XMLTableInTableTD, strconv.FormatInt(int64(vv.TDW), 10)) 412 | } 413 | 414 | XMLTable.WriteString(ttd) 415 | if vv.TDM < 2 { 416 | tds := 0 417 | if inline { 418 | XMLTable.WriteString(XMLTableTD2) 419 | 420 | } 421 | for _, vvv := range vv.TData { 422 | table, ok := vvv.(*Table) 423 | if !inline && !ok { 424 | XMLTable.WriteString(XMLTableTD2) 425 | } 426 | if ok { 427 | Bused = true 428 | tablestr, err := replaceTableToBuffer(table) 429 | if err != nil { 430 | return "", err 431 | } 432 | XMLTable.WriteString(tablestr) 433 | XMLTable.WriteString(XMLMagicFooter) 434 | } else { 435 | if text, ko := vvv.(*Text); ko { 436 | if vv.TDM == 1 { 437 | text.IsCenter = true 438 | } 439 | if text.IsCenter { 440 | if text.Isbold { 441 | XMLTable.WriteString(XMLHeadtableTDTextBC) 442 | } else { 443 | XMLTable.WriteString(XMLHeadtableTDTextC) 444 | } 445 | } else { 446 | if text.Isbold { 447 | XMLTable.WriteString(XMLHeadtableTDTextB) 448 | } else { 449 | fmt.Printf("model:%+v", vv) 450 | XMLTable.WriteString(XMLHeadtableTDText) 451 | } 452 | } 453 | } 454 | //not end with table 455 | Bused = false 456 | var next bool 457 | if tds < len(vv.TData)-1 { 458 | _, next = vv.TData[tds+1].(*Table) 459 | } 460 | 461 | if !inline { 462 | XMLTable.WriteString(XMLIMGtail) 463 | } else if inline && next { 464 | XMLTable.WriteString(XMLIMGtail) 465 | } 466 | } 467 | tds++ 468 | } 469 | if inline && !Bused { 470 | XMLTable.WriteString(XMLIMGtail) 471 | } 472 | } 473 | XMLTable.WriteString(XMLHeadTableTDEnd) 474 | } 475 | XMLTable.WriteString(XMLTableEndTR) 476 | } 477 | XMLTable.WriteString(XMLTableFooter) 478 | //serialization 479 | var rows []interface{} 480 | 481 | for _, row := range tableBody { 482 | for _, rowdata := range row { 483 | for _, rowEle := range rowdata.TData { 484 | if _, ok := rowEle.([][][]interface{}); !ok { 485 | if text, ok := rowEle.(*Text); ok { 486 | tColor := text.Color 487 | tSize := text.Size 488 | tWord := text.Words 489 | rows = append(rows, tColor, tSize, tSize, tWord) 490 | } 491 | } 492 | } 493 | } 494 | } 495 | 496 | //data fill in 497 | tabledata := fmt.Sprintf(XMLTable.String(), rows...) 498 | 499 | return tabledata, nil 500 | } 501 | 502 | //NewTable create a table 503 | func NewTable(d *Docx, tbname string, inline bool, tableBody [][]*TableTD, tableHead []*TableTHead, headCenter bool) (*Table, error) { 504 | table := &Table{} 505 | table.Tbname = tbname 506 | table.Inline = inline 507 | table.TableBody = tableBody 508 | table.TableHead = tableHead 509 | table.Thcenter = headCenter 510 | err := d.ReplaceTable(table) 511 | return table, err 512 | } 513 | 514 | func NewTableTD(tdata []interface{}, tdproperty map[string]interface{}) *TableTD { 515 | Tabletd := &TableTD{ 516 | TData: tdata, 517 | TDBG: 0, 518 | TDW: 0, 519 | TDM: 0, 520 | } 521 | if tdproperty != nil { 522 | if tdproperty["tdbg"] != nil { 523 | Tabletd.TDBG = tdproperty["tdbg"].(int) 524 | } 525 | if tdproperty["tdw"] != nil { 526 | Tabletd.TDW = tdproperty["tdw"].(int) 527 | } 528 | if tdproperty["tdm"] != nil { 529 | Tabletd.TDM = tdproperty["tdm"].(int) 530 | } 531 | } 532 | return Tabletd 533 | } 534 | 535 | func NewText(words string) *Text { 536 | words, _ = encode(words) 537 | text := &Text{} 538 | text.Words = words 539 | text.Color = "000000" 540 | text.Size = "19" 541 | text.Isbold = false 542 | text.IsCenter = false 543 | return text 544 | } 545 | 546 | func (d *Docx) Write(ioWriter io.Writer) (err error) { 547 | w := zip.NewWriter(ioWriter) 548 | for _, file := range d.Files { 549 | var writer io.Writer 550 | var readCloser io.ReadCloser 551 | 552 | writer, err = w.Create(file.Name) 553 | if err != nil { 554 | return err 555 | } 556 | readCloser, err = file.Open() 557 | if err != nil { 558 | return err 559 | } 560 | if file.Name == "word/document.xml" { 561 | writer.Write([]byte(d.Content)) 562 | } else { 563 | writer.Write(streamToByte(readCloser)) 564 | } 565 | } 566 | w.Close() 567 | return 568 | } 569 | 570 | func (d *Docx) WriteToFile(path string) (err error) { 571 | var target *os.File 572 | target, err = os.Create(path) 573 | if err != nil { 574 | return 575 | } 576 | defer target.Close() 577 | err = d.Write(target) 578 | return 579 | } 580 | 581 | func streamToByte(stream io.Reader) []byte { 582 | buf := new(bytes.Buffer) 583 | buf.ReadFrom(stream) 584 | return buf.Bytes() 585 | } 586 | 587 | func encode(s string) (string, error) { 588 | var b bytes.Buffer 589 | enc := xml.NewEncoder(bufio.NewWriter(&b)) 590 | if err := enc.Encode(s); err != nil { 591 | return s, err 592 | } 593 | output := strings.Replace(b.String(), "", "", 1) // remove string tag 594 | output = strings.Replace(output, "", "", 1) 595 | output = strings.Replace(output, " ", "", -1) // \r\n => newline 596 | return output, nil 597 | } 598 | -------------------------------------------------------------------------------- /docx/escape.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | ) 7 | 8 | type writer interface { 9 | WriteString(string) (int, error) 10 | } 11 | 12 | const escapedChars = `&'<>"` 13 | 14 | func escape(w writer, s string) error { 15 | i := strings.IndexAny(s, escapedChars) 16 | for i != -1 { 17 | if _, err := w.WriteString(s[:i]); err != nil { 18 | return err 19 | } 20 | var esc string 21 | switch s[i] { 22 | case '&': 23 | esc = "&" 24 | case '\'': 25 | esc = "'" 26 | case '<': 27 | esc = "<" 28 | case '>': 29 | esc = ">" 30 | case '"': 31 | esc = """ 32 | default: 33 | panic("unrecognized escape character") 34 | } 35 | s = s[i+1:] 36 | if _, err := w.WriteString(esc); err != nil { 37 | return err 38 | } 39 | i = strings.IndexAny(s, escapedChars) 40 | } 41 | _, err := w.WriteString(s) 42 | return err 43 | } 44 | 45 | // Escape escapes special HTML characters. 46 | // 47 | // It can be used by helpers that return a SafeString and that need to escape some content by themselves. 48 | func Escape(s string) string { 49 | if strings.IndexAny(s, escapedChars) == -1 { 50 | return s 51 | } 52 | var buf bytes.Buffer 53 | escape(&buf, s) 54 | return buf.String() 55 | } 56 | -------------------------------------------------------------------------------- /docx/xml_config.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | const ( 4 | 5 | //XMLText == 正文 6 | XMLText = ` 7 | 8 | 9 | 10 | 11 | 12 | 13 | %s 14 | 15 | 16 | ` 17 | //XMLCenterText == 居中正文 18 | XMLCenterText = ` 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | %s 29 | 30 | 31 | ` 32 | //XMLCenterBoldText 居中粗体 33 | XMLCenterBoldText = ` 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | %s 46 | 47 | 48 | ` 49 | //XMLBoldText ==粗体 50 | XMLBoldText = ` 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | %s 59 | 60 | 61 | ` 62 | //XMLInlineText == 不换行的正文 63 | XMLInlineText = ` 64 | %s 65 | 66 | ` 67 | //XMLFontStyle defines fontStyle 68 | XMLFontStyle = ` 69 | 70 | 71 | 72 | 73 | ` 74 | //XMLTableHead ... 75 | XMLTableHead = ` 76 | 77 | 78 | 79 | 80 | ` 81 | //XMLTableNoHead == 没有表头的样式把table top line remove掉 82 | XMLTableNoHead = ` 83 | 84 | 85 | 86 | 87 | ` 88 | //XMLTableInTableHead == 表中表的样式头 89 | XMLTableInTableHead = ` 90 | 91 | 92 | 93 | 94 | 95 | ` 96 | //XMLTableInTableNoHead ... 97 | XMLTableInTableNoHead = ` 98 | 99 | 100 | 101 | 102 | 103 | ` 104 | //XMLTableTR ... 105 | XMLTableTR = ` 106 | ` 107 | //XMLTableHeadTR ... 108 | XMLTableHeadTR = ` 109 | ` 110 | //XMLTableTD ... 111 | XMLTableTD = ` 112 | 113 | 114 | 115 | 116 | ` 117 | // XMLTableMergeSTD ... 118 | XMLTableMergeSTD = ` 119 | 120 | 121 | 122 | 123 | 124 | 125 | ` 126 | // XMLTableMergeCTD ... 127 | XMLTableMergeCTD = ` 128 | 129 | 130 | 131 | 132 | 133 | 134 | ` 135 | 136 | //XMLTableInTableTD ... 137 | XMLTableInTableTD = ` 138 | 139 | 140 | 141 | ` 142 | // XMLTableInTableMergeSTD ... 143 | XMLTableInTableMergeSTD = ` 144 | 145 | 146 | 147 | 148 | 149 | ` 150 | // XMLTableInTableMergeCTD ... 151 | XMLTableInTableMergeCTD = ` 152 | 153 | 154 | 155 | 156 | 157 | ` 158 | 159 | //XMLTableTD2 ... 160 | XMLTableTD2 = ` 161 | 162 | 163 | 164 | 165 | 166 | 167 | ` 168 | //XMLHeadTableTDBegin ... 169 | XMLHeadTableTDBegin = ` 170 | 171 | 172 | 173 | ` 174 | //XMLHeadTableInTableTDBegin ... 175 | XMLHeadTableInTableTDBegin = ` 176 | 177 | 178 | 179 | ` 180 | //XMLHeadTableTDBegin2 ... 181 | XMLHeadTableTDBegin2 = ` 182 | 183 | 184 | 185 | ` 186 | 187 | //XMLHeadTableTDBegin2C ... 188 | XMLHeadTableTDBegin2C = ` 189 | 190 | 191 | 192 | ` 193 | 194 | //XMLHeadtableTDTextB ... 195 | XMLHeadtableTDTextB = ` 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | %s 204 | 205 | ` 206 | //XMLHeadtableTDTextC ... 207 | XMLHeadtableTDTextC = ` 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | %s 217 | 218 | ` 219 | //XMLHeadtableTDTextBC ... 220 | XMLHeadtableTDTextBC = ` 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | %s 231 | 232 | ` 233 | //XMLHeadtableTDText ... 234 | XMLHeadtableTDText = ` 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | %s 245 | 246 | ` 247 | 248 | // XMLTableGridBegin ... 249 | XMLTableGridBegin = ` 250 | ` 251 | 252 | // XMLTableGridCol ... 253 | XMLTableGridCol = ` 254 | ` 255 | 256 | // XMLTableGridEnd ... 257 | XMLTableGridEnd = ` 258 | 259 | ` 260 | 261 | //XMLHeadTableTDEnd ... 262 | XMLHeadTableTDEnd = ` 263 | 264 | ` 265 | //XMLTableEndTR ... 266 | XMLTableEndTR = ` 267 | ` 268 | //XMLMagicFooter HACK:I struggle for a long time,at last ,I find it is necessary,and don't konw why. 269 | XMLMagicFooter = ` 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | ` 281 | 282 | //XMLTableFooter ... 283 | XMLTableFooter = ` 284 | 285 | ` 286 | 287 | //XMLIMGtail ... 288 | XMLIMGtail = ` 289 | ` 290 | //XMLBr == 换行 291 | XMLBr = ` 292 | ` 293 | ) 294 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/CR903/smarterlab-docx/docx" 8 | ) 9 | 10 | func main() { 11 | 12 | r, err := docx.ReadDocxFile("template.docx") 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | docxObj := r.Editable() 18 | docxObj.Replace("DETECTNUMBER", "JC2018001", -1) 19 | 20 | // table1 21 | // row1TD1Key := docx.NewTableTD([]interface{}{docx.NewText("委托单位")}, map[string]interface{}{"tdbg": 1, "tdw": 2095}) 22 | // row1TD1Value := docx.NewTableTD([]interface{}{docx.NewText("立为科技")}, map[string]interface{}{"tdbg": 1, "tdw": 2095}) 23 | // row1TD2Key := docx.NewTableTD([]interface{}{docx.NewText("联系人")}, map[string]interface{}{"tdbg": 1, "tdw": 2095}) 24 | // row1TD2Value := docx.NewTableTD([]interface{}{docx.NewText("联系人")}, map[string]interface{}{"tdbg": 1, "tdw": 2095}) 25 | 26 | // row2TD1Key := docx.NewTableTD([]interface{}{docx.NewText("委托单位地址")}, nil) 27 | // row2TD1Value := docx.NewTableTD([]interface{}{docx.NewText("地址1")}, nil) 28 | // row2TD2Key := docx.NewTableTD([]interface{}{docx.NewText("电 话")}, nil) 29 | // row2TD2Value := docx.NewTableTD([]interface{}{docx.NewText("13600000000")}, nil) 30 | 31 | // row3TD1Key := docx.NewTableTD([]interface{}{docx.NewText("付款单位")}, nil) 32 | // row3TD1Value := docx.NewTableTD([]interface{}{docx.NewText("")}, nil) 33 | // row3TD2Key := docx.NewTableTD([]interface{}{docx.NewText("税号")}, nil) 34 | // row3TD2Value := docx.NewTableTD([]interface{}{docx.NewText("")}, nil) 35 | 36 | // row4TD1Key := docx.NewTableTD([]interface{}{docx.NewText("受测单位")}, nil) 37 | // row4TD1Value := docx.NewTableTD([]interface{}{docx.NewText("垃圾场")}, nil) 38 | // row4TD2Key := docx.NewTableTD([]interface{}{docx.NewText("联系人")}, nil) 39 | // row4TD2Value := docx.NewTableTD([]interface{}{docx.NewText("赵六")}, nil) 40 | 41 | // row5TD1Key := docx.NewTableTD([]interface{}{docx.NewText("通讯地址")}, nil) 42 | // row5TD1Value := docx.NewTableTD([]interface{}{docx.NewText("地址4")}, nil) 43 | // row5TD2Key := docx.NewTableTD([]interface{}{docx.NewText("电 话")}, nil) 44 | // row5TD2Value := docx.NewTableTD([]interface{}{docx.NewText("13996966666")}, nil) 45 | 46 | // row6TD1Key := docx.NewTableTD([]interface{}{docx.NewText("样品名称")}, nil) 47 | // row6TD1Value := docx.NewTableTD([]interface{}{docx.NewText("水")}, nil) 48 | // row6TD2Key := docx.NewTableTD([]interface{}{docx.NewText("样品数量")}, nil) 49 | // row6TD2Value := docx.NewTableTD([]interface{}{docx.NewText("12.00")}, nil) 50 | 51 | // row7TD1Key := docx.NewTableTD([]interface{}{docx.NewText("样品名称")}, nil) 52 | // row7TD1Value := docx.NewTableTD([]interface{}{docx.NewText("气")}, nil) 53 | // row7TD2Key := docx.NewTableTD([]interface{}{docx.NewText("样品数量")}, nil) 54 | // row7TD2Value := docx.NewTableTD([]interface{}{docx.NewText("12.00")}, nil) 55 | 56 | // row8TD1Key := docx.NewTableTD([]interface{}{docx.NewText("检测项目")}, map[string]interface{}{"tdm": 1}) 57 | // row8TD1Value := docx.NewTableTD([]interface{}{docx.NewText("水:砷, ph")}, map[string]interface{}{"tdbg": 3}) 58 | 59 | // row81TD1Key := docx.NewTableTD([]interface{}{docx.NewText("")}, map[string]interface{}{"tdm": 2}) 60 | // row81TD1Value := docx.NewTableTD([]interface{}{docx.NewText("气:测试检测项, 四氯化碳")}, map[string]interface{}{"tdbg": 3}) 61 | 62 | // row82TD1Key := docx.NewTableTD([]interface{}{docx.NewText("")}, map[string]interface{}{"tdm": 2}) 63 | // row82TD1Value := docx.NewTableTD([]interface{}{docx.NewText("气:测试检测项, 四氯化碳")}, map[string]interface{}{"tdbg": 3}) 64 | 65 | // row9TD1Key := docx.NewTableTD([]interface{}{docx.NewText("检测标准/检测方法")}, nil) 66 | // row9TD1Value := docx.NewTableTD([]interface{}{docx.NewText("标准1/标准1, 标准1/标准1, 测试检测标准一号/测试检测标准一号, 标准1/标准1")}, map[string]interface{}{"tdbg": 3, "tdw": 0}) 67 | 68 | // row10TD1Key := docx.NewTableTD([]interface{}{docx.NewText("具体检测项目及执行标准/检测方法以检测项目附件单为准(需双方签字确认)")}, map[string]interface{}{"tdbg": 4, "tdw": 0}) 69 | 70 | // row11TD1Key := docx.NewTableTD([]interface{}{docx.NewText("服务类型")}, nil) 71 | // row11TD1Value := docx.NewTableTD([]interface{}{docx.NewText("☑ 标准服务:7个工作日"), docx.NewText("☐ 加急服务:3.5个工作日 加收100%附加费")}, map[string]interface{}{"tdbg": 3, "tdw": 0}) 72 | 73 | // row12TD1Key := docx.NewTableTD([]interface{}{docx.NewText("报告份数")}, nil) 74 | // row12TD1Value := docx.NewTableTD([]interface{}{docx.NewText("")}, nil) 75 | // row12TD2Key := docx.NewTableTD([]interface{}{docx.NewText("预计完成时间")}, nil) 76 | // row12TD2Value := docx.NewTableTD([]interface{}{docx.NewText("2018-10-31")}, nil) 77 | 78 | // row13TD1Key := docx.NewTableTD([]interface{}{docx.NewText("取报告方式")}, nil) 79 | // row13TD1Value := docx.NewTableTD([]interface{}{docx.NewText("☑ 自取"), docx.NewText("☐ 快递")}, nil) 80 | // row13TD2Key := docx.NewTableTD([]interface{}{docx.NewText("总费用")}, nil) 81 | // row13TD2Value := docx.NewTableTD([]interface{}{docx.NewText("200.000000")}, nil) 82 | 83 | // row14TD1Key := docx.NewTableTD([]interface{}{docx.NewText("说明:"), 84 | // docx.NewText("1)委托单位如对检测方法有特殊要求,请在执行标准/检测方法中详细说明。"), 85 | // docx.NewText("2)委托单位如对盖资质章有要求,请在备注中说明。"), 86 | // docx.NewText("3)若双方另有其他要求可附页说明。")}, map[string]interface{}{"tdbg": 4, "tdw": 0}) 87 | 88 | // row15TD1Key := docx.NewTableTD([]interface{}{docx.NewText("备注:"), 89 | // docx.NewText("")}, map[string]interface{}{"tdbg": 4, "tdw": 0}) 90 | 91 | // table := [][]*docx.TableTD{ 92 | // {row1TD1Key, row1TD1Value, row1TD2Key, row1TD2Value}, 93 | // {row2TD1Key, row2TD1Value, row2TD2Key, row2TD2Value}, 94 | // {row3TD1Key, row3TD1Value, row3TD2Key, row3TD2Value}, 95 | // {row4TD1Key, row4TD1Value, row4TD2Key, row4TD2Value}, 96 | // {row5TD1Key, row5TD1Value, row5TD2Key, row5TD2Value}, 97 | // {row6TD1Key, row6TD1Value, row6TD2Key, row6TD2Value}, 98 | // {row7TD1Key, row7TD1Value, row7TD2Key, row7TD2Value}, 99 | // {row8TD1Key, row8TD1Value}, 100 | // {row81TD1Key, row81TD1Value}, 101 | // {row82TD1Key, row82TD1Value}, 102 | // {row9TD1Key, row9TD1Value}, 103 | // {row10TD1Key}, 104 | // {row11TD1Key, row11TD1Value}, 105 | // {row12TD1Key, row12TD1Value, row12TD2Key, row12TD2Value}, 106 | // {row13TD1Key, row13TD1Value, row13TD2Key, row13TD2Value}, 107 | // {row14TD1Key}, 108 | // {row15TD1Key}, 109 | // } 110 | // _, err1 := docx.NewTable(docxObj, "DETECTCONTENT", false, table, nil, false) 111 | // if err1 != nil { 112 | // panic(err1) 113 | // } 114 | 115 | // Table2 116 | table := make([][]*docx.TableTD, 0) 117 | tableHead := []*docx.TableTHead{ 118 | &docx.TableTHead{TData: docx.NewText("样品编号"), TDW: 823}, 119 | &docx.TableTHead{TData: docx.NewText("样品名称"), TDW: 823}, 120 | &docx.TableTHead{TData: docx.NewText("检测项"), TDW: 823}, 121 | &docx.TableTHead{TData: docx.NewText("检测标准"), TDW: 823}, 122 | &docx.TableTHead{TData: docx.NewText("限值要求"), TDW: 823}, 123 | &docx.TableTHead{TData: docx.NewText("截止日期"), TDW: 823}, 124 | &docx.TableTHead{TData: docx.NewText("检验组"), TDW: 823}, 125 | &docx.TableTHead{TData: docx.NewText("检验人"), TDW: 823}, 126 | &docx.TableTHead{TData: docx.NewText("备注"), TDW: 823}, 127 | &docx.TableTHead{TData: docx.NewText("状态"), TDW: 823}, 128 | } 129 | 130 | for t := 1; t <= 10; t++ { 131 | 132 | var td = make([]*docx.TableTD, 0) 133 | for i := 1; i <= 10; i++ { 134 | tdValue := fmt.Sprintf("列:%s", strconv.Itoa(i)) 135 | row1TD1Key := docx.NewTableTD([]interface{}{docx.NewText(tdValue)}, map[string]interface{}{"tdbg": 1, "tdw": 823}) 136 | td = append(td, row1TD1Key) 137 | } 138 | table = append(table, td) 139 | } 140 | 141 | _, err1 := docx.NewTable(docxObj, "DETECTCONTENT", true, table, tableHead, true) 142 | if err1 != nil { 143 | panic(err1) 144 | } 145 | docxObj.WriteToFile("template_new.docx") 146 | r.Close() 147 | fmt.Println("Success") 148 | } 149 | -------------------------------------------------------------------------------- /template.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CR903/smarterlab-docx/a6e77e7daf438eee1fa75bc3801c188151c7ef01/template.docx -------------------------------------------------------------------------------- /template_new.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CR903/smarterlab-docx/a6e77e7daf438eee1fa75bc3801c188151c7ef01/template_new.docx --------------------------------------------------------------------------------