├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── common ├── constants │ ├── constants.go │ └── conversion.go └── units │ └── units.go ├── dml ├── anchor.go ├── anchor_test.go ├── cNvGraphicFramePr.go ├── cNvGraphicFramePr_test.go ├── dmlct │ ├── cNvPr.go │ ├── cNvPr_test.go │ ├── doc.go │ ├── optBoolElem.go │ ├── optBoolElem_test.go │ ├── pstvSz2D.go │ ├── pstvSz2D_test.go │ ├── pt2D.go │ ├── pt2D_test.go │ ├── relativeRect.go │ └── relativeRect_test.go ├── dmlpic │ ├── blip.go │ ├── blipFill.go │ ├── blipFill_test.go │ ├── blip_test.go │ ├── cNvPicPr.go │ ├── cNvPicPr_test.go │ ├── doc.go │ ├── pic.go │ ├── pic_test.go │ ├── spPr.go │ └── spPr_test.go ├── dmlprops │ ├── picLocks.go │ └── picLocks_test.go ├── dmlst │ ├── doc.go │ ├── optbool.go │ ├── rectAlignment.go │ ├── rectAlignment_test.go │ ├── relfrom.go │ ├── relfrom_test.go │ ├── tileFlipMode.go │ ├── tileFlipMode_test.go │ ├── wraptext.go │ └── wraptext_test.go ├── doc.go ├── docPr.go ├── docPr_test.go ├── drawing.go ├── drawing_test.go ├── effectExtent.go ├── effectExtent_test.go ├── extLst.go ├── geom │ ├── avList.go │ ├── avList_test.go │ ├── doc.go │ ├── gd.go │ └── gd_test.go ├── graphic.go ├── graphicFrameLocks.go ├── graphic_test.go ├── inline.go ├── inline_test.go ├── position.go ├── position_test.go ├── shapes │ ├── stretch.go │ ├── stretch_test.go │ ├── tile.go │ └── tile_test.go ├── wrap.go └── wrap_test.go ├── document.go ├── docx ├── background.go ├── background_test.go ├── body.go ├── contentType.go ├── contentType_test.go ├── core.go ├── doc.go ├── document.go ├── document_test.go ├── docx_test.go ├── heading.go ├── hyperlink.go ├── link.go ├── paragraph.go ├── paragraph_test.go ├── pic.go ├── props.go ├── rels.go ├── root.go ├── run.go ├── styles.go ├── table.go └── writer.go ├── go.mod ├── go.sum ├── godocx.png ├── internal ├── fileops.go └── testhelper.go ├── packager ├── doc.go ├── packuri.go ├── rels.go ├── unpack.go └── zip.go ├── templates └── default.docx ├── testdata └── test.docx └── wml ├── color └── color.go ├── ctypes ├── border.go ├── border_test.go ├── br.go ├── br_test.go ├── cell.go ├── cellBorder.go ├── cellBorder_test.go ├── cellMar.go ├── cellMar_test.go ├── cellMerge.go ├── cellMerge_test.go ├── cellProp.go ├── cellProp_test.go ├── cell_test.go ├── col.go ├── col_test.go ├── color.go ├── color_test.go ├── common.go ├── common_test.go ├── doc.go ├── docDefaults.go ├── docDefaults_test.go ├── docGrid.go ├── docGrid_test.go ├── eALayout.go ├── eALayout_test.go ├── effect.go ├── effect_test.go ├── expaComp.go ├── expaComp_test.go ├── fitText.go ├── fitText_test.go ├── fontSize.go ├── fontSize_test.go ├── footer.go ├── footer_test.go ├── framePr.go ├── framePr_test.go ├── grid.go ├── gridChg.go ├── gridChg_test.go ├── grid_test.go ├── header.go ├── header_test.go ├── indent.go ├── indent_test.go ├── lang.go ├── lang_test.go ├── latentStyle.go ├── latentStyle_test.go ├── lsdException.go ├── lsdException_test.go ├── numPr.go ├── numPr_test.go ├── onoff.go ├── onoff_test.go ├── pBdr.go ├── pBdr_test.go ├── pPr.go ├── pPr_test.go ├── pageMargin.go ├── pageMargin_test.go ├── pageNum.go ├── pageNum_test.go ├── pageSize.go ├── pageSize_test.go ├── pagesize_default.go ├── para.go ├── paragraph_test.go ├── rFonts.go ├── rFonts_test.go ├── rngMkElems.go ├── row.go ├── rowProp.go ├── rowProp_test.go ├── run.go ├── run_test.go ├── runprop.go ├── runprop_test.go ├── runstyle.go ├── runstyle_test.go ├── sectionProp.go ├── sectionProp_test.go ├── shd.go ├── shd_test.go ├── spacing.go ├── spacing_test.go ├── style.go ├── style_test.go ├── tab.go ├── tab_test.go ├── table.go ├── tblBorders.go ├── tblBorders_test.go ├── tblHeight.go ├── tblHeight_test.go ├── tblLayout.go ├── tblLayout_test.go ├── tblPr.go ├── tblPrEx.go ├── tblPr_test.go ├── tblStylePr.go ├── tblStylePr_test.go ├── tblWidth.go ├── tblWidth_test.go ├── tblpPr.go ├── tblpPr_test.go ├── text.go ├── text_test.go ├── trackChange.go ├── trackChange_test.go ├── trkchgnum.go ├── trkchgnum_test.go └── unit.go └── stypes ├── align.go ├── align_test.go ├── anchor.go ├── anchor_test.go ├── borderStyle.go ├── borderStyle_test.go ├── break.go ├── break_test.go ├── combBracket.go ├── combBracket_test.go ├── common.go ├── doc.go ├── docGridType.go ├── docGridType_test.go ├── dropCap.go ├── dropCap_test.go ├── em.go ├── em_test.go ├── fontTypeHint.go ├── fontTypeHint_test.go ├── hdrFtr.go ├── hdrFtr_test.go ├── heightRule.go ├── heightRule_test.go ├── hex.go ├── hex_test.go ├── jc.go ├── jc_test.go ├── lnSpacRule.go ├── lnSpacRule_test.go ├── merge.go ├── merge_test.go ├── numFmt.go ├── numFmt_test.go ├── onff_test.go ├── onoff.go ├── pageOrient.go ├── pageOrient_test.go ├── ptab.go ├── ptab_test.go ├── sectionMark.go ├── sectionMark_test.go ├── shd.go ├── shd_test.go ├── styleType.go ├── styleType_test.go ├── tabJc.go ├── tabJc_test.go ├── tabTlc.go ├── tabTlc_test.go ├── tblLayoutType.go ├── tblLayoutType_test.go ├── tblOverlap.go ├── tblOverlap_test.go ├── tblStyleOvrr.go ├── tblStyleOvrr_test.go ├── tblWidth.go ├── tblWidth_test.go ├── textAlign.go ├── textAlign_test.go ├── textDirection.go ├── textDirection_test.go ├── textEffect.go ├── textEffect_test.go ├── textScale.go ├── textScale_test.go ├── textboxTightWrap.go ├── textboxTightWrap_test.go ├── themeColor.go ├── themeColor_test.go ├── themeFont.go ├── themeFont_test.go ├── underline.go ├── underline_test.go ├── verJc.go ├── verJc_test.go ├── vertAlignRun.go ├── vertAlignRun_test.go ├── wrap.go └── wrap_test.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | 7 | --- 8 | 9 | ## Issue Description 10 | 11 | 12 | 13 | ## Steps to Reproduce 14 | 15 | 19 | 20 | 1. 21 | 2. 22 | 3. 23 | 24 | ## Expected Behavior 25 | 26 | 27 | 28 | ## Actual Behavior 29 | 30 | 31 | 32 | ## Environment Details 33 | 34 | 35 | 36 | - **GoDocx Library Version:** 37 | - **Go Version:** 38 | - **Operating System:** 39 | - [ ] Windows 40 | - [ ] macOS 41 | - [ ] Linux 42 | - **Word Processor Used:** 43 | - [ ] Microsoft Word 44 | - [ ] LibreOffice 45 | - [ ] Google Docs 46 | - [ ] Other (please specify) 47 | - **Word Processor Version:** 48 | 49 | ## Sample Code 50 | 51 | 54 | 55 | ```go 56 | // Your sample code here 57 | ``` 58 | 59 | ## Additional Information 60 | 63 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: [ "main" ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x] 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v4 24 | with: 25 | go-version: ${{ matrix.go-version }} 26 | 27 | - name: Install dependencies 28 | run: go get . 29 | 30 | - name: Test with Go 31 | run: go test -json ./... > TestResults-${{ matrix.go-version }}.json 32 | 33 | - name: Upload Go test results 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: Go-results-${{ matrix.go-version }} 37 | path: TestResults-${{ matrix.go-version }}.json 38 | 39 | - name: Upload coverage reports to Codecov 40 | uses: codecov/codecov-action@v4.0.1 41 | with: 42 | token: ${{ secrets.CODECOV_TOKEN }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /bin/ 3 | .idea/ 4 | .vscode/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 gomutex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /common/units/units.go: -------------------------------------------------------------------------------- 1 | package units 2 | 3 | // Inch represents a dimension in inches. 4 | type Inch float64 5 | 6 | // Emu represents a dimension in English Metric Units (EMUs). 7 | type Emu int64 8 | 9 | // ToEmu converts inches to EMUs. 10 | func (i Inch) ToEmu() Emu { 11 | return Emu(i * 914400) 12 | } 13 | -------------------------------------------------------------------------------- /dml/cNvGraphicFramePr.go: -------------------------------------------------------------------------------- 1 | package dml 2 | 3 | import ( 4 | "encoding/xml" 5 | ) 6 | 7 | type NonVisualGraphicFrameProp struct { 8 | GraphicFrameLocks *GraphicFrameLocks `xml:"graphicFrameLocks,omitempty"` 9 | } 10 | 11 | func (n NonVisualGraphicFrameProp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 12 | start.Name.Local = "wp:cNvGraphicFramePr" 13 | start.Attr = []xml.Attr{} 14 | 15 | err := e.EncodeToken(start) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | if n.GraphicFrameLocks != nil { 21 | if err := e.EncodeElement(n.GraphicFrameLocks, xml.StartElement{Name: xml.Name{Local: "a:graphicFrameLocks"}}); err != nil { 22 | return err 23 | } 24 | } 25 | 26 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 27 | } 28 | -------------------------------------------------------------------------------- /dml/dmlct/cNvPr.go: -------------------------------------------------------------------------------- 1 | package dmlct 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // Non-Visual Drawing Properties 9 | type CNvPr struct { 10 | ID uint `xml:"id,attr,omitempty"` 11 | Name string `xml:"name,attr,omitempty"` 12 | 13 | //Alternative Text for Object - Default value is "". 14 | Description string `xml:"descr,attr,omitempty"` 15 | 16 | // Hidden - Default value is "false". 17 | Hidden *bool `xml:"hidden,attr,omitempty"` 18 | 19 | //TODO: implement child elements 20 | // Sequence [1..1] 21 | // a:hlinkClick [0..1] Drawing Element On Click Hyperlink 22 | // a:hlinkHover [0..1] Hyperlink for Hover 23 | // a:extLst [0..1] Extension List 24 | } 25 | 26 | func NewNonVisProp(id uint, name string) *CNvPr { 27 | return &CNvPr{ 28 | ID: id, 29 | Name: name, 30 | } 31 | } 32 | 33 | func (c CNvPr) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 34 | // ! NOTE: Disabling the empty name check for the Picture 35 | // since popular docx tools allow them 36 | // if c.Name == "" { 37 | // return fmt.Errorf("invalid Name for Non-Visual Drawing Properties when marshaling") 38 | // } 39 | 40 | start.Attr = []xml.Attr{ 41 | {Name: xml.Name{Local: "id"}, Value: strconv.FormatUint(uint64(c.ID), 10)}, 42 | {Name: xml.Name{Local: "name"}, Value: c.Name}, 43 | } 44 | 45 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "descr"}, Value: c.Description}) 46 | 47 | if c.Hidden != nil { 48 | if *c.Hidden { 49 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "hidden"}, Value: "true"}) 50 | } else { 51 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "hidden"}, Value: "false"}) 52 | } 53 | } 54 | 55 | err := e.EncodeToken(start) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 61 | } 62 | -------------------------------------------------------------------------------- /dml/dmlct/doc.go: -------------------------------------------------------------------------------- 1 | // Package dmlct provides complex types related to DrawingML (Drawing Markup Language), 2 | // which is part of the Office Open XML (OOXML) standard. DrawingML is used to represent 3 | // graphics and drawings in WordprocessingML, SpreadsheetML, and PresentationML documents. 4 | package dmlct 5 | -------------------------------------------------------------------------------- /dml/dmlct/optBoolElem.go: -------------------------------------------------------------------------------- 1 | package dmlct 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/dml/dmlst" 7 | ) 8 | 9 | // Optional Bool Element: Helper element that only has one attribute which is optional 10 | type OptBoolElem struct { 11 | Val dmlst.OptBool 12 | } 13 | 14 | func NewOptBoolElem(value bool) *OptBoolElem { 15 | return &OptBoolElem{ 16 | Val: dmlst.NewOptBool(value), 17 | } 18 | } 19 | 20 | // Disable sets the value to false and valexists true 21 | func (n *OptBoolElem) Disable() { 22 | n.Val = dmlst.NewOptBool(false) 23 | } 24 | 25 | // MarshalXML implements the xml.Marshaler interface for the Bold type. 26 | // It encodes the instance into XML using the "w:XMLName" element with a "w:val" attribute. 27 | func (n OptBoolElem) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 28 | 29 | if n.Val.Valid { // Add val attribute only if the val exists 30 | if n.Val.Bool { 31 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: "true"}) 32 | } else { 33 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: "false"}) 34 | } 35 | } 36 | 37 | err := e.EncodeToken(start) 38 | if err != nil { 39 | return err 40 | } 41 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 42 | 43 | // return e.EncodeElement("", start) 44 | } 45 | 46 | // UnmarshalXML implements the xml.Unmarshaler interface for the type. 47 | // It decodes the XML representation, extracting the value from the "w:val" attribute. 48 | // The inner content of the XML element is skipped. 49 | func (n *OptBoolElem) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 50 | for _, a := range start.Attr { 51 | if a.Name.Local == "val" { 52 | // If value is "true", then set it to true 53 | n.Val = dmlst.NewOptBool(a.Value == "true") 54 | break 55 | } 56 | } 57 | 58 | return d.Skip() // Skipping the inner content 59 | } 60 | -------------------------------------------------------------------------------- /dml/dmlct/optBoolElem_test.go: -------------------------------------------------------------------------------- 1 | package dmlct 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/dml/dmlst" 9 | ) 10 | 11 | func TestOptBoolElem_MarshalXML(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | input OptBoolElem 15 | expected string 16 | }{ 17 | { 18 | name: "Valid true", 19 | input: OptBoolElem{Val: dmlst.NewOptBool(true)}, 20 | expected: ``, 21 | }, 22 | { 23 | name: "Valid false", 24 | input: OptBoolElem{Val: dmlst.NewOptBool(false)}, 25 | expected: ``, 26 | }, 27 | { 28 | name: "Invalid", 29 | input: OptBoolElem{Val: dmlst.OptBool{Valid: false}}, 30 | expected: ``, 31 | }, 32 | } 33 | 34 | for _, tt := range tests { 35 | t.Run(tt.name, func(t *testing.T) { 36 | var result strings.Builder 37 | encoder := xml.NewEncoder(&result) 38 | start := xml.StartElement{Name: xml.Name{Local: "w:b"}} 39 | 40 | err := tt.input.MarshalXML(encoder, start) 41 | if err != nil { 42 | t.Fatalf("Error marshaling XML: %v", err) 43 | } 44 | 45 | // Finalize encoding 46 | encoder.Flush() 47 | 48 | if result.String() != tt.expected { 49 | t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) 50 | } 51 | }) 52 | } 53 | } 54 | 55 | func TestOptBoolElem_UnmarshalXML(t *testing.T) { 56 | tests := []struct { 57 | name string 58 | inputXML string 59 | expected OptBoolElem 60 | }{ 61 | { 62 | name: "Valid true", 63 | inputXML: ``, 64 | expected: OptBoolElem{Val: dmlst.NewOptBool(true)}, 65 | }, 66 | { 67 | name: "Valid false", 68 | inputXML: ``, 69 | expected: OptBoolElem{Val: dmlst.NewOptBool(false)}, 70 | }, 71 | { 72 | name: "Invalid", 73 | inputXML: `>`, 74 | expected: OptBoolElem{Val: dmlst.OptBool{Valid: false}}, 75 | }, 76 | } 77 | 78 | for _, tt := range tests { 79 | t.Run(tt.name, func(t *testing.T) { 80 | var result OptBoolElem 81 | 82 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 83 | if err != nil { 84 | t.Fatalf("Error unmarshaling XML: %v", err) 85 | } 86 | 87 | if result.Val.Valid != tt.expected.Val.Valid || result.Val.Bool != tt.expected.Val.Bool { 88 | t.Errorf("Expected Val %v but got %v", tt.expected.Val, result.Val) 89 | } 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /dml/dmlct/pstvSz2D.go: -------------------------------------------------------------------------------- 1 | package dmlct 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | 7 | "github.com/gomutex/godocx/common/units" 8 | ) 9 | 10 | // Complex Type: CT_PositiveSize2D 11 | type PSize2D struct { 12 | Width uint64 `xml:"cx,attr,omitempty"` 13 | Height uint64 `xml:"cy,attr,omitempty"` 14 | } 15 | 16 | func NewPostvSz2D(width units.Emu, height units.Emu) *PSize2D { 17 | return &PSize2D{ 18 | Height: uint64(height), 19 | Width: uint64(width), 20 | } 21 | } 22 | 23 | func (p PSize2D) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 24 | 25 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "cx"}, Value: strconv.FormatUint(p.Width, 10)}) 26 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "cy"}, Value: strconv.FormatUint(p.Height, 10)}) 27 | 28 | return e.EncodeElement("", start) 29 | } 30 | -------------------------------------------------------------------------------- /dml/dmlct/pt2D.go: -------------------------------------------------------------------------------- 1 | package dmlct 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // Wrapping Polygon Point2D 9 | type Point2D struct { 10 | XAxis uint64 `xml:"x,attr,omitempty"` 11 | YAxis uint64 `xml:"y,attr,omitempty"` 12 | } 13 | 14 | func NewPoint2D(x, y uint64) Point2D { 15 | return Point2D{ 16 | XAxis: uint64(x), 17 | YAxis: uint64(y), 18 | } 19 | } 20 | 21 | func (p Point2D) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 22 | 23 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "x"}, Value: strconv.FormatUint(p.XAxis, 10)}) 24 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "y"}, Value: strconv.FormatUint(p.YAxis, 10)}) 25 | 26 | return e.EncodeElement("", start) 27 | } 28 | -------------------------------------------------------------------------------- /dml/dmlct/pt2D_test.go: -------------------------------------------------------------------------------- 1 | package dmlct 2 | 3 | import ( 4 | "encoding/xml" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestNewPoint2D(t *testing.T) { 11 | x := uint64(100) 12 | y := uint64(200) 13 | start := NewPoint2D(x, y) 14 | 15 | if start.XAxis != x { 16 | t.Errorf("NewPoint2D() failed: Expected XAxis %d, got %d", x, start.XAxis) 17 | } 18 | 19 | if start.YAxis != y { 20 | t.Errorf("NewPoint2D() failed: Expected YAxis %d, got %d", y, start.YAxis) 21 | } 22 | } 23 | 24 | func TestMarshalPoint2D(t *testing.T) { 25 | start := &Point2D{ 26 | XAxis: 100, 27 | YAxis: 200, 28 | } 29 | 30 | var result strings.Builder 31 | encoder := xml.NewEncoder(&result) 32 | 33 | startElement := xml.StartElement{Name: xml.Name{Local: "wp:start"}} 34 | err := start.MarshalXML(encoder, startElement) 35 | if err != nil { 36 | t.Fatalf("MarshalXML() failed: %v", err) 37 | } 38 | 39 | err = encoder.Flush() 40 | if err != nil { 41 | t.Fatalf("Flush() failed: %v", err) 42 | } 43 | 44 | expectedXML := `` 45 | if result.String() != expectedXML { 46 | t.Errorf("MarshalXML() failed: Expected XML:\n%s\nBut got:\n%s", expectedXML, result.String()) 47 | } 48 | } 49 | 50 | func TestUnmarshalPoint2D(t *testing.T) { 51 | tests := []struct { 52 | inputXML string 53 | expectedPoint2D Point2D 54 | }{ 55 | { 56 | inputXML: ``, 57 | expectedPoint2D: Point2D{ 58 | XAxis: 100, 59 | YAxis: 200, 60 | }, 61 | }, 62 | } 63 | 64 | for _, tt := range tests { 65 | t.Run(tt.inputXML, func(t *testing.T) { 66 | var start Point2D 67 | 68 | err := xml.Unmarshal([]byte(tt.inputXML), &start) 69 | if err != nil { 70 | t.Fatalf("Unmarshal() failed: %v", err) 71 | } 72 | 73 | if !reflect.DeepEqual(start, tt.expectedPoint2D) { 74 | t.Errorf("Unmarshal() failed: Expected %+v, got %+v", tt.expectedPoint2D, start) 75 | } 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /dml/dmlct/relativeRect.go: -------------------------------------------------------------------------------- 1 | package dmlct 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // RelativeRect represents a Relative Rectangle structure with abbreviated attributes. 9 | type RelativeRect struct { 10 | Top *int `xml:"t,attr,omitempty"` // Top margin 11 | Left *int `xml:"l,attr,omitempty"` // Left margin 12 | Bottom *int `xml:"b,attr,omitempty"` // Bottom margin 13 | Right *int `xml:"r,attr,omitempty"` // Right margin 14 | } 15 | 16 | // MarshalXML implements the xml.Marshaler interface for RelativeRect. 17 | func (r RelativeRect) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 18 | start.Attr = []xml.Attr{} 19 | 20 | if r.Top != nil { 21 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "t"}, Value: strconv.Itoa(*r.Top)}) 22 | } 23 | 24 | if r.Left != nil { 25 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "l"}, Value: strconv.Itoa(*r.Left)}) 26 | } 27 | 28 | if r.Bottom != nil { 29 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "b"}, Value: strconv.Itoa(*r.Bottom)}) 30 | } 31 | 32 | if r.Right != nil { 33 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "r"}, Value: strconv.Itoa(*r.Right)}) 34 | } 35 | 36 | return e.EncodeElement("", start) 37 | } 38 | -------------------------------------------------------------------------------- /dml/dmlpic/blip.go: -------------------------------------------------------------------------------- 1 | package dmlpic 2 | 3 | import "encoding/xml" 4 | 5 | // Binary large image or picture 6 | type Blip struct { 7 | EmbedID string `xml:"embed,attr,omitempty"` 8 | } 9 | 10 | func (b Blip) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 11 | start.Name.Local = "a:blip" 12 | 13 | start.Attr = []xml.Attr{ 14 | {Name: xml.Name{Local: "r:embed"}, Value: b.EmbedID}, 15 | } 16 | 17 | err := e.EncodeToken(start) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 23 | } 24 | -------------------------------------------------------------------------------- /dml/dmlpic/blipFill.go: -------------------------------------------------------------------------------- 1 | package dmlpic 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | 7 | "github.com/gomutex/godocx/dml/dmlct" 8 | "github.com/gomutex/godocx/dml/shapes" 9 | ) 10 | 11 | type BlipFill struct { 12 | // 1. Blip 13 | Blip *Blip `xml:"blip,omitempty"` 14 | 15 | //2.Source Rectangle 16 | SrcRect *dmlct.RelativeRect `xml:"srcRect,omitempty"` 17 | 18 | // 3. Choice of a:EG_FillModeProperties 19 | FillModeProps FillModeProps `xml:",any"` 20 | 21 | //Attributes: 22 | DPI *uint32 `xml:"dpi,attr,omitempty"` //DPI Setting 23 | RotWithShape *bool `xml:"rotWithShape,attr,omitempty"` //Rotate With Shape 24 | } 25 | 26 | // NewBlipFill creates a new BlipFill with the given relationship ID (rID) 27 | // The rID is used to reference the image in the presentation. 28 | func NewBlipFill(rID string) BlipFill { 29 | return BlipFill{ 30 | Blip: &Blip{ 31 | EmbedID: rID, 32 | }, 33 | } 34 | } 35 | 36 | func (b BlipFill) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 37 | start.Name.Local = "pic:blipFill" 38 | 39 | if b.DPI != nil { 40 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "dpi"}, Value: fmt.Sprintf("%d", *b.DPI)}) 41 | } 42 | 43 | if b.RotWithShape != nil { 44 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "rotWithShape"}, Value: fmt.Sprintf("%t", *b.RotWithShape)}) 45 | } 46 | 47 | err := e.EncodeToken(start) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | // 1. Blip 53 | if b.Blip != nil { 54 | if err := b.Blip.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "a:blip"}}); err != nil { 55 | return err 56 | } 57 | } 58 | 59 | // 2. SrcRect 60 | if b.SrcRect != nil { 61 | if err = b.SrcRect.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "a:SrcRect"}}); err != nil { 62 | return err 63 | } 64 | } 65 | 66 | // 3. Choice: FillModProperties 67 | if err = b.FillModeProps.MarshalXML(e, xml.StartElement{}); err != nil { 68 | return err 69 | } 70 | 71 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 72 | } 73 | 74 | type FillModeProps struct { 75 | Stretch *shapes.Stretch `xml:"stretch,omitempty"` 76 | Tile *shapes.Tile `xml:"tile,omitempty"` 77 | } 78 | 79 | func (f FillModeProps) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 80 | 81 | if f.Stretch != nil { 82 | return f.Stretch.MarshalXML(e, xml.StartElement{}) 83 | } 84 | 85 | if f.Tile != nil { 86 | return f.Tile.MarshalXML(e, xml.StartElement{}) 87 | } 88 | 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /dml/dmlpic/blipFill_test.go: -------------------------------------------------------------------------------- 1 | package dmlpic 2 | -------------------------------------------------------------------------------- /dml/dmlpic/blip_test.go: -------------------------------------------------------------------------------- 1 | package dmlpic 2 | -------------------------------------------------------------------------------- /dml/dmlpic/doc.go: -------------------------------------------------------------------------------- 1 | // These elements encompass the definition of pictures within the DrawingML framework. While pictures are in many ways very similar to shapes they have specific properties that are unique in order to optimize for picture-specific scenarios. Some of these properties include Fill behavior, Border behavior and Resize behavior. 2 | package dmlpic 3 | -------------------------------------------------------------------------------- /dml/dmlpic/spPr.go: -------------------------------------------------------------------------------- 1 | package dmlpic 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | ) 7 | 8 | const ( 9 | BlackWhiteModeClr = "clr" 10 | BlackWhiteModeAuto = "auto" 11 | BlackWhiteModeGray = "gray" 12 | BlackWhiteModeLtGray = "ltGray" 13 | BlackWhiteModeInvGray = "invGray" 14 | BlackWhiteModeGrayWhite = "grayWhite" 15 | BlackWhiteModeBlackGray = "blackGray" 16 | BlackWhiteModeBlackWhite = "blackWhite" 17 | BlackWhiteModeBlack = "black" 18 | BlackWhiteModeWhite = "white" 19 | BlackWhiteModeHidden = "hidden" 20 | ) 21 | 22 | type PicShapeProp struct { 23 | // -- Attributes -- 24 | //Black and White Mode 25 | BwMode *string `xml:"bwMode,attr,omitempty"` 26 | 27 | // -- Child Elements -- 28 | //1.2D Transform for Individual Objects 29 | TransformGroup *TransformGroup `xml:"xfrm,omitempty"` 30 | 31 | // 2. Choice 32 | //TODO: Modify it as Geometry choice 33 | PresetGeometry *PresetGeometry `xml:"prstGeom,omitempty"` 34 | 35 | //TODO: Remaining sequcence of elements 36 | } 37 | 38 | type PicShapePropOption func(*PicShapeProp) 39 | 40 | func WithTransformGroup(options ...TFGroupOption) PicShapePropOption { 41 | return func(p *PicShapeProp) { 42 | p.TransformGroup = NewTransformGroup(options...) 43 | } 44 | } 45 | 46 | func WithPrstGeom(preset string) PicShapePropOption { 47 | return func(p *PicShapeProp) { 48 | p.PresetGeometry = NewPresetGeom(preset) 49 | } 50 | } 51 | 52 | func NewPicShapeProp(options ...PicShapePropOption) *PicShapeProp { 53 | p := &PicShapeProp{} 54 | 55 | for _, opt := range options { 56 | opt(p) 57 | } 58 | 59 | return p 60 | } 61 | 62 | func (p PicShapeProp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 63 | start.Name.Local = "pic:spPr" 64 | 65 | if p.BwMode != nil { 66 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "bwMode"}, Value: *p.BwMode}) 67 | } 68 | 69 | err := e.EncodeToken(start) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | //1. Transform 75 | if p.TransformGroup != nil { 76 | if err = p.TransformGroup.MarshalXML(e, xml.StartElement{ 77 | Name: xml.Name{Local: "a:xfrm"}, 78 | }); err != nil { 79 | return fmt.Errorf("marshalling TransformGroup: %w", err) 80 | } 81 | } 82 | 83 | //2. Geometry 84 | if p.PresetGeometry != nil { 85 | 86 | if err = p.PresetGeometry.MarshalXML(e, xml.StartElement{ 87 | Name: xml.Name{Local: "a:prstGeom"}, 88 | }); err != nil { 89 | return fmt.Errorf("marshalling PresetGeometry: %w", err) 90 | } 91 | } 92 | 93 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 94 | } 95 | -------------------------------------------------------------------------------- /dml/dmlst/doc.go: -------------------------------------------------------------------------------- 1 | // Package dml provides simple types used in DrawingML (Drawing Markup Language), 2 | // part of the Office Open XML (OOXML) standard for representing graphical elements 3 | // in documents. 4 | package dmlst 5 | -------------------------------------------------------------------------------- /dml/dmlst/optbool.go: -------------------------------------------------------------------------------- 1 | package dmlst 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // 9 | // Based on https://pkg.go.dev/database/sql#OptBool 10 | 11 | // OptBool represents a bool that may be null. 12 | // OptBool implements the [Scanner] interface so 13 | // it can be used as a scan destination, similar to [NullString]. 14 | type OptBool struct { 15 | Bool bool 16 | Valid bool // Valid is true if Bool is not NULL 17 | } 18 | 19 | func NewOptBool(value bool) OptBool { 20 | return OptBool{ 21 | Bool: value, 22 | Valid: true, 23 | } 24 | } 25 | 26 | // OptBoolFromStr creates a new OptBool instance from a string. 27 | // The function accepts a string parameter 'value' which can be either "true", "1", "false", or "0". 28 | // If the input string matches "true" or "1", the function sets the Bool field of the returned OptBool instance to true. 29 | // If the input string matches "false" or "0", the function sets the Bool field of the returned OptBool instance to false. 30 | // 31 | // Example usage: 32 | // 33 | // nBool := OptBoolFromStr("true") 34 | // fmt.Println(nBool.Bool) // Output: true 35 | // fmt.Println(nBool.Valid) // Output: true 36 | // 37 | // nBool = OptBoolFromStr("0") 38 | // fmt.Println(nBool.Bool) // Output: false 39 | // fmt.Println(nBool.Valid) // Output: true 40 | func OptBoolFromStr(value string) OptBool { 41 | nBool := OptBool{Valid: true} 42 | if value == "true" || value == "1" { 43 | nBool.Bool = true 44 | } 45 | 46 | return nBool 47 | } 48 | 49 | // ToIntFlag returns the integer representation of the OptBool. 50 | // If the Bool field is true, it returns 1. 51 | // If the Bool field is false, it also returns 0. 52 | // Note: This method does not consider the Valid field. You ensure to check Valid field before calling this if you want optional field 53 | func (n OptBool) ToIntFlag() int { 54 | if n.Bool { 55 | return 1 56 | } 57 | 58 | return 0 59 | } 60 | 61 | // ToIntFlag returns the string representation of the OptBool. 62 | // If the Bool field is true, it returns "1". 63 | // If the Bool field is false, it also returns "0". 64 | // Note: This method does not consider the Valid field. You ensure to check Valid field before calling this if you want optional field 65 | func (n OptBool) ToStringFlag() string { 66 | if n.Bool { 67 | return "1" 68 | } 69 | return "0" 70 | } 71 | 72 | func (n *OptBool) UnmarshalXMLAttr(attr xml.Attr) error { 73 | val, err := strconv.ParseBool(attr.Value) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | n.Bool = val 79 | n.Valid = true 80 | return nil 81 | 82 | } 83 | -------------------------------------------------------------------------------- /dml/dmlst/rectAlignment.go: -------------------------------------------------------------------------------- 1 | package dmlst 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type RectAlignment string 9 | 10 | const ( 11 | RectAlignmentTopLeft RectAlignment = "tl" // Rectangle Alignment Enum (Top Left) 12 | RectAlignmentTop RectAlignment = "t" // Rectangle Alignment Enum (Top) 13 | RectAlignmentTopRight RectAlignment = "tr" // Rectangle Alignment Enum (Top Right) 14 | RectAlignmentLeft RectAlignment = "l" // Rectangle Alignment Enum (Left) 15 | RectAlignmentCenter RectAlignment = "ctr" // Rectangle Alignment Enum (Center) 16 | RectAlignmentRight RectAlignment = "r" // Rectangle Alignment Enum (Right) 17 | RectAlignmentBottomLeft RectAlignment = "bl" // Rectangle Alignment Enum (Bottom Left) 18 | RectAlignmentBottom RectAlignment = "b" // Rectangle Alignment Enum (Bottom) 19 | RectAlignmentBottomRight RectAlignment = "br" // Rectangle Alignment Enum (Bottom Right) 20 | ) 21 | 22 | // RectAlignmentFromStr converts a string to RectAlignment type. 23 | func RectAlignmentFromStr(value string) (RectAlignment, error) { 24 | switch value { 25 | case "tl": 26 | return RectAlignmentTopLeft, nil 27 | case "t": 28 | return RectAlignmentTop, nil 29 | case "tr": 30 | return RectAlignmentTopRight, nil 31 | case "l": 32 | return RectAlignmentLeft, nil 33 | case "ctr": 34 | return RectAlignmentCenter, nil 35 | case "r": 36 | return RectAlignmentRight, nil 37 | case "bl": 38 | return RectAlignmentBottomLeft, nil 39 | case "b": 40 | return RectAlignmentBottom, nil 41 | case "br": 42 | return RectAlignmentBottomRight, nil 43 | default: 44 | return "", errors.New("Invalid RectAlignment value") 45 | } 46 | } 47 | 48 | // UnmarshalXMLAttr unmarshals XML attribute into RectAlignment. 49 | func (r *RectAlignment) UnmarshalXMLAttr(attr xml.Attr) error { 50 | val, err := RectAlignmentFromStr(attr.Value) 51 | if err != nil { 52 | return err 53 | } 54 | *r = val 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /dml/dmlst/tileFlipMode.go: -------------------------------------------------------------------------------- 1 | package dmlst 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // TileFlipMode represents tile flip mode values based on the schema. 9 | type TileFlipMode string 10 | 11 | // Constants representing valid TileFlipMode values as per the schema. 12 | const ( 13 | TileFlipModeNone TileFlipMode = "none" // Tile Flip Mode Enum (None) 14 | TileFlipModeHorizontal TileFlipMode = "x" // Tile Flip Mode Enum (Horizontal) 15 | TileFlipModeVertical TileFlipMode = "y" // Tile Flip Mode Enum (Vertical) 16 | TileFlipModeBoth TileFlipMode = "xy" // Tile Flip Mode Enum (Horizontal and Vertical) 17 | ) 18 | 19 | // TileFlipModeFromStr converts a string to TileFlipMode type. 20 | // Returns an error if the string does not match any valid TileFlipMode value. 21 | func TileFlipModeFromStr(value string) (TileFlipMode, error) { 22 | switch value { 23 | case "none": 24 | return TileFlipModeNone, nil 25 | case "x": 26 | return TileFlipModeHorizontal, nil 27 | case "y": 28 | return TileFlipModeVertical, nil 29 | case "xy": 30 | return TileFlipModeBoth, nil 31 | default: 32 | return "", errors.New("Invalid TileFlipMode value") 33 | } 34 | } 35 | 36 | // UnmarshalXMLAttr unmarshals XML attribute into TileFlipMode. 37 | // Implements the xml.UnmarshalerAttr interface. 38 | func (t *TileFlipMode) UnmarshalXMLAttr(attr xml.Attr) error { 39 | val, err := TileFlipModeFromStr(attr.Value) 40 | if err != nil { 41 | return err 42 | } 43 | *t = val 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /dml/dmlst/wraptext.go: -------------------------------------------------------------------------------- 1 | package dmlst 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // WrapText type 9 | type WrapText string 10 | 11 | // Constants for valid values 12 | const ( 13 | WrapTextBothSides WrapText = "bothSides" // Both Sides 14 | WrapTextLeft WrapText = "left" // Left Side Only 15 | WrapTextRight WrapText = "right" // Right Side Only 16 | WrapTextLargest WrapText = "largest" // Largest Side Only 17 | ) 18 | 19 | // WrapTextFromStr converts a string to WrapText type. 20 | func WrapTextFromStr(value string) (WrapText, error) { 21 | switch value { 22 | case "bothSides": 23 | return WrapTextBothSides, nil 24 | case "left": 25 | return WrapTextLeft, nil 26 | case "right": 27 | return WrapTextRight, nil 28 | case "largest": 29 | return WrapTextLargest, nil 30 | default: 31 | return "", errors.New("Invalid WrapText value") 32 | } 33 | } 34 | 35 | // UnmarshalXMLAttr unmarshals XML attribute into WrapText. 36 | func (w *WrapText) UnmarshalXMLAttr(attr xml.Attr) error { 37 | val, err := WrapTextFromStr(attr.Value) 38 | if err != nil { 39 | return err 40 | } 41 | *w = val 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /dml/doc.go: -------------------------------------------------------------------------------- 1 | // Package dml provides functionality for working with DrawingML (Drawing Markup Language) 2 | // which is part of the Office Open XML (OOXML) standard. DrawingML is used to represent 3 | // graphics and drawings in WordprocessingML, SpreadsheetML, and PresentationML documents. 4 | package dml 5 | -------------------------------------------------------------------------------- /dml/docPr.go: -------------------------------------------------------------------------------- 1 | package dml 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | type DocProp struct { 9 | ID uint64 `xml:"id,attr,omitempty"` 10 | Name string `xml:"name,attr,omitempty"` 11 | Description string `xml:"descr,attr,omitempty"` 12 | 13 | //TODO: Remaining attrs & child elements 14 | } 15 | 16 | func (d DocProp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 17 | start.Name.Local = "wp:docPr" 18 | start.Attr = []xml.Attr{ 19 | {Name: xml.Name{Local: "id"}, Value: strconv.FormatUint(d.ID, 10)}, 20 | {Name: xml.Name{Local: "name"}, Value: d.Name}, 21 | } 22 | 23 | if d.Description != "" { 24 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "descr"}, Value: d.Description}) 25 | } 26 | 27 | err := e.EncodeToken(start) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 33 | } 34 | -------------------------------------------------------------------------------- /dml/docPr_test.go: -------------------------------------------------------------------------------- 1 | package dml 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestMarshalDocProp(t *testing.T) { 10 | tests := []struct { 11 | docProp *DocProp 12 | expectedXML string 13 | }{ 14 | { 15 | docProp: &DocProp{ 16 | ID: 1, 17 | Name: "Document1", 18 | Description: "Description of Document1", 19 | }, 20 | expectedXML: ``, 21 | }, 22 | { 23 | docProp: &DocProp{ 24 | ID: 2, 25 | Name: "Document2", 26 | }, 27 | expectedXML: ``, 28 | }, 29 | } 30 | 31 | for _, tt := range tests { 32 | t.Run(tt.expectedXML, func(t *testing.T) { 33 | generatedXML, err := xml.Marshal(tt.docProp) 34 | if err != nil { 35 | t.Fatalf("Error marshaling XML: %v", err) 36 | } 37 | 38 | if strings.TrimSpace(string(generatedXML)) != tt.expectedXML { 39 | t.Errorf("Expected XML:\n%s\nBut got:\n%s", tt.expectedXML, generatedXML) 40 | } 41 | }) 42 | } 43 | } 44 | 45 | func TestUnmarshalDocProp(t *testing.T) { 46 | tests := []struct { 47 | inputXML string 48 | expectedDocProp DocProp 49 | }{ 50 | { 51 | inputXML: ``, 52 | expectedDocProp: DocProp{ 53 | ID: 1, 54 | Name: "Document1", 55 | Description: "Description of Document1", 56 | }, 57 | }, 58 | { 59 | inputXML: ``, 60 | expectedDocProp: DocProp{ 61 | ID: 2, 62 | Name: "Document2", 63 | }, 64 | }, 65 | } 66 | 67 | for _, tt := range tests { 68 | t.Run(tt.inputXML, func(t *testing.T) { 69 | var docProp DocProp 70 | 71 | err := xml.Unmarshal([]byte(tt.inputXML), &docProp) 72 | if err != nil { 73 | t.Fatalf("Error unmarshaling XML: %v", err) 74 | } 75 | 76 | if docProp.ID != tt.expectedDocProp.ID { 77 | t.Errorf("Expected ID %d, but got %d", tt.expectedDocProp.ID, docProp.ID) 78 | } 79 | if docProp.Name != tt.expectedDocProp.Name { 80 | t.Errorf("Expected Name %s, but got %s", tt.expectedDocProp.Name, docProp.Name) 81 | } 82 | if docProp.Description != tt.expectedDocProp.Description { 83 | t.Errorf("Expected Description %s, but got %s", tt.expectedDocProp.Description, docProp.Description) 84 | } 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /dml/drawing.go: -------------------------------------------------------------------------------- 1 | package dml 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/common/constants" 7 | ) 8 | 9 | type DrawingPositionType string 10 | 11 | const ( 12 | DrawingPositionAnchor DrawingPositionType = "wp:anchor" 13 | DrawingPositionInline DrawingPositionType = "wp:inline" 14 | ) 15 | 16 | type Drawing struct { 17 | Inline []Inline `xml:"inline,omitempty"` 18 | Anchor []*Anchor `xml:"anchor,omitempty"` 19 | } 20 | 21 | func (dr *Drawing) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 22 | loop: 23 | for { 24 | currentToken, err := d.Token() 25 | if err != nil { 26 | return err 27 | } 28 | 29 | switch elem := currentToken.(type) { 30 | case xml.StartElement: 31 | switch elem.Name { 32 | case xml.Name{Space: constants.WMLDrawingNS, Local: "anchor"}: 33 | ar := NewAnchor() 34 | if err = d.DecodeElement(ar, &elem); err != nil { 35 | return err 36 | } 37 | 38 | dr.Anchor = append(dr.Anchor, ar) 39 | case xml.Name{Space: constants.WMLDrawingNS, Local: "inline"}: 40 | il := Inline{} 41 | if err = d.DecodeElement(&il, &elem); err != nil { 42 | return err 43 | } 44 | 45 | dr.Inline = append(dr.Inline, il) 46 | default: 47 | if err = d.Skip(); err != nil { 48 | return err 49 | } 50 | } 51 | case xml.EndElement: 52 | break loop 53 | } 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (dr Drawing) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 60 | start.Name.Local = "w:drawing" 61 | 62 | err := e.EncodeToken(start) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | for _, data := range dr.Anchor { 68 | if err = data.MarshalXML(e, xml.StartElement{}); err != nil { 69 | return err 70 | } 71 | } 72 | 73 | for _, data := range dr.Inline { 74 | if err = data.MarshalXML(e, xml.StartElement{}); err != nil { 75 | return err 76 | } 77 | } 78 | 79 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 80 | } 81 | -------------------------------------------------------------------------------- /dml/effectExtent.go: -------------------------------------------------------------------------------- 1 | package dml 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | type EffectExtent struct { 9 | LeftEdge int64 `xml:"l,attr,omitempty"` 10 | TopEdge int64 `xml:"t,attr,omitempty"` 11 | RightEdge int64 `xml:"r,attr,omitempty"` 12 | BottomEdge int64 `xml:"b,attr,omitempty"` 13 | } 14 | 15 | func NewEffectExtent(left, top, right, bottom int64) *EffectExtent { 16 | return &EffectExtent{ 17 | LeftEdge: left, 18 | TopEdge: top, 19 | RightEdge: right, 20 | BottomEdge: bottom, 21 | } 22 | } 23 | 24 | func (x EffectExtent) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 25 | start.Name.Local = "wp:effectExtent" 26 | start.Attr = []xml.Attr{ 27 | {Name: xml.Name{Local: "l"}, Value: strconv.FormatInt(x.LeftEdge, 10)}, 28 | {Name: xml.Name{Local: "t"}, Value: strconv.FormatInt(x.TopEdge, 10)}, 29 | {Name: xml.Name{Local: "r"}, Value: strconv.FormatInt(x.RightEdge, 10)}, 30 | {Name: xml.Name{Local: "b"}, Value: strconv.FormatInt(x.BottomEdge, 10)}, 31 | } 32 | 33 | err := e.EncodeToken(start) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 39 | } 40 | 41 | /* 42 | func (x *EffectExtent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 43 | for _, a := range start.Attr { 44 | if a.Name.Local == "cx" { 45 | cx, err := strconv.ParseUint(a.Value, 10, 32) 46 | if err != nil { 47 | return nil 48 | } 49 | x.Width = cx 50 | } else if a.Name.Local == "cy" { 51 | cy, err := strconv.ParseUint(a.Value, 10, 32) 52 | if err != nil { 53 | return nil 54 | } 55 | x.Height = cy 56 | } 57 | } 58 | 59 | for { 60 | token, err := d.Token() 61 | if err != nil { 62 | return err 63 | } 64 | 65 | switch elem := token.(type) { 66 | case xml.StartElement: 67 | switch elem.Name.Local { 68 | 69 | default: 70 | if err = d.Skip(); err != nil { 71 | return err 72 | } 73 | } 74 | case xml.EndElement: 75 | if elem == start.End() { 76 | return nil 77 | } 78 | } 79 | } 80 | } */ 81 | -------------------------------------------------------------------------------- /dml/extLst.go: -------------------------------------------------------------------------------- 1 | package dml 2 | -------------------------------------------------------------------------------- /dml/geom/avList.go: -------------------------------------------------------------------------------- 1 | package geom 2 | 3 | import ( 4 | "encoding/xml" 5 | ) 6 | 7 | // List of Shape Adjust Values 8 | type AdjustValues struct { 9 | ShapeGuides []ShapeGuide `xml:"gd,omitempty"` 10 | } 11 | 12 | func (a AdjustValues) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { 13 | start.Name.Local = "a:avLst" 14 | 15 | err = e.EncodeToken(start) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | for _, data := range a.ShapeGuides { 21 | err := data.MarshalXML(e, xml.StartElement{}) 22 | if err != nil { 23 | return err 24 | } 25 | } 26 | 27 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 28 | } 29 | -------------------------------------------------------------------------------- /dml/geom/doc.go: -------------------------------------------------------------------------------- 1 | // The Shape Definitions and Attributes portion of the DrawingML framework deals with all geometric properties for shapes within a document. This includes both preset geometries that publicly are interpreted by the generating application and custom geometries that have their points and curves explicitly specified. In addition to the underlying geometry of the shape there are also other coordinate-based properties for each shape that this framework describes. 2 | 3 | package geom 4 | -------------------------------------------------------------------------------- /dml/geom/gd.go: -------------------------------------------------------------------------------- 1 | package geom 2 | 3 | import "encoding/xml" 4 | 5 | type ShapeGuide struct { 6 | Name string `xml:"name,attr,omitempty"` 7 | Formula string `xml:"fmla,attr,omitempty"` 8 | } 9 | 10 | func (s ShapeGuide) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 11 | start.Name.Local = "a:gd" 12 | start.Attr = []xml.Attr{ 13 | {Name: xml.Name{Local: "name"}, Value: s.Name}, 14 | {Name: xml.Name{Local: "fmla"}, Value: s.Formula}, 15 | } 16 | 17 | err := e.EncodeToken(start) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 23 | } 24 | -------------------------------------------------------------------------------- /dml/geom/gd_test.go: -------------------------------------------------------------------------------- 1 | package geom 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestShapeGuide_MarshalXML(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | input ShapeGuide 13 | expected string 14 | }{ 15 | { 16 | name: "With name and formula", 17 | input: ShapeGuide{ 18 | Name: "height", 19 | Formula: "val 100", 20 | }, 21 | expected: ``, 22 | }, 23 | { 24 | name: "Empty name and formula", 25 | input: ShapeGuide{ 26 | Name: "", 27 | Formula: "", 28 | }, 29 | expected: ``, 30 | }, 31 | } 32 | 33 | for _, tt := range tests { 34 | t.Run(tt.name, func(t *testing.T) { 35 | var result strings.Builder 36 | encoder := xml.NewEncoder(&result) 37 | start := xml.StartElement{Name: xml.Name{Local: "a:gd"}} 38 | 39 | err := tt.input.MarshalXML(encoder, start) 40 | if err != nil { 41 | t.Fatalf("Error marshaling XML: %v", err) 42 | } 43 | 44 | encoder.Flush() 45 | 46 | if result.String() != tt.expected { 47 | t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func TestShapeGuide_UnmarshalXML(t *testing.T) { 54 | tests := []struct { 55 | name string 56 | inputXML string 57 | expected ShapeGuide 58 | }{ 59 | { 60 | name: "With name and formula", 61 | inputXML: ``, 62 | expected: ShapeGuide{ 63 | Name: "height", 64 | Formula: "val 100", 65 | }, 66 | }, 67 | { 68 | name: "Empty name and formula", 69 | inputXML: ``, 70 | expected: ShapeGuide{ 71 | Name: "", 72 | Formula: "", 73 | }, 74 | }, 75 | } 76 | 77 | for _, tt := range tests { 78 | t.Run(tt.name, func(t *testing.T) { 79 | var result ShapeGuide 80 | 81 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 82 | if err != nil { 83 | t.Fatalf("Error unmarshaling XML: %v", err) 84 | } 85 | 86 | if result.Name != tt.expected.Name { 87 | t.Errorf("Expected Name %s but got %s", tt.expected.Name, result.Name) 88 | } 89 | if result.Formula != tt.expected.Formula { 90 | t.Errorf("Expected Formula %s but got %s", tt.expected.Formula, result.Formula) 91 | } 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /dml/graphic.go: -------------------------------------------------------------------------------- 1 | package dml 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/common/constants" 7 | "github.com/gomutex/godocx/dml/dmlpic" 8 | ) 9 | 10 | type Graphic struct { 11 | Data *GraphicData `xml:"graphicData,omitempty"` 12 | } 13 | 14 | func NewGraphic(data *GraphicData) *Graphic { 15 | return &Graphic{Data: data} 16 | } 17 | 18 | func DefaultGraphic() *Graphic { 19 | return &Graphic{} 20 | } 21 | 22 | type GraphicData struct { 23 | URI string `xml:"uri,attr,omitempty"` 24 | Pic *dmlpic.Pic `xml:"pic,omitempty"` 25 | } 26 | 27 | func NewPicGraphic(pic *dmlpic.Pic) *Graphic { 28 | return &Graphic{ 29 | Data: &GraphicData{ 30 | URI: constants.DrawingMLPicNS, 31 | Pic: pic, 32 | }, 33 | } 34 | } 35 | 36 | func (g Graphic) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 37 | start.Name.Local = "a:graphic" 38 | start.Attr = []xml.Attr{ 39 | {Name: xml.Name{Local: "xmlns:a"}, Value: constants.DrawingMLMainNS}, 40 | } 41 | 42 | err := e.EncodeToken(start) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | if g.Data != nil { 48 | if err = g.Data.MarshalXML(e, xml.StartElement{}); err != nil { 49 | return err 50 | } 51 | } 52 | 53 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 54 | } 55 | 56 | func (gd GraphicData) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 57 | start.Name.Local = "a:graphicData" 58 | start.Attr = []xml.Attr{ 59 | {Name: xml.Name{Local: "uri"}, Value: constants.DrawingMLPicNS}, 60 | } 61 | 62 | err := e.EncodeToken(start) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | if gd.Pic != nil { 68 | if err := e.EncodeElement(gd.Pic, xml.StartElement{Name: xml.Name{Local: "pic:pic"}}); err != nil { 69 | return err 70 | } 71 | } 72 | 73 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 74 | } 75 | -------------------------------------------------------------------------------- /dml/graphicFrameLocks.go: -------------------------------------------------------------------------------- 1 | package dml 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/common/constants" 7 | "github.com/gomutex/godocx/dml/dmlst" 8 | ) 9 | 10 | type GraphicFrameLocks struct { 11 | //Disallow Aspect Ratio Change 12 | NoChangeAspect dmlst.OptBool 13 | } 14 | 15 | func (g GraphicFrameLocks) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 16 | start.Name.Local = "a:graphicFrameLocks" 17 | 18 | start.Attr = []xml.Attr{ 19 | {Name: xml.Name{Local: "xmlns:a"}, Value: constants.DrawingMLMainNS}, 20 | } 21 | 22 | if g.NoChangeAspect.Valid { 23 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "noChangeAspect"}, Value: g.NoChangeAspect.ToStringFlag()}) 24 | } 25 | 26 | err := e.EncodeToken(start) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 32 | } 33 | 34 | func (g *GraphicFrameLocks) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error { 35 | for _, a := range start.Attr { 36 | if a.Name.Local == "noChangeAspect" { 37 | g.NoChangeAspect = dmlst.OptBoolFromStr(a.Value) 38 | } 39 | } 40 | 41 | return decoder.Skip() 42 | } 43 | -------------------------------------------------------------------------------- /dml/position.go: -------------------------------------------------------------------------------- 1 | package dml 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | 7 | "github.com/gomutex/godocx/dml/dmlst" 8 | ) 9 | 10 | type PoistionH struct { 11 | RelativeFrom dmlst.RelFromH `xml:"relativeFrom,attr"` 12 | PosOffset int `xml:"posOffset"` 13 | } 14 | 15 | type PoistionV struct { 16 | RelativeFrom dmlst.RelFromV `xml:"relativeFrom,attr"` 17 | PosOffset int `xml:"posOffset"` 18 | } 19 | 20 | func (p PoistionH) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 21 | 22 | if p.RelativeFrom == "" { 23 | return errors.New("Invalid RelativeFrom in PoistionH") 24 | } 25 | 26 | start.Name.Local = "wp:positionH" 27 | 28 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "relativeFrom"}, Value: string(p.RelativeFrom)}) 29 | 30 | err := e.EncodeToken(start) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | offsetElem := xml.StartElement{Name: xml.Name{Local: "wp:posOffset"}} 36 | if err = e.EncodeElement(p.PosOffset, offsetElem); err != nil { 37 | return err 38 | } 39 | 40 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 41 | } 42 | 43 | func (p PoistionV) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 44 | if p.RelativeFrom == "" { 45 | return errors.New("Invalid RelativeFrom in PoistionV") 46 | } 47 | 48 | start.Name.Local = "wp:positionV" 49 | 50 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "relativeFrom"}, Value: string(p.RelativeFrom)}) 51 | 52 | err := e.EncodeToken(start) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | offsetElem := xml.StartElement{Name: xml.Name{Local: "wp:posOffset"}} 58 | if err = e.EncodeElement(p.PosOffset, offsetElem); err != nil { 59 | return err 60 | } 61 | 62 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 63 | } 64 | -------------------------------------------------------------------------------- /dml/shapes/stretch.go: -------------------------------------------------------------------------------- 1 | package shapes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/dml/dmlct" 7 | ) 8 | 9 | type Stretch struct { 10 | FillRect *dmlct.RelativeRect `xml:"fillRect,omitempty"` 11 | } 12 | 13 | func (s Stretch) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 14 | start.Name.Local = "a:stretch" 15 | 16 | err := e.EncodeToken(start) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | if s.FillRect != nil { 22 | if err := s.FillRect.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "a:fillRect"}}); err != nil { 23 | return err 24 | } 25 | } 26 | 27 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 28 | } 29 | -------------------------------------------------------------------------------- /dml/shapes/stretch_test.go: -------------------------------------------------------------------------------- 1 | package shapes 2 | 3 | import ( 4 | "encoding/xml" 5 | "testing" 6 | 7 | "github.com/gomutex/godocx/dml/dmlct" 8 | ) 9 | 10 | func TestMarshalStretch(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | stretch *Stretch 14 | expectedXML string 15 | }{ 16 | { 17 | name: "With FillRect", 18 | stretch: &Stretch{FillRect: &dmlct.RelativeRect{}}, 19 | expectedXML: ``, 20 | }, 21 | { 22 | name: "Without FillRect", 23 | stretch: &Stretch{}, 24 | expectedXML: ``, 25 | }, 26 | } 27 | 28 | for _, tt := range tests { 29 | t.Run(tt.name, func(t *testing.T) { 30 | generatedXML, err := xml.Marshal(tt.stretch) 31 | if err != nil { 32 | t.Fatalf("Error marshaling XML: %v", err) 33 | } 34 | 35 | if string(generatedXML) != tt.expectedXML { 36 | t.Errorf("Expected XML:\n%s\nBut got:\n%s", tt.expectedXML, generatedXML) 37 | } 38 | }) 39 | } 40 | } 41 | 42 | func TestUnmarshalStretch(t *testing.T) { 43 | tests := []struct { 44 | name string 45 | inputXML string 46 | expectedResult Stretch 47 | }{ 48 | { 49 | name: "With FillRect", 50 | inputXML: ``, 51 | expectedResult: Stretch{ 52 | FillRect: &dmlct.RelativeRect{}, 53 | }, 54 | }, 55 | { 56 | name: "Without FillRect", 57 | inputXML: ``, 58 | expectedResult: Stretch{}, 59 | }, 60 | } 61 | 62 | for _, tt := range tests { 63 | t.Run(tt.name, func(t *testing.T) { 64 | var result Stretch 65 | 66 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 67 | if err != nil { 68 | t.Fatalf("Error unmarshaling XML: %v", err) 69 | } 70 | 71 | if (result.FillRect == nil) != (tt.expectedResult.FillRect == nil) { 72 | t.Errorf("Expected FillRect to be %v, but got %v", tt.expectedResult.FillRect, result.FillRect) 73 | } 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /dml/shapes/tile.go: -------------------------------------------------------------------------------- 1 | package shapes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | 7 | "github.com/gomutex/godocx/dml/dmlst" 8 | ) 9 | 10 | // Tile 11 | type Tile struct { 12 | Tx *int64 `xml:"tx,attr,omitempty"` // Horizontal Offset 13 | Ty *int64 `xml:"ty,attr,omitempty"` // Vertical Offset 14 | Sx *int `xml:"sx,attr,omitempty"` // Horizontal Ratio 15 | Sy *int `xml:"sy,attr,omitempty"` // Vertical Ratio 16 | Flip *dmlst.TileFlipMode `xml:"flip,attr,omitempty"` // Tile Flipping 17 | Algn *dmlst.RectAlignment `xml:"algn,attr,omitempty"` // Alignment 18 | } 19 | 20 | // MarshalXML marshals the Tile struct into XML. 21 | func (t Tile) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 22 | start.Name.Local = "a:tile" 23 | start.Attr = []xml.Attr{} 24 | 25 | if t.Tx != nil { 26 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "tx"}, Value: strconv.FormatInt(*t.Tx, 10)}) 27 | } 28 | if t.Ty != nil { 29 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "ty"}, Value: strconv.FormatInt(*t.Ty, 10)}) 30 | } 31 | if t.Sx != nil { 32 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "sx"}, Value: strconv.Itoa(*t.Sx)}) 33 | } 34 | if t.Sy != nil { 35 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "sy"}, Value: strconv.Itoa(*t.Sy)}) 36 | } 37 | if t.Flip != nil { 38 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "flip"}, Value: string(*t.Flip)}) 39 | } 40 | if t.Algn != nil { 41 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "algn"}, Value: string(*t.Algn)}) 42 | } 43 | 44 | err := e.EncodeToken(start) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 50 | } 51 | -------------------------------------------------------------------------------- /document.go: -------------------------------------------------------------------------------- 1 | package godocx 2 | 3 | import ( 4 | _ "embed" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/gomutex/godocx/docx" 9 | "github.com/gomutex/godocx/packager" 10 | ) 11 | 12 | //go:embed templates/default.docx 13 | var defaultDocx []byte 14 | 15 | // NewDocument creates a new document from the default template. 16 | func NewDocument() (*docx.RootDoc, error) { 17 | return packager.Unpack(&defaultDocx) 18 | } 19 | 20 | // OpenDocument opens a document from the given file name. 21 | func OpenDocument(fileName string) (*docx.RootDoc, error) { 22 | docxContent, err := os.ReadFile(filepath.Clean(fileName)) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return packager.Unpack(&docxContent) 27 | } 28 | -------------------------------------------------------------------------------- /docx/background.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // Specifies the background information for this document 10 | // 11 | // This background shall be displayed on all pages of the document, behind all other document content. 12 | type Background struct { 13 | Color *string `xml:"color,attr,omitempty"` 14 | ThemeColor *stypes.ThemeColor `xml:"themeColor,attr,omitempty"` 15 | ThemeTint *string `xml:"themeTint,attr,omitempty"` 16 | ThemeShade *string `xml:"themeShade,attr,omitempty"` 17 | } 18 | 19 | func NewBackground() *Background { 20 | return &Background{} 21 | } 22 | func (b Background) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 23 | start.Name.Local = "w:background" 24 | if b.Color != nil { 25 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:color"}, Value: *b.Color}) 26 | } 27 | if b.ThemeColor != nil { 28 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:themeColor"}, Value: string(*b.ThemeColor)}) 29 | } 30 | if b.ThemeTint != nil { 31 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:themeTint"}, Value: *b.ThemeTint}) 32 | } 33 | if b.ThemeShade != nil { 34 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:themeShade"}, Value: *b.ThemeShade}) 35 | } 36 | if err := e.EncodeToken(start); err != nil { 37 | return err 38 | } 39 | return e.EncodeToken(start.End()) 40 | } 41 | -------------------------------------------------------------------------------- /docx/core.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/common/constants" 7 | ) 8 | 9 | // docxDcTerms represents an XML element with text content and an xsi:type attribute. 10 | // Core Document Properties 11 | type docxDcTerms struct { 12 | Text string `xml:",chardata"` 13 | Type string `xml:"xsi:type,attr"` 14 | } 15 | 16 | // Core Document Properties 17 | type decodeDcTerms struct { 18 | Text string `xml:",chardata"` 19 | Type string `xml:"xsi:type,attr"` 20 | } 21 | 22 | // The function adds an XML header to the encoded data. 23 | func marshal(data any) ([]byte, error) { 24 | body, err := xml.Marshal(data) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | out := append(constants.XMLHeader, body...) 30 | return out, nil 31 | } 32 | -------------------------------------------------------------------------------- /docx/doc.go: -------------------------------------------------------------------------------- 1 | // Package docx provides a comprehensive set of functions and structures 2 | // for manipulating DOCX documents. It allows for the creation, modification, 3 | // and retrieval of document elements such as paragraphs, styles, and images. 4 | // The package is designed to be accessed through the RootDoc element or 5 | // instances of inner elements, providing a flexible and intuitive API for 6 | // working with Office Open XML (OOXML) documents. 7 | // 8 | // // The RootDoc structure is initialized from the main godocx package, 9 | // which provides methods for creating a new document from a default template or 10 | // opening an existing document. 11 | package docx 12 | -------------------------------------------------------------------------------- /docx/document_test.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/wml/stypes" 9 | ) 10 | 11 | func TestDocument_MarshalXML(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | input Document 15 | expected []string 16 | }{ 17 | { 18 | name: "With Background and Body", 19 | input: Document{ 20 | Background: &Background{ 21 | Color: StringPtr("FF0000"), 22 | ThemeColor: ThemeColorPtr(stypes.ThemeColorAccent1), 23 | ThemeTint: StringPtr("500"), 24 | ThemeShade: StringPtr("200"), 25 | }, 26 | Body: &Body{}, 27 | }, 28 | expected: []string{ 29 | `xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"`, 30 | ``, 31 | ``, 32 | `xmlns:o="urn:schemas-microsoft-com:office:office"`, 33 | `xmlns:v="urn:schemas-microsoft-com:vml"`, 34 | `xmlns:w10="urn:schemas-microsoft-com:office:word"`, 35 | `xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"`, 36 | `xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"`, 37 | `xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"`, 38 | `xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"`, 39 | `xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"`, 40 | `xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"`, 41 | `xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"`, 42 | `mc:Ignorable="w14 wp14 w15"`, 43 | }, 44 | }, 45 | } 46 | 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | var result strings.Builder 50 | encoder := xml.NewEncoder(&result) 51 | start := xml.StartElement{Name: xml.Name{Local: "w:document"}} 52 | 53 | err := tt.input.MarshalXML(encoder, start) 54 | if err != nil { 55 | t.Fatalf("Error marshaling XML: %v", err) 56 | } 57 | 58 | encoder.Flush() 59 | actual := result.String() 60 | 61 | for _, exp := range tt.expected { 62 | if !strings.Contains(actual, exp) { 63 | t.Errorf("Expected XML part not found in actual XML:\nExpected part: %s\nActual XML: %s", exp, actual) 64 | } 65 | } 66 | }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docx/docx_test.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/gomutex/godocx/wml/ctypes" 8 | ) 9 | 10 | func setupRootDoc(t *testing.T) *RootDoc { 11 | log.Println("setting root doc") 12 | 13 | return &RootDoc{ 14 | Path: "/tmp/test", 15 | RootRels: Relationships{}, 16 | ContentType: ContentTypes{}, 17 | Document: &Document{ 18 | Body: &Body{}, 19 | }, 20 | DocStyles: &ctypes.Styles{}, 21 | rID: 1, 22 | ImageCount: 1, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docx/heading.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/gomutex/godocx/wml/ctypes" 8 | ) 9 | 10 | // Return a heading paragraph newly added to the end of the document. 11 | // The heading paragraph will contain text and have its paragraph style determined by level. 12 | // If level is 0, the style is set to Title. 13 | // The style is set to Heading {level}. 14 | // if level is outside the range 0-9, error will be returned 15 | func (rd *RootDoc) AddHeading(text string, level uint) (*Paragraph, error) { 16 | if level < 0 || level > 9 { 17 | return nil, errors.New("Heading level not supported") 18 | } 19 | 20 | p := newParagraph(rd) 21 | p.ct.Property = ctypes.DefaultParaProperty() 22 | 23 | style := "Title" 24 | if level != 0 { 25 | style = fmt.Sprintf("Heading%d", level) 26 | } 27 | 28 | p.ct.Property.Style = ctypes.NewParagraphStyle(style) 29 | 30 | bodyElem := DocumentChild{ 31 | Para: p, 32 | } 33 | rd.Document.Body.Children = append(rd.Document.Body.Children, bodyElem) 34 | 35 | p.AddText(text) 36 | return p, nil 37 | } 38 | -------------------------------------------------------------------------------- /docx/link.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/gomutex/godocx/common/constants" 7 | ) 8 | 9 | // addLinkRelation adds a hyperlink relationship to the document's relationships collection. 10 | // 11 | // Parameters: 12 | // - link: A string representing the target URL or location of the hyperlink. 13 | // 14 | // Returns: 15 | // - string: The ID ("rId" + relation ID) of the added relationship. 16 | // 17 | // This function generates a new relationship ID, creates a Relationship object with the specified link as the target, 18 | // and appends it to the document's relationships collection (DocRels.Relationships). It returns the generated ID of the relationship. 19 | func (doc *Document) addLinkRelation(link string) string { 20 | 21 | rID := doc.IncRelationID() 22 | 23 | rel := &Relationship{ 24 | ID: "rId" + strconv.Itoa(rID), 25 | TargetMode: "External", 26 | Type: constants.SourceRelationshipHyperLink, 27 | Target: link, 28 | } 29 | 30 | doc.DocRels.Relationships = append(doc.DocRels.Relationships, rel) 31 | 32 | return "rId" + strconv.Itoa(rID) 33 | } 34 | 35 | // addRelation adds a generic relationship to the document's relationships collection. 36 | // 37 | // Parameters: 38 | // - relType: A string representing the type of relationship (e.g., constants.SourceRelationshipImage). 39 | // - fileName: A string representing the target file name or location related to the relationship. 40 | // 41 | // Returns: 42 | // - string: The ID ("rId" + relation ID) of the added relationship. 43 | // 44 | // This function generates a new relationship ID, creates a Relationship object with the specified type and target, 45 | // and appends it to the document's relationships collection (DocRels.Relationships). It returns the generated ID of the relationship. 46 | func (doc *Document) addRelation(relType string, fileName string) string { 47 | rID := doc.IncRelationID() 48 | rel := &Relationship{ 49 | ID: "rId" + strconv.Itoa(rID), 50 | Type: relType, 51 | Target: fileName, 52 | } 53 | 54 | doc.DocRels.Relationships = append(doc.DocRels.Relationships, rel) 55 | 56 | return "rId" + strconv.Itoa(rID) 57 | } 58 | -------------------------------------------------------------------------------- /docx/pic.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "github.com/gomutex/godocx/common/units" 5 | "github.com/gomutex/godocx/dml" 6 | ) 7 | 8 | type PicMeta struct { 9 | Para *Paragraph 10 | Inline *dml.Inline 11 | } 12 | 13 | // AddPicture adds a new image to the document. 14 | // 15 | // Example usage: 16 | // 17 | // // Add a picture to the document 18 | // _, err = document.AddPicture("gopher.png", units.Inch(2.9), units.Inch(2.9)) 19 | // if err != nil { 20 | // log.Fatal(err) 21 | // } 22 | // 23 | // Parameters: 24 | // - path: The path of the image file to be added. 25 | // - width: The width of the image in inches. 26 | // - height: The height of the image in inches. 27 | // 28 | // Returns: 29 | // - *PicMeta: Metadata about the added picture, including the Paragraph instance and Inline element. 30 | // - error: An error, if any occurred during the process. 31 | func (rd *RootDoc) AddPicture(path string, width units.Inch, height units.Inch) (*PicMeta, error) { 32 | 33 | p := newParagraph(rd) 34 | 35 | bodyElem := DocumentChild{ 36 | Para: p, 37 | } 38 | rd.Document.Body.Children = append(rd.Document.Body.Children, bodyElem) 39 | 40 | return p.AddPicture(path, width, height) 41 | } 42 | -------------------------------------------------------------------------------- /docx/rels.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "encoding/xml" 5 | ) 6 | 7 | // Relationship represents a relationship between elements in an Office Open XML (OOXML) document. 8 | // It includes essential information such as ID, type, target, and target mode. 9 | type Relationship struct { 10 | XMLName xml.Name `xml:"Relationship"` 11 | ID string `xml:"Id,attr"` 12 | Type string `xml:"Type,attr"` 13 | Target string `xml:"Target,attr"` 14 | TargetMode string `xml:"TargetMode,attr,omitempty"` 15 | } 16 | 17 | // Relationships represents a collection of relationships in an OOXML document. 18 | // It includes the relative path, XML namespace, and a slice of Relationship instances. 19 | type Relationships struct { 20 | RelativePath string `xml:"-"` 21 | XMLName xml.Name `xml:"Relationships"` 22 | Xmlns string `xml:"xmlns,attr"` 23 | Relationships []*Relationship `xml:"Relationship"` 24 | } 25 | 26 | func (r Relationship) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 27 | start.Name.Local = "Relationship" 28 | start.Attr = []xml.Attr{} 29 | 30 | if r.ID != "" { 31 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "Id"}, Value: r.ID}) 32 | } 33 | 34 | if r.Type != "" { 35 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "Type"}, Value: r.Type}) 36 | } 37 | 38 | if r.Target != "" { 39 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "Target"}, Value: r.Target}) 40 | } 41 | 42 | if r.TargetMode != "" { 43 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "TargetMode"}, Value: r.TargetMode}) 44 | } 45 | 46 | return e.EncodeElement("", start) 47 | } 48 | -------------------------------------------------------------------------------- /docx/root.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "encoding/xml" 5 | "sync" 6 | 7 | "github.com/gomutex/godocx/wml/ctypes" 8 | ) 9 | 10 | // RootDoc represents the root document of an Office Open XML (OOXML) document. 11 | // It contains information about the document path, file map, the document structure, 12 | // and relationships with other parts of the document. 13 | type RootDoc struct { 14 | Path string // Path represents the path of the document. 15 | FileMap sync.Map // FileMap is a synchronized map for managing files related to the document. 16 | RootRels Relationships // RootRels represents relationships at the root level. 17 | ContentType ContentTypes 18 | Document *Document // Document is the main document structure. 19 | DocStyles *ctypes.Styles // Document styles 20 | 21 | rID int // rId is used to generate unique relationship IDs. 22 | ImageCount uint 23 | } 24 | 25 | // NewRootDoc creates a new instance of the RootDoc structure. 26 | func NewRootDoc() *RootDoc { 27 | return &RootDoc{} 28 | } 29 | 30 | // LoadDocXml decodes the provided XML data and returns a Document instance. 31 | // It is used to load the main document structure from the document file. 32 | // 33 | // Parameters: 34 | // - fileName: The name of the document file. 35 | // - fileBytes: The XML data representing the main document structure. 36 | // 37 | // Returns: 38 | // - doc: The Document instance containing the decoded main document structure. 39 | // - err: An error, if any occurred during the decoding process. 40 | func LoadDocXml(rd *RootDoc, fileName string, fileBytes []byte) (*Document, error) { 41 | doc := Document{ 42 | Root: rd, 43 | } 44 | err := xml.Unmarshal(fileBytes, &doc) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | doc.relativePath = fileName 50 | return &doc, nil 51 | } 52 | 53 | // Load styles.xml into Styles struct 54 | func LoadStyles(fileName string, fileBytes []byte) (*ctypes.Styles, error) { 55 | styles := ctypes.Styles{} 56 | err := xml.Unmarshal(fileBytes, &styles) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | styles.RelativePath = fileName 62 | return &styles, nil 63 | } 64 | -------------------------------------------------------------------------------- /docx/styles.go: -------------------------------------------------------------------------------- 1 | package docx 2 | 3 | import ( 4 | "github.com/gomutex/godocx/wml/ctypes" 5 | "github.com/gomutex/godocx/wml/stypes" 6 | ) 7 | 8 | // GetStyleByID retrieves a style from the document styles collection based on the given style ID and type. 9 | // 10 | // Parameters: 11 | // - styleID: A string representing the ID of the style to retrieve. 12 | // - styleType: An stypes.StyleType indicating the type of style (e.g., paragraph, character, table). 13 | // 14 | // Returns: 15 | // - *ctypes.Style: A pointer to the style matching the provided ID and type, if found; otherwise, nil. 16 | // 17 | // This method searches through the document's style list to find a style with the specified ID and type. 18 | // If no matching style is found or if the document styles collection is nil, it returns nil. 19 | func (rd *RootDoc) GetStyleByID(styleID string, styleType stypes.StyleType) *ctypes.Style { 20 | if rd.DocStyles == nil { 21 | return nil 22 | } 23 | 24 | for _, style := range rd.DocStyles.StyleList { 25 | if style.ID == nil || style.Type == nil { 26 | continue 27 | } 28 | 29 | if *style.ID == styleID && *style.Type == styleType { 30 | return &style 31 | } 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gomutex/godocx 2 | 3 | go 1.18 4 | 5 | require github.com/stretchr/testify v1.9.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /godocx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gomutex/godocx/cb7db9474fbabada5d935f98f1a40398d838dc7c/godocx.png -------------------------------------------------------------------------------- /internal/fileops.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "io" 7 | "os" 8 | ) 9 | 10 | func ReadFileFromZip(file *zip.File) ([]byte, error) { 11 | f, err := file.Open() 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | dat := make([]byte, 0, file.FileInfo().Size()) 17 | buff := bytes.NewBuffer(dat) 18 | _, _ = io.Copy(buff, f) 19 | 20 | return buff.Bytes(), f.Close() 21 | } 22 | 23 | func FileToByte(fileName string) ([]byte, error) { 24 | f, err := os.Open(fileName) 25 | if err != nil { 26 | return nil, err 27 | } 28 | defer f.Close() 29 | 30 | fileInfo, err := f.Stat() 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | fileBytes := make([]byte, fileInfo.Size()) 36 | _, err = f.Read(fileBytes) 37 | 38 | return fileBytes, nil 39 | } 40 | -------------------------------------------------------------------------------- /internal/testhelper.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func ToPtr[T any](input T) *T { 9 | return &input 10 | } 11 | 12 | func FormatPtr[T any](ptr *T) string { 13 | if ptr == nil { 14 | return "" 15 | } 16 | return fmt.Sprintf("%v", *ptr) 17 | } 18 | 19 | // func ComparePtr[T comparable](fieldName string, a, b *T) error { 20 | // if a == nil || b == nil { 21 | // if a != b { 22 | // return fmt.Errorf("%s: expected %v but got %v", fieldName, FormatPtr(a), FormatPtr(b)) 23 | // } 24 | // } else if *a != *b { 25 | // return fmt.Errorf("%s: expected %v but got %v", fieldName, *a, *b) 26 | // } 27 | // return nil 28 | // } 29 | 30 | func ComparePtr[T comparable](fieldName string, expected, result *T) error { 31 | // Check if T is a struct 32 | if reflect.TypeOf(*new(T)).Kind() == reflect.Struct { 33 | if expected == nil || result == nil { 34 | if expected != result { 35 | return fmt.Errorf("%s: expected %v but got %v", fieldName, FormatPtr(expected), FormatPtr(result)) 36 | } 37 | } 38 | } else { 39 | // For non-struct types, perform value comparison 40 | if expected == nil || result == nil { 41 | if expected != result { 42 | return fmt.Errorf("%s: expected %v but got %v", fieldName, FormatPtr(expected), FormatPtr(result)) 43 | } 44 | } else if *expected != *result { 45 | return fmt.Errorf("%s: expected %v but got %v", fieldName, *expected, *result) 46 | } 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /packager/doc.go: -------------------------------------------------------------------------------- 1 | // Package opc provides functionality related to Open Packaging Conventions (OPC). 2 | // 3 | // Overview: 4 | // 5 | // Open Packaging Conventions (OPC) is a container-file technology initially created by Microsoft to store a combination 6 | // of XML and non-XML files that together form a single entity. OPC is used in various document formats, including the 7 | // Office Open XML (OOXML) standard. 8 | package packager 9 | -------------------------------------------------------------------------------- /packager/packuri.go: -------------------------------------------------------------------------------- 1 | package packager 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | ) 7 | 8 | // GetRelsURI returns the URI of the .rels file for the specified OPC file. 9 | func GetRelsURI(filePath string) (*string, error) { 10 | baseURI := path.Dir(filePath) 11 | _, fileName := path.Split(filePath) 12 | relsFilename := fmt.Sprintf("%s.rels", fileName) 13 | relsURI := path.Join(baseURI, "_rels", relsFilename) 14 | return &relsURI, nil 15 | } 16 | -------------------------------------------------------------------------------- /packager/rels.go: -------------------------------------------------------------------------------- 1 | package packager 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/common/constants" 7 | "github.com/gomutex/godocx/docx" 8 | ) 9 | 10 | // LoadRelationShips loads the relationships from the specified file. 11 | func LoadRelationShips(fileName string, fileBytes []byte) (*docx.Relationships, error) { 12 | rels := docx.Relationships{Xmlns: constants.XMLNS_R} 13 | err := xml.Unmarshal(fileBytes, &rels) 14 | if err != nil { 15 | return nil, err 16 | } 17 | rels.RelativePath = fileName 18 | return &rels, nil 19 | } 20 | 21 | // LoadContentTypes loads the content type from the content types file 22 | func LoadContentTypes(fileBytes []byte) (*docx.ContentTypes, error) { 23 | ct := docx.ContentTypes{} 24 | err := xml.Unmarshal(fileBytes, &ct) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &ct, nil 29 | } 30 | -------------------------------------------------------------------------------- /packager/zip.go: -------------------------------------------------------------------------------- 1 | package packager 2 | 3 | import ( 4 | "archive/zip" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | // readZipFile reads the contents of a zip file and returns it as a byte slice. 10 | // It returns an error if the file is not a valid zip file or if there is an error 11 | // reading the file. 12 | func readZipFile(zf *zip.File) ([]byte, error) { 13 | if zf == nil { 14 | return nil, fmt.Errorf("Invalid Zip file") 15 | } 16 | reader, err := zf.Open() 17 | if err != nil { 18 | return nil, err 19 | } 20 | defer reader.Close() 21 | return io.ReadAll(reader) 22 | } 23 | -------------------------------------------------------------------------------- /templates/default.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gomutex/godocx/cb7db9474fbabada5d935f98f1a40398d838dc7c/templates/default.docx -------------------------------------------------------------------------------- /testdata/test.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gomutex/godocx/cb7db9474fbabada5d935f98f1a40398d838dc7c/testdata/test.docx -------------------------------------------------------------------------------- /wml/ctypes/br.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | type Break struct { 10 | BreakType *stypes.BreakType `xml:"type,attr,omitempty"` 11 | Clear *stypes.BreakClear `xml:"clear,attr,omitempty"` 12 | } 13 | 14 | // NewBreak creates a new Break element with the given break type. 15 | func NewBreak(breakType stypes.BreakType) *Break { 16 | return &Break{ 17 | BreakType: &breakType, 18 | } 19 | } 20 | 21 | // MarshalXML implements the xml.Marshaler interface. 22 | func (b Break) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 23 | start.Name.Local = "w:br" 24 | 25 | if b.BreakType != nil { 26 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:type"}, Value: string(*b.BreakType)}) 27 | } 28 | 29 | if b.Clear != nil { 30 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:clear"}, Value: string(*b.Clear)}) 31 | } 32 | 33 | return e.EncodeElement("", start) 34 | } 35 | -------------------------------------------------------------------------------- /wml/ctypes/br_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/wml/stypes" 9 | ) 10 | 11 | func TestBreakMarshaling(t *testing.T) { 12 | breakType := stypes.BreakTypePage 13 | br := NewBreak(breakType) 14 | 15 | expectedXML := `` 16 | 17 | xmlData, err := xml.Marshal(br) 18 | if err != nil { 19 | t.Fatalf("Error marshaling Break to XML: %v", err) 20 | } 21 | 22 | if strings.TrimSpace(string(xmlData)) != expectedXML { 23 | t.Errorf("Unexpected XML output. Expected:\n%s\nGot:\n%s", expectedXML, string(xmlData)) 24 | } 25 | 26 | var unmarshalledBreak Break 27 | err = xml.Unmarshal(xmlData, &unmarshalledBreak) 28 | if err != nil { 29 | t.Fatalf("Error unmarshaling XML to Break: %v", err) 30 | } 31 | 32 | if *unmarshalledBreak.BreakType != *br.BreakType { 33 | t.Errorf("Expected BreakType %s, got %s", *br.BreakType, *unmarshalledBreak.BreakType) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /wml/ctypes/cellMar.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // Table Cell Margin Defaults 10 | type CellMargins struct { 11 | // 1. Table Cell Top Margin Default 12 | Top *TableWidth `xml:"top,omitempty"` 13 | 14 | // 2. Table Cell Left Margin Default 15 | Left *TableWidth `xml:"left,omitempty"` 16 | 17 | // 3. Table Cell Bottom Margin Default 18 | Bottom *TableWidth `xml:"bottom,omitempty"` 19 | 20 | // 4. Table Cell Right Margin Default 21 | Right *TableWidth `xml:"right,omitempty"` 22 | } 23 | 24 | func DefaultCellMargins() CellMargins { 25 | return CellMargins{} 26 | } 27 | 28 | func (tcm CellMargins) Margin(top, left, bottom, right int) CellMargins { 29 | tcm.Top = NewTableWidth(top, stypes.TableWidthDxa) 30 | tcm.Left = NewTableWidth(left, stypes.TableWidthDxa) 31 | tcm.Bottom = NewTableWidth(bottom, stypes.TableWidthDxa) 32 | tcm.Right = NewTableWidth(right, stypes.TableWidthDxa) 33 | return tcm 34 | } 35 | 36 | func (tcm CellMargins) MarginTop(v int, t stypes.TableWidth) CellMargins { 37 | tcm.Top = NewTableWidth(v, t) 38 | return tcm 39 | } 40 | 41 | func (tcm CellMargins) MarginRight(v int, t stypes.TableWidth) CellMargins { 42 | tcm.Right = NewTableWidth(v, t) 43 | return tcm 44 | } 45 | 46 | func (tcm CellMargins) MarginLeft(v int, t stypes.TableWidth) CellMargins { 47 | tcm.Left = NewTableWidth(v, t) 48 | return tcm 49 | } 50 | 51 | func (tcm CellMargins) MarginBottom(v int, t stypes.TableWidth) CellMargins { 52 | tcm.Bottom = NewTableWidth(v, t) 53 | return tcm 54 | } 55 | 56 | func (tcm CellMargins) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { 57 | 58 | if err = e.EncodeToken(start); err != nil { 59 | return err 60 | } 61 | 62 | // 1. Top 63 | if tcm.Top != nil { 64 | if err = tcm.Top.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:top"}}); err != nil { 65 | return err 66 | } 67 | } 68 | 69 | // 2. Left 70 | if tcm.Left != nil { 71 | if err = tcm.Left.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:left"}}); err != nil { 72 | return err 73 | } 74 | } 75 | 76 | // 3. Bottom 77 | if tcm.Bottom != nil { 78 | if err = tcm.Bottom.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:bottom"}}); err != nil { 79 | return err 80 | } 81 | } 82 | 83 | // 4. Right 84 | if tcm.Right != nil { 85 | if err = tcm.Right.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:right"}}); err != nil { 86 | return err 87 | } 88 | } 89 | 90 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 91 | } 92 | -------------------------------------------------------------------------------- /wml/ctypes/cellMerge.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | "strconv" 7 | ) 8 | 9 | type CellMerge struct { 10 | ID int `xml:"id,attr"` 11 | Author string `xml:"author,attr"` 12 | Date *string `xml:"date,attr,omitempty"` 13 | VMerge *AnnotationVMerge `xml:"vMerge,attr,omitempty"` //Revised Vertical Merge Setting 14 | VMergeOrig *AnnotationVMerge `xml:"vMergeOrig,attr,omitempty"` //Vertical Merge Setting Removed by Revision 15 | 16 | } 17 | 18 | func (t CellMerge) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 19 | start.Attr = []xml.Attr{ 20 | {Name: xml.Name{Local: "w:id"}, Value: strconv.Itoa(t.ID)}, 21 | {Name: xml.Name{Local: "w:author"}, Value: t.Author}, 22 | } 23 | 24 | if t.Date != nil { 25 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:date"}, Value: *t.Date}) 26 | } 27 | 28 | if t.VMerge != nil { 29 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:vMerge"}, Value: string(*t.VMerge)}) 30 | } 31 | 32 | if t.VMergeOrig != nil { 33 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:vMergeOrig"}, Value: string(*t.VMergeOrig)}) 34 | } 35 | 36 | return e.EncodeElement("", start) 37 | } 38 | 39 | type AnnotationVMerge string 40 | 41 | const ( 42 | // AnnotationVMergeCont represents a vertically merged cell. 43 | AnnotationVMergeCont AnnotationVMerge = "cont" 44 | // AnnotationVMergeRest represents a vertically split cell. 45 | AnnotationVMergeRest AnnotationVMerge = "rest" 46 | ) 47 | 48 | // AnnotationVMergeFromStr converts a string to AnnotationVMerge type. 49 | func AnnotationVMergeFromStr(value string) (AnnotationVMerge, error) { 50 | switch value { 51 | case "cont": 52 | return AnnotationVMergeCont, nil 53 | case "rest": 54 | return AnnotationVMergeRest, nil 55 | default: 56 | return "", errors.New("invalid AnnotationVMerge value") 57 | } 58 | } 59 | 60 | // UnmarshalXMLAttr unmarshals XML attribute into AnnotationVMerge. 61 | func (a *AnnotationVMerge) UnmarshalXMLAttr(attr xml.Attr) error { 62 | val, err := AnnotationVMergeFromStr(attr.Value) 63 | if err != nil { 64 | return err 65 | } 66 | *a = val 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /wml/ctypes/cellMerge_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/gomutex/godocx/internal" 10 | ) 11 | 12 | func TestCellMerge_MarshalXML(t *testing.T) { 13 | // Create a CellMerge instance for testing 14 | cellMerge := &CellMerge{ 15 | ID: 1, 16 | Author: "John Doe", 17 | Date: nil, 18 | VMerge: internal.ToPtr(AnnotationVMergeCont), 19 | VMergeOrig: internal.ToPtr(AnnotationVMergeRest), 20 | } 21 | 22 | // Marshal the CellMerge instance to XML 23 | xmlData, err := xml.Marshal(cellMerge) 24 | if err != nil { 25 | t.Fatalf("Error marshaling CellMerge to XML: %v", err) 26 | } 27 | 28 | // Define the expected XML string 29 | expectedXMLString := `` 30 | 31 | // Compare the generated XML with the expected XML string 32 | if !bytes.Contains(xmlData, []byte(expectedXMLString)) { 33 | t.Errorf("Expected XML string %s, got %s", expectedXMLString, string(xmlData)) 34 | } 35 | } 36 | 37 | func TestCellMerge_UnmarshalXML(t *testing.T) { 38 | // Define a sample XML data corresponding to CellMerge structure 39 | xmlData := []byte(``) 40 | 41 | // Define the expected CellMerge instance after unmarshaling 42 | expectedCellMerge := &CellMerge{ 43 | ID: 2, 44 | Author: "Jane Smith", 45 | Date: xmlStrPtr("2024-06-25"), // Helper function to get pointer to string 46 | VMerge: internal.ToPtr(AnnotationVMergeRest), 47 | VMergeOrig: nil, 48 | } 49 | 50 | // Variable to hold unmarshaled CellMerge instance 51 | var unmarshaledCellMerge CellMerge 52 | 53 | // Unmarshal the XML into the CellMerge instance 54 | err := xml.Unmarshal(xmlData, &unmarshaledCellMerge) 55 | if err != nil { 56 | t.Fatalf("Error unmarshaling XML to CellMerge: %v", err) 57 | } 58 | 59 | // Compare the unmarshaled CellMerge instance with the expected instance 60 | if !reflect.DeepEqual(&unmarshaledCellMerge, expectedCellMerge) { 61 | t.Errorf("Expected %#v, got %#v", expectedCellMerge, unmarshaledCellMerge) 62 | } 63 | } 64 | 65 | // Helper function to return pointer to string 66 | func xmlStrPtr(s string) *string { 67 | return &s 68 | } 69 | -------------------------------------------------------------------------------- /wml/ctypes/col.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // Grid Column Definition 9 | type Column struct { 10 | Width *uint64 `xml:"w,attr,omitempty"` //Grid Column Width 11 | } 12 | 13 | func (c Column) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 14 | start.Name.Local = "w:gridCol" 15 | 16 | if c.Width != nil { 17 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:w"}, Value: strconv.FormatUint(*c.Width, 10)}) 18 | } 19 | 20 | if err := e.EncodeToken(start); err != nil { 21 | return err 22 | } 23 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 24 | } 25 | -------------------------------------------------------------------------------- /wml/ctypes/col_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/internal" 9 | ) 10 | 11 | func TestColumn_MarshalXML(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | input Column 15 | expected string 16 | }{ 17 | { 18 | name: "With Width", 19 | input: Column{Width: internal.ToPtr(uint64(500))}, 20 | expected: ``, 21 | }, 22 | { 23 | name: "Without Width", 24 | input: Column{}, 25 | expected: ``, 26 | }, 27 | } 28 | 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | var result strings.Builder 32 | encoder := xml.NewEncoder(&result) 33 | start := xml.StartElement{Name: xml.Name{Local: "w:gridCol"}} 34 | 35 | err := tt.input.MarshalXML(encoder, start) 36 | if err != nil { 37 | t.Fatalf("Error marshaling XML: %v", err) 38 | } 39 | 40 | encoder.Flush() 41 | 42 | if result.String() != tt.expected { 43 | t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | func TestColumn_UnmarshalXML(t *testing.T) { 50 | tests := []struct { 51 | name string 52 | inputXML string 53 | expected Column 54 | }{ 55 | { 56 | name: "With Width", 57 | inputXML: ``, 58 | expected: Column{Width: internal.ToPtr(uint64(750))}, 59 | }, 60 | { 61 | name: "Without Width", 62 | inputXML: ``, 63 | expected: Column{}, 64 | }, 65 | } 66 | 67 | for _, tt := range tests { 68 | t.Run(tt.name, func(t *testing.T) { 69 | var result Column 70 | 71 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 72 | if err != nil { 73 | t.Fatalf("Error unmarshaling XML: %v", err) 74 | } 75 | 76 | if result.Width == nil && tt.expected.Width == nil { 77 | // Both are nil, which is fine 78 | } else if result.Width == nil || tt.expected.Width == nil || *result.Width != *tt.expected.Width { 79 | t.Errorf("Expected Width %v but got %v", tt.expected.Width, result.Width) 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /wml/ctypes/color.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // Color represents the color of a text or element. 10 | type Color struct { 11 | //Run Content Color 12 | Val string `xml:"val,attr"` 13 | 14 | //Run Content Theme Color 15 | ThemeColor *stypes.ThemeColor `xml:"themeColor,attr,omitempty"` 16 | 17 | //Run Content Theme Color Tint 18 | ThemeTint *string `xml:"themeTint,attr,omitempty"` 19 | 20 | //Run Content Theme Color Shade 21 | ThemeShade *string `xml:"themeShade,attr,omitempty"` 22 | } 23 | 24 | // NewColor creates a new Color instance with the specified color value. 25 | func NewColor(value string) *Color { 26 | return &Color{Val: value} 27 | } 28 | 29 | // MarshalXML implements the xml.Marshaler interface for the Color type. 30 | func (c Color) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 31 | start.Name.Local = "w:color" 32 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: c.Val}) 33 | 34 | if c.ThemeColor != nil { 35 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:themeColor"}, Value: string(*c.ThemeColor)}) 36 | } 37 | 38 | if c.ThemeTint != nil { 39 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:themeTint"}, Value: *c.ThemeTint}) 40 | } 41 | 42 | if c.ThemeShade != nil { 43 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:themeShade"}, Value: *c.ThemeShade}) 44 | } 45 | 46 | return e.EncodeElement("", start) 47 | } 48 | -------------------------------------------------------------------------------- /wml/ctypes/color_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestColor(t *testing.T) { 10 | testColor := NewColor("FF0000") 11 | 12 | xmlData, err := xml.Marshal(testColor) 13 | if err != nil { 14 | t.Fatalf("Error marshaling Color to XML: %v", err) 15 | } 16 | 17 | var unmarshaledColor Color 18 | err = xml.Unmarshal(xmlData, &unmarshaledColor) 19 | if err != nil { 20 | t.Fatalf("Error unmarshaling XML to Color: %v", err) 21 | } 22 | 23 | if testColor.Val != unmarshaledColor.Val { 24 | t.Errorf("Expected color value %s, got %s", testColor.Val, unmarshaledColor.Val) 25 | } 26 | 27 | expectedXMLString := `` 28 | if !strings.Contains(string(xmlData), expectedXMLString) { 29 | t.Errorf("Expected XML string %s, got %s", expectedXMLString, string(xmlData)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /wml/ctypes/doc.go: -------------------------------------------------------------------------------- 1 | // Package ctypes provides complex types used in various components of Office Open XML 2 | // (OOXML) WordprocessingML (WML) documents. It includes structured definitions for 3 | // paragraphs, tables, styles, headers, footers, and other complex document elements, 4 | // facilitating structured data handling and manipulation within WML files. 5 | package ctypes 6 | -------------------------------------------------------------------------------- /wml/ctypes/docDefaults.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | ) 7 | 8 | // Document Default Paragraph and Run Properties 9 | type DocDefault struct { 10 | //Sequence 11 | 12 | //1.Default Run Properties 13 | RunProp *RunPropDefault `xml:"rPrDefault,omitempty"` 14 | 15 | //2.Default Paragraph Properties 16 | ParaProp *ParaPropDefault `xml:"pPrDefault,omitempty"` 17 | } 18 | 19 | type RunPropDefault struct { 20 | RunProp *RunProperty `xml:"rPr,omitempty"` 21 | } 22 | 23 | type ParaPropDefault struct { 24 | ParaProp *ParagraphProp `xml:"pPr,omitempty"` 25 | } 26 | 27 | func (d *DocDefault) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 28 | start.Name.Local = "w:docDefaults" 29 | 30 | if err := e.EncodeToken(start); err != nil { 31 | return err 32 | } 33 | 34 | if d.RunProp != nil { 35 | if err := d.RunProp.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:rPrDefault"}}); err != nil { 36 | return fmt.Errorf("DocDefault RunProp: %w", err) 37 | } 38 | } 39 | 40 | if d.ParaProp != nil { 41 | if err := d.ParaProp.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:pPrDefault"}}); err != nil { 42 | return fmt.Errorf("DocDefault ParaProp: %w", err) 43 | } 44 | } 45 | 46 | return e.EncodeToken(start.End()) 47 | } 48 | 49 | func (r *RunPropDefault) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 50 | start.Name.Local = "w:rPrDefault" 51 | if err := e.EncodeToken(start); err != nil { 52 | return err 53 | } 54 | 55 | if r.RunProp != nil { 56 | if err := e.EncodeElement(r.RunProp, xml.StartElement{ 57 | Name: xml.Name{Local: "w:rPr"}, 58 | }); err != nil { 59 | return fmt.Errorf("RunPropDefault: %w", err) 60 | } 61 | } 62 | 63 | return e.EncodeToken(start.End()) 64 | } 65 | 66 | func (p *ParaPropDefault) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 67 | start.Name.Local = "w:pPrDefault" 68 | if err := e.EncodeToken(start); err != nil { 69 | return err 70 | } 71 | 72 | if p.ParaProp != nil { 73 | if err := e.EncodeElement(p.ParaProp, xml.StartElement{ 74 | Name: xml.Name{Local: "w:pPr"}, 75 | }); err != nil { 76 | return fmt.Errorf("ParaPropDefault: %w", err) 77 | } 78 | } 79 | 80 | return e.EncodeToken(start.End()) 81 | } 82 | -------------------------------------------------------------------------------- /wml/ctypes/docGrid.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | 7 | "github.com/gomutex/godocx/wml/stypes" 8 | ) 9 | 10 | // DocGrid represents the document grid settings. 11 | type DocGrid struct { 12 | Type stypes.DocGridType `xml:"type,attr,omitempty"` 13 | LinePitch *int `xml:"linePitch,attr,omitempty"` 14 | CharSpace *int `xml:"charSpace,attr,omitempty"` 15 | } 16 | 17 | // MarshalXML implements the xml.Marshaler interface for the DocGrid type. 18 | // It encodes the DocGrid to its corresponding XML representation. 19 | func (docGrid DocGrid) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 20 | start.Name.Local = "w:docGrid" 21 | if docGrid.Type != "" { 22 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:type"}, Value: string(docGrid.Type)}) 23 | } 24 | if docGrid.LinePitch != nil { 25 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:linePitch"}, Value: strconv.Itoa(*docGrid.LinePitch)}) 26 | } 27 | if docGrid.CharSpace != nil { 28 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:charSpace"}, Value: strconv.Itoa(*docGrid.CharSpace)}) 29 | } 30 | return e.EncodeElement("", start) 31 | } 32 | -------------------------------------------------------------------------------- /wml/ctypes/eALayout.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | 7 | "github.com/gomutex/godocx/wml/stypes" 8 | ) 9 | 10 | // East Asian Typography Settings 11 | type EALayout struct { 12 | ID *int `xml:"id,attr,omitempty"` 13 | Combine *stypes.OnOff `xml:"combine,attr,omitempty"` 14 | CombineBrkts *stypes.CombineBrackets `xml:"combineBrackets,attr,omitempty"` 15 | Vert *stypes.OnOff `xml:"vert,attr,omitempty"` 16 | VertCompress *stypes.OnOff `xml:"vertCompress,attr,omitempty"` 17 | } 18 | 19 | func (ea EALayout) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 20 | start.Name.Local = "w:eastAsianLayout" 21 | 22 | if ea.ID != nil { 23 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:id"}, Value: strconv.Itoa(*ea.ID)}) 24 | } 25 | if ea.Combine != nil { 26 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:combine"}, Value: string(*ea.Combine)}) 27 | } 28 | if ea.CombineBrkts != nil { 29 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:combineBrackets"}, Value: string(*ea.CombineBrkts)}) 30 | } 31 | if ea.Vert != nil { 32 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:vert"}, Value: string(*ea.Vert)}) 33 | } 34 | if ea.VertCompress != nil { 35 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:vertCompress"}, Value: string(*ea.VertCompress)}) 36 | } 37 | 38 | return e.EncodeElement("", start) 39 | } 40 | -------------------------------------------------------------------------------- /wml/ctypes/effect.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | type Effect struct { 10 | Val *stypes.TextEffect `xml:"val,attr,omitempty"` 11 | } 12 | 13 | // MarshalXML implements the xml.Marshaler interface for the Effect type. 14 | // It encodes the instance into XML using the "w:XMLName" element with a "w:val" attribute. 15 | func (eff Effect) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 16 | if eff.Val != nil { 17 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: string(*eff.Val)}) 18 | } 19 | err := e.EncodeElement("", start) 20 | 21 | return err 22 | } 23 | -------------------------------------------------------------------------------- /wml/ctypes/effect_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/wml/stypes" 9 | ) 10 | 11 | func TestEffect_MarshalXML(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | input Effect 15 | expected string 16 | }{ 17 | { 18 | name: "With value", 19 | input: Effect{Val: TextEffectPtr(stypes.TextEffectBlinkBackground)}, 20 | expected: ``, 21 | }, 22 | { 23 | name: "Without value", 24 | input: Effect{}, 25 | expected: ``, 26 | }, 27 | } 28 | 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | var result strings.Builder 32 | encoder := xml.NewEncoder(&result) 33 | start := xml.StartElement{Name: xml.Name{Local: "w:effect"}} 34 | 35 | err := tt.input.MarshalXML(encoder, start) 36 | if err != nil { 37 | t.Fatalf("Error marshaling XML: %v", err) 38 | } 39 | 40 | encoder.Flush() 41 | 42 | if result.String() != tt.expected { 43 | t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | func TestEffect_UnmarshalXML(t *testing.T) { 50 | tests := []struct { 51 | name string 52 | inputXML string 53 | expected Effect 54 | }{ 55 | { 56 | name: "With value", 57 | inputXML: ``, 58 | expected: Effect{Val: TextEffectPtr(stypes.TextEffectBlinkBackground)}, 59 | }, 60 | { 61 | name: "Without value", 62 | inputXML: ``, 63 | expected: Effect{}, 64 | }, 65 | } 66 | 67 | for _, tt := range tests { 68 | t.Run(tt.name, func(t *testing.T) { 69 | var result Effect 70 | 71 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 72 | if err != nil { 73 | t.Fatalf("Error unmarshaling XML: %v", err) 74 | } 75 | 76 | if tt.expected.Val != nil { 77 | if result.Val == nil { 78 | t.Errorf("Expected Value %s but got nil", *tt.expected.Val) 79 | } else if *tt.expected.Val != *result.Val { 80 | t.Errorf("Expected Value %s but got %s", *tt.expected.Val, *result.Val) 81 | } 82 | } else { 83 | if result.Val != nil { 84 | t.Errorf("Expected nil but got %s", *result.Val) 85 | } 86 | } 87 | 88 | }) 89 | } 90 | } 91 | 92 | func TextEffectPtr(value stypes.TextEffect) *stypes.TextEffect { 93 | return &value 94 | } 95 | -------------------------------------------------------------------------------- /wml/ctypes/expaComp.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // This element specifies the amount by which each character shall be expanded or when the character is rendered in the document 10 | // 11 | // This property has an of stretching or compressing each character in the run, as opposed to the spacing element (§2.3.2.33) which expands/compresses the text by adding additional character pitch but not changing the width of the actual characters displayed on the line. 12 | type ExpaComp struct { 13 | Val *stypes.TextScale `xml:"val,attr,omitempty"` 14 | } 15 | 16 | // MarshalXML implements the xml.Marshaler interface for the ExpaComp type. 17 | // It encodes the instance into XML using the "w:XMLName" element with a "w:val" attribute. 18 | func (ec ExpaComp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 19 | if ec.Val != nil { 20 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: ec.Val.ToStr()}) 21 | } 22 | err := e.EncodeElement("", start) 23 | 24 | return err 25 | } 26 | -------------------------------------------------------------------------------- /wml/ctypes/expaComp_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/wml/stypes" 9 | ) 10 | 11 | func TestExpaComp_MarshalXML(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | input ExpaComp 15 | expected string 16 | }{ 17 | { 18 | name: "With value", 19 | input: ExpaComp{Val: TextScalePtr(24)}, 20 | expected: ``, 21 | }, 22 | { 23 | name: "Without value", 24 | input: ExpaComp{}, 25 | expected: ``, 26 | }, 27 | } 28 | 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | var result strings.Builder 32 | encoder := xml.NewEncoder(&result) 33 | start := xml.StartElement{Name: xml.Name{Local: "w:sz"}} 34 | 35 | err := tt.input.MarshalXML(encoder, start) 36 | if err != nil { 37 | t.Fatalf("Error marshaling XML: %v", err) 38 | } 39 | 40 | encoder.Flush() 41 | 42 | if result.String() != tt.expected { 43 | t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | func TestExpaComp_UnmarshalXML(t *testing.T) { 50 | tests := []struct { 51 | name string 52 | inputXML string 53 | expected ExpaComp 54 | }{ 55 | { 56 | name: "With value", 57 | inputXML: ``, 58 | expected: ExpaComp{Val: TextScalePtr(24)}, 59 | }, 60 | { 61 | name: "Without value", 62 | inputXML: ``, 63 | expected: ExpaComp{}, 64 | }, 65 | } 66 | 67 | for _, tt := range tests { 68 | t.Run(tt.name, func(t *testing.T) { 69 | var result ExpaComp 70 | 71 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 72 | if err != nil { 73 | t.Fatalf("Error unmarshaling XML: %v", err) 74 | } 75 | 76 | if tt.expected.Val != nil { 77 | if result.Val == nil { 78 | t.Errorf("Expected Value %d but got nil", *tt.expected.Val) 79 | } else if *tt.expected.Val != *result.Val { 80 | t.Errorf("Expected Value %d but got %d", *tt.expected.Val, *result.Val) 81 | } 82 | } else { 83 | if result.Val != nil { 84 | t.Errorf("Expected nil but got %d", *result.Val) 85 | } 86 | } 87 | 88 | }) 89 | } 90 | } 91 | 92 | func TextScalePtr(value uint16) *stypes.TextScale { 93 | ts := stypes.TextScale(value) 94 | return &ts 95 | } 96 | -------------------------------------------------------------------------------- /wml/ctypes/fitText.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | type FitText struct { 9 | Val uint64 `xml:"val,attr"` 10 | ID *int `xml:"id,attr,omitempty"` 11 | } 12 | 13 | func (f FitText) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 14 | start.Name.Local = "w:fitText" 15 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: strconv.FormatUint(f.Val, 10)}) 16 | 17 | if f.ID != nil { 18 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:id"}, Value: strconv.Itoa(*f.ID)}) 19 | } 20 | 21 | return e.EncodeElement("", start) 22 | } 23 | -------------------------------------------------------------------------------- /wml/ctypes/fitText_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestFitText_MarshalXML(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | input FitText 14 | expected string 15 | }{ 16 | { 17 | name: "With ID", 18 | input: FitText{Val: 123, ID: IntPtr(456)}, 19 | expected: ``, 20 | }, 21 | { 22 | name: "Without ID", 23 | input: FitText{Val: 789}, 24 | expected: ``, 25 | }, 26 | } 27 | 28 | for _, tt := range tests { 29 | t.Run(tt.name, func(t *testing.T) { 30 | var result strings.Builder 31 | encoder := xml.NewEncoder(&result) 32 | start := xml.StartElement{Name: xml.Name{Local: "w:fitText"}} 33 | 34 | err := tt.input.MarshalXML(encoder, start) 35 | if err != nil { 36 | t.Fatalf("Error marshaling XML: %v", err) 37 | } 38 | 39 | encoder.Flush() 40 | 41 | if result.String() != tt.expected { 42 | t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) 43 | } 44 | }) 45 | } 46 | } 47 | 48 | func TestFitText_UnmarshalXML(t *testing.T) { 49 | tests := []struct { 50 | name string 51 | inputXML string 52 | expected FitText 53 | }{ 54 | { 55 | name: "With ID", 56 | inputXML: ``, 57 | expected: FitText{Val: 123, ID: IntPtr(456)}, 58 | }, 59 | { 60 | name: "Without ID", 61 | inputXML: ``, 62 | expected: FitText{Val: 789}, 63 | }, 64 | } 65 | 66 | for _, tt := range tests { 67 | t.Run(tt.name, func(t *testing.T) { 68 | var result FitText 69 | 70 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 71 | if err != nil { 72 | t.Fatalf("Error unmarshaling XML: %v", err) 73 | } 74 | 75 | if !reflect.DeepEqual(result, tt.expected) { 76 | t.Errorf("Expected %+v but got %+v", tt.expected, result) 77 | } 78 | }) 79 | } 80 | } 81 | 82 | func IntPtr(i int) *int { 83 | return &i 84 | } 85 | -------------------------------------------------------------------------------- /wml/ctypes/fontSize.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // FontSize represents the font size of a text or element. 9 | type FontSize struct { 10 | Value uint64 `xml:"val,attr,omitempty"` 11 | } 12 | 13 | // NewFontSize creates a new FontSize with the specified font size value. 14 | func NewFontSize(value uint64) *FontSize { 15 | return &FontSize{Value: value} 16 | } 17 | 18 | func (s FontSize) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 19 | start.Name.Local = "w:sz" 20 | start.Attr = []xml.Attr{{Name: xml.Name{Local: "w:val"}, Value: strconv.FormatUint(s.Value, 10)}} 21 | return e.EncodeElement("", start) 22 | } 23 | 24 | // FontSizeCs represents the font size of a text or element. 25 | type FontSizeCS struct { 26 | Value uint64 `xml:"val,attr,omitempty"` 27 | } 28 | 29 | // NewFontSizeCs creates a new FontSizeCs with the specified font size value. 30 | func NewFontSizeCS(value uint64) *FontSizeCS { 31 | return &FontSizeCS{Value: value} 32 | } 33 | 34 | func (s FontSizeCS) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 35 | start.Name.Local = "w:szCs" 36 | start.Attr = []xml.Attr{{Name: xml.Name{Local: "w:val"}, Value: strconv.FormatUint(s.Value, 10)}} 37 | return e.EncodeElement("", start) 38 | } 39 | -------------------------------------------------------------------------------- /wml/ctypes/footer.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // Footer Reference 10 | type FooterReference struct { 11 | Type stypes.HdrFtrType `xml:"type,attr"` //Footer or Footer Type 12 | ID string `xml:"id,attr"` //Relationship to Part 13 | } 14 | 15 | func (h FooterReference) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 16 | start.Name.Local = "w:footerReference" 17 | 18 | if h.Type != "" { 19 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:type"}, Value: string(h.Type)}) 20 | } 21 | 22 | if h.ID != "" { 23 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "r:id"}, Value: h.ID}) 24 | } 25 | 26 | return e.EncodeElement("", start) 27 | } 28 | -------------------------------------------------------------------------------- /wml/ctypes/grid.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | ) 7 | 8 | // Table Grid 9 | type Grid struct { 10 | //1. Grid Column Definition 11 | Col []Column `xml:"gridCol,omitempty"` 12 | 13 | //2.Revision Information for Table Grid Column Definitions 14 | GridChange *GridChange `xml:"tblGridChange,omitempty"` 15 | } 16 | 17 | func (g Grid) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 18 | start.Name.Local = "w:tblGrid" 19 | 20 | err := e.EncodeToken(start) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | //1. Grid Column Definition 26 | for _, col := range g.Col { 27 | if err := col.MarshalXML(e, xml.StartElement{}); err != nil { 28 | return fmt.Errorf("table grid marshalling column: %w", err) 29 | } 30 | } 31 | 32 | //2.Revision Information for Table Grid Column Definitions 33 | if g.GridChange != nil { 34 | if err := g.GridChange.MarshalXML(e, xml.StartElement{}); err != nil { 35 | return fmt.Errorf("table grid marshalling gridchange : %w", err) 36 | } 37 | } 38 | 39 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 40 | } 41 | -------------------------------------------------------------------------------- /wml/ctypes/gridChg.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // Revision Information for Table Grid Column Definitions 9 | type GridChange struct { 10 | ID int `xml:"id,attr,omitempty"` //Annotation Identifier 11 | } 12 | 13 | func (g GridChange) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 14 | start.Name.Local = "w:tblGridChange" 15 | 16 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:id"}, Value: strconv.Itoa(g.ID)}) 17 | 18 | if err := e.EncodeToken(start); err != nil { 19 | return err 20 | } 21 | 22 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 23 | } 24 | -------------------------------------------------------------------------------- /wml/ctypes/gridChg_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestGridChange_MarshalXML(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | input GridChange 14 | expected string 15 | }{ 16 | { 17 | name: "With ID", 18 | input: GridChange{ID: 1}, 19 | expected: ``, 20 | }, 21 | } 22 | 23 | for _, tt := range tests { 24 | t.Run(tt.name, func(t *testing.T) { 25 | var result strings.Builder 26 | encoder := xml.NewEncoder(&result) 27 | start := xml.StartElement{Name: xml.Name{Local: "w:tblGridChange"}} 28 | 29 | err := tt.input.MarshalXML(encoder, start) 30 | if err != nil { 31 | t.Fatalf("Error marshaling XML: %v", err) 32 | } 33 | 34 | encoder.Flush() 35 | 36 | if result.String() != tt.expected { 37 | t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) 38 | } 39 | }) 40 | } 41 | } 42 | 43 | func TestGridChange_UnmarshalXML(t *testing.T) { 44 | tests := []struct { 45 | name string 46 | inputXML string 47 | expected GridChange 48 | expectFail bool // Whether unmarshalling is expected to fail 49 | }{ 50 | { 51 | name: "With ID", 52 | inputXML: ``, 53 | expected: GridChange{ID: 1}, 54 | }, 55 | } 56 | 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | decoder := xml.NewDecoder(strings.NewReader(tt.inputXML)) 60 | var result GridChange 61 | 62 | err := decoder.Decode(&result) 63 | 64 | if tt.expectFail { 65 | if err == nil { 66 | t.Error("Expected unmarshaling to fail but it did not") 67 | } 68 | return 69 | } 70 | 71 | if err != nil { 72 | t.Fatalf("Error unmarshaling XML: %v", err) 73 | } 74 | 75 | if !reflect.DeepEqual(result, tt.expected) { 76 | t.Errorf("Unmarshaled GridChange struct does not match expected:\nExpected: %+v\nActual: %+v", tt.expected, result) 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /wml/ctypes/header.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // Header Reference 10 | type HeaderReference struct { 11 | Type stypes.HdrFtrType `xml:"type,attr"` //Header or Footer Type 12 | ID string `xml:"id,attr"` //Relationship to Part 13 | } 14 | 15 | func (h HeaderReference) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 16 | start.Name.Local = "w:headerReference" 17 | 18 | if h.Type != "" { 19 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:type"}, Value: string(h.Type)}) 20 | } 21 | 22 | if h.ID != "" { 23 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "r:id"}, Value: h.ID}) 24 | } 25 | 26 | return e.EncodeElement("", start) 27 | } 28 | -------------------------------------------------------------------------------- /wml/ctypes/lang.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | ) 6 | 7 | // Languages for Run Content 8 | type Lang struct { 9 | Val *string `xml:"val,attr,omitempty"` 10 | EastAsia *string `xml:"eastAsia,attr,omitempty"` 11 | Bidi *string `xml:"bidi,attr,omitempty"` 12 | } 13 | 14 | func (l Lang) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 15 | start.Name.Local = "w:lang" 16 | if l.Val != nil { 17 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: *l.Val}) 18 | } 19 | if l.EastAsia != nil { 20 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:eastAsia"}, Value: *l.EastAsia}) 21 | } 22 | if l.Bidi != nil { 23 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:bidi"}, Value: *l.Bidi}) 24 | } 25 | return e.EncodeElement("", start) 26 | } 27 | -------------------------------------------------------------------------------- /wml/ctypes/latentStyle.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/gomutex/godocx/wml/stypes" 9 | ) 10 | 11 | // Latent Style Information 12 | type LatentStyle struct { 13 | //Default Style Locking Setting 14 | DefLockedState *stypes.OnOff `xml:"defLockedState,attr,omitempty"` 15 | 16 | //Default User Interface Priority Setting 17 | DefUIPriority *int `xml:"defUIPriority,attr,omitempty"` 18 | 19 | //Default Semi-Hidden Setting 20 | DefSemiHidden *stypes.OnOff `xml:"defSemiHidden,attr,omitempty"` 21 | 22 | //Default Hidden Until Used Setting 23 | DefUnhideWhenUsed *stypes.OnOff `xml:"defUnhideWhenUsed,attr,omitempty"` 24 | 25 | //Default Primary Style Setting 26 | DefQFormat *stypes.OnOff `xml:"defQFormat,attr,omitempty"` 27 | 28 | //Latent Style Count 29 | Count *int `xml:"count,attr,omitempty"` 30 | 31 | LsdExceptions []LsdException `xml:",any"` 32 | } 33 | 34 | func (l LatentStyle) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 35 | start.Name.Local = "w:latentStyles" 36 | 37 | if l.DefLockedState != nil { 38 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "", Local: "w:defLockedState"}, Value: string(*l.DefLockedState)}) 39 | } 40 | 41 | if l.DefUIPriority != nil { 42 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "", Local: "w:defUIPriority"}, Value: strconv.Itoa(*l.DefUIPriority)}) 43 | } 44 | 45 | if l.DefSemiHidden != nil { 46 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "", Local: "w:defSemiHidden"}, Value: string(*l.DefSemiHidden)}) 47 | } 48 | 49 | if l.DefUnhideWhenUsed != nil { 50 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "", Local: "w:defUnhideWhenUsed"}, Value: string(*l.DefUnhideWhenUsed)}) 51 | } 52 | 53 | if l.DefQFormat != nil { 54 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "", Local: "w:defQFormat"}, Value: string(*l.DefQFormat)}) 55 | } 56 | 57 | if l.Count != nil { 58 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "", Local: "w:count"}, Value: strconv.Itoa(*l.Count)}) 59 | } 60 | 61 | if err := e.EncodeToken(start); err != nil { 62 | return err 63 | } 64 | 65 | for _, elem := range l.LsdExceptions { 66 | if err := elem.MarshalXML(e, xml.StartElement{ 67 | Name: xml.Name{Local: "w:lsdException"}, 68 | }); err != nil { 69 | return fmt.Errorf("LsdException: %w", err) 70 | } 71 | } 72 | 73 | return e.EncodeToken(start.End()) 74 | } 75 | -------------------------------------------------------------------------------- /wml/ctypes/lsdException.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | 7 | "github.com/gomutex/godocx/wml/stypes" 8 | ) 9 | 10 | // Latent Style Exception 11 | type LsdException struct { 12 | Name string `xml:"name,attr"` 13 | Locked *stypes.OnOff `xml:"locked,attr,omitempty"` 14 | UIPriority *int `xml:"uiPriority,attr,omitempty"` 15 | SemiHidden *stypes.OnOff `xml:"semiHidden,attr,omitempty"` 16 | UnhideWhenUsed *stypes.OnOff `xml:"unhideWhenUsed,attr,omitempty"` 17 | QFormat *stypes.OnOff `xml:"qFormat,attr,omitempty"` 18 | } 19 | 20 | func (l LsdException) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 21 | 22 | start.Name.Local = "w:lsdException" 23 | 24 | start.Attr = append(start.Attr, 25 | xml.Attr{Name: xml.Name{Local: "w:name"}, Value: l.Name}, 26 | ) 27 | 28 | if l.Locked != nil { 29 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:locked"}, Value: string(*l.Locked)}) 30 | } 31 | if l.UIPriority != nil { 32 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:uiPriority"}, Value: strconv.Itoa(*l.UIPriority)}) 33 | } 34 | if l.SemiHidden != nil { 35 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:semiHidden"}, Value: string(*l.SemiHidden)}) 36 | } 37 | if l.UnhideWhenUsed != nil { 38 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:unhideWhenUsed"}, Value: string(*l.UnhideWhenUsed)}) 39 | } 40 | if l.QFormat != nil { 41 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:qFormat"}, Value: string(*l.QFormat)}) 42 | } 43 | 44 | return e.EncodeElement("", start) 45 | } 46 | -------------------------------------------------------------------------------- /wml/ctypes/numPr.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | ) 7 | 8 | // Numbering Definition Instance Reference 9 | type NumProp struct { 10 | //Numbering Level Reference 11 | ILvl *DecimalNum `xml:"ilvl,omitempty"` 12 | 13 | //Numbering Definition Instance Reference 14 | NumID *DecimalNum `xml:"numId,omitempty"` 15 | 16 | //Previous Paragraph Numbering Properties 17 | NumChange *TrackChangeNum `xml:"numberingChange,omitempty"` 18 | 19 | //Inserted Numbering Properties 20 | Ins *TrackChange `xml:"ins,omitempty"` 21 | } 22 | 23 | // NewNumberingProperty creates a new NumberingProperty instance. 24 | func NewNumberingProperty() *NumProp { 25 | return &NumProp{} 26 | } 27 | 28 | func (n NumProp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 29 | start.Name.Local = "w:numPr" 30 | 31 | err := e.EncodeToken(start) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | if n.ILvl != nil { 37 | if err = n.ILvl.MarshalXML(e, xml.StartElement{ 38 | Name: xml.Name{Local: "w:ilvl"}, 39 | }); err != nil { 40 | return fmt.Errorf("ILvl: %w", err) 41 | } 42 | } 43 | 44 | if n.NumID != nil { 45 | if err = n.NumID.MarshalXML(e, xml.StartElement{ 46 | Name: xml.Name{Local: "w:numId"}, 47 | }); err != nil { 48 | return fmt.Errorf("NumID: %w", err) 49 | } 50 | } 51 | 52 | if n.NumChange != nil { 53 | if err = n.NumChange.MarshalXML(e, xml.StartElement{ 54 | Name: xml.Name{Local: "w:numberingChange"}, 55 | }); err != nil { 56 | return fmt.Errorf("NumChange: %w", err) 57 | } 58 | } 59 | 60 | if n.Ins != nil { 61 | if err = n.Ins.MarshalXML(e, xml.StartElement{ 62 | Name: xml.Name{Local: "w:ins"}, 63 | }); err != nil { 64 | return fmt.Errorf("NumID: %w", err) 65 | } 66 | } 67 | 68 | return e.EncodeToken(start.End()) 69 | } 70 | -------------------------------------------------------------------------------- /wml/ctypes/onoff.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // Optional Bool Element: Helper element that only has one attribute which is optional 10 | type OnOff struct { 11 | Val *stypes.OnOff `xml:"val,attr,omitempty"` 12 | } 13 | 14 | func OnOffFromBool(value bool) *OnOff { 15 | o := stypes.OnOffFalse 16 | if value { 17 | o = stypes.OnOffTrue 18 | } 19 | 20 | return &OnOff{ 21 | Val: &o, 22 | } 23 | } 24 | 25 | func OnOffFromStr(value string) (*OnOff, error) { 26 | o, err := stypes.OnOffFromStr(value) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return &OnOff{ 31 | Val: &o, 32 | }, nil 33 | } 34 | 35 | // Disable sets the value to false and valexists true 36 | func (n *OnOff) Disable() { 37 | o := stypes.OnOffFalse 38 | n.Val = &o 39 | } 40 | 41 | // MarshalXML implements the xml.Marshaler interface for the Bold type. 42 | // It encodes the instance into XML using the "w:XMLName" element with a "w:val" attribute. 43 | func (n OnOff) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 44 | if n.Val != nil { // Add val attribute only if the val exists 45 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: string(*n.Val)}) 46 | } 47 | 48 | err := e.EncodeToken(start) 49 | if err != nil { 50 | return err 51 | } 52 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 53 | } 54 | -------------------------------------------------------------------------------- /wml/ctypes/onoff_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/internal" 9 | "github.com/gomutex/godocx/wml/stypes" 10 | ) 11 | 12 | func TestOnOff_MarshalXML(t *testing.T) { 13 | 14 | tests := []struct { 15 | name string 16 | input OnOff 17 | expected string 18 | }{ 19 | { 20 | name: "With value", 21 | input: OnOff{Val: internal.ToPtr(stypes.OnOffFalse)}, 22 | expected: ``, 23 | }, 24 | { 25 | name: "Empty value", 26 | input: OnOff{Val: nil}, 27 | expected: ``, 28 | }, 29 | } 30 | 31 | for _, tt := range tests { 32 | t.Run(tt.name, func(t *testing.T) { 33 | var result strings.Builder 34 | encoder := xml.NewEncoder(&result) 35 | start := xml.StartElement{Name: xml.Name{Local: "w:rStyle"}} 36 | 37 | err := tt.input.MarshalXML(encoder, start) 38 | if err != nil { 39 | t.Fatalf("Error marshaling XML: %v", err) 40 | } 41 | 42 | // Finalize encoding 43 | encoder.Flush() 44 | 45 | if result.String() != tt.expected { 46 | t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) 47 | } 48 | }) 49 | } 50 | } 51 | 52 | func TestOnOff_UnmarshalXML(t *testing.T) { 53 | tests := []struct { 54 | name string 55 | inputXML string 56 | expected OnOff 57 | }{ 58 | { 59 | name: "With value", 60 | inputXML: ``, 61 | expected: OnOff{Val: internal.ToPtr(stypes.OnOffTrue)}, 62 | }, 63 | { 64 | name: "Empty value", 65 | inputXML: ``, 66 | expected: OnOff{Val: nil}, 67 | }, 68 | } 69 | 70 | for _, tt := range tests { 71 | t.Run(tt.name, func(t *testing.T) { 72 | var result OnOff 73 | 74 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 75 | if err != nil { 76 | t.Fatalf("Error unmarshaling XML: %v", err) 77 | } 78 | 79 | if err = internal.ComparePtr("Val", tt.expected.Val, result.Val); err != nil { 80 | t.Error(err) 81 | } 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /wml/ctypes/pBdr.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | ) 7 | 8 | type ParaBorder struct { 9 | Top *Border `xml:"top,omitempty"` 10 | Left *Border `xml:"left,omitempty"` 11 | Right *Border `xml:"right,omitempty"` 12 | Bottom *Border `xml:"bottom,omitempty"` 13 | Between *Border `xml:"between,omitempty"` 14 | Bar *Border `xml:"bar,omitempty"` 15 | } 16 | 17 | func (p ParaBorder) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 18 | start.Name.Local = "w:pBdr" 19 | err := e.EncodeToken(start) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | if p.Top != nil { 25 | if err = p.Top.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:top"}}); err != nil { 26 | return fmt.Errorf("Paragraph border-Top: %w", err) 27 | } 28 | } 29 | 30 | if p.Left != nil { 31 | if err = p.Left.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:left"}}); err != nil { 32 | return fmt.Errorf("Paragraph border-Left: %w", err) 33 | } 34 | } 35 | 36 | if p.Right != nil { 37 | if err = p.Right.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:right"}}); err != nil { 38 | return fmt.Errorf("Paragraph border-Right: %w", err) 39 | } 40 | } 41 | 42 | if p.Bottom != nil { 43 | if err = p.Bottom.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:bottom"}}); err != nil { 44 | return fmt.Errorf("Paragraph border-Bottom: %w", err) 45 | } 46 | } 47 | 48 | if p.Between != nil { 49 | if err = p.Between.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:between"}}); err != nil { 50 | return fmt.Errorf("Paragraph border-Between: %w", err) 51 | } 52 | } 53 | 54 | if p.Bar != nil { 55 | if err = p.Bar.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:bar"}}); err != nil { 56 | return fmt.Errorf("Paragraph border-Bar: %w", err) 57 | } 58 | } 59 | 60 | return e.EncodeToken(start.End()) 61 | } 62 | -------------------------------------------------------------------------------- /wml/ctypes/pageMargin.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // PageMargin represents the page margins of a Word document. 9 | type PageMargin struct { 10 | Left *int `xml:"left,attr,omitempty"` 11 | Right *int `xml:"right,attr,omitempty"` 12 | Gutter *int `xml:"gutter,attr,omitempty"` 13 | Header *int `xml:"header,attr,omitempty"` 14 | Top *int `xml:"top,attr,omitempty"` 15 | Footer *int `xml:"footer,attr,omitempty"` 16 | Bottom *int `xml:"bottom,attr,omitempty"` 17 | } 18 | 19 | // MarshalXML implements the xml.Marshaler interface for the PageMargin type. 20 | // It encodes the PageMargin to its corresponding XML representation. 21 | func (p PageMargin) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 22 | start.Name.Local = "w:pgMar" 23 | 24 | start.Attr = []xml.Attr{} 25 | 26 | attrs := []struct { 27 | local string 28 | value *int 29 | }{ 30 | {"w:left", p.Left}, 31 | {"w:right", p.Right}, 32 | {"w:gutter", p.Gutter}, 33 | {"w:header", p.Header}, 34 | {"w:top", p.Top}, 35 | {"w:footer", p.Footer}, 36 | {"w:bottom", p.Bottom}, 37 | } 38 | 39 | for _, attr := range attrs { 40 | if attr.value == nil { 41 | continue 42 | } 43 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: attr.local}, Value: strconv.Itoa(*attr.value)}) 44 | } 45 | 46 | return e.EncodeElement("", start) 47 | } 48 | -------------------------------------------------------------------------------- /wml/ctypes/pageNum.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // PageNumbering represents the page numbering format in a Word document. 10 | type PageNumbering struct { 11 | Format stypes.NumFmt `xml:"fmt,attr,omitempty"` 12 | } 13 | 14 | // MarshalXML implements the xml.Marshaler interface for the PageNumbering type. 15 | // It encodes the PageNumbering to its corresponding XML representation. 16 | func (p PageNumbering) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 17 | start.Name.Local = "w:pgNumType" 18 | if p.Format != "" { 19 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:fmt"}, Value: string(p.Format)}) 20 | } 21 | return e.EncodeElement("", start) 22 | } 23 | -------------------------------------------------------------------------------- /wml/ctypes/pageNum_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/wml/stypes" 9 | ) 10 | 11 | func TestPageNumbering_MarshalXML(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | input PageNumbering 15 | expected string 16 | }{ 17 | { 18 | name: "With format", 19 | input: PageNumbering{Format: stypes.NumFmtDecimal}, 20 | expected: ``, 21 | }, 22 | { 23 | name: "Without format", 24 | input: PageNumbering{}, 25 | expected: ``, 26 | }, 27 | } 28 | 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | var result strings.Builder 32 | encoder := xml.NewEncoder(&result) 33 | start := xml.StartElement{Name: xml.Name{Local: "w:pgNumType"}} 34 | 35 | err := tt.input.MarshalXML(encoder, start) 36 | if err != nil { 37 | t.Fatalf("Error marshaling XML: %v", err) 38 | } 39 | 40 | encoder.Flush() 41 | 42 | if result.String() != tt.expected { 43 | t.Errorf("Expected XML:\n%s\n\nGot:\n%s", tt.expected, result.String()) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | func TestPageNumbering_UnmarshalXML(t *testing.T) { 50 | tests := []struct { 51 | name string 52 | inputXML string 53 | expected PageNumbering 54 | }{ 55 | { 56 | name: "With format", 57 | inputXML: ``, 58 | expected: PageNumbering{Format: stypes.NumFmtDecimal}, 59 | }, 60 | { 61 | name: "Without format", 62 | inputXML: ``, 63 | expected: PageNumbering{}, 64 | }, 65 | } 66 | 67 | for _, tt := range tests { 68 | t.Run(tt.name, func(t *testing.T) { 69 | var result PageNumbering 70 | 71 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 72 | if err != nil { 73 | t.Fatalf("Error during unmarshaling: %v", err) 74 | } 75 | 76 | if result.Format != tt.expected.Format { 77 | t.Errorf("Expected Format %s but got %s", tt.expected.Format, result.Format) 78 | } 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /wml/ctypes/pageSize.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | 7 | "github.com/gomutex/godocx/wml/stypes" 8 | ) 9 | 10 | // Page Size : w:pgSz 11 | type PageSize struct { 12 | Width *uint64 `xml:"w,attr,omitempty"` 13 | Height *uint64 `xml:"h,attr,omitempty"` 14 | Orient stypes.PageOrient `xml:"orient,attr,omitempty"` 15 | Code *int `xml:"code,attr,omitempty"` 16 | } 17 | 18 | func (p PageSize) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 19 | start.Name.Local = "w:pgSz" 20 | 21 | if p.Width != nil { 22 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:w"}, Value: strconv.FormatUint(*p.Width, 10)}) 23 | } 24 | 25 | if p.Height != nil { 26 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:h"}, Value: strconv.FormatUint(*p.Height, 10)}) 27 | } 28 | 29 | if p.Orient != "" { 30 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:orient"}, Value: string(p.Orient)}) 31 | } 32 | 33 | if p.Code != nil { 34 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:code"}, Value: strconv.Itoa(*p.Code)}) 35 | } 36 | 37 | err := e.EncodeToken(start) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 43 | } 44 | -------------------------------------------------------------------------------- /wml/ctypes/pagesize_default.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import "github.com/gomutex/godocx/wml/stypes" 4 | 5 | /* 6 | code | name | size 7 | --------------------------- 8 | 1 | A4 | 210 × 297 mm 9 | 2 | Letter | 8.5 × 11 inches 10 | 3 | Legal | 8.5 × 14 inches 11 | 4 | A3 | 297 × 420 mm 12 | 5 | B4 | 250 × 353 mm 13 | 6 | B5 | 176 × 250 mm 14 | 9 | A5 | 148 × 210 mm 15 | 11 | A6 | 105 × 148 mm 16 | */ 17 | 18 | var ( 19 | // A1 20 | A1Width = uint64(23386) // 594mm in twips 21 | A1Height = uint64(33110) // 841mm in twips 22 | A1Code = 4 23 | A1 = &PageSize{ 24 | Width: &A1Width, 25 | Height: &A1Height, 26 | Orient: stypes.PageOrientPortrait, 27 | Code: &A1Code, 28 | } 29 | 30 | // A2 31 | A2Width = uint64(16535) // 420mm in twips 32 | A2Height = uint64(23386) // 594mm in twips 33 | A2Code = 4 34 | A2 = &PageSize{ 35 | Width: &A2Width, 36 | Height: &A2Height, 37 | Orient: stypes.PageOrientPortrait, 38 | Code: &A2Code, 39 | } 40 | 41 | // A3 42 | A3Width = uint64(11693) // 297mm in twips 43 | A3Height = uint64(16535) // 420mm in twips 44 | A3Code = 4 45 | A3 = &PageSize{ 46 | Width: &A3Width, 47 | Height: &A3Height, 48 | Orient: stypes.PageOrientPortrait, 49 | Code: &A3Code, 50 | } 51 | 52 | // A4 53 | A4Width = uint64(11906) // 210mm in twips 54 | A4Height = uint64(16838) // 297mm in twips 55 | A4Code = 1 56 | A4 = &PageSize{ 57 | Width: &A4Width, 58 | Height: &A4Height, 59 | Orient: stypes.PageOrientPortrait, 60 | Code: &A4Code, 61 | } 62 | 63 | // A5 64 | A5Width = uint64(8268) // 148mm in twips 65 | A5Height = uint64(11693) // 210mm in twips 66 | A5Code = 1 67 | A5 = &PageSize{ 68 | Width: &A5Width, 69 | Height: &A5Height, 70 | Orient: stypes.PageOrientPortrait, 71 | Code: &A5Code, 72 | } 73 | ) 74 | -------------------------------------------------------------------------------- /wml/ctypes/paragraph_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/gomutex/godocx/common/constants" 10 | ) 11 | 12 | func TestParagraphXML(t *testing.T) { 13 | // Create a sample paragraph 14 | p := Paragraph{} 15 | // p.Style("Heading1") 16 | // p.Numbering(1, 0) 17 | p.AddText("This is a sample paragraph.") 18 | 19 | // Marshal the paragraph to XML 20 | var buf bytes.Buffer 21 | 22 | encoder := xml.NewEncoder(&buf) 23 | start := xml.StartElement{Name: xml.Name{Local: "fake"}} 24 | if err := p.MarshalXML(encoder, start); err != nil { 25 | t.Errorf("Error during MarshalXML: %v", err) 26 | } 27 | 28 | err := encoder.Flush() 29 | if err != nil { 30 | t.Errorf("Error flushing encoder: %v", err) 31 | } 32 | 33 | // Unmarshal the XML back to a paragraph 34 | var paraUnmarshaled Paragraph 35 | ns := map[string]string{ 36 | "w": constants.WMLNamespace, 37 | } 38 | decoder := xml.NewDecoder(&buf) 39 | decoder.DefaultSpace = constants.WMLNamespace 40 | decoder.Entity = ns 41 | 42 | if err := decoder.Decode(¶Unmarshaled); err != nil { 43 | t.Errorf("Error unmarshaling XML to paragraph: %v", err) 44 | return 45 | } 46 | 47 | if !reflect.DeepEqual(p, paraUnmarshaled) { 48 | t.Errorf("Original and unmarshaled paragraphs are not equal.") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /wml/ctypes/rFonts.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // Run Fonts 10 | type RunFonts struct { 11 | Hint stypes.FontTypeHint `xml:"hint,attr,omitempty"` 12 | Ascii string `xml:"ascii,attr,omitempty"` 13 | HAnsi string `xml:"hAnsi,attr,omitempty"` 14 | EastAsia string `xml:"eastAsia,attr,omitempty"` 15 | CS string `xml:"cs,attr,omitempty"` 16 | AsciiTheme stypes.ThemeFont `xml:"asciiTheme,attr,omitempty"` 17 | HAnsiTheme stypes.ThemeFont `xml:"hAnsiTheme,attr,omitempty"` 18 | EastAsiaTheme stypes.ThemeFont `xml:"eastAsiaTheme,attr,omitempty"` 19 | CSTheme stypes.ThemeFont `xml:"cstheme,attr,omitempty"` 20 | } 21 | 22 | func (rf RunFonts) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 23 | start.Name.Local = "w:rFonts" 24 | // start.Attr = []xml.Attr{} 25 | 26 | if rf.EastAsia != "" { 27 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:eastAsia"}, Value: rf.EastAsia}) 28 | } 29 | 30 | if rf.Hint != "" { 31 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:hint"}, Value: string(rf.Hint)}) 32 | } 33 | 34 | if rf.Ascii != "" { 35 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:ascii"}, Value: rf.Ascii}) 36 | } 37 | 38 | if rf.HAnsi != "" { 39 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:hAnsi"}, Value: rf.HAnsi}) 40 | } 41 | 42 | if rf.CS != "" { 43 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:cs"}, Value: rf.CS}) 44 | } 45 | 46 | if rf.AsciiTheme != "" { 47 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:asciiTheme"}, Value: string(rf.AsciiTheme)}) 48 | } 49 | 50 | if rf.HAnsiTheme != "" { 51 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:hAnsiTheme"}, Value: string(rf.HAnsiTheme)}) 52 | } 53 | 54 | if rf.EastAsiaTheme != "" { 55 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:eastAsiaTheme"}, Value: string(rf.EastAsiaTheme)}) 56 | } 57 | 58 | if rf.CSTheme != "" { 59 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:cstheme"}, Value: string(rf.CSTheme)}) 60 | } 61 | 62 | err := e.EncodeToken(start) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 68 | } 69 | -------------------------------------------------------------------------------- /wml/ctypes/rFonts_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/wml/stypes" 9 | ) 10 | 11 | func TestRunFontsMarshalXML(t *testing.T) { 12 | rf := RunFonts{ 13 | Hint: stypes.FontTypeHintDefault, 14 | Ascii: "Arial", 15 | HAnsi: "Calibri", 16 | EastAsia: "SimSun", 17 | CS: "Arial", 18 | AsciiTheme: stypes.ThemeFontMajorAscii, 19 | HAnsiTheme: stypes.ThemeFontMajorHAnsi, 20 | EastAsiaTheme: stypes.ThemeFontMajorEastAsia, 21 | CSTheme: stypes.ThemeFontMajorBidi, 22 | } 23 | 24 | var output strings.Builder 25 | encoder := xml.NewEncoder(&output) 26 | err := rf.MarshalXML(encoder, xml.StartElement{}) 27 | if err != nil { 28 | t.Fatalf("Error marshaling XML: %v", err) 29 | } 30 | encoder.Flush() 31 | 32 | expected := `` 33 | if output.String() != expected { 34 | t.Errorf("Expected %s but got %s", expected, output.String()) 35 | } 36 | } 37 | 38 | func TestRunFontsUnmarshalXML(t *testing.T) { 39 | input := `` 40 | 41 | var rf RunFonts 42 | err := xml.Unmarshal([]byte(input), &rf) 43 | if err != nil { 44 | t.Fatalf("Error unmarshaling XML: %v", err) 45 | } 46 | 47 | expected := RunFonts{ 48 | Hint: stypes.FontTypeHintDefault, 49 | Ascii: "Arial", 50 | HAnsi: "Calibri", 51 | EastAsia: "SimSun", 52 | CS: "Arial", 53 | AsciiTheme: stypes.ThemeFontMajorAscii, 54 | HAnsiTheme: stypes.ThemeFontMajorHAnsi, 55 | EastAsiaTheme: stypes.ThemeFontMajorEastAsia, 56 | CSTheme: stypes.ThemeFontMajorBidi, 57 | } 58 | 59 | if rf != expected { 60 | t.Errorf("Expected %+v but got %+v", expected, rf) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /wml/ctypes/rngMkElems.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import "encoding/xml" 4 | 5 | // Range Markup elements 6 | type RngMarkupElem struct { 7 | } 8 | 9 | func (r RngMarkupElem) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 10 | // TODO: 11 | return nil 12 | } 13 | -------------------------------------------------------------------------------- /wml/ctypes/runstyle.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | // NewRunStyle creates a new RunStyle. 4 | func NewRunStyle(value string) *CTString { 5 | return NewCTString(value) 6 | } 7 | 8 | // DefaultRunStyle creates the default RunStyle with the value "Normal". 9 | func DefaultRunStyle() *CTString { 10 | return NewCTString("Normal") 11 | } 12 | -------------------------------------------------------------------------------- /wml/ctypes/runstyle_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewRunStyle(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | value string 11 | expected *CTString 12 | }{ 13 | { 14 | name: "Custom RunStyle", 15 | value: "CustomStyle", 16 | expected: NewCTString("CustomStyle"), 17 | }, 18 | { 19 | name: "Empty RunStyle", 20 | value: "", 21 | expected: NewCTString(""), 22 | }, 23 | } 24 | 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | result := NewRunStyle(tt.value) 28 | 29 | if result.Val != tt.expected.Val { 30 | t.Errorf("Expected Val %s but got %s", tt.expected.Val, result.Val) 31 | } 32 | }) 33 | } 34 | } 35 | 36 | func TestDefaultRunStyle(t *testing.T) { 37 | expected := NewCTString("Normal") 38 | result := DefaultRunStyle() 39 | 40 | if result.Val != expected.Val { 41 | t.Errorf("Expected Val %s but got %s", expected.Val, result.Val) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /wml/ctypes/spacing.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | 7 | "github.com/gomutex/godocx/wml/stypes" 8 | ) 9 | 10 | // Spacing Between Lines and Above/Below Paragraph 11 | type Spacing struct { 12 | //Spacing Above Paragraph 13 | Before *uint64 `xml:"before,attr,omitempty"` 14 | 15 | //Spacing Above Paragraph IN Line Units 16 | BeforeLines *int `xml:"beforeLines,attr,omitempty"` 17 | 18 | //Spacing Below Paragraph 19 | After *uint64 `xml:"after,attr,omitempty"` 20 | 21 | // Automatically Determine Spacing Above Paragraph 22 | BeforeAutospacing *stypes.OnOff `xml:"beforeAutospacing,attr,omitempty"` 23 | 24 | // Automatically Determine Spacing Below Paragraph 25 | AfterAutospacing *stypes.OnOff `xml:"afterAutospacing,attr,omitempty"` 26 | 27 | //Spacing Between Lines in Paragraph 28 | Line *int `xml:"line,omitempty"` 29 | 30 | //Type of Spacing Between Lines 31 | LineRule *stypes.LineSpacingRule `xml:"lineRule,attr,omitempty"` 32 | } 33 | 34 | func NewParagraphSpacing(before uint64, after uint64) *Spacing { 35 | return &Spacing{ 36 | Before: &before, 37 | After: &after, 38 | } 39 | } 40 | 41 | func (s Spacing) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 42 | start.Name.Local = "w:spacing" 43 | 44 | start.Attr = []xml.Attr{} 45 | 46 | if s.Before != nil { 47 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:before"}, Value: strconv.FormatUint(*s.Before, 10)}) 48 | } 49 | 50 | if s.After != nil { 51 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:after"}, Value: strconv.FormatUint(*s.After, 10)}) 52 | } 53 | 54 | if s.BeforeLines != nil { 55 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:beforeLines"}, Value: strconv.Itoa(*s.BeforeLines)}) 56 | } 57 | 58 | if s.BeforeAutospacing != nil { 59 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:beforeAutospacing"}, Value: string(*s.BeforeAutospacing)}) 60 | } 61 | 62 | if s.AfterAutospacing != nil { 63 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:afterAutospacing"}, Value: string(*s.AfterAutospacing)}) 64 | } 65 | 66 | if s.Line != nil { 67 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:line"}, Value: strconv.Itoa(*s.Line)}) 68 | } 69 | 70 | if s.LineRule != nil { 71 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:lineRule"}, Value: string(*s.LineRule)}) 72 | } 73 | 74 | return e.EncodeElement("", start) 75 | } 76 | -------------------------------------------------------------------------------- /wml/ctypes/spacing_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/internal" 9 | "github.com/gomutex/godocx/wml/stypes" 10 | ) 11 | 12 | func TestSpacing_MarshalXML(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | input Spacing 16 | expected string 17 | }{ 18 | { 19 | name: "All fields set", 20 | input: Spacing{ 21 | Before: internal.ToPtr(uint64(120)), 22 | After: internal.ToPtr(uint64(240)), 23 | BeforeLines: internal.ToPtr(10), 24 | BeforeAutospacing: internal.ToPtr(stypes.OnOffOn), 25 | AfterAutospacing: internal.ToPtr(stypes.OnOffOff), 26 | Line: internal.ToPtr(360), 27 | LineRule: internal.ToPtr(stypes.LineSpacingRuleExact), 28 | }, 29 | expected: ``, 30 | }, 31 | { 32 | name: "Some fields set", 33 | input: Spacing{ 34 | Before: internal.ToPtr(uint64(120)), 35 | After: internal.ToPtr(uint64(240)), 36 | Line: internal.ToPtr(360), 37 | LineRule: internal.ToPtr(stypes.LineSpacingRuleAuto), 38 | }, 39 | expected: ``, 40 | }, 41 | { 42 | name: "No fields set", 43 | input: Spacing{}, 44 | expected: ``, 45 | }, 46 | } 47 | 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | var result strings.Builder 51 | encoder := xml.NewEncoder(&result) 52 | 53 | start := xml.StartElement{Name: xml.Name{Local: "w:spacing"}} 54 | if err := tt.input.MarshalXML(encoder, start); err != nil { 55 | t.Fatalf("Error marshaling XML: %v", err) 56 | } 57 | 58 | // Finalize encoding 59 | encoder.Flush() 60 | 61 | got := strings.TrimSpace(result.String()) 62 | if got != tt.expected { 63 | t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, got) 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /wml/ctypes/tab.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/gomutex/godocx/wml/stypes" 9 | ) 10 | 11 | // Custom Tab Stop 12 | type Tab struct { 13 | // Tab Stop Type 14 | Val stypes.CustTabStop `xml:"val,attr,omitempty"` 15 | 16 | //Tab Stop Position 17 | Position int `xml:"pos,attr,omitempty"` 18 | 19 | //Custom Tab Stop Leader Character 20 | LeaderChar *stypes.CustLeadChar `xml:"leader,attr,omitempty"` 21 | } 22 | 23 | func (t Tab) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 24 | start.Name.Local = "w:tab" 25 | start.Attr = []xml.Attr{} 26 | 27 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: string(t.Val)}) 28 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:pos"}, Value: strconv.Itoa(t.Position)}) 29 | 30 | if t.LeaderChar != nil { 31 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:leader"}, Value: string(*t.LeaderChar)}) 32 | } 33 | 34 | err := e.EncodeToken(start) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 40 | } 41 | 42 | type Tabs struct { 43 | Tab []Tab `xml:"tab,omitempty"` 44 | } 45 | 46 | func (t Tabs) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 47 | 48 | if len(t.Tab) == 0 { 49 | return nil 50 | } 51 | 52 | // Create the enclosing XML element 53 | start.Name = xml.Name{Local: "w:tabs"} 54 | 55 | err := e.EncodeToken(start) 56 | if err != nil { 57 | return fmt.Errorf("error encoding start element: %v", err) 58 | } 59 | 60 | for _, tab := range t.Tab { 61 | if err := tab.MarshalXML(e, xml.StartElement{}); err != nil { 62 | return fmt.Errorf("error encoding tab: %v", err) 63 | } 64 | } 65 | 66 | err = e.EncodeToken(start.End()) 67 | if err != nil { 68 | return fmt.Errorf("error encoding end element: %v", err) 69 | } 70 | 71 | return nil 72 | } 73 | 74 | type PTab struct { 75 | Alignment stypes.PTabAlignment `xml:"alignment,attr,omitempty"` 76 | RelativeTo stypes.PTabRelativeTo `xml:"relativeTo,attr,omitempty"` 77 | Leader stypes.PTabLeader `xml:"leader,attr,omitempty"` 78 | } 79 | 80 | func (t PTab) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 81 | start.Attr = []xml.Attr{ 82 | {Name: xml.Name{Local: "w:alignment"}, Value: string(t.Alignment)}, 83 | {Name: xml.Name{Local: "w:relativeTo"}, Value: string(t.RelativeTo)}, 84 | {Name: xml.Name{Local: "w:leader"}, Value: string(t.Leader)}, 85 | } 86 | 87 | return e.EncodeElement("", start) 88 | } 89 | -------------------------------------------------------------------------------- /wml/ctypes/tblBorders.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | ) 6 | 7 | type TableBorders struct { 8 | // 1. Table Top Border 9 | Top *Border `xml:"top,omitempty"` 10 | 11 | // 2. Table Left Border 12 | Left *Border `xml:"left,omitempty"` 13 | 14 | // 3. Table Bottom Border 15 | Bottom *Border `xml:"bottom,omitempty"` 16 | 17 | // 4. Table Right Border 18 | Right *Border `xml:"right,omitempty"` 19 | 20 | // 5. Table Inside Horizontal Edges Border 21 | InsideH *Border `xml:"insideH,omitempty"` 22 | // 6. Table Inside Vertical Edges Border 23 | InsideV *Border `xml:"insideV,omitempty"` 24 | } 25 | 26 | func DefaultTableBorders() *TableBorders { 27 | return &TableBorders{} 28 | } 29 | 30 | func (t TableBorders) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 31 | start.Name.Local = "w:tblBorders" 32 | 33 | if err := e.EncodeToken(start); err != nil { 34 | return err 35 | } 36 | 37 | // 1. Top 38 | if t.Top != nil { 39 | if err := t.Top.MarshalXML(e, xml.StartElement{ 40 | Name: xml.Name{Local: "w:top"}, 41 | }); err != nil { 42 | return err 43 | } 44 | } 45 | 46 | // 2. Left 47 | if t.Left != nil { 48 | if err := t.Left.MarshalXML(e, xml.StartElement{ 49 | Name: xml.Name{Local: "w:left"}, 50 | }); err != nil { 51 | return err 52 | } 53 | } 54 | 55 | // 3. Bottom 56 | if t.Bottom != nil { 57 | if err := t.Bottom.MarshalXML(e, xml.StartElement{ 58 | Name: xml.Name{Local: "w:bottom"}, 59 | }); err != nil { 60 | return err 61 | } 62 | } 63 | 64 | // 4. Right 65 | if t.Right != nil { 66 | if err := t.Right.MarshalXML(e, xml.StartElement{ 67 | Name: xml.Name{Local: "w:right"}, 68 | }); err != nil { 69 | return err 70 | } 71 | } 72 | 73 | // 5. insideH 74 | if t.InsideH != nil { 75 | if err := t.InsideH.MarshalXML(e, xml.StartElement{ 76 | Name: xml.Name{Local: "w:insideH"}, 77 | }); err != nil { 78 | return err 79 | } 80 | } 81 | 82 | // 6. InsideV 83 | if t.InsideV != nil { 84 | if err := t.InsideV.MarshalXML(e, xml.StartElement{ 85 | Name: xml.Name{Local: "w:insideV"}, 86 | }); err != nil { 87 | return err 88 | } 89 | } 90 | 91 | return e.EncodeToken(xml.EndElement{Name: start.Name}) 92 | 93 | } 94 | -------------------------------------------------------------------------------- /wml/ctypes/tblHeight.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | "strconv" 7 | 8 | "github.com/gomutex/godocx/wml/stypes" 9 | ) 10 | 11 | // TableRowHeight represents the height of a table row in a document. 12 | type TableRowHeight struct { 13 | Val *int `xml:"val,attr,omitempty"` 14 | HRule *stypes.HeightRule `xml:"hRule,attr,omitempty"` 15 | } 16 | 17 | // NewTableRowHeight creates a new TableRowHeight instance. 18 | func NewTableRowHeight(val int, hRule stypes.HeightRule) *TableRowHeight { 19 | return &TableRowHeight{ 20 | Val: &val, 21 | HRule: &hRule, 22 | } 23 | } 24 | 25 | // MarshalXML marshals TableRowHeight to XML. 26 | func (h TableRowHeight) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 27 | if h.Val != nil { 28 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: strconv.Itoa(*h.Val)}) 29 | } 30 | if h.HRule != nil { 31 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:hRule"}, Value: string(*h.HRule)}) 32 | } 33 | return e.EncodeElement("", start) 34 | } 35 | 36 | // UnmarshalXML unmarshals XML into TableRowHeight. 37 | func (h *TableRowHeight) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 38 | for _, attr := range start.Attr { 39 | switch attr.Name.Local { 40 | case "val": 41 | val, err := strconv.Atoi(attr.Value) 42 | if err != nil { 43 | return err 44 | } 45 | h.Val = &val 46 | case "hRule": 47 | hrule, err := stypes.HeightRuleFromStr(attr.Value) 48 | if err != nil { 49 | return err 50 | } 51 | h.HRule = &hrule 52 | } 53 | } 54 | return d.Skip() // skip any inner elements 55 | } 56 | 57 | // HeightRuleFromStr converts a string to HeightRule type. 58 | func HeightRuleFromStr(value string) (stypes.HeightRule, error) { 59 | switch value { 60 | case "auto": 61 | return stypes.HeightRuleAuto, nil 62 | case "exact": 63 | return stypes.HeightRuleExact, nil 64 | case "atLeast": 65 | return stypes.HeightRuleAtLeast, nil 66 | default: 67 | return "", errors.New("Invalid HeightRule value") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /wml/ctypes/tblLayout.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/gomutex/godocx/wml/stypes" 7 | ) 8 | 9 | // TableLayout represents the layout of a table in a document. 10 | type TableLayout struct { 11 | LayoutType *stypes.TableLayout `xml:"type,attr,omitempty"` 12 | } 13 | 14 | func DefaultTableLayout() *TableLayout { 15 | return &TableLayout{} 16 | } 17 | 18 | // NewTableLayout creates a new TableLayout instance. 19 | func NewTableLayout(t stypes.TableLayout) *TableLayout { 20 | return &TableLayout{LayoutType: &t} 21 | } 22 | 23 | // MarshalXML implements the xml.Marshaler interface for TableLayout. 24 | func (t TableLayout) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 25 | start.Name.Local = "w:tblLayout" 26 | if t.LayoutType != nil { 27 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:type"}, Value: string(*t.LayoutType)}) 28 | } 29 | return e.EncodeElement("", start) 30 | } 31 | -------------------------------------------------------------------------------- /wml/ctypes/tblLayout_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/internal" 9 | "github.com/gomutex/godocx/wml/stypes" 10 | ) 11 | 12 | func TestTableLayout_MarshalXML(t *testing.T) { 13 | layout := &TableLayout{LayoutType: internal.ToPtr(stypes.TableLayoutFixed)} 14 | 15 | expected := `` 16 | 17 | var builder strings.Builder 18 | encoder := xml.NewEncoder(&builder) 19 | err := encoder.Encode(layout) 20 | if err != nil { 21 | t.Fatalf("Error encoding TableLayout: %v", err) 22 | } 23 | 24 | result := builder.String() 25 | if result != expected { 26 | t.Errorf("Unexpected XML. Expected: %s, Got: %s", expected, result) 27 | } 28 | } 29 | 30 | func TestLayout_UnmarshalXML(t *testing.T) { 31 | tests := []struct { 32 | name string 33 | inputXML string 34 | expected TableLayout 35 | expectFail bool // Whether unmarshalling is expected to fail 36 | }{ 37 | { 38 | name: "Test with Overlap Value `never`", 39 | inputXML: ``, 40 | expected: TableLayout{LayoutType: internal.ToPtr(stypes.TableLayoutFixed)}, 41 | }, 42 | } 43 | 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | var result TableLayout 47 | err := xml.Unmarshal([]byte(tt.inputXML), &result) 48 | 49 | if err != nil { 50 | t.Fatalf("Error unmarshaling XML: %v", err) 51 | } 52 | 53 | err = internal.ComparePtr("Type", tt.expected.LayoutType, result.LayoutType) 54 | if err != nil { 55 | t.Error(err) 56 | } 57 | 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /wml/ctypes/tblPrEx.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import "encoding/xml" 4 | 5 | type PropException struct { 6 | } 7 | 8 | func (p PropException) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 9 | // TODO: 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /wml/ctypes/tblWidth.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | 7 | "github.com/gomutex/godocx/wml/stypes" 8 | ) 9 | 10 | // TableWidth represents the width of a table in a document. 11 | type TableWidth struct { 12 | Width *int `xml:"w,attr,omitempty"` 13 | WidthType *stypes.TableWidth `xml:"type,attr,omitempty"` 14 | } 15 | 16 | func NewTableWidth(width int, widthType stypes.TableWidth) *TableWidth { 17 | return &TableWidth{ 18 | Width: &width, 19 | WidthType: &widthType, 20 | } 21 | } 22 | 23 | func (t TableWidth) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { 24 | 25 | if t.Width != nil { 26 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:w"}, Value: strconv.Itoa(*t.Width)}) 27 | } 28 | 29 | if t.WidthType != nil { 30 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:type"}, Value: string(*t.WidthType)}) 31 | } 32 | 33 | return e.EncodeElement("", start) 34 | } 35 | -------------------------------------------------------------------------------- /wml/ctypes/text.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "strings" 7 | ) 8 | 9 | type Text struct { 10 | Text string 11 | Space *string 12 | } 13 | 14 | const ( 15 | TextSpaceDefault = "default" 16 | TextSpacePreserve = "preserve" 17 | ) 18 | 19 | func NewText() *Text { 20 | return &Text{} 21 | } 22 | 23 | func TextFromString(text string) *Text { 24 | t := &Text{Text: text} 25 | if strings.TrimSpace(text) != text { 26 | xmlSpace := "preserve" 27 | t.Space = &xmlSpace 28 | } 29 | return t 30 | } 31 | 32 | func (t Text) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { 33 | 34 | if t.Space != nil { 35 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xml:space"}, Value: *t.Space}) 36 | } 37 | 38 | if err = e.EncodeElement(t.Text, start); err != nil { 39 | return err 40 | } 41 | 42 | return nil 43 | } 44 | 45 | func (t *Text) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) { 46 | var buf bytes.Buffer 47 | 48 | for _, attr := range start.Attr { 49 | if attr.Name.Local == "space" { 50 | t.Space = &attr.Value 51 | break 52 | } 53 | } 54 | 55 | for { 56 | token, err := d.Token() 57 | if err != nil { 58 | return err 59 | } 60 | 61 | switch tokenElem := token.(type) { 62 | case xml.CharData: 63 | buf.Write([]byte(tokenElem)) 64 | case xml.EndElement: 65 | if tokenElem == start.End() { 66 | t.Text = buf.String() 67 | return nil 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /wml/ctypes/text_test.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gomutex/godocx/internal" 9 | ) 10 | 11 | func TestTextMarshalXML(t *testing.T) { 12 | testCases := []struct { 13 | input *Text 14 | expected string 15 | }{ 16 | {NewText(), ``}, 17 | {TextFromString("Hello, World!"), `Hello, World!`}, 18 | } 19 | 20 | for _, tc := range testCases { 21 | var result strings.Builder 22 | encoder := xml.NewEncoder(&result) 23 | 24 | start := xml.StartElement{Name: xml.Name{Local: "w:t"}} 25 | err := tc.input.MarshalXML(encoder, start) 26 | 27 | if err != nil { 28 | t.Errorf("Error during MarshalXML: %v", err) 29 | } 30 | 31 | err = encoder.Flush() 32 | if err != nil { 33 | t.Errorf("Error flushing encoder: %v", err) 34 | } 35 | 36 | if result.String() != tc.expected { 37 | t.Errorf("Expected XML:\n%s\n\nActual XML:\n%s", tc.expected, result.String()) 38 | } 39 | } 40 | } 41 | 42 | func TestTextUnmarshalXML(t *testing.T) { 43 | testCases := []struct { 44 | input string 45 | expected *Text 46 | }{ 47 | {``, NewText()}, 48 | {`Hello, World!`, &Text{ 49 | Text: "Hello, World!", 50 | Space: internal.ToPtr("preserve"), 51 | }}, 52 | {`Some text`, &Text{ 53 | Text: "Some text", 54 | Space: internal.ToPtr("preserve"), 55 | }}, 56 | } 57 | 58 | for _, tc := range testCases { 59 | t.Run(tc.input, func(t *testing.T) { 60 | decoder := xml.NewDecoder(strings.NewReader(tc.input)) 61 | var result Text 62 | 63 | startToken, err := decoder.Token() 64 | if err != nil { 65 | t.Fatalf("Error getting start token: %v", err) 66 | } 67 | 68 | err = result.UnmarshalXML(decoder, startToken.(xml.StartElement)) 69 | if err != nil { 70 | t.Fatalf("Error during UnmarshalXML: %v", err) 71 | } 72 | 73 | if result.Text != tc.expected.Text { 74 | t.Errorf("Expected text %q, but got %q", tc.expected.Text, result.Text) 75 | } 76 | 77 | if err := internal.ComparePtr("space", tc.expected.Space, result.Space); err != nil { 78 | t.Errorf(err.Error()) 79 | } 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /wml/ctypes/trackChange.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // TrackChange represents the complex type for track change 9 | type TrackChange struct { 10 | ID int `xml:"id,attr"` 11 | Author string `xml:"author,attr"` 12 | Date *string `xml:"date,attr,omitempty"` 13 | } 14 | 15 | func (t TrackChange) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 16 | start.Attr = []xml.Attr{ 17 | {Name: xml.Name{Local: "w:id"}, Value: strconv.Itoa(t.ID)}, 18 | {Name: xml.Name{Local: "w:author"}, Value: t.Author}, 19 | } 20 | 21 | if t.Date != nil { 22 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:date"}, Value: *t.Date}) 23 | } 24 | 25 | return e.EncodeElement("", start) 26 | } 27 | -------------------------------------------------------------------------------- /wml/ctypes/trkchgnum.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | // TrackChangeNum represents the complex type for track change numbering 9 | type TrackChangeNum struct { 10 | ID int `xml:"id,attr"` 11 | Author string `xml:"author,attr"` 12 | Date *string `xml:"date,attr,omitempty"` 13 | Original *string `xml:"original,attr,omitempty"` 14 | } 15 | 16 | func (t TrackChangeNum) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 17 | start.Attr = []xml.Attr{ 18 | {Name: xml.Name{Local: "w:id"}, Value: strconv.Itoa(t.ID)}, 19 | {Name: xml.Name{Local: "w:author"}, Value: t.Author}, 20 | } 21 | 22 | if t.Date != nil { 23 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:date"}, Value: *t.Date}) 24 | } 25 | 26 | if t.Original != nil { 27 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:original"}, Value: *t.Original}) 28 | } 29 | 30 | return e.EncodeElement("", start) 31 | } 32 | -------------------------------------------------------------------------------- /wml/ctypes/unit.go: -------------------------------------------------------------------------------- 1 | package ctypes 2 | 3 | // 单位转换函数 4 | func InchesToTwips(inches float64) uint64 { 5 | return uint64(inches * 1440) 6 | } 7 | 8 | func CentimetersToTwips(cm float64) uint64 { 9 | return uint64(cm * 567) 10 | } 11 | 12 | func MillimetersToTwips(mm float64) uint64 { 13 | return uint64(mm * 56.7) 14 | } 15 | 16 | func PointsToTwips(points float64) int { 17 | return int(points * 20) 18 | } 19 | -------------------------------------------------------------------------------- /wml/stypes/align.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // XAlign Type 9 | type XAlign string 10 | 11 | // Constants for valid values 12 | const ( 13 | XAlignLeft XAlign = "left" // Left XAligned Horizontally 14 | XAlignCenter XAlign = "center" // Centered Horizontally 15 | XAlignRight XAlign = "right" // Right XAligned Horizontally 16 | XAlignInside XAlign = "inside" // Inside 17 | XAlignOutside XAlign = "outside" // Outside 18 | ) 19 | 20 | // XAlignFromStr converts a string to XAlign type. 21 | func XAlignFromStr(value string) (XAlign, error) { 22 | switch value { 23 | case "left": 24 | return XAlignLeft, nil 25 | case "center": 26 | return XAlignCenter, nil 27 | case "right": 28 | return XAlignRight, nil 29 | case "inside": 30 | return XAlignInside, nil 31 | case "outside": 32 | return XAlignOutside, nil 33 | default: 34 | return "", errors.New("Invalid XAlign value") 35 | } 36 | } 37 | 38 | // UnmarshalXMLAttr unmarshals XML attribute into XAlign. 39 | func (x *XAlign) UnmarshalXMLAttr(attr xml.Attr) error { 40 | val, err := XAlignFromStr(attr.Value) 41 | if err != nil { 42 | return err 43 | } 44 | *x = val 45 | return nil 46 | } 47 | 48 | // YAlign Type 49 | type YAlign string 50 | 51 | // Constants for valid values 52 | const ( 53 | YAlignInline YAlign = "inline" // In line With Text 54 | YAlignTop YAlign = "top" // Top 55 | YAlignCenter YAlign = "center" // Centered Vertically 56 | YAlignBottom YAlign = "bottom" // Bottom 57 | YAlignInside YAlign = "inside" // Inside Anchor Extents 58 | YAlignOutside YAlign = "outside" // Outside Anchor Extents 59 | ) 60 | 61 | // YAlignFromStr converts a string to YAlign type. 62 | func YAlignFromStr(value string) (YAlign, error) { 63 | switch value { 64 | case "inline": 65 | return YAlignInline, nil 66 | case "top": 67 | return YAlignTop, nil 68 | case "center": 69 | return YAlignCenter, nil 70 | case "bottom": 71 | return YAlignBottom, nil 72 | case "inside": 73 | return YAlignInside, nil 74 | case "outside": 75 | return YAlignOutside, nil 76 | default: 77 | return "", errors.New("Invalid YAlign value") 78 | } 79 | } 80 | 81 | // UnmarshalXMLAttr unmarshals XML attribute into YAlign. 82 | func (x *YAlign) UnmarshalXMLAttr(attr xml.Attr) error { 83 | val, err := YAlignFromStr(attr.Value) 84 | if err != nil { 85 | return err 86 | } 87 | *x = val 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /wml/stypes/anchor.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // Anchor Type 9 | type Anchor string 10 | 11 | // Constants for valid values 12 | const ( 13 | AnchorText Anchor = "text" // Relative to Text Extents 14 | AnchorMargin Anchor = "margin" // Relative To Margin 15 | AnchorPage Anchor = "page" // Relative to Page 16 | ) 17 | 18 | // AnchorFromStr converts a string to Anchor type. 19 | func AnchorFromStr(value string) (Anchor, error) { 20 | switch value { 21 | case "text": 22 | return AnchorText, nil 23 | case "margin": 24 | return AnchorMargin, nil 25 | case "page": 26 | return AnchorPage, nil 27 | default: 28 | return "", errors.New("Invalid Anchor value") 29 | } 30 | } 31 | 32 | // UnmarshalXMLAttr unmarshals XML attribute into Anchor. 33 | func (h *Anchor) UnmarshalXMLAttr(attr xml.Attr) error { 34 | val, err := AnchorFromStr(attr.Value) 35 | if err != nil { 36 | return err 37 | } 38 | *h = val 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /wml/stypes/borderStyle_test.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "testing" 6 | ) 7 | 8 | // TestBorderStyleFromStr tests the BorderStyleFromStr function with a few example values. 9 | func TestBorderStyleFromStr(t *testing.T) { 10 | tests := []struct { 11 | input string 12 | expected BorderStyle 13 | hasError bool 14 | }{ 15 | {"nil", BorderStyleNil, false}, 16 | {"single", BorderStyleSingle, false}, 17 | {"double", BorderStyleDouble, false}, 18 | {"invalid", "", true}, 19 | } 20 | 21 | for _, test := range tests { 22 | result, err := BorderStyleFromStr(test.input) 23 | if (err != nil) != test.hasError { 24 | t.Errorf("BorderStyleFromStr(%q) error = %v, hasError %v", test.input, err, test.hasError) 25 | } 26 | if result != test.expected { 27 | t.Errorf("BorderStyleFromStr(%q) = %v, want %v", test.input, result, test.expected) 28 | } 29 | } 30 | } 31 | 32 | // TestUnmarshalXMLAttr tests the UnmarshalXMLAttr method for the BorderStyle type. 33 | func TestBorderStyleUnmarshal(t *testing.T) { 34 | tests := []struct { 35 | input xml.Attr 36 | expected BorderStyle 37 | hasError bool 38 | }{ 39 | {xml.Attr{Name: xml.Name{Local: "border"}, Value: "dotted"}, BorderStyleDotted, false}, 40 | {xml.Attr{Name: xml.Name{Local: "border"}, Value: "dashed"}, BorderStyleDashed, false}, 41 | {xml.Attr{Name: xml.Name{Local: "border"}, Value: "invalid"}, "", true}, 42 | } 43 | 44 | for _, test := range tests { 45 | var result BorderStyle 46 | err := result.UnmarshalXMLAttr(test.input) 47 | if (err != nil) != test.hasError { 48 | t.Errorf("UnmarshalXMLAttr(%v) error = %v, hasError %v", test.input, err, test.hasError) 49 | } 50 | if result != test.expected { 51 | t.Errorf("UnmarshalXMLAttr(%v) = %v, want %v", test.input, result, test.expected) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /wml/stypes/break.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type BreakType string 9 | 10 | const ( 11 | BreakTypePage BreakType = "page" // Page Break 12 | BreakTypeColumn BreakType = "column" // Column Break 13 | BreakTypeTextWrapping BreakType = "textWrapping" // Line Break 14 | BreakTypeInvalid BreakType = "" 15 | ) 16 | 17 | type BreakClear string 18 | 19 | const ( 20 | BreakClearNone BreakClear = "none" // Restart On Next Line 21 | BreakClearLeft BreakClear = "left" // Restart In Next Text Region When In Leftmost Position 22 | BreakClearRight BreakClear = "right" // Restart In Next Text Region When In Rightmost Position 23 | BreakClearAll BreakClear = "all" // Restart On Next Full Line 24 | BreakClearInvalid BreakClear = "" 25 | ) 26 | 27 | func BreakTypeFromStr(value string) (BreakType, error) { 28 | switch value { 29 | case "page": 30 | return BreakTypePage, nil 31 | case "column": 32 | return BreakTypeColumn, nil 33 | case "textWrapping": 34 | return BreakTypeTextWrapping, nil 35 | default: 36 | return BreakTypeInvalid, errors.New("invalid BreakType value") 37 | } 38 | } 39 | 40 | func (d *BreakType) UnmarshalXMLAttr(attr xml.Attr) error { 41 | val, err := BreakTypeFromStr(attr.Value) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | *d = val 47 | 48 | return nil 49 | } 50 | func BreakClearFromStr(value string) (BreakClear, error) { 51 | switch value { 52 | case "none": 53 | return BreakClearNone, nil 54 | case "left": 55 | return BreakClearLeft, nil 56 | case "right": 57 | return BreakClearRight, nil 58 | case "all": 59 | return BreakClearAll, nil 60 | default: 61 | return BreakClearInvalid, errors.New("invalid BreakClear value") 62 | } 63 | } 64 | 65 | func (d *BreakClear) UnmarshalXMLAttr(attr xml.Attr) error { 66 | val, err := BreakClearFromStr(attr.Value) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | *d = val 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /wml/stypes/combBracket.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // CombineBrackets Type 9 | type CombineBrackets string 10 | 11 | const ( 12 | CombineBracketsNone CombineBrackets = "none" // No Enclosing Brackets 13 | CombineBracketsRound CombineBrackets = "round" // Round Brackets 14 | CombineBracketsSquare CombineBrackets = "square" // Square Brackets 15 | CombineBracketsAngle CombineBrackets = "angle" // Angle Brackets 16 | CombineBracketsCurly CombineBrackets = "curly" // Curly Brackets 17 | ) 18 | 19 | func CombineBracketsFromStr(value string) (CombineBrackets, error) { 20 | switch value { 21 | case "none": 22 | return CombineBracketsNone, nil 23 | case "round": 24 | return CombineBracketsRound, nil 25 | case "square": 26 | return CombineBracketsSquare, nil 27 | case "angle": 28 | return CombineBracketsAngle, nil 29 | case "curly": 30 | return CombineBracketsCurly, nil 31 | default: 32 | return "", errors.New("invalid CombineBrackets value") 33 | } 34 | } 35 | 36 | func (c *CombineBrackets) UnmarshalXMLAttr(attr xml.Attr) error { 37 | val, err := CombineBracketsFromStr(attr.Value) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | *c = val 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /wml/stypes/common.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | const ( 4 | ColorIndexAuto = "default" 5 | ColorIndexBlack = "black" 6 | ColorIndexBlue = "blue" 7 | ColorIndexBrightGreen = "green" 8 | ColorIndexDarkBlue = "darkBlue" 9 | ColorIndexDarkRed = "darkRed" 10 | ColorIndexDarkYellow = "darkYellow" 11 | ColorIndexGray25 = "lightGray" 12 | ColorIndexGray50 = "darkGray" 13 | ColorIndexGreen = "darkGreen" 14 | ColorIndexMagenta = "magenta" 15 | ColorIndexRed = "red" 16 | ColorIndexDarkCyan = "darkCyan" 17 | ColorIndexCyan = "cyan" 18 | ColorIndexDarkMagenta = "darkMagenta" 19 | ColorIndexWhite = "white" 20 | ColorIndexYellow = "yellow" 21 | ) 22 | -------------------------------------------------------------------------------- /wml/stypes/doc.go: -------------------------------------------------------------------------------- 1 | // Package stypes provides simple types used in Office Open XML (OOXML) WordprocessingML 2 | // (WML) documents. It encompasses basic definitions for character formatting, paragraph 3 | // properties, numbering formats, and other fundamental elements essential for text 4 | // representation and formatting in WML files. These types facilitate updating properties 5 | // such as justification (e.g., left, center, right, both, etc.) across various document 6 | // elements. 7 | package stypes 8 | -------------------------------------------------------------------------------- /wml/stypes/docGridType.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // Document Grid Types 9 | type DocGridType string 10 | 11 | const ( 12 | DocGridDefault DocGridType = "default" //No Document Grid 13 | DocGridLines DocGridType = "lines" //Line Grid Only 14 | DocGridLinesAndChars DocGridType = "linesAndChars" //Line and Character Grid 15 | DocGridSnapToChars DocGridType = "snapToChars" //Character Grid Only 16 | ) 17 | 18 | func DocGridTypeFromStr(value string) (DocGridType, error) { 19 | switch value { 20 | case "default": 21 | return DocGridDefault, nil 22 | case "lines": 23 | return DocGridLines, nil 24 | case "linesAndChars": 25 | return DocGridLinesAndChars, nil 26 | case "snapToChars": 27 | return DocGridSnapToChars, nil 28 | default: 29 | return "", errors.New("Invalid Docgrid Type") 30 | 31 | } 32 | } 33 | 34 | func (d *DocGridType) UnmarshalXMLAttr(attr xml.Attr) error { 35 | val, err := DocGridTypeFromStr(attr.Value) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | *d = val 41 | 42 | return nil 43 | 44 | } 45 | -------------------------------------------------------------------------------- /wml/stypes/dropCap.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // Text Frame Drop Cap Location 9 | type DropCap string 10 | 11 | const ( 12 | DropCapNone DropCap = "none" // Not Drop Cap 13 | DropCapInside DropCap = "drop" // Drop Cap Inside Margin 14 | DropCapMargin DropCap = "margin" // Drop Cap Outside Margin 15 | ) 16 | 17 | // DropCapFromStr converts a string to DropCap type. 18 | func DropCapFromStr(value string) (DropCap, error) { 19 | switch value { 20 | case "none": 21 | return DropCapNone, nil 22 | case "drop": 23 | return DropCapInside, nil 24 | case "margin": 25 | return DropCapMargin, nil 26 | default: 27 | return "", errors.New("Invalid DropCap value") 28 | } 29 | } 30 | 31 | // UnmarshalXMLAttr unmarshals XML attribute into DropCap. 32 | func (d *DropCap) UnmarshalXMLAttr(attr xml.Attr) error { 33 | val, err := DropCapFromStr(attr.Value) 34 | if err != nil { 35 | return err 36 | } 37 | *d = val 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /wml/stypes/em.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // Em Type 9 | type Em string 10 | 11 | const ( 12 | EmNone Em = "none" // No Emphasis Mark 13 | EmDot Em = "dot" // Dot Emphasis Mark Above Characters 14 | EmComma Em = "comma" // Comma Emphasis Mark Above Characters 15 | EmCircle Em = "circle" // Circle Emphasis Mark Above Characters 16 | EmUnderDot Em = "underDot" // Dot Emphasis Mark Below Characters 17 | ) 18 | 19 | func EmFromStr(value string) (Em, error) { 20 | switch value { 21 | case "none": 22 | return EmNone, nil 23 | case "dot": 24 | return EmDot, nil 25 | case "comma": 26 | return EmComma, nil 27 | case "circle": 28 | return EmCircle, nil 29 | case "underDot": 30 | return EmUnderDot, nil 31 | default: 32 | return "", errors.New("Invalid Em value") 33 | } 34 | } 35 | 36 | func (e *Em) UnmarshalXMLAttr(attr xml.Attr) error { 37 | val, err := EmFromStr(attr.Value) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | *e = val 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /wml/stypes/fontTypeHint.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type FontTypeHint string 9 | 10 | const ( 11 | FontTypeHintDefault FontTypeHint = "default" // High ANSI Font 12 | FontTypeHintEastAsia FontTypeHint = "eastAsia" // East Asian Font 13 | FontTypeHintCS FontTypeHint = "cs" // Complex Script Font 14 | ) 15 | 16 | func FontTypeHintFromStr(value string) (FontTypeHint, error) { 17 | switch value { 18 | case "default": 19 | return FontTypeHintDefault, nil 20 | case "eastAsia": 21 | return FontTypeHintEastAsia, nil 22 | case "cs": 23 | return FontTypeHintCS, nil 24 | default: 25 | return "", errors.New("invalid FontTypeHint value") 26 | } 27 | } 28 | 29 | func (d *FontTypeHint) UnmarshalXMLAttr(attr xml.Attr) error { 30 | val, err := FontTypeHintFromStr(attr.Value) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | *d = val 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /wml/stypes/hdrFtr.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // Header or Footer Type 9 | type HdrFtrType string 10 | 11 | const ( 12 | HdrFtrEven HdrFtrType = "even" //Even Numbered Pages Only 13 | HdrFtrDefault HdrFtrType = "default" //Default Header or Footer 14 | HdrFtrFirst HdrFtrType = "first" //First Page Only 15 | ) 16 | 17 | func HdrFtrFromStr(value string) (HdrFtrType, error) { 18 | switch value { 19 | case "default": 20 | return HdrFtrDefault, nil 21 | case "even": 22 | return HdrFtrEven, nil 23 | case "first": 24 | return HdrFtrFirst, nil 25 | default: 26 | return "", errors.New("Invalid Header or Footer Type") 27 | 28 | } 29 | } 30 | 31 | func (d *HdrFtrType) UnmarshalXMLAttr(attr xml.Attr) error { 32 | val, err := HdrFtrFromStr(attr.Value) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | *d = val 38 | 39 | return nil 40 | 41 | } 42 | -------------------------------------------------------------------------------- /wml/stypes/heightRule.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // HeightRule Type 9 | type HeightRule string 10 | 11 | // Constants for valid values 12 | const ( 13 | HeightRuleAuto HeightRule = "auto" // Determine Height Based On Contents 14 | HeightRuleExact HeightRule = "exact" // Exact Height 15 | HeightRuleAtLeast HeightRule = "atLeast" // Minimum Height 16 | ) 17 | 18 | // HeightRuleFromStr converts a string to HeightRule type. 19 | func HeightRuleFromStr(value string) (HeightRule, error) { 20 | switch value { 21 | case "auto": 22 | return HeightRuleAuto, nil 23 | case "exact": 24 | return HeightRuleExact, nil 25 | case "atLeast": 26 | return HeightRuleAtLeast, nil 27 | default: 28 | return "", errors.New("Invalid HeightRule value") 29 | } 30 | } 31 | 32 | // UnmarshalXMLAttr unmarshals XML attribute into HeightRule. 33 | func (h *HeightRule) UnmarshalXMLAttr(attr xml.Attr) error { 34 | val, err := HeightRuleFromStr(attr.Value) 35 | if err != nil { 36 | return err 37 | } 38 | *h = val 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /wml/stypes/hex.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/xml" 6 | "errors" 7 | "strings" 8 | ) 9 | 10 | type LongHexNum string 11 | 12 | // LongHexNumberFromStr validates and converts a string to a LongHexNumber type 13 | func LongHexNumFromStr(s string) (LongHexNum, error) { 14 | // Allow an empty value as valid 15 | if s == "" { 16 | return LongHexNum(s), nil 17 | } 18 | 19 | // // Ensure the string is exactly 4 characters long 20 | // if len(s) != 4 { 21 | // return "", errors.New("invalid LongHexNumber length") 22 | // } 23 | 24 | // Ensure the string contains valid hexadecimal characters 25 | if _, err := hex.DecodeString(s); err != nil { 26 | return "", errors.New("invalid LongHexNumber format") 27 | } 28 | 29 | return LongHexNum(strings.ToUpper(s)), nil 30 | } 31 | 32 | func (d *LongHexNum) UnmarshalXMLAttr(attr xml.Attr) error { 33 | val, err := LongHexNumFromStr(attr.Value) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | *d = val 39 | 40 | return nil 41 | 42 | } 43 | -------------------------------------------------------------------------------- /wml/stypes/hex_test.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestLongHexNumFromStr(t *testing.T) { 8 | tests := []struct { 9 | input string 10 | expected LongHexNum 11 | err bool 12 | }{ 13 | {"", "", false}, // Empty string 14 | {"1a2b", "1A2B", false}, // Valid lowercase hex string 15 | {"1A2B", "1A2B", false}, // Valid uppercase hex string 16 | {"123", "", true}, // Invalid length (too short) 17 | {"12345", "", true}, // Invalid length (too long) 18 | {"1G2H", "", true}, // Invalid characters 19 | {"ZZZZ", "", true}, // Invalid characters 20 | } 21 | 22 | for _, tt := range tests { 23 | result, err := LongHexNumFromStr(tt.input) 24 | if (err != nil) != tt.err { 25 | t.Errorf("LongHexNumFromStr(%q) error = %v, expected error = %v", tt.input, err, tt.err) 26 | } 27 | if result != tt.expected { 28 | t.Errorf("LongHexNumFromStr(%q) = %v, expected %v", tt.input, result, tt.expected) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /wml/stypes/jc.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type Justification string 9 | 10 | const ( 11 | JustificationLeft Justification = "left" // Align Left 12 | JustificationCenter Justification = "center" // Align Center 13 | JustificationRight Justification = "right" // Align Right 14 | JustificationBoth Justification = "both" // Justified 15 | JustificationMediumKashida Justification = "mediumKashida" // Medium Kashida Length 16 | JustificationDistribute Justification = "distribute" // Distribute All Characters Equally 17 | JustificationNumTab Justification = "numTab" // Align to List Tab 18 | JustificationHighKashida Justification = "highKashida" // Widest Kashida Length 19 | JustificationLowKashida Justification = "lowKashida" // Low Kashida Length 20 | JustificationThaiDistribute Justification = "thaiDistribute" // Thai Language Justification 21 | ) 22 | 23 | func JustificationFromStr(value string) (Justification, error) { 24 | switch value { 25 | case "left": 26 | return JustificationLeft, nil 27 | case "center": 28 | return JustificationCenter, nil 29 | case "right": 30 | return JustificationRight, nil 31 | case "both": 32 | return JustificationBoth, nil 33 | case "mediumKashida": 34 | return JustificationMediumKashida, nil 35 | case "distribute": 36 | return JustificationDistribute, nil 37 | case "numTab": 38 | return JustificationNumTab, nil 39 | case "highKashida": 40 | return JustificationHighKashida, nil 41 | case "lowKashida": 42 | return JustificationLowKashida, nil 43 | case "thaiDistribute": 44 | return JustificationThaiDistribute, nil 45 | default: 46 | return "", errors.New("invalid Justification value") 47 | } 48 | } 49 | 50 | func (j *Justification) UnmarshalXMLAttr(attr xml.Attr) error { 51 | val, err := JustificationFromStr(attr.Value) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | *j = val 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /wml/stypes/lnSpacRule.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type LineSpacingRule string 9 | 10 | const ( 11 | LineSpacingRuleAuto LineSpacingRule = "auto" // Automatically Determined Line Height 12 | LineSpacingRuleExact LineSpacingRule = "exact" // Exact Line Height 13 | LineSpacingRuleAtLeast LineSpacingRule = "atLeast" // Minimum Line Height 14 | ) 15 | 16 | func LineSpacingRuleFromStr(value string) (LineSpacingRule, error) { 17 | switch value { 18 | case "auto": 19 | return LineSpacingRuleAuto, nil 20 | case "exact": 21 | return LineSpacingRuleExact, nil 22 | case "atLeast": 23 | return LineSpacingRuleAtLeast, nil 24 | default: 25 | return "", errors.New("invalid LineSpacingRule value") 26 | } 27 | } 28 | 29 | func (d *LineSpacingRule) UnmarshalXMLAttr(attr xml.Attr) error { 30 | val, err := LineSpacingRuleFromStr(attr.Value) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | *d = val 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /wml/stypes/merge.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type MergeCell string 9 | 10 | const ( 11 | MergeCellContinue MergeCell = "continue" // Continue Merged Region 12 | MergeCellRestart MergeCell = "restart" // Start/Restart Merged Region 13 | ) 14 | 15 | func MergeCellFromStr(value string) (MergeCell, error) { 16 | switch value { 17 | case "continue": 18 | return MergeCellContinue, nil 19 | case "restart": 20 | return MergeCellRestart, nil 21 | default: 22 | return "", errors.New("invalid MergeCell value") 23 | } 24 | } 25 | 26 | func (m *MergeCell) UnmarshalXMLAttr(attr xml.Attr) error { 27 | val, err := MergeCellFromStr(attr.Value) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | *m = val 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /wml/stypes/merge_test.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "testing" 6 | ) 7 | 8 | func TestMergeCellFromStr_ValidValues(t *testing.T) { 9 | tests := []struct { 10 | input string 11 | expected MergeCell 12 | }{ 13 | {"continue", MergeCellContinue}, 14 | {"restart", MergeCellRestart}, 15 | } 16 | 17 | for _, tt := range tests { 18 | t.Run(tt.input, func(t *testing.T) { 19 | result, err := MergeCellFromStr(tt.input) 20 | if err != nil { 21 | t.Fatalf("Unexpected error: %v", err) 22 | } 23 | 24 | if result != tt.expected { 25 | t.Errorf("Expected %s but got %s", tt.expected, result) 26 | } 27 | }) 28 | } 29 | } 30 | 31 | func TestMergeCellFromStr_InvalidValue(t *testing.T) { 32 | input := "invalidValue" 33 | 34 | result, err := MergeCellFromStr(input) 35 | 36 | if err == nil { 37 | t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) 38 | } 39 | 40 | expectedError := "invalid MergeCell value" 41 | if err.Error() != expectedError { 42 | t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) 43 | } 44 | } 45 | 46 | func TestMergeCell_UnmarshalXMLAttr_ValidValues(t *testing.T) { 47 | tests := []struct { 48 | inputXML string 49 | expected MergeCell 50 | }{ 51 | {``, MergeCellContinue}, 52 | {``, MergeCellRestart}, 53 | } 54 | 55 | for _, tt := range tests { 56 | t.Run(tt.inputXML, func(t *testing.T) { 57 | type Element struct { 58 | XMLName xml.Name `xml:"element"` 59 | Val MergeCell `xml:"val,attr"` 60 | } 61 | 62 | var elem Element 63 | 64 | err := xml.Unmarshal([]byte(tt.inputXML), &elem) 65 | if err != nil { 66 | t.Fatalf("Error unmarshaling XML: %v", err) 67 | } 68 | 69 | if elem.Val != tt.expected { 70 | t.Errorf("Expected %s but got %s", tt.expected, elem.Val) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func TestMergeCell_UnmarshalXMLAttr_InvalidValue(t *testing.T) { 77 | inputXML := `` 78 | 79 | type Element struct { 80 | XMLName xml.Name `xml:"element"` 81 | Val MergeCell `xml:"val,attr"` 82 | } 83 | 84 | var elem Element 85 | 86 | err := xml.Unmarshal([]byte(inputXML), &elem) 87 | 88 | if err == nil { 89 | t.Fatalf("Expected error for invalid value, but got none") 90 | } 91 | 92 | expectedError := "invalid MergeCell value" 93 | if err.Error() != expectedError { 94 | t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /wml/stypes/onoff.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // This simple type specifies a set of values for any binary (on or off) property defined in a WordprocessingML document. 9 | // A value of on, 1, or true specifies that the property shall be turned on. This is the default value for this attribute, and is implied when the parent element is present, but this attribute is omitted. 10 | // 11 | // A value of off, 0, or false specifies that the property shall be explicitly turned off. 12 | type OnOff string 13 | 14 | const ( 15 | OnOffZero OnOff = "0" 16 | OnOffOne OnOff = "1" 17 | OnOffFalse OnOff = "false" 18 | OnOffTrue OnOff = "true" 19 | OnOffOff OnOff = "off" 20 | OnOffOn OnOff = "on" 21 | ) 22 | 23 | func OnOffFromStr(s string) (OnOff, error) { 24 | switch s { 25 | case "0": 26 | return OnOffZero, nil 27 | case "1": 28 | return OnOffOne, nil 29 | case "false": 30 | return OnOffFalse, nil 31 | case "true": 32 | return OnOffTrue, nil 33 | case "off": 34 | return OnOffOff, nil 35 | case "on": 36 | return OnOffOn, nil 37 | default: 38 | return "", errors.New("invalid OnOff string") 39 | } 40 | } 41 | 42 | func (d *OnOff) UnmarshalXMLAttr(attr xml.Attr) error { 43 | val, err := OnOffFromStr(attr.Value) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | *d = val 49 | 50 | return nil 51 | 52 | } 53 | -------------------------------------------------------------------------------- /wml/stypes/pageOrient.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type PageOrient string 9 | 10 | const ( 11 | PageOrientPortrait PageOrient = "portrait" 12 | PageOrientLandscape PageOrient = "landscape" 13 | ) 14 | 15 | func PageOrientFromStr(value string) (PageOrient, error) { 16 | switch value { 17 | case "portrait": 18 | return PageOrientPortrait, nil 19 | case "landscape": 20 | return PageOrientLandscape, nil 21 | default: 22 | return "", errors.New("Invalid Orient Input") 23 | } 24 | } 25 | 26 | func (d *PageOrient) UnmarshalXMLAttr(attr xml.Attr) error { 27 | val, err := PageOrientFromStr(attr.Value) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | *d = val 33 | 34 | return nil 35 | 36 | } 37 | -------------------------------------------------------------------------------- /wml/stypes/sectionMark.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type SectionMark string 9 | 10 | const ( 11 | SectionMarkNextPage SectionMark = "nextPage" //Next Page Section Break 12 | SectionMarkNextColumn SectionMark = "nextColumn" //Column Section Break 13 | SectionMarkNextContinuous SectionMark = "continuous" //Continuous Section Break 14 | SectionMarkEvenPage SectionMark = "evenPage" //Even Page Section Break 15 | SectionMarkOddPage SectionMark = "oddPage" //Odd Page Section Break 16 | ) 17 | 18 | func SectionMarkFromStr(value string) (SectionMark, error) { 19 | switch value { 20 | case "nextPage": 21 | return SectionMarkNextPage, nil 22 | case "nextColumn": 23 | return SectionMarkNextColumn, nil 24 | case "continuous": 25 | return SectionMarkNextContinuous, nil 26 | case "evenPage": 27 | return SectionMarkEvenPage, nil 28 | case "oddPage": 29 | return SectionMarkOddPage, nil 30 | default: 31 | return "", errors.New("Invalid Section Mark") 32 | } 33 | } 34 | 35 | func (d *SectionMark) UnmarshalXMLAttr(attr xml.Attr) error { 36 | val, err := SectionMarkFromStr(attr.Value) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | *d = val 42 | 43 | return nil 44 | 45 | } 46 | -------------------------------------------------------------------------------- /wml/stypes/shd_test.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "testing" 6 | ) 7 | 8 | // TestShadingFromStr tests the ShadingFromStr function with a few example values. 9 | func TestShadingFromStr(t *testing.T) { 10 | tests := []struct { 11 | input string 12 | expected Shading 13 | hasError bool 14 | }{ 15 | {"nil", ShdNil, false}, 16 | {"solid", ShdSolid, false}, 17 | {"horzStripe", ShdHorzStripe, false}, 18 | {"pct50", ShdPct50, false}, 19 | {"invalid", "", true}, 20 | } 21 | 22 | for _, test := range tests { 23 | result, err := ShadingFromStr(test.input) 24 | if (err != nil) != test.hasError { 25 | t.Errorf("ShadingFromStr(%q) error = %v, hasError %v", test.input, err, test.hasError) 26 | } 27 | if result != test.expected { 28 | t.Errorf("ShadingFromStr(%q) = %v, want %v", test.input, result, test.expected) 29 | } 30 | } 31 | } 32 | 33 | // TestUnmarshalXMLAttr tests the UnmarshalXMLAttr method for the Shading type. 34 | func TestUnmarshalXMLAttr(t *testing.T) { 35 | tests := []struct { 36 | input xml.Attr 37 | expected Shading 38 | hasError bool 39 | }{ 40 | {xml.Attr{Name: xml.Name{Local: "pattern"}, Value: "diagStripe"}, ShdDiagStripe, false}, 41 | {xml.Attr{Name: xml.Name{Local: "pattern"}, Value: "thinHorzCross"}, ShdThinHorzCross, false}, 42 | {xml.Attr{Name: xml.Name{Local: "pattern"}, Value: "invalid"}, "", true}, 43 | } 44 | 45 | for _, test := range tests { 46 | var result Shading 47 | err := result.UnmarshalXMLAttr(test.input) 48 | if (err != nil) != test.hasError { 49 | t.Errorf("UnmarshalXMLAttr(%v) error = %v, hasError %v", test.input, err, test.hasError) 50 | } 51 | if result != test.expected { 52 | t.Errorf("UnmarshalXMLAttr(%v) = %v, want %v", test.input, result, test.expected) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /wml/stypes/styleType.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type StyleType string 9 | 10 | const ( 11 | StyleTypeParagraph StyleType = "paragraph" 12 | StyleTypeCharacter StyleType = "character" 13 | StyleTypeTable StyleType = "table" 14 | StyleTypeNumbering StyleType = "numbering" 15 | ) 16 | 17 | func StyleTypeFromStr(value string) (StyleType, error) { 18 | switch value { 19 | case "paragraph": 20 | return StyleTypeParagraph, nil 21 | case "character": 22 | return StyleTypeCharacter, nil 23 | case "table": 24 | return StyleTypeTable, nil 25 | case "numbering": 26 | return StyleTypeNumbering, nil 27 | default: 28 | return "", errors.New("invalid StyleType value") 29 | } 30 | } 31 | 32 | func (s *StyleType) UnmarshalXMLAttr(attr xml.Attr) error { 33 | val, err := StyleTypeFromStr(attr.Value) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | *s = val 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /wml/stypes/tabJc.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // CustTabStop represents the custom tab stop type. 9 | type CustTabStop string 10 | 11 | const ( 12 | CustTabStopClear CustTabStop = "clear" 13 | CustTabStopLeft CustTabStop = "left" 14 | CustTabStopCenter CustTabStop = "center" 15 | CustTabStopRight CustTabStop = "right" 16 | CustTabStopDecimal CustTabStop = "decimal" 17 | CustTabStopBar CustTabStop = "bar" 18 | CustTabStopNum CustTabStop = "num" 19 | CustTabStopInvalid CustTabStop = "" 20 | ) 21 | 22 | // Function to convert string to CustTabStop. 23 | func CustTabStopFromStr(val string) (CustTabStop, error) { 24 | switch val { 25 | case "clear": 26 | return CustTabStopClear, nil 27 | case "left": 28 | return CustTabStopLeft, nil 29 | case "center": 30 | return CustTabStopCenter, nil 31 | case "right": 32 | return CustTabStopRight, nil 33 | case "decimal": 34 | return CustTabStopDecimal, nil 35 | case "bar": 36 | return CustTabStopBar, nil 37 | case "num": 38 | return CustTabStopNum, nil 39 | default: 40 | return CustTabStopInvalid, errors.New("Invalid CustTabStop value") 41 | } 42 | } 43 | 44 | // Method to unmarshal XML attribute into CustTabStop. 45 | func (t *CustTabStop) UnmarshalXMLAttr(attr xml.Attr) error { 46 | val, err := CustTabStopFromStr(attr.Value) 47 | if err != nil { 48 | return err 49 | } 50 | *t = val 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /wml/stypes/tabJc_test.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "testing" 6 | ) 7 | 8 | func TestCustTabStopFromStr(t *testing.T) { 9 | tests := []struct { 10 | input string 11 | expected CustTabStop 12 | }{ 13 | {"clear", CustTabStopClear}, 14 | {"left", CustTabStopLeft}, 15 | {"center", CustTabStopCenter}, 16 | {"right", CustTabStopRight}, 17 | {"decimal", CustTabStopDecimal}, 18 | {"bar", CustTabStopBar}, 19 | {"num", CustTabStopNum}, 20 | {"invalid", CustTabStopInvalid}, 21 | } 22 | 23 | for _, tt := range tests { 24 | t.Run(tt.input, func(t *testing.T) { 25 | result, err := CustTabStopFromStr(tt.input) 26 | if tt.expected == CustTabStopInvalid && err == nil { 27 | t.Fatalf("Expected error for input %s but got none", tt.input) 28 | } 29 | 30 | if result != tt.expected { 31 | t.Errorf("Expected %s but got %s", tt.expected, result) 32 | } 33 | }) 34 | } 35 | } 36 | 37 | func TestCustTabStop_UnmarshalXMLAttr(t *testing.T) { 38 | tests := []struct { 39 | name string 40 | inputXML string 41 | expected CustTabStop 42 | }{ 43 | { 44 | name: "Valid attribute clear", 45 | inputXML: ``, 46 | expected: CustTabStopClear, 47 | }, 48 | { 49 | name: "Valid attribute left", 50 | inputXML: ``, 51 | expected: CustTabStopLeft, 52 | }, 53 | { 54 | name: "Valid attribute center", 55 | inputXML: ``, 56 | expected: CustTabStopCenter, 57 | }, 58 | { 59 | name: "Invalid attribute", 60 | inputXML: ``, 61 | expected: CustTabStopInvalid, 62 | }, 63 | } 64 | 65 | for _, tt := range tests { 66 | t.Run(tt.name, func(t *testing.T) { 67 | type Element struct { 68 | XMLName xml.Name `xml:"element"` 69 | Tab CustTabStop `xml:"tab,attr"` 70 | } 71 | 72 | var elem Element 73 | 74 | err := xml.Unmarshal([]byte(tt.inputXML), &elem) 75 | if tt.expected == CustTabStopInvalid && err == nil { 76 | t.Fatalf("Expected error for input XML %s but got none", tt.inputXML) 77 | } 78 | 79 | if elem.Tab != tt.expected { 80 | t.Errorf("Expected %s but got %s", tt.expected, elem.Tab) 81 | } 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /wml/stypes/tabTlc.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // Custom Tab Stop Leader Character 9 | type CustLeadChar string 10 | 11 | const ( 12 | CustLeadCharNone CustLeadChar = "none" 13 | CustLeadCharDot CustLeadChar = "dot" 14 | CustLeadCharHyphen CustLeadChar = "hyphen" 15 | CustLeadCharUnderScore CustLeadChar = "underscore" 16 | CustLeadCharHeavy CustLeadChar = "heavy" 17 | CustLeadCharMiddleDot CustLeadChar = "middleDot" 18 | CustLeadCharInvalid CustLeadChar = "" 19 | ) 20 | 21 | func CustLeadCharFromStr(val string) (CustLeadChar, error) { 22 | switch val { 23 | case "none": 24 | return CustLeadCharNone, nil 25 | case "dot": 26 | return CustLeadCharDot, nil 27 | case "hyphen": 28 | return CustLeadCharHyphen, nil 29 | case "underscore": 30 | return CustLeadCharUnderScore, nil 31 | case "heavy": 32 | return CustLeadCharHeavy, nil 33 | case "middleDot": 34 | return CustLeadCharMiddleDot, nil 35 | default: 36 | return CustLeadCharInvalid, errors.New("Invalid Lead Char") 37 | } 38 | } 39 | 40 | func (d *CustLeadChar) UnmarshalXMLAttr(attr xml.Attr) error { 41 | val, err := CustLeadCharFromStr(attr.Value) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | *d = val 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /wml/stypes/tabTlc_test.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "testing" 6 | ) 7 | 8 | func TestCustLeadCharFromStr(t *testing.T) { 9 | tests := []struct { 10 | input string 11 | expected CustLeadChar 12 | }{ 13 | {"none", CustLeadCharNone}, 14 | {"dot", CustLeadCharDot}, 15 | {"hyphen", CustLeadCharHyphen}, 16 | {"underscore", CustLeadCharUnderScore}, 17 | {"heavy", CustLeadCharHeavy}, 18 | {"middleDot", CustLeadCharMiddleDot}, 19 | {"invalid", CustLeadCharInvalid}, 20 | } 21 | 22 | for _, tt := range tests { 23 | t.Run(tt.input, func(t *testing.T) { 24 | result, err := CustLeadCharFromStr(tt.input) 25 | if tt.expected == CustLeadCharInvalid && err == nil { 26 | t.Fatalf("Expected error for input %s but got none", tt.input) 27 | } 28 | 29 | if result != tt.expected { 30 | t.Errorf("Expected %s but got %s", tt.expected, result) 31 | } 32 | }) 33 | } 34 | } 35 | 36 | func TestCustLeadChar_UnmarshalXMLAttr(t *testing.T) { 37 | tests := []struct { 38 | name string 39 | inputXML string 40 | expected CustLeadChar 41 | }{ 42 | { 43 | name: "Valid attribute none", 44 | inputXML: ``, 45 | expected: CustLeadCharNone, 46 | }, 47 | { 48 | name: "Valid attribute dot", 49 | inputXML: ``, 50 | expected: CustLeadCharDot, 51 | }, 52 | { 53 | name: "Valid attribute hyphen", 54 | inputXML: ``, 55 | expected: CustLeadCharHyphen, 56 | }, 57 | { 58 | name: "Invalid attribute", 59 | inputXML: ``, 60 | expected: CustLeadCharInvalid, 61 | }, 62 | } 63 | 64 | for _, tt := range tests { 65 | t.Run(tt.name, func(t *testing.T) { 66 | type Element struct { 67 | XMLName xml.Name `xml:"element"` 68 | Leader CustLeadChar `xml:"leader,attr"` 69 | } 70 | 71 | var elem Element 72 | 73 | err := xml.Unmarshal([]byte(tt.inputXML), &elem) 74 | if tt.expected == CustLeadCharInvalid && err == nil { 75 | t.Fatalf("Expected error for input XML %s but got none", tt.inputXML) 76 | } 77 | 78 | if elem.Leader != tt.expected { 79 | t.Errorf("Expected %s but got %s", tt.expected, elem.Leader) 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /wml/stypes/tblLayoutType.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // Table Layout Type 9 | type TableLayout string 10 | 11 | const ( 12 | TableLayoutFixed TableLayout = "fixed" 13 | TableLayoutAutoFit TableLayout = "autofit" 14 | TableLayoutInvalid TableLayout = "" 15 | ) 16 | 17 | func TableLayoutFromStr(val string) (TableLayout, error) { 18 | switch val { 19 | case "fixed": 20 | return TableLayoutFixed, nil 21 | case "autofit": 22 | return TableLayoutAutoFit, nil 23 | default: 24 | return TableLayoutInvalid, errors.New("Invalid Table Layout Type") 25 | } 26 | } 27 | 28 | func (t *TableLayout) UnmarshalXMLAttr(attr xml.Attr) error { 29 | val, err := TableLayoutFromStr(attr.Value) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | *t = val 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /wml/stypes/tblOverlap.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type TblOverlap string 9 | 10 | const ( 11 | TblOverlapNever TblOverlap = "never" 12 | TblOverlapOverlap TblOverlap = "overlap" 13 | ) 14 | 15 | func TblOverlapFromStr(value string) (TblOverlap, error) { 16 | switch value { 17 | case "never": 18 | return TblOverlapNever, nil 19 | case "overlap": 20 | return TblOverlapOverlap, nil 21 | default: 22 | return "", errors.New("Invalid TblOverlap value") 23 | } 24 | } 25 | 26 | func (to *TblOverlap) UnmarshalXMLAttr(attr xml.Attr) error { 27 | val, err := TblOverlapFromStr(attr.Value) 28 | if err != nil { 29 | return err 30 | } 31 | *to = val 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /wml/stypes/tblStyleOvrr.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type TblStyleOverrideType string 9 | 10 | const ( 11 | TblStyleOverrideWholeTable TblStyleOverrideType = "wholeTable" 12 | TblStyleOverrideFirstRow TblStyleOverrideType = "firstRow" 13 | TblStyleOverrideLastRow TblStyleOverrideType = "lastRow" 14 | TblStyleOverrideFirstCol TblStyleOverrideType = "firstCol" 15 | TblStyleOverrideLastCol TblStyleOverrideType = "lastCol" 16 | TblStyleOverrideBand1Vert TblStyleOverrideType = "band1Vert" 17 | TblStyleOverrideBand2Vert TblStyleOverrideType = "band2Vert" 18 | TblStyleOverrideBand1Horz TblStyleOverrideType = "band1Horz" 19 | TblStyleOverrideBand2Horz TblStyleOverrideType = "band2Horz" 20 | TblStyleOverrideNeCell TblStyleOverrideType = "neCell" 21 | TblStyleOverrideNwCell TblStyleOverrideType = "nwCell" 22 | TblStyleOverrideSeCell TblStyleOverrideType = "seCell" 23 | TblStyleOverrideSwCell TblStyleOverrideType = "swCell" 24 | ) 25 | 26 | func TblStyleOverrideTypeFromStr(value string) (TblStyleOverrideType, error) { 27 | switch value { 28 | case "wholeTable": 29 | return TblStyleOverrideWholeTable, nil 30 | case "firstRow": 31 | return TblStyleOverrideFirstRow, nil 32 | case "lastRow": 33 | return TblStyleOverrideLastRow, nil 34 | case "firstCol": 35 | return TblStyleOverrideFirstCol, nil 36 | case "lastCol": 37 | return TblStyleOverrideLastCol, nil 38 | case "band1Vert": 39 | return TblStyleOverrideBand1Vert, nil 40 | case "band2Vert": 41 | return TblStyleOverrideBand2Vert, nil 42 | case "band1Horz": 43 | return TblStyleOverrideBand1Horz, nil 44 | case "band2Horz": 45 | return TblStyleOverrideBand2Horz, nil 46 | case "neCell": 47 | return TblStyleOverrideNeCell, nil 48 | case "nwCell": 49 | return TblStyleOverrideNwCell, nil 50 | case "seCell": 51 | return TblStyleOverrideSeCell, nil 52 | case "swCell": 53 | return TblStyleOverrideSwCell, nil 54 | default: 55 | return "", errors.New("invalid TblStyleOverrideType value") 56 | } 57 | } 58 | 59 | func (t *TblStyleOverrideType) UnmarshalXMLAttr(attr xml.Attr) error { 60 | val, err := TblStyleOverrideTypeFromStr(attr.Value) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | *t = val 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /wml/stypes/tblWidth.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type TableWidth string 9 | 10 | const ( 11 | TableWidthDxa TableWidth = "dxa" //Width in Twentieths of a Point 12 | TableWidthAuto TableWidth = "auto" //Automatically Determined Width 13 | TableWidthPct TableWidth = "pct" //Width in Fiftieths of a Percent 14 | TableWidthNil TableWidth = "nil" //No Width 15 | TableWidthUnsupported TableWidth = "" 16 | ) 17 | 18 | func TableWidthFromStr(value string) (TableWidth, error) { 19 | switch value { 20 | case "dxa": 21 | return TableWidthDxa, nil 22 | case "auto": 23 | return TableWidthAuto, nil 24 | case "pct": 25 | return TableWidthPct, nil 26 | case "nil": 27 | return TableWidthNil, nil 28 | default: 29 | return "", errors.New("Invalid TableWidth value") 30 | } 31 | } 32 | 33 | func (to *TableWidth) UnmarshalXMLAttr(attr xml.Attr) error { 34 | val, err := TableWidthFromStr(attr.Value) 35 | if err != nil { 36 | return err 37 | } 38 | *to = val 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /wml/stypes/textAlign.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type TextAlign string 9 | 10 | const ( 11 | TextAlignTop TextAlign = "top" // Align Text at Top 12 | TextAlignCenter TextAlign = "center" // Align Text at Center 13 | TextAlignBaseline TextAlign = "baseline" // Align Text at Baseline 14 | TextAlignBottom TextAlign = "bottom" // Align Text at Bottom 15 | TextAlignAuto TextAlign = "auto" // Automatically Determine Alignment 16 | ) 17 | 18 | func TextAlignFromStr(value string) (TextAlign, error) { 19 | switch value { 20 | case "top": 21 | return TextAlignTop, nil 22 | case "center": 23 | return TextAlignCenter, nil 24 | case "baseline": 25 | return TextAlignBaseline, nil 26 | case "bottom": 27 | return TextAlignBottom, nil 28 | case "auto": 29 | return TextAlignAuto, nil 30 | default: 31 | return "", errors.New("Invalid Text Alignment") 32 | } 33 | } 34 | 35 | func (a *TextAlign) UnmarshalXMLAttr(attr xml.Attr) error { 36 | val, err := TextAlignFromStr(attr.Value) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | *a = val 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /wml/stypes/textDirection.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type TextDirection string 9 | 10 | const ( 11 | TextDirectionLrTb TextDirection = "lrTb" // Left to Right, Top to Bottom 12 | TextDirectionTbRl TextDirection = "tbRl" // Top to Bottom, Right to Left 13 | TextDirectionBtLr TextDirection = "btLr" // Bottom to Top, Left to Right 14 | TextDirectionLrTbV TextDirection = "lrTbV" // Left to Right, Top to Bottom Rotated 15 | TextDirectionTbRlV TextDirection = "tbRlV" // Top to Bottom, Right to Left Rotated 16 | TextDirectionTbLrV TextDirection = "tbLrV" // Top to Bottom, Left to Right Rotated 17 | ) 18 | 19 | func TextDirectionFromStr(value string) (TextDirection, error) { 20 | switch value { 21 | case "lrTb": 22 | return TextDirectionLrTb, nil 23 | case "tbRl": 24 | return TextDirectionTbRl, nil 25 | case "btLr": 26 | return TextDirectionBtLr, nil 27 | case "lrTbV": 28 | return TextDirectionLrTbV, nil 29 | case "tbRlV": 30 | return TextDirectionTbRlV, nil 31 | case "tbLrV": 32 | return TextDirectionTbLrV, nil 33 | default: 34 | return "", errors.New("Invalid Text Direction") 35 | } 36 | } 37 | 38 | func (d *TextDirection) UnmarshalXMLAttr(attr xml.Attr) error { 39 | val, err := TextDirectionFromStr(attr.Value) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | *d = val 45 | 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /wml/stypes/textEffect.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // TextEffect represents the possible values for text animation effects. 9 | type TextEffect string 10 | 11 | const ( 12 | TextEffectBlinkBackground TextEffect = "blinkBackground" // Blinking Background Animation 13 | TextEffectLights TextEffect = "lights" // Colored Lights Animation 14 | TextEffectAntsBlack TextEffect = "antsBlack" // Black Dashed Line Animation 15 | TextEffectAntsRed TextEffect = "antsRed" // Marching Red Ants 16 | TextEffectShimmer TextEffect = "shimmer" // Shimmer Animation 17 | TextEffectSparkle TextEffect = "sparkle" // Sparkling Lights Animation 18 | TextEffectNone TextEffect = "none" // No Animation 19 | ) 20 | 21 | // TextEffectFromStr converts a string to a TextEffect. 22 | func TextEffectFromStr(value string) (TextEffect, error) { 23 | switch value { 24 | case "blinkBackground": 25 | return TextEffectBlinkBackground, nil 26 | case "lights": 27 | return TextEffectLights, nil 28 | case "antsBlack": 29 | return TextEffectAntsBlack, nil 30 | case "antsRed": 31 | return TextEffectAntsRed, nil 32 | case "shimmer": 33 | return TextEffectShimmer, nil 34 | case "sparkle": 35 | return TextEffectSparkle, nil 36 | case "none": 37 | return TextEffectNone, nil 38 | default: 39 | return "", errors.New("invalid TextEffect value") 40 | } 41 | } 42 | 43 | // UnmarshalXMLAttr unmarshals an XML attribute into a TextEffect. 44 | func (t *TextEffect) UnmarshalXMLAttr(attr xml.Attr) error { 45 | val, err := TextEffectFromStr(attr.Value) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | *t = val 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /wml/stypes/textScale.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | "strconv" 7 | ) 8 | 9 | type TextScale uint16 10 | 11 | func TextScaleFromUint16(u uint16) (TextScale, error) { 12 | if u > 600 { 13 | return 0, errors.New("Invalid Text Scale") 14 | } 15 | 16 | return TextScale(u), nil 17 | } 18 | 19 | func TextScaleFromStr(s string) (TextScale, error) { 20 | u, err := strconv.ParseUint(s, 10, 16) 21 | if err != nil { 22 | return 0, err 23 | } 24 | return TextScaleFromUint16(uint16(u)) 25 | } 26 | 27 | func (t *TextScale) UnmarshalXMLAttr(attr xml.Attr) error { 28 | val, err := TextScaleFromStr(attr.Value) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | *t = val 34 | 35 | return nil 36 | 37 | } 38 | 39 | func (t *TextScale) ToStr() string { 40 | return strconv.FormatUint(uint64(*t), 10) 41 | } 42 | -------------------------------------------------------------------------------- /wml/stypes/textboxTightWrap.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type TextboxTightWrap string 9 | 10 | const ( 11 | TextboxTightWrapNone TextboxTightWrap = "none" // Do Not Tight Wrap 12 | TextboxTightWrapAllLines TextboxTightWrap = "allLines" // Tight Wrap All Lines 13 | TextboxTightWrapFirstAndLastLine TextboxTightWrap = "firstAndLastLine" // Tight Wrap First and Last Lines 14 | TextboxTightWrapFirstLineOnly TextboxTightWrap = "firstLineOnly" // Tight Wrap First Line 15 | TextboxTightWrapLastLineOnly TextboxTightWrap = "lastLineOnly" // Tight Wrap Last Line 16 | ) 17 | 18 | func TextboxTightWrapFromStr(value string) (TextboxTightWrap, error) { 19 | switch value { 20 | case "none": 21 | return TextboxTightWrapNone, nil 22 | case "allLines": 23 | return TextboxTightWrapAllLines, nil 24 | case "firstAndLastLine": 25 | return TextboxTightWrapFirstAndLastLine, nil 26 | case "firstLineOnly": 27 | return TextboxTightWrapFirstLineOnly, nil 28 | case "lastLineOnly": 29 | return TextboxTightWrapLastLineOnly, nil 30 | default: 31 | return "", errors.New("Invalid Textbox Tight Wrap value") 32 | } 33 | } 34 | 35 | func (t *TextboxTightWrap) UnmarshalXMLAttr(attr xml.Attr) error { 36 | val, err := TextboxTightWrapFromStr(attr.Value) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | *t = val 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /wml/stypes/themeColor_test.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "testing" 6 | ) 7 | 8 | // TestThemeColorFromStr tests the ThemeColorFromStr function with a few example values. 9 | func TestThemeColorFromStr(t *testing.T) { 10 | tests := []struct { 11 | input string 12 | expected ThemeColor 13 | hasError bool 14 | }{ 15 | {"dark1", ThemeColorDark1, false}, 16 | {"light2", ThemeColorLight2, false}, 17 | {"accent5", ThemeColorAccent5, false}, 18 | {"invalid", "", true}, 19 | } 20 | 21 | for _, test := range tests { 22 | result, err := ThemeColorFromStr(test.input) 23 | if (err != nil) != test.hasError { 24 | t.Errorf("ThemeColorFromStr(%q) error = %v, hasError %v", test.input, err, test.hasError) 25 | } 26 | if result != test.expected { 27 | t.Errorf("ThemeColorFromStr(%q) = %v, want %v", test.input, result, test.expected) 28 | } 29 | } 30 | } 31 | 32 | // TestUnmarshalXMLAttr tests the UnmarshalXMLAttr method for the ThemeColor type. 33 | func TestThemeColorUnmarshal(t *testing.T) { 34 | tests := []struct { 35 | input xml.Attr 36 | expected ThemeColor 37 | hasError bool 38 | }{ 39 | {xml.Attr{Name: xml.Name{Local: "color"}, Value: "accent3"}, ThemeColorAccent3, false}, 40 | {xml.Attr{Name: xml.Name{Local: "color"}, Value: "background2"}, ThemeColorBackground2, false}, 41 | {xml.Attr{Name: xml.Name{Local: "color"}, Value: "invalid"}, "", true}, 42 | } 43 | 44 | for _, test := range tests { 45 | var result ThemeColor 46 | err := result.UnmarshalXMLAttr(test.input) 47 | if (err != nil) != test.hasError { 48 | t.Errorf("UnmarshalXMLAttr(%v) error = %v, hasError %v", test.input, err, test.hasError) 49 | } 50 | if result != test.expected { 51 | t.Errorf("UnmarshalXMLAttr(%v) = %v, want %v", test.input, result, test.expected) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /wml/stypes/themeFont.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | type ThemeFont string 9 | 10 | const ( 11 | ThemeFontMajorEastAsia ThemeFont = "majorEastAsia" // Major East Asian Theme Font 12 | ThemeFontMajorBidi ThemeFont = "majorBidi" // Major Complex Script Theme Font 13 | ThemeFontMajorAscii ThemeFont = "majorAscii" // Major ASCII Theme Font 14 | ThemeFontMajorHAnsi ThemeFont = "majorHAnsi" // Major High ANSI Theme Font 15 | ThemeFontMinorEastAsia ThemeFont = "minorEastAsia" // Minor East Asian Theme Font 16 | ThemeFontMinorBidi ThemeFont = "minorBidi" // Minor Complex Script Theme Font 17 | ThemeFontMinorAscii ThemeFont = "minorAscii" // Minor ASCII Theme Font 18 | ThemeFontMinorHAnsi ThemeFont = "minorHAnsi" // Minor High ANSI Theme Font 19 | ) 20 | 21 | func ThemeFontFromStr(value string) (ThemeFont, error) { 22 | switch value { 23 | case "majorEastAsia": 24 | return ThemeFontMajorEastAsia, nil 25 | case "majorBidi": 26 | return ThemeFontMajorBidi, nil 27 | case "majorAscii": 28 | return ThemeFontMajorAscii, nil 29 | case "majorHAnsi": 30 | return ThemeFontMajorHAnsi, nil 31 | case "minorEastAsia": 32 | return ThemeFontMinorEastAsia, nil 33 | case "minorBidi": 34 | return ThemeFontMinorBidi, nil 35 | case "minorAscii": 36 | return ThemeFontMinorAscii, nil 37 | case "minorHAnsi": 38 | return ThemeFontMinorHAnsi, nil 39 | default: 40 | return "", errors.New("invalid ThemeFont value") 41 | } 42 | } 43 | 44 | func (d *ThemeFont) UnmarshalXMLAttr(attr xml.Attr) error { 45 | val, err := ThemeFontFromStr(attr.Value) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | *d = val 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /wml/stypes/verJc.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | ) 7 | 8 | // Vertical Alignment Type 9 | type VerticalJc string 10 | 11 | const ( 12 | VerticalJcTop VerticalJc = "top" 13 | VerticalJcCenter VerticalJc = "center" 14 | VerticalJcBoth VerticalJc = "both" 15 | VerticalJcBottom VerticalJc = "bottom" 16 | ) 17 | 18 | // MarshalXMLAttr marshals the VerticalJc type as an XML attribute. 19 | func (v VerticalJc) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { 20 | return xml.Attr{Name: name, Value: string(v)}, nil 21 | } 22 | 23 | // UnmarshalXMLAttr unmarshals an XML attribute into a VerticalJc type. 24 | func (v *VerticalJc) UnmarshalXMLAttr(attr xml.Attr) error { 25 | switch attr.Value { 26 | case "top", "center", "both", "bottom": 27 | *v = VerticalJc(attr.Value) 28 | default: 29 | return fmt.Errorf("unexpected value for VerticalJc: %s", attr.Value) 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /wml/stypes/vertAlignRun.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // VerticalAlignRun Type 9 | type VerticalAlignRun string 10 | 11 | const ( 12 | VerticalAlignRunBaseline VerticalAlignRun = "baseline" // Regular Vertical Positioning 13 | VerticalAlignRunSuperscript VerticalAlignRun = "superscript" // Superscript 14 | VerticalAlignRunSubscript VerticalAlignRun = "subscript" // Subscript 15 | ) 16 | 17 | func VerticalAlignRunFromStr(value string) (VerticalAlignRun, error) { 18 | switch value { 19 | case "baseline": 20 | return VerticalAlignRunBaseline, nil 21 | case "superscript": 22 | return VerticalAlignRunSuperscript, nil 23 | case "subscript": 24 | return VerticalAlignRunSubscript, nil 25 | default: 26 | return "", errors.New("Invalid VerticalAlignRun Type") 27 | } 28 | } 29 | 30 | func (v *VerticalAlignRun) UnmarshalXMLAttr(attr xml.Attr) error { 31 | val, err := VerticalAlignRunFromStr(attr.Value) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | *v = val 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /wml/stypes/wrap.go: -------------------------------------------------------------------------------- 1 | package stypes 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | ) 7 | 8 | // Wrap Type 9 | type Wrap string 10 | 11 | // Constants for valid values 12 | const ( 13 | WrapAuto Wrap = "auto" // Default Text Wrapping Around Frame 14 | WrapNotBeside Wrap = "notBeside" // No Text Wrapping Beside Frame 15 | WrapAround Wrap = "around" // Allow Text Wrapping Around Frame 16 | WrapTight Wrap = "tight" // Tight Text Wrapping Around Frame 17 | WrapThrough Wrap = "through" // Through Text Wrapping Around Frame 18 | WrapNone Wrap = "none" // No Text Wrapping Around Frame 19 | ) 20 | 21 | // WrapFromStr converts a string to Wrap type. 22 | func WrapFromStr(value string) (Wrap, error) { 23 | switch value { 24 | case "auto": 25 | return WrapAuto, nil 26 | case "notBeside": 27 | return WrapNotBeside, nil 28 | case "around": 29 | return WrapAround, nil 30 | case "tight": 31 | return WrapTight, nil 32 | case "through": 33 | return WrapThrough, nil 34 | case "none": 35 | return WrapNone, nil 36 | default: 37 | return "", errors.New("Invalid Wrap value") 38 | } 39 | } 40 | 41 | // UnmarshalXMLAttr unmarshals XML attribute into Wrap. 42 | func (w *Wrap) UnmarshalXMLAttr(attr xml.Attr) error { 43 | val, err := WrapFromStr(attr.Value) 44 | if err != nil { 45 | return err 46 | } 47 | *w = val 48 | return nil 49 | } 50 | --------------------------------------------------------------------------------