├── .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 |
--------------------------------------------------------------------------------