diff --git a/elemtypes/optOnOffElem.go b/elemtypes/optOnOffElem.go index 887d94a..dbfc229 100644 --- a/elemtypes/optOnOffElem.go +++ b/elemtypes/optOnOffElem.go @@ -6,14 +6,14 @@ import ( "github.com/gomutex/godocx/wml/stypes" ) -// OptOnOffElem helper struct that has only one optional field which is OnOff type -type OptOnOffElem struct { - Val stypes.OnOff `xml:"val,attr,omitempty"` +// OptBinFlagElem helper struct that has only one optional field which is BinFlag type +type OptBinFlagElem struct { + Val stypes.BinFlag `xml:"val,attr,omitempty"` } -// MarshalXML implements the xml.Marshaler interface for the OnOffElem type. -// It encodes the OnOffElem to its corresponding XML representation. -func (s *OptOnOffElem) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +// MarshalXML implements the xml.Marshaler interface for the BinFlagElem type. +// It encodes the BinFlagElem to its corresponding XML representation. +func (s *OptBinFlagElem) MarshalXML(e *xml.Encoder, start xml.StartElement) error { if s.Val != "" { start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: string(s.Val)}) } diff --git a/elemtypes/singleIntVal.go b/elemtypes/singleIntVal.go deleted file mode 100644 index ace8d5f..0000000 --- a/elemtypes/singleIntVal.go +++ /dev/null @@ -1,28 +0,0 @@ -package elemtypes - -import ( - "encoding/xml" - "strconv" -) - -// SingleIntVal - Generic Element that has only one string-type attribute -// And the String type does not have validation -// dont use this if the element requires validation -type SingleIntVal struct { - Val int `xml:"val,attr"` -} - -func NewSingleIntVal(value int) *SingleIntVal { - return &SingleIntVal{ - Val: value, - } -} - -// MarshalXML implements the xml.Marshaler interface for the SingleIntVal type. -// It encodes the instance into XML using the "w:ELEMENT_NAME" element with a "w:val" attribute. -func (s *SingleIntVal) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: strconv.Itoa(s.Val)}) - err := e.EncodeElement("", start) - - return err -} diff --git a/internal/testhelper.go b/internal/testhelper.go new file mode 100644 index 0000000..c877db5 --- /dev/null +++ b/internal/testhelper.go @@ -0,0 +1,49 @@ +package internal + +import ( + "fmt" + "reflect" +) + +func ToPtr[T any](input T) *T { + return &input +} + +func FormatPtr[T any](ptr *T) string { + if ptr == nil { + return "" + } + return fmt.Sprintf("%v", *ptr) +} + +// func ComparePtr[T comparable](fieldName string, a, b *T) error { +// if a == nil || b == nil { +// if a != b { +// return fmt.Errorf("%s: expected %v but got %v", fieldName, FormatPtr(a), FormatPtr(b)) +// } +// } else if *a != *b { +// return fmt.Errorf("%s: expected %v but got %v", fieldName, *a, *b) +// } +// return nil +// } + +func ComparePtr[T comparable](fieldName string, a, b *T) error { + // Check if T is a struct + if reflect.TypeOf(*new(T)).Kind() == reflect.Struct { + if a == nil || b == nil { + if a != b { + return fmt.Errorf("%s: expected %v but got %v", fieldName, FormatPtr(a), FormatPtr(b)) + } + } + } else { + // For non-struct types, perform value comparison + if a == nil || b == nil { + if a != b { + return fmt.Errorf("%s: expected %v but got %v", fieldName, FormatPtr(a), FormatPtr(b)) + } + } else if *a != *b { + return fmt.Errorf("%s: expected %v but got %v", fieldName, *a, *b) + } + } + return nil +} diff --git a/wml/docxrun/border.go b/wml/ctypes/border.go similarity index 85% rename from wml/docxrun/border.go rename to wml/ctypes/border.go index 20806fd..e9f943a 100644 --- a/wml/docxrun/border.go +++ b/wml/ctypes/border.go @@ -1,4 +1,4 @@ -package docxrun +package ctypes import ( "encoding/xml" @@ -6,18 +6,18 @@ import ( "github.com/gomutex/godocx/wml/stypes" ) -type TextBorder struct { +type Border struct { Val stypes.BorderStyle `xml:"val,attr"` Color *string `xml:"color,attr,omitempty"` ThemeColor *stypes.ThemeColor `xml:"themeColor,attr,omitempty"` ThemeTint *string `xml:"themeTint,attr,omitempty"` ThemeShade *string `xml:"themeShade,attr,omitempty"` Space *string `xml:"space,attr,omitempty"` - Shadow *stypes.OnOff `xml:"shadow,attr,omitempty"` - Frame *stypes.OnOff `xml:"frame,attr,omitempty"` + Shadow *stypes.BinFlag `xml:"shadow,attr,omitempty"` + Frame *stypes.BinFlag `xml:"frame,attr,omitempty"` } -func (t *TextBorder) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +func (t *Border) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: string(t.Val)}) if t.Color != nil { diff --git a/wml/docxrun/border_test.go b/wml/ctypes/border_test.go similarity index 91% rename from wml/docxrun/border_test.go rename to wml/ctypes/border_test.go index 56c209b..c95627d 100644 --- a/wml/docxrun/border_test.go +++ b/wml/ctypes/border_test.go @@ -1,4 +1,4 @@ -package docxrun +package ctypes import ( "encoding/xml" @@ -8,29 +8,29 @@ import ( "github.com/gomutex/godocx/wml/stypes" ) -func TestTextBorder_MarshalXML(t *testing.T) { +func TestBorder_MarshalXML(t *testing.T) { tests := []struct { name string - input TextBorder + input Border expected string }{ { name: "With all attributes", - input: TextBorder{ + input: Border{ Val: stypes.BorderStyleSingle, Color: StringPtr("FF0000"), ThemeColor: themeColorPointer(stypes.ThemeColorAccent1), ThemeTint: StringPtr("500"), ThemeShade: StringPtr("200"), Space: StringPtr("0"), - Shadow: OnOffPtr(stypes.OnOffTrue), - Frame: OnOffPtr(stypes.OnOffTrue), + Shadow: BinFlagPtr(stypes.BinFlagTrue), + Frame: BinFlagPtr(stypes.BinFlagTrue), }, expected: ``, }, { name: "Without optional attributes", - input: TextBorder{ + input: Border{ Val: stypes.BorderStyleDouble, }, expected: ``, @@ -57,31 +57,31 @@ func TestTextBorder_MarshalXML(t *testing.T) { } } -func TestTextBorder_UnmarshalXML(t *testing.T) { +func TestBorder_UnmarshalXML(t *testing.T) { tests := []struct { name string inputXML string - expected TextBorder + expected Border }{ { name: "With all attributes", inputXML: ``, - expected: TextBorder{ + expected: Border{ Val: stypes.BorderStyleSingle, Color: StringPtr("FF0000"), ThemeColor: themeColorPointer(stypes.ThemeColorAccent1), ThemeTint: StringPtr("500"), ThemeShade: StringPtr("200"), Space: StringPtr("0"), - Shadow: OnOffPtr(stypes.OnOffTrue), - Frame: OnOffPtr(stypes.OnOffTrue), + Shadow: BinFlagPtr(stypes.BinFlagTrue), + Frame: BinFlagPtr(stypes.BinFlagTrue), }, }, { name: "Without optional attributes", inputXML: ``, - expected: TextBorder{ + expected: Border{ Val: stypes.BorderStyleDouble, }, }, @@ -89,7 +89,7 @@ func TestTextBorder_UnmarshalXML(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var result TextBorder + var result Border err := xml.Unmarshal([]byte(tt.inputXML), &result) if err != nil { @@ -171,7 +171,7 @@ func StringPtr(s string) *string { return &s } -func OnOffPtr(o stypes.OnOff) *stypes.OnOff { +func BinFlagPtr(o stypes.BinFlag) *stypes.BinFlag { return &o } diff --git a/wml/ctypes/cnf.go b/wml/ctypes/cnf.go new file mode 100644 index 0000000..29338b2 --- /dev/null +++ b/wml/ctypes/cnf.go @@ -0,0 +1,17 @@ +package ctypes + +import ( + "encoding/xml" +) + +type Cnf struct { + Val string `xml:"val,attr"` +} + +func (c *Cnf) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name.Local = "w:cnfStyle" + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: c.Val}) + err := e.EncodeElement("", start) + + return err +} diff --git a/wml/ctypes/cnf_test.go b/wml/ctypes/cnf_test.go new file mode 100644 index 0000000..595d562 --- /dev/null +++ b/wml/ctypes/cnf_test.go @@ -0,0 +1,71 @@ +package ctypes + +import ( + "encoding/xml" + "strings" + "testing" +) + +func TestCnf_MarshalXML(t *testing.T) { + tests := []struct { + name string + cnf Cnf + expected string + }{ + { + name: "WithVal", + cnf: Cnf{Val: "12345"}, + expected: ``, + }, + { + name: "EmptyVal", + cnf: Cnf{Val: ""}, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + xmlBytes, err := xml.Marshal(&tt.cnf) + if err != nil { + t.Fatalf("Error marshaling Cnf: %v", err) + } + + result := string(xmlBytes) + if !strings.Contains(result, tt.expected) { + t.Errorf("Expected XML:\n%s\nBut got:\n%s", tt.expected, result) + } + }) + } +} + +func TestCnf_UnmarshalXML(t *testing.T) { + tests := []struct { + xmlStr string + expected Cnf + }{ + { + xmlStr: ``, + expected: Cnf{Val: "12345"}, + }, + { + xmlStr: ``, + expected: Cnf{Val: ""}, + }, + } + + for _, tt := range tests { + t.Run(tt.xmlStr, func(t *testing.T) { + var cnf Cnf + + err := xml.Unmarshal([]byte(tt.xmlStr), &cnf) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if cnf.Val != tt.expected.Val { + t.Errorf("Expected Val %s but got %s", tt.expected.Val, cnf.Val) + } + }) + } +} diff --git a/wml/ctypes/decNum.go b/wml/ctypes/decNum.go new file mode 100644 index 0000000..096ce6b --- /dev/null +++ b/wml/ctypes/decNum.go @@ -0,0 +1,23 @@ +package ctypes + +import ( + "encoding/xml" + "strconv" +) + +type DecimalNum struct { + Val int `xml:"val,attr"` +} + +func NewDecimalNum(value int) *DecimalNum { + return &DecimalNum{ + Val: value, + } +} + +func (s *DecimalNum) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: strconv.Itoa(s.Val)}) + err := e.EncodeElement("", start) + + return err +} diff --git a/elemtypes/singleIntVal_test.go b/wml/ctypes/decNum_test.go similarity index 63% rename from elemtypes/singleIntVal_test.go rename to wml/ctypes/decNum_test.go index 4a5c9ca..92c052d 100644 --- a/elemtypes/singleIntVal_test.go +++ b/wml/ctypes/decNum_test.go @@ -1,4 +1,4 @@ -package elemtypes +package ctypes import ( "encoding/xml" @@ -6,21 +6,21 @@ import ( "testing" ) -func TestSingleIntVal_MarshalXML(t *testing.T) { +func TestDecimalNum_MarshalXML(t *testing.T) { tests := []struct { name string - input SingleIntVal + input DecimalNum expected string }{ { name: "With value", - input: SingleIntVal{Val: 10}, - expected: ``, + input: DecimalNum{Val: 10}, + expected: ``, }, { name: "Empty value", - input: SingleIntVal{Val: -1}, - expected: ``, + input: DecimalNum{Val: -1}, + expected: ``, }, } @@ -28,7 +28,7 @@ func TestSingleIntVal_MarshalXML(t *testing.T) { t.Run(tt.name, func(t *testing.T) { var result strings.Builder encoder := xml.NewEncoder(&result) - start := xml.StartElement{Name: xml.Name{Local: "w:spacing"}} + start := xml.StartElement{Name: xml.Name{Local: "w:outlineLvl"}} err := tt.input.MarshalXML(encoder, start) if err != nil { @@ -45,27 +45,27 @@ func TestSingleIntVal_MarshalXML(t *testing.T) { } } -func TestSingleIntVal_UnmarshalXML(t *testing.T) { +func TestDecimalNum_UnmarshalXML(t *testing.T) { tests := []struct { name string inputXML string - expected SingleIntVal + expected DecimalNum }{ { name: "With value", - inputXML: ``, - expected: SingleIntVal{Val: 122}, + inputXML: ``, + expected: DecimalNum{Val: 122}, }, { name: "Empty value", - inputXML: ``, - expected: SingleIntVal{Val: 3}, + inputXML: ``, + expected: DecimalNum{Val: 3}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var result SingleIntVal + var result DecimalNum err := xml.Unmarshal([]byte(tt.inputXML), &result) if err != nil { diff --git a/wml/ctypes/shd.go b/wml/ctypes/shd.go index b568a6d..df6cd94 100644 --- a/wml/ctypes/shd.go +++ b/wml/ctypes/shd.go @@ -56,7 +56,7 @@ func DefaultShading() *Shading { color := "auto" fill := "FFFFFF" return &Shading{ - Val: "Clear", + Val: stypes.ShdClear, Color: &color, Fill: &fill, } diff --git a/wml/ctypes/textAlign.go b/wml/ctypes/textAlign.go new file mode 100644 index 0000000..1cf783d --- /dev/null +++ b/wml/ctypes/textAlign.go @@ -0,0 +1,25 @@ +package ctypes + +import ( + "encoding/xml" + "errors" + + "github.com/gomutex/godocx/wml/stypes" +) + +// TextAlign represents the text alignment settings in a Word document. +type TextAlign struct { + Val stypes.TextAlign `xml:"val,attr,omitempty"` +} + +// MarshalXML implements the xml.Marshaler interface for the TextAlign type. +// It encodes the TextAlign to its corresponding XML representation. +func (s *TextAlign) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if s.Val == "" { + return errors.New("Invalid TextAlign") + } + + start.Name.Local = "w:textAlignment" + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: string(s.Val)}) + return e.EncodeElement("", start) +} diff --git a/wml/ctypes/textAlign_test.go b/wml/ctypes/textAlign_test.go new file mode 100644 index 0000000..d97eef6 --- /dev/null +++ b/wml/ctypes/textAlign_test.go @@ -0,0 +1,91 @@ +package ctypes + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/gomutex/godocx/wml/stypes" +) + +func TestTextAlign_MarshalXML(t *testing.T) { + tests := []struct { + name string + textAlignment TextAlign + expected string + shouldMarshal bool // Indicates if the test should perform marshaling + }{ + { + name: "WithVal", + textAlignment: TextAlign{Val: stypes.TextAlignTop}, + expected: ``, + shouldMarshal: true, + }, + { + name: "WithoutVal", + textAlignment: TextAlign{}, + expected: ``, + shouldMarshal: false, + }, + { + name: "Empty", + textAlignment: TextAlign{Val: stypes.TextAlign("")}, + expected: ``, + shouldMarshal: false, // Not expected to marshal because Val is empty + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldMarshal { + xmlBytes, err := xml.Marshal(&tt.textAlignment) + if err != nil { + t.Fatalf("Error marshaling TextAlign: %v", err) + } + + result := string(xmlBytes) + if !strings.Contains(result, tt.expected) { + t.Errorf("Expected XML:\n%s\nBut got:\n%s", tt.expected, result) + } + } else { + t.Skip("Skipping marshaling test for empty TextAlign") + } + }) + } +} + +func TestTextAlign_UnmarshalXML(t *testing.T) { + tests := []struct { + xmlStr string + expected TextAlign + shouldErr bool + }{ + { + xmlStr: ``, + expected: TextAlign{Val: stypes.TextAlignTop}, + shouldErr: false, + }, + // { + // xmlStr: ``, + // expected: TextAlign{}, + // shouldErr: true, + // }, + } + + for _, tt := range tests { + t.Run(tt.xmlStr, func(t *testing.T) { + var textAlignment TextAlign + + err := xml.Unmarshal([]byte(tt.xmlStr), &textAlignment) + if tt.shouldErr && err == nil { + t.Fatal("err should not be nil") + } else if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } else { + if textAlignment.Val != tt.expected.Val { + t.Errorf("Expected Val %s but got %s", tt.expected.Val, textAlignment.Val) + } + } + }) + } +} diff --git a/wml/sections/testDirecion_test.go b/wml/ctypes/textDirecion_test.go similarity index 99% rename from wml/sections/testDirecion_test.go rename to wml/ctypes/textDirecion_test.go index 4cc4ef6..dfc2434 100644 --- a/wml/sections/testDirecion_test.go +++ b/wml/ctypes/textDirecion_test.go @@ -1,4 +1,4 @@ -package sections +package ctypes import ( "encoding/xml" diff --git a/wml/sections/textDirection.go b/wml/ctypes/textDirection.go similarity index 97% rename from wml/sections/textDirection.go rename to wml/ctypes/textDirection.go index 624c20c..0941dce 100644 --- a/wml/sections/textDirection.go +++ b/wml/ctypes/textDirection.go @@ -1,4 +1,4 @@ -package sections +package ctypes import ( "encoding/xml" diff --git a/wml/ctypes/textboxTightWrap.go b/wml/ctypes/textboxTightWrap.go new file mode 100644 index 0000000..d326ac8 --- /dev/null +++ b/wml/ctypes/textboxTightWrap.go @@ -0,0 +1,22 @@ +package ctypes + +import ( + "encoding/xml" + + "github.com/gomutex/godocx/wml/stypes" +) + +// TextboxTightWrap represents the tight wrap settings in a Word document text box. +type TextboxTightWrap struct { + Val stypes.TextboxTightWrap `xml:"val,attr,omitempty"` +} + +// MarshalXML implements the xml.Marshaler interface for the TextboxTightWrap type. +// It encodes the TextboxTightWrap to its corresponding XML representation. +func (s *TextboxTightWrap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name.Local = "w:textboxTightWrap" + if s.Val != "" { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: string(s.Val)}) + } + return e.EncodeElement("", start) +} diff --git a/wml/ctypes/textboxTightWrap_test.go b/wml/ctypes/textboxTightWrap_test.go new file mode 100644 index 0000000..5f75477 --- /dev/null +++ b/wml/ctypes/textboxTightWrap_test.go @@ -0,0 +1,120 @@ +package ctypes + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/gomutex/godocx/wml/stypes" +) + +func TestTextboxTightWrap_MarshalXML(t *testing.T) { + tests := []struct { + name string + tightWrap TextboxTightWrap + expected string + shouldMarshal bool // Indicates if the test should perform marshaling + }{ + { + name: "None", + tightWrap: TextboxTightWrap{Val: stypes.TextboxTightWrapNone}, + expected: ``, + shouldMarshal: true, + }, + { + name: "AllLines", + tightWrap: TextboxTightWrap{Val: stypes.TextboxTightWrapAllLines}, + expected: ``, + shouldMarshal: true, + }, + { + name: "FirstAndLastLine", + tightWrap: TextboxTightWrap{Val: stypes.TextboxTightWrapFirstAndLastLine}, + expected: ``, + shouldMarshal: true, + }, + { + name: "FirstLineOnly", + tightWrap: TextboxTightWrap{Val: stypes.TextboxTightWrapFirstLineOnly}, + expected: ``, + shouldMarshal: true, + }, + { + name: "LastLineOnly", + tightWrap: TextboxTightWrap{Val: stypes.TextboxTightWrapLastLineOnly}, + expected: ``, + shouldMarshal: true, + }, + { + name: "Empty", + tightWrap: TextboxTightWrap{Val: stypes.TextboxTightWrap("")}, + expected: ``, + shouldMarshal: false, // Not expected to marshal because Val is empty + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldMarshal { + xmlBytes, err := xml.Marshal(&tt.tightWrap) + if err != nil { + t.Fatalf("Error marshaling TextboxTightWrap: %v", err) + } + + result := string(xmlBytes) + if !strings.Contains(result, tt.expected) { + t.Errorf("Expected XML:\n%s\nBut got:\n%s", tt.expected, result) + } + } else { + t.Skip("Skipping marshaling test for empty TextboxTightWrap") + } + }) + } +} + +func TestTextboxTightWrap_UnmarshalXML(t *testing.T) { + tests := []struct { + xmlStr string + expected TextboxTightWrap + }{ + { + xmlStr: ``, + expected: TextboxTightWrap{Val: stypes.TextboxTightWrapNone}, + }, + { + xmlStr: ``, + expected: TextboxTightWrap{Val: stypes.TextboxTightWrapAllLines}, + }, + { + xmlStr: ``, + expected: TextboxTightWrap{Val: stypes.TextboxTightWrapFirstAndLastLine}, + }, + { + xmlStr: ``, + expected: TextboxTightWrap{Val: stypes.TextboxTightWrapFirstLineOnly}, + }, + { + xmlStr: ``, + expected: TextboxTightWrap{Val: stypes.TextboxTightWrapLastLineOnly}, + }, + { + xmlStr: ``, + expected: TextboxTightWrap{}, + }, + } + + for _, tt := range tests { + t.Run(tt.xmlStr, func(t *testing.T) { + var tightWrap TextboxTightWrap + + err := xml.Unmarshal([]byte(tt.xmlStr), &tightWrap) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if tightWrap.Val != tt.expected.Val { + t.Errorf("Expected Val %s but got %s", tt.expected.Val, tightWrap.Val) + } + }) + } +} diff --git a/wml/ctypes/trackChange.go b/wml/ctypes/trackChange.go new file mode 100644 index 0000000..bd9c07f --- /dev/null +++ b/wml/ctypes/trackChange.go @@ -0,0 +1,26 @@ +package ctypes + +import ( + "encoding/xml" + "strconv" +) + +// TrackChange represents the complex type for track change +type TrackChange struct { + ID int `xml:"id,attr"` + Author string `xml:"author,attr"` + Date *string `xml:"date,attr,omitempty"` +} + +func (t *TrackChange) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Attr = []xml.Attr{ + {Name: xml.Name{Local: "w:id"}, Value: strconv.Itoa(t.ID)}, + {Name: xml.Name{Local: "w:author"}, Value: t.Author}, + } + + if t.Date != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:date"}, Value: *t.Date}) + } + + return e.EncodeElement("", start) +} diff --git a/wml/ctypes/trackChange_test.go b/wml/ctypes/trackChange_test.go new file mode 100644 index 0000000..b7c27bf --- /dev/null +++ b/wml/ctypes/trackChange_test.go @@ -0,0 +1,110 @@ +package ctypes + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/gomutex/godocx/internal" +) + +func TestTrackChange_MarshalXML(t *testing.T) { + tests := []struct { + name string + input TrackChange + expected string + }{ + { + name: "With all attributes", + input: TrackChange{ + ID: 123, + Author: "John Doe", + Date: internal.ToPtr("2023-06-18T12:34:56Z"), + }, + expected: ``, + }, + { + name: "Without date attribute", + input: TrackChange{ + ID: 124, + Author: "Jane Doe", + }, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result strings.Builder + encoder := xml.NewEncoder(&result) + start := xml.StartElement{Name: xml.Name{Local: "w:TrackChange"}} + + err := tt.input.MarshalXML(encoder, start) + if err != nil { + t.Fatalf("Error marshaling XML: %v", err) + } + + encoder.Flush() + + if result.String() != tt.expected { + t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) + } + }) + } +} + +func TestTrackChange_UnmarshalXML(t *testing.T) { + tests := []struct { + name string + inputXML string + expected TrackChange + }{ + { + name: "With all attributes", + inputXML: ``, + expected: TrackChange{ + ID: 123, + Author: "John Doe", + Date: internal.ToPtr("2023-06-18T12:34:56Z"), + }, + }, + { + name: "Without date attribute", + inputXML: ``, + expected: TrackChange{ + ID: 124, + Author: "Jane Doe", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result TrackChange + + err := xml.Unmarshal([]byte(tt.inputXML), &result) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + // Compare ID + if result.ID != tt.expected.ID { + t.Errorf("Expected ID %d but got %d", tt.expected.ID, result.ID) + } + + // Compare Author + if result.Author != tt.expected.Author { + t.Errorf("Expected Author %s but got %s", tt.expected.Author, result.Author) + } + + // Compare Date + if tt.expected.Date != nil { + if result.Date == nil || *result.Date != *tt.expected.Date { + t.Errorf("Expected Date %s but got %v", *tt.expected.Date, result.Date) + } + } else if result.Date != nil { + t.Errorf("Expected Date nil but got %v", result.Date) + } + }) + } +} diff --git a/wml/ctypes/trkchgnum.go b/wml/ctypes/trkchgnum.go new file mode 100644 index 0000000..ec1c73a --- /dev/null +++ b/wml/ctypes/trkchgnum.go @@ -0,0 +1,31 @@ +package ctypes + +import ( + "encoding/xml" + "strconv" +) + +// TrackChangeNum represents the complex type for track change numbering +type TrackChangeNum struct { + ID int `xml:"id,attr"` + Author string `xml:"author,attr"` + Date *string `xml:"date,attr,omitempty"` + Original *string `xml:"original,attr,omitempty"` +} + +func (t *TrackChangeNum) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Attr = []xml.Attr{ + {Name: xml.Name{Local: "w:id"}, Value: strconv.Itoa(t.ID)}, + {Name: xml.Name{Local: "w:author"}, Value: t.Author}, + } + + if t.Date != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:date"}, Value: *t.Date}) + } + + if t.Original != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:original"}, Value: *t.Original}) + } + + return e.EncodeElement("", start) +} diff --git a/wml/ctypes/trkchgnum_test.go b/wml/ctypes/trkchgnum_test.go new file mode 100644 index 0000000..4361c81 --- /dev/null +++ b/wml/ctypes/trkchgnum_test.go @@ -0,0 +1,150 @@ +package ctypes + +import ( + "encoding/xml" + "testing" + + "github.com/gomutex/godocx/internal" +) + +func TestTrackChangeNum_MarshalXML(t *testing.T) { + tests := []struct { + name string + input TrackChangeNum + expected string + }{ + { + name: "With all attributes", + input: TrackChangeNum{ + ID: 123, + Author: "John Doe", + Date: internal.ToPtr("2023-06-18T12:34:56Z"), + Original: internal.ToPtr("42"), + }, + expected: ``, + }, + { + name: "Without optional attributes", + input: TrackChangeNum{ + ID: 124, + Author: "Jane Doe", + }, + expected: ``, + }, + { + name: "With only date attribute", + input: TrackChangeNum{ + ID: 125, + Author: "Alice", + Date: internal.ToPtr("2024-06-18T12:34:56Z"), + }, + expected: ``, + }, + { + name: "With only original attribute", + input: TrackChangeNum{ + ID: 126, + Author: "Bob", + Original: internal.ToPtr("99"), + }, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output, err := xml.Marshal(tt.input) + if err != nil { + t.Fatalf("Error marshaling to XML: %v", err) + } + + if string(output) != tt.expected { + t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, string(output)) + } + }) + } +} + +func TestTrackChangeNum_UnmarshalXML(t *testing.T) { + tests := []struct { + name string + inputXML string + expected TrackChangeNum + }{ + { + name: "With all attributes", + inputXML: ``, + expected: TrackChangeNum{ + ID: 123, + Author: "John Doe", + Date: internal.ToPtr("2023-06-18T12:34:56Z"), + Original: internal.ToPtr("42"), + }, + }, + { + name: "Without optional attributes", + inputXML: ``, + expected: TrackChangeNum{ + ID: 124, + Author: "Jane Doe", + }, + }, + { + name: "With only date attribute", + inputXML: ``, + expected: TrackChangeNum{ + ID: 125, + Author: "Alice", + Date: internal.ToPtr("2024-06-18T12:34:56Z"), + }, + }, + { + name: "With only original attribute", + inputXML: ``, + expected: TrackChangeNum{ + ID: 126, + Author: "Bob", + Original: internal.ToPtr("99"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result TrackChangeNum + + err := xml.Unmarshal([]byte(tt.inputXML), &result) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + // Compare ID + if result.ID != tt.expected.ID { + t.Errorf("Expected ID %d but got %d", tt.expected.ID, result.ID) + } + + // Compare Author + if result.Author != tt.expected.Author { + t.Errorf("Expected Author %s but got %s", tt.expected.Author, result.Author) + } + + // Compare Date + if tt.expected.Date != nil { + if result.Date == nil || *result.Date != *tt.expected.Date { + t.Errorf("Expected Date %s but got %v", *tt.expected.Date, result.Date) + } + } else if result.Date != nil { + t.Errorf("Expected Date nil but got %v", result.Date) + } + + // Compare Original + if tt.expected.Original != nil { + if result.Original == nil || *result.Original != *tt.expected.Original { + t.Errorf("Expected Original %s but got %v", *tt.expected.Original, result.Original) + } + } else if result.Original != nil { + t.Errorf("Expected Original nil but got %v", result.Original) + } + }) + } +} diff --git a/wml/docxpara/borders.go b/wml/docxpara/borders.go new file mode 100644 index 0000000..f5d85e2 --- /dev/null +++ b/wml/docxpara/borders.go @@ -0,0 +1,63 @@ +package docxpara + +import ( + "encoding/xml" + "fmt" + + "github.com/gomutex/godocx/wml/ctypes" +) + +type ParaBorder struct { + Top *ctypes.Border `xml:"top,omitempty"` + Left *ctypes.Border `xml:"left,omitempty"` + Right *ctypes.Border `xml:"right,omitempty"` + Bottom *ctypes.Border `xml:"bottom,omitempty"` + Between *ctypes.Border `xml:"between,omitempty"` + Bar *ctypes.Border `xml:"bar,omitempty"` +} + +func (p *ParaBorder) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name.Local = "w:pBdr" + err := e.EncodeToken(start) + if err != nil { + return err + } + + if p.Top != nil { + if err = p.Top.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:top"}}); err != nil { + return fmt.Errorf("Paragraph border-Top: %w", err) + } + } + + if p.Left != nil { + if err = p.Left.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:left"}}); err != nil { + return fmt.Errorf("Paragraph border-Left: %w", err) + } + } + + if p.Right != nil { + if err = p.Right.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:right"}}); err != nil { + return fmt.Errorf("Paragraph border-Right: %w", err) + } + } + + if p.Bottom != nil { + if err = p.Bottom.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:bottom"}}); err != nil { + return fmt.Errorf("Paragraph border-Bottom: %w", err) + } + } + + if p.Between != nil { + if err = p.Between.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:between"}}); err != nil { + return fmt.Errorf("Paragraph border-Between: %w", err) + } + } + + if p.Bar != nil { + if err = p.Bar.MarshalXML(e, xml.StartElement{Name: xml.Name{Local: "w:bar"}}); err != nil { + return fmt.Errorf("Paragraph border-Bar: %w", err) + } + } + + return e.EncodeToken(start.End()) +} diff --git a/wml/docxpara/borders_test.go b/wml/docxpara/borders_test.go new file mode 100644 index 0000000..29ca9fd --- /dev/null +++ b/wml/docxpara/borders_test.go @@ -0,0 +1,182 @@ +package docxpara + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/gomutex/godocx/internal" + "github.com/gomutex/godocx/wml/ctypes" +) + +func TestParaBorder_MarshalXML(t *testing.T) { + tests := []struct { + name string + input ParaBorder + expected string + }{ + { + name: "All fields set", + input: ParaBorder{ + Top: &ctypes.Border{Val: "single", Color: internal.ToPtr("auto")}, + Left: &ctypes.Border{Val: "dashed", Color: internal.ToPtr("FF0000")}, + Right: &ctypes.Border{Val: "double", Color: internal.ToPtr("00FF00")}, + Bottom: &ctypes.Border{Val: "dotted", Color: internal.ToPtr("0000FF")}, + Between: &ctypes.Border{Val: "wave", Color: internal.ToPtr("123456")}, + Bar: &ctypes.Border{Val: "thick", Color: internal.ToPtr("654321")}, + }, + expected: ``, + }, + { + name: "Some fields set", + input: ParaBorder{ + Top: &ctypes.Border{Val: "single", Color: internal.ToPtr("auto")}, + Bottom: &ctypes.Border{Val: "dotted", Color: internal.ToPtr("0000FF")}, + }, + expected: ``, + }, + { + name: "No fields set", + input: ParaBorder{}, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result strings.Builder + encoder := xml.NewEncoder(&result) + + start := xml.StartElement{Name: xml.Name{Local: "w:pBdr"}} + if err := tt.input.MarshalXML(encoder, start); err != nil { + t.Fatalf("Error marshaling XML: %v", err) + } + + encoder.Flush() + + got := strings.TrimSpace(result.String()) + if got != tt.expected { + t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, got) + } + }) + } +} + +func TestParaBorder_UnmarshalXML(t *testing.T) { + tests := []struct { + name string + inputXML string + expected ParaBorder + }{ + { + name: "All fields set", + inputXML: ` + + + + + + + `, + expected: ParaBorder{ + Top: &ctypes.Border{Val: "single", Color: internal.ToPtr("auto")}, + Left: &ctypes.Border{Val: "dashed", Color: internal.ToPtr("FF0000")}, + Right: &ctypes.Border{Val: "double", Color: internal.ToPtr("00FF00")}, + Bottom: &ctypes.Border{Val: "dotted", Color: internal.ToPtr("0000FF")}, + Between: &ctypes.Border{Val: "wave", Color: internal.ToPtr("123456")}, + Bar: &ctypes.Border{Val: "thick", Color: internal.ToPtr("654321")}, + }, + }, + { + name: "Some fields set", + inputXML: ` + + + `, + expected: ParaBorder{ + Top: &ctypes.Border{Val: "single", Color: internal.ToPtr("auto")}, + Bottom: &ctypes.Border{Val: "dotted", Color: internal.ToPtr("0000FF")}, + }, + }, + { + name: "No fields set", + inputXML: ` + `, + expected: ParaBorder{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result ParaBorder + err := xml.Unmarshal([]byte(tt.inputXML), &result) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + // Compare unmarshaled result with expected + if !paraBorderEqual(result, tt.expected) { + t.Errorf("Unmarshaled ParaBorder does not match expected:\nExpected: %+v\nGot: %+v", tt.expected, result) + } + }) + } +} + +// Helper function to compare ParaBorder structs +func paraBorderEqual(p1, p2 ParaBorder) bool { + if (p1.Top == nil && p2.Top != nil) || (p1.Top != nil && p2.Top == nil) { + return false + } + if p1.Top != nil && p2.Top != nil { + if p1.Top.Val != p2.Top.Val || *p1.Top.Color != *p2.Top.Color { + return false + } + } + + if (p1.Left == nil && p2.Left != nil) || (p1.Left != nil && p2.Left == nil) { + return false + } + if p1.Left != nil && p2.Left != nil { + if p1.Left.Val != p2.Left.Val || *p1.Left.Color != *p2.Left.Color { + return false + } + } + + if (p1.Right == nil && p2.Right != nil) || (p1.Right != nil && p2.Right == nil) { + return false + } + if p1.Right != nil && p2.Right != nil { + if p1.Right.Val != p2.Right.Val || *p1.Right.Color != *p2.Right.Color { + return false + } + } + + if (p1.Bottom == nil && p2.Bottom != nil) || (p1.Bottom != nil && p2.Bottom == nil) { + return false + } + if p1.Bottom != nil && p2.Bottom != nil { + if p1.Bottom.Val != p2.Bottom.Val || *p1.Bottom.Color != *p2.Bottom.Color { + return false + } + } + + if (p1.Between == nil && p2.Between != nil) || (p1.Between != nil && p2.Between == nil) { + return false + } + if p1.Between != nil && p2.Between != nil { + if p1.Between.Val != p2.Between.Val || *p1.Between.Color != *p2.Between.Color { + return false + } + } + + if (p1.Bar == nil && p2.Bar != nil) || (p1.Bar != nil && p2.Bar == nil) { + return false + } + if p1.Bar != nil && p2.Bar != nil { + if p1.Bar.Val != p2.Bar.Val || *p1.Bar.Color != *p2.Bar.Color { + return false + } + } + + return true +} diff --git a/wml/docxpara/framePr.go b/wml/docxpara/framePr.go new file mode 100644 index 0000000..07a56d6 --- /dev/null +++ b/wml/docxpara/framePr.go @@ -0,0 +1,119 @@ +package docxpara + +import ( + "encoding/xml" + "strconv" + + "github.com/gomutex/godocx/wml/stypes" +) + +type FrameProp struct { + Width *int64 `xml:"w,attr,omitempty"` + Height *int64 `xml:"h,attr,omitempty"` + + //Drop Cap Frame + DropCap *stypes.DropCap `xml:"dropCap,attr,omitempty"` + + //Drop Cap Vertical Height in Lines + Lines *int `xml:"lines,attr,omitempty"` + + //Frame Padding + VSpace *int64 `xml:"vSpace,attr,omitempty"` + HSpace *int64 `xml:"hSpace,attr,omitempty"` + + //Text Wrapping Around Frame + Wrap *stypes.Wrap `xml:"wrap,attr,omitempty"` + + //Frame Horizontal Positioning Base + HAnchor *stypes.Anchor `xml:"hAnchor,attr,omitempty"` + + //Frame Vertical Positioning Base + VAnchor *stypes.Anchor `xml:"vAnchor,attr,omitempty"` + + //Absolute Horizontal Position + AbsHPos *int `xml:"x,attr,omitempty"` + + //Absolute Vertical Position + AbsVPos *int `xml:"y,attr,omitempty"` + + //Relative Horizontal Position + XAlign *stypes.Align `xml:"xAlign,attr,omitempty"` + + //Relative Vertical Position + YAlign *stypes.Align `xml:"yAlign,attr,omitempty"` + + //Frame Height Type + HRule *stypes.HeightRule `xml:"hRule,attr,omitempty"` + + //Lock Frame Anchor to Paragraph + AnchorLock *stypes.BinFlag `xml:"anchorLock,attr,omitempty"` +} + +func (f *FrameProp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name.Local = "w:framePr" + + start.Attr = []xml.Attr{} + + if f.Width != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:w"}, Value: strconv.FormatInt(*f.Width, 10)}) + } + + if f.Height != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:h"}, Value: strconv.FormatInt(*f.Height, 10)}) + } + + if f.DropCap != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:dropCap"}, Value: string(*f.DropCap)}) + } + + if f.Lines != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:lines"}, Value: strconv.Itoa(*f.Lines)}) + } + + if f.HSpace != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:hSpace"}, Value: strconv.FormatInt(*f.HSpace, 10)}) + } + + if f.VSpace != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:vSpace"}, Value: strconv.FormatInt(*f.VSpace, 10)}) + } + + if f.Wrap != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:wrap"}, Value: string(*f.Wrap)}) + } + + if f.HAnchor != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:hAnchor"}, Value: string(*f.HAnchor)}) + } + + if f.VAnchor != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:vAnchor"}, Value: string(*f.VAnchor)}) + } + + if f.AbsHPos != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:x"}, Value: strconv.Itoa(*f.AbsHPos)}) + } + + if f.AbsVPos != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:y"}, Value: strconv.Itoa(*f.AbsVPos)}) + } + + if f.XAlign != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:xAlign"}, Value: string(*f.XAlign)}) + } + + if f.YAlign != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:yAlign"}, Value: string(*f.YAlign)}) + } + + if f.HRule != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:hRule"}, Value: string(*f.HRule)}) + } + + if f.AnchorLock != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:anchorLock"}, Value: string(*f.AnchorLock)}) + } + + return e.EncodeElement("", start) + +} diff --git a/wml/docxpara/framePr_test.go b/wml/docxpara/framePr_test.go new file mode 100644 index 0000000..0a14356 --- /dev/null +++ b/wml/docxpara/framePr_test.go @@ -0,0 +1,144 @@ +package docxpara + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/gomutex/godocx/internal" + "github.com/gomutex/godocx/wml/stypes" +) + +func TestFrameProp_MarshalXML(t *testing.T) { + tests := []struct { + name string + input FrameProp + expected string + }{ + { + name: "With all attributes", + input: FrameProp{ + Width: internal.ToPtr(int64(500)), + Height: internal.ToPtr(int64((300))), + DropCap: internal.ToPtr(stypes.DropCapMargin), + Lines: internal.ToPtr(3), + VSpace: internal.ToPtr(int64((50))), + HSpace: internal.ToPtr(int64((20))), + Wrap: internal.ToPtr(stypes.WrapAround), + HAnchor: internal.ToPtr(stypes.AnchorMargin), + VAnchor: internal.ToPtr(stypes.AnchorPage), + AbsHPos: internal.ToPtr(100), + AbsVPos: internal.ToPtr(200), + XAlign: internal.ToPtr(stypes.AlignLeft), + YAlign: internal.ToPtr(stypes.AlignCenter), + HRule: internal.ToPtr(stypes.HeightRuleExact), + AnchorLock: internal.ToPtr(stypes.BinFlagTrue), + }, + expected: ``, + }, + { + name: "Without optional attributes", + input: FrameProp{ + Width: internal.ToPtr(int64(500)), + Height: internal.ToPtr(int64(300)), + }, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result strings.Builder + encoder := xml.NewEncoder(&result) + start := xml.StartElement{Name: xml.Name{Local: "w:framePr"}} + + err := tt.input.MarshalXML(encoder, start) + if err != nil { + t.Fatalf("Error marshaling XML: %v", err) + } + + encoder.Flush() + + if result.String() != tt.expected { + t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) + } + }) + } +} + +func TestFrameProp_UnmarshalXML(t *testing.T) { + tests := []struct { + name string + inputXML string + expected FrameProp + }{ + { + name: "With all attributes", + inputXML: ``, + expected: FrameProp{ + Width: internal.ToPtr(int64(500)), + Height: internal.ToPtr(int64(300)), + DropCap: internal.ToPtr(stypes.DropCapMargin), + Lines: internal.ToPtr(3), + VSpace: internal.ToPtr(int64(50)), + HSpace: internal.ToPtr(int64(20)), + Wrap: internal.ToPtr(stypes.WrapAround), + HAnchor: internal.ToPtr(stypes.AnchorMargin), + VAnchor: internal.ToPtr(stypes.AnchorPage), + AbsHPos: internal.ToPtr(100), + AbsVPos: internal.ToPtr(200), + XAlign: internal.ToPtr(stypes.AlignLeft), + YAlign: internal.ToPtr(stypes.AlignCenter), + HRule: internal.ToPtr(stypes.HeightRuleExact), + AnchorLock: internal.ToPtr(stypes.BinFlagTrue), + }, + }, + { + name: "Without optional attributes", + inputXML: ``, + expected: FrameProp{ + Width: internal.ToPtr(int64(500)), + Height: internal.ToPtr(int64(300)), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result FrameProp + + err := xml.Unmarshal([]byte(tt.inputXML), &result) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + // Compare each field individually due to pointer comparisons + if !compareFrameProps(result, tt.expected) { + t.Errorf("Expected:\n%+v\nGot:\n%+v", tt.expected, result) + } + }) + } +} + +// Helper function to compare FrameProp structs +// Helper function to compare FrameProp structs +func compareFrameProps(a, b FrameProp) bool { + // Check each pointer for nil before comparing their dereferenced values + return (a.Width == nil && b.Width == nil || (a.Width != nil && b.Width != nil && *a.Width == *b.Width)) && + (a.Height == nil && b.Height == nil || (a.Height != nil && b.Height != nil && *a.Height == *b.Height)) && + (a.DropCap == nil && b.DropCap == nil || (a.DropCap != nil && b.DropCap != nil && *a.DropCap == *b.DropCap)) && + (a.Lines == nil && b.Lines == nil || (a.Lines != nil && b.Lines != nil && *a.Lines == *b.Lines)) && + (a.VSpace == nil && b.VSpace == nil || (a.VSpace != nil && b.VSpace != nil && *a.VSpace == *b.VSpace)) && + (a.HSpace == nil && b.HSpace == nil || (a.HSpace != nil && b.HSpace != nil && *a.HSpace == *b.HSpace)) && + (a.Wrap == nil && b.Wrap == nil || (a.Wrap != nil && b.Wrap != nil && *a.Wrap == *b.Wrap)) && + (a.HAnchor == nil && b.HAnchor == nil || (a.HAnchor != nil && b.HAnchor != nil && *a.HAnchor == *b.HAnchor)) && + (a.VAnchor == nil && b.VAnchor == nil || (a.VAnchor != nil && b.VAnchor != nil && *a.VAnchor == *b.VAnchor)) && + (a.AbsHPos == nil && b.AbsHPos == nil || (a.AbsHPos != nil && b.AbsHPos != nil && *a.AbsHPos == *b.AbsHPos)) && + (a.AbsVPos == nil && b.AbsVPos == nil || (a.AbsVPos != nil && b.AbsVPos != nil && *a.AbsVPos == *b.AbsVPos)) && + (a.XAlign == nil && b.XAlign == nil || (a.XAlign != nil && b.XAlign != nil && *a.XAlign == *b.XAlign)) && + (a.YAlign == nil && b.YAlign == nil || (a.YAlign != nil && b.YAlign != nil && *a.YAlign == *b.YAlign)) && + (a.HRule == nil && b.HRule == nil || (a.HRule != nil && b.HRule != nil && *a.HRule == *b.HRule)) && + (a.AnchorLock == nil && b.AnchorLock == nil || (a.AnchorLock != nil && b.AnchorLock != nil && *a.AnchorLock == *b.AnchorLock)) +} diff --git a/wml/docxpara/indent.go b/wml/docxpara/indent.go new file mode 100644 index 0000000..59646ad --- /dev/null +++ b/wml/docxpara/indent.go @@ -0,0 +1,58 @@ +package docxpara + +import ( + "encoding/xml" + "strconv" +) + +// Indent represents the Paragraph Indentation structure. +type Indent struct { + Left *int `xml:"left,attr,omitempty"` // Left Indentation + LeftChars *int `xml:"leftChars,attr,omitempty"` // Left Indentation in Character Units + Right *int `xml:"right,attr,omitempty"` // Right Indentation + RightChars *int `xml:"rightChars,attr,omitempty"` // Right Indentation in Character Units + Hanging *uint64 `xml:"hanging,attr,omitempty"` // Indentation Removed from First Line + HangingChars *int `xml:"hangingChars,attr,omitempty"` // Indentation Removed From First Line in Character Units + FirstLine *uint64 `xml:"firstLine,attr,omitempty"` // Additional First Line Indentation + FirstLineChars *int `xml:"firstLineChars,attr,omitempty"` // Additional First Line Indentation in Character Units +} + +// MarshalXML implements the xml.Marshaler interface for Indent. +func (i Indent) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name.Local = "w:ind" + start.Attr = []xml.Attr{} + + if i.Left != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:left"}, Value: strconv.Itoa(*i.Left)}) + } + + if i.LeftChars != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:leftChars"}, Value: strconv.Itoa(*i.LeftChars)}) + } + + if i.Right != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:right"}, Value: strconv.Itoa(*i.Right)}) + } + + if i.RightChars != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:rightChars"}, Value: strconv.Itoa(*i.RightChars)}) + } + + if i.Hanging != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:hanging"}, Value: strconv.FormatUint(*i.Hanging, 10)}) + } + + if i.HangingChars != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:hangingChars"}, Value: strconv.Itoa(*i.HangingChars)}) + } + + if i.FirstLine != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:firstLine"}, Value: strconv.FormatUint(*i.FirstLine, 10)}) + } + + if i.FirstLineChars != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:firstLineChars"}, Value: strconv.Itoa(*i.FirstLineChars)}) + } + + return e.EncodeElement("", start) +} diff --git a/wml/docxpara/indent_test.go b/wml/docxpara/indent_test.go new file mode 100644 index 0000000..8924387 --- /dev/null +++ b/wml/docxpara/indent_test.go @@ -0,0 +1,137 @@ +package docxpara + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/gomutex/godocx/internal" +) + +func TestInd_MarshalXML(t *testing.T) { + tests := []struct { + name string + input Indent + expected string + }{ + { + name: "With all attributes", + input: Indent{ + Left: internal.ToPtr(720), + LeftChars: internal.ToPtr(2), + Right: internal.ToPtr(360), + RightChars: internal.ToPtr(1), + Hanging: internal.ToPtr(uint64(360)), + HangingChars: internal.ToPtr(1), + FirstLine: internal.ToPtr(uint64(720)), + FirstLineChars: internal.ToPtr(2), + }, + expected: ``, + }, + { + name: "Without attributes", + input: Indent{}, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result strings.Builder + encoder := xml.NewEncoder(&result) + + start := xml.StartElement{Name: xml.Name{Local: "w:ind"}} + if err := tt.input.MarshalXML(encoder, start); err != nil { + t.Fatalf("Error marshaling XML: %v", err) + } + + // Finalize encoding + encoder.Flush() + + got := strings.TrimSpace(result.String()) + if got != tt.expected { + t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, got) + } + }) + } +} + +func TestInd_UnmarshalXML(t *testing.T) { + tests := []struct { + name string + inputXML string + expected Indent + }{ + { + name: "With all attributes", + inputXML: ``, + expected: Indent{ + Left: internal.ToPtr(720), + LeftChars: internal.ToPtr(2), + Right: internal.ToPtr(360), + RightChars: internal.ToPtr(1), + Hanging: internal.ToPtr(uint64(360)), + HangingChars: internal.ToPtr(1), + FirstLine: internal.ToPtr(uint64(720)), + FirstLineChars: internal.ToPtr(2), + }, + }, + { + name: "Without attributes", + inputXML: ``, + expected: Indent{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ind Indent + err := xml.Unmarshal([]byte(tt.inputXML), &ind) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + // + err = isEqualInd(tt.expected, ind) + if err != nil { + t.Fatal(err) + } + }) + } +} + +// Utility function to compare two Indent structs +func isEqualInd(a, b Indent) error { + if err := internal.ComparePtr("Left", a.Left, b.Left); err != nil { + return err + } + + if err := internal.ComparePtr("LeftChars", a.LeftChars, b.LeftChars); err != nil { + return err + } + + if err := internal.ComparePtr("Right", a.Right, b.Right); err != nil { + return err + } + + if err := internal.ComparePtr("RightChars", a.RightChars, b.RightChars); err != nil { + return err + } + + if err := internal.ComparePtr("Hanging", a.Hanging, b.Hanging); err != nil { + return err + } + + if err := internal.ComparePtr("HangingChars", a.HangingChars, b.HangingChars); err != nil { + return err + } + + if err := internal.ComparePtr("FirstLine", a.FirstLine, b.FirstLine); err != nil { + return err + } + + if err := internal.ComparePtr("FirstLineChars", a.FirstLineChars, b.FirstLineChars); err != nil { + return err + } + + return nil +} diff --git a/wml/docxpara/numPr.go b/wml/docxpara/numPr.go new file mode 100644 index 0000000..a6153a1 --- /dev/null +++ b/wml/docxpara/numPr.go @@ -0,0 +1,71 @@ +package docxpara + +import ( + "encoding/xml" + "fmt" + + "github.com/gomutex/godocx/wml/ctypes" +) + +// Numbering Definition Instance Reference +type NumProp struct { + //Numbering Level Reference + ILvl *ctypes.DecimalNum `xml:"ilvl,omitempty"` + + //Numbering Definition Instance Reference + NumID *ctypes.DecimalNum `xml:"numId,omitempty"` + + //Previous Paragraph Numbering Properties + NumChange *ctypes.TrackChangeNum `xml:"numberingChange,omitempty"` + + //Inserted Numbering Properties + Ins *ctypes.TrackChange `xml:"ins,omitempty"` +} + +// NewNumberingProperty creates a new NumberingProperty instance. +func NewNumberingProperty() *NumProp { + return &NumProp{} +} + +func (n *NumProp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name.Local = "w:numPr" + + err := e.EncodeToken(start) + if err != nil { + return err + } + + if n.ILvl != nil { + if err = n.ILvl.MarshalXML(e, xml.StartElement{ + Name: xml.Name{Local: "w:ilvl"}, + }); err != nil { + return fmt.Errorf("ILvl: %w", err) + } + } + + if n.NumID != nil { + if err = n.NumID.MarshalXML(e, xml.StartElement{ + Name: xml.Name{Local: "w:numId"}, + }); err != nil { + return fmt.Errorf("NumID: %w", err) + } + } + + if n.NumChange != nil { + if err = n.NumChange.MarshalXML(e, xml.StartElement{ + Name: xml.Name{Local: "w:numberingChange"}, + }); err != nil { + return fmt.Errorf("NumChange: %w", err) + } + } + + if n.Ins != nil { + if err = n.Ins.MarshalXML(e, xml.StartElement{ + Name: xml.Name{Local: "w:ins"}, + }); err != nil { + return fmt.Errorf("NumID: %w", err) + } + } + + return e.EncodeToken(start.End()) +} diff --git a/wml/docxpara/numPr_test.go b/wml/docxpara/numPr_test.go new file mode 100644 index 0000000..9b7e8c1 --- /dev/null +++ b/wml/docxpara/numPr_test.go @@ -0,0 +1,175 @@ +package docxpara + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/gomutex/godocx/internal" + "github.com/gomutex/godocx/wml/ctypes" +) + +func TestNumProp_MarshalXML(t *testing.T) { + date := "2023-06-18T12:34:56Z" + tests := []struct { + name string + input NumProp + expected string + }{ + { + name: "With all attributes", + input: NumProp{ + ILvl: &ctypes.DecimalNum{Val: 1}, + NumID: &ctypes.DecimalNum{Val: 42}, + NumChange: &ctypes.TrackChangeNum{ + ID: 123, + Author: "John Doe", + Date: &date, + Original: internal.ToPtr("original"), + }, + Ins: &ctypes.TrackChange{ + ID: 124, + Author: "Jane Doe", + Date: &date, + }, + }, + expected: ``, + }, + { + name: "Without optional attributes", + input: NumProp{ + ILvl: &ctypes.DecimalNum{Val: 1}, + NumID: &ctypes.DecimalNum{Val: 42}, + }, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result strings.Builder + encoder := xml.NewEncoder(&result) + start := xml.StartElement{} + + err := tt.input.MarshalXML(encoder, start) + if err != nil { + t.Fatalf("Error marshaling XML: %v", err) + } + + // Finalize encoding + encoder.Flush() + + if result.String() != tt.expected { + t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, result.String()) + } + }) + } +} + +func TestNumProp_UnmarshalXML(t *testing.T) { + tests := []struct { + name string + inputXML string + expected NumProp + }{ + { + name: "All fields present", + inputXML: ` + + + + + `, + expected: NumProp{ + ILvl: &ctypes.DecimalNum{Val: 2}, + NumID: &ctypes.DecimalNum{Val: 5}, + NumChange: &ctypes.TrackChangeNum{ + ID: 1, + Author: "author", + Date: internal.ToPtr("2024-06-19T12:00:00Z"), + }, + Ins: &ctypes.TrackChange{ + ID: 2, + Author: "author", + Date: internal.ToPtr("2024-06-19T12:00:00Z"), + }, + }, + }, + { + name: "Some fields missing", + inputXML: ` + + `, + expected: NumProp{ + ILvl: &ctypes.DecimalNum{Val: 1}, + }, + }, + { + name: "Empty struct", + inputXML: ``, + expected: NumProp{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result NumProp + err := xml.Unmarshal([]byte(tt.inputXML), &result) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + // Validate ILvl field + if tt.expected.ILvl != nil { + if result.ILvl == nil { + t.Errorf("Expected ILvl %d but got nil", tt.expected.ILvl.Val) + } else if *result.ILvl != *tt.expected.ILvl { + t.Errorf("Expected ILvl %d but got %d", tt.expected.ILvl.Val, result.ILvl.Val) + } + } else if result.ILvl != nil { + t.Errorf("Expected ILvl nil but got %d", result.ILvl.Val) + } + + // Validate NumID field + if tt.expected.NumID != nil { + if result.NumID == nil { + t.Errorf("Expected NumID %d but got nil", tt.expected.NumID.Val) + } else if *result.NumID != *tt.expected.NumID { + t.Errorf("Expected NumID %d but got %d", tt.expected.NumID.Val, result.NumID.Val) + } + } else if result.NumID != nil { + t.Errorf("Expected NumID nil but got %d", result.NumID.Val) + } + + // Validate NumChange field + if tt.expected.NumChange != nil { + if result.NumChange == nil { + t.Errorf("Expected NumChange but got nil") + } else if !compareTrackChangeNum(*tt.expected.NumChange, *result.NumChange) { + t.Errorf("Expected NumChange %+v but got %+v", *tt.expected.NumChange, *result.NumChange) + } + } else if result.NumChange != nil { + t.Errorf("Expected NumChange nil but got %+v", *result.NumChange) + } + + // Validate Ins field + if tt.expected.Ins != nil { + if result.Ins == nil { + t.Errorf("Expected Ins but got nil") + } else if !compareTrackChange(*tt.expected.Ins, *result.Ins) { + t.Errorf("Expected Ins %+v but got %+v", *tt.expected.Ins, *result.Ins) + } + } else if result.Ins != nil { + t.Errorf("Expected Ins nil but got %+v", *result.Ins) + } + }) + } +} + +func compareTrackChangeNum(a, b ctypes.TrackChangeNum) bool { + return a.ID == b.ID && a.Author == b.Author && *a.Date == *b.Date +} + +func compareTrackChange(a, b ctypes.TrackChange) bool { + return a.ID == b.ID && a.Author == b.Author && *a.Date == *b.Date +} diff --git a/wml/docxpara/pPrChange.go b/wml/docxpara/pPrChange.go new file mode 100644 index 0000000..51bc50c --- /dev/null +++ b/wml/docxpara/pPrChange.go @@ -0,0 +1,41 @@ +package docxpara + +import ( + "encoding/xml" + "strconv" +) + +// Revision Information for Paragraph Properties +type PPrChange struct { + ID int `xml:"id,attr"` + Author string `xml:"author,attr"` + Date *string `xml:"date,attr,omitempty"` + ParaProp *ParagraphProp `xml:"pPr"` +} + +func (p PPrChange) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name.Local = "w:pPrChange" + + start.Attr = []xml.Attr{ + {Name: xml.Name{Local: "id"}, Value: strconv.Itoa(p.ID)}, + {Name: xml.Name{Local: "author"}, Value: p.Author}, + } + + if p.Date != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "date"}, Value: *p.Date}) + } + + err := e.EncodeToken(start) + if err != nil { + return err + } + + if p.ParaProp != nil { + if err := p.ParaProp.MarshalXML(e, xml.StartElement{}); err != nil { + return err + } + } + + return e.EncodeToken(xml.EndElement{Name: start.Name}) + +} diff --git a/wml/docxpara/pPrChange_test.go b/wml/docxpara/pPrChange_test.go new file mode 100644 index 0000000..587bf47 --- /dev/null +++ b/wml/docxpara/pPrChange_test.go @@ -0,0 +1,70 @@ +package docxpara + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/gomutex/godocx/internal" +) + +func TestPPrChange_MarshalXML(t *testing.T) { + tests := []struct { + name string + input PPrChange + expected string + }{ + { + name: "With all attributes", + input: PPrChange{ + ID: 123, + Author: "John Doe", + Date: internal.ToPtr("2024-06-19"), + ParaProp: &ParagraphProp{ + // Initialize ParagraphProp fields here if needed + }, + }, + expected: ``, + }, + { + name: "Without date attribute", + input: PPrChange{ + ID: 456, + Author: "Jane Smith", + ParaProp: &ParagraphProp{ + // Initialize ParagraphProp fields here if needed + }, + }, + expected: ``, + }, + { + name: "Without paraProp", + input: PPrChange{ + ID: 789, + Author: "Alice Brown", + Date: internal.ToPtr("2024-06-20"), + }, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result strings.Builder + encoder := xml.NewEncoder(&result) + + start := xml.StartElement{Name: xml.Name{Local: "w:pPrChange"}} + if err := tt.input.MarshalXML(encoder, start); err != nil { + t.Fatalf("Error marshaling XML: %v", err) + } + + // Finalize encoding + encoder.Flush() + + got := strings.TrimSpace(result.String()) + if got != tt.expected { + t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, got) + } + }) + } +} diff --git a/wml/docxpara/paragraph.go b/wml/docxpara/paragraph.go index edf719e..c235972 100644 --- a/wml/docxpara/paragraph.go +++ b/wml/docxpara/paragraph.go @@ -5,9 +5,9 @@ import ( "github.com/gomutex/godocx/common/units" "github.com/gomutex/godocx/dml" + "github.com/gomutex/godocx/wml/ctypes" "github.com/gomutex/godocx/wml/docxrun" "github.com/gomutex/godocx/wml/formatting" - "github.com/gomutex/godocx/wml/liststyle" "github.com/gomutex/godocx/wml/runcontent" ) @@ -18,7 +18,7 @@ type ParagraphChild struct { type Paragraph struct { id string - Property *ParagraphProperty + Property *ParagraphProp Children []*ParagraphChild } @@ -85,17 +85,16 @@ func (p *Paragraph) Justification(value string) *Paragraph { } func (p *Paragraph) Numbering(id int, level int) { - numberingID := liststyle.NewNumberingID(id) - indentLevel := liststyle.NewIndentLevel(level) if p.Property == nil { p.Property = DefaultParaProperty() } - if p.Property.NumberingProperty == nil { - p.Property.NumberingProperty = liststyle.NewNumberingProperty() + if p.Property.NumProp == nil { + p.Property.NumProp = &NumProp{} } - p.Property.NumberingProperty.AddNumber(numberingID, indentLevel) + p.Property.NumProp.NumID = ctypes.NewDecimalNum(id) + p.Property.NumProp.ILvl = ctypes.NewDecimalNum(level) } // Appends a new text to the Paragraph. @@ -211,7 +210,7 @@ loop: p.Children = append(p.Children, &ParagraphChild{Run: r}) case "pPr": - p.Property = &ParagraphProperty{} + p.Property = &ParagraphProp{} if err = d.DecodeElement(p.Property, &elem); err != nil { return err } diff --git a/wml/docxpara/props.go b/wml/docxpara/props.go index 40709ca..1846c7d 100644 --- a/wml/docxpara/props.go +++ b/wml/docxpara/props.go @@ -8,93 +8,123 @@ import ( "github.com/gomutex/godocx/wml/ctypes" "github.com/gomutex/godocx/wml/docxrun" "github.com/gomutex/godocx/wml/formatting" - "github.com/gomutex/godocx/wml/liststyle" + "github.com/gomutex/godocx/wml/sections" ) // Numbering Level Associated Paragraph Properties -type ParagraphProperty struct { +type ParagraphProp struct { // This element specifies the style ID of the paragraph style which shall be used to format the contents of this paragraph. Style *elemtypes.SingleStrVal `xml:"pStyle,omitempty"` //Keep Paragraph With Next Paragraph - KeepNext *elemtypes.OptOnOffElem `xml:"keepNext,omitempty"` + KeepNext *elemtypes.OptBinFlagElem `xml:"keepNext,omitempty"` //Keep All Lines On One Page - KeepLines *elemtypes.OptOnOffElem `xml:"keepLines,omitempty"` + KeepLines *elemtypes.OptBinFlagElem `xml:"keepLines,omitempty"` //Start Paragraph on Next Page - PageBreakBefore *elemtypes.OptOnOffElem `xml:"pageBreakBefore,omitempty"` + PageBreakBefore *elemtypes.OptBinFlagElem `xml:"pageBreakBefore,omitempty"` //Allow First/Last Line to Display on a Separate Page - WindowControl *elemtypes.OptOnOffElem `xml:"widowControl,omitempty"` + WindowControl *elemtypes.OptBinFlagElem `xml:"widowControl,omitempty"` //Suppress Line Numbers for Paragraph - SuppressLineNmbrs *elemtypes.OptOnOffElem `xml:"suppressLineNumbers,omitempty"` + SuppressLineNmbrs *elemtypes.OptBinFlagElem `xml:"suppressLineNumbers,omitempty"` //Suppress Hyphenation for Paragraph - SuppressAutoHyphens *elemtypes.OptOnOffElem `xml:"suppressAutoHyphens,omitempty"` + SuppressAutoHyphens *elemtypes.OptBinFlagElem `xml:"suppressAutoHyphens,omitempty"` //Use East Asian Typography Rules for First and Last Character per Line - Kinsoku *elemtypes.OptOnOffElem `xml:"kinsoku,omitempty"` + Kinsoku *elemtypes.OptBinFlagElem `xml:"kinsoku,omitempty"` //Allow Line Breaking At Character Level - WordWrap *elemtypes.OptOnOffElem `xml:"wordWrap,omitempty"` + WordWrap *elemtypes.OptBinFlagElem `xml:"wordWrap,omitempty"` //Allow Punctuation to Extent Past Text Extents - OverflowPunct *elemtypes.OptOnOffElem `xml:"overflowPunct,omitempty"` + OverflowPunct *elemtypes.OptBinFlagElem `xml:"overflowPunct,omitempty"` //Compress Punctuation at Start of a Line - TopLinePunct *elemtypes.OptOnOffElem `xml:"topLinePunct,omitempty"` + TopLinePunct *elemtypes.OptBinFlagElem `xml:"topLinePunct,omitempty"` //Automatically Adjust Spacing of Latin and East Asian Text - AutoSpaceDE *elemtypes.OptOnOffElem `xml:"autoSpaceDE,omitempty"` + AutoSpaceDE *elemtypes.OptBinFlagElem `xml:"autoSpaceDE,omitempty"` //Automatically Adjust Spacing of East Asian Text and Numbers - AutoSpaceDN *elemtypes.OptOnOffElem `xml:"autoSpaceDN,omitempty"` + AutoSpaceDN *elemtypes.OptBinFlagElem `xml:"autoSpaceDN,omitempty"` //Right to Left Paragraph Layout - Bidi *elemtypes.OptOnOffElem `xml:"bidi,omitempty"` + Bidi *elemtypes.OptBinFlagElem `xml:"bidi,omitempty"` //Automatically Adjust Right Indent When Using Document Grid - AdjustRightInd *elemtypes.OptOnOffElem `xml:"adjustRightInd,omitempty"` + AdjustRightInd *elemtypes.OptBinFlagElem `xml:"adjustRightInd,omitempty"` //Use Document Grid Settings for Inter-Line Paragraph Spacing - SnapToGrid *elemtypes.OptOnOffElem `xml:"snapToGrid,omitempty"` + SnapToGrid *elemtypes.OptBinFlagElem `xml:"snapToGrid,omitempty"` //Ignore Spacing Above and Below When Using Identical Styles - CtxlSpacing *elemtypes.OptOnOffElem `xml:"contextualSpacing,omitempty"` + CtxlSpacing *elemtypes.OptBinFlagElem `xml:"contextualSpacing,omitempty"` // Use Left/Right Indents as Inside/Outside Indents - MirrorIndents *elemtypes.OptOnOffElem `xml:"mirrorIndents,omitempty"` + MirrorIndents *elemtypes.OptBinFlagElem `xml:"mirrorIndents,omitempty"` + + // Prevent Text Frames From Overlapping + SuppressOverlap *elemtypes.OptBinFlagElem `xml:"suppressOverlap,omitempty"` //This element specifies the shading applied to the contents of the paragraph. Shading *ctypes.Shading `xml:"shd,omitempty"` + //Paragraph Text Flow Direction + TextDirection *ctypes.TextDirection `xml:"textDirection,omitempty"` + + //Vertical Character Alignment on Line + TextAlignment *ctypes.TextAlign `xml:"textAlignment,omitempty"` + + //Allow Surrounding Paragraphs to Tight Wrap to Text Box Contents + TextboxTightWrap *ctypes.TextboxTightWrap `xml:"textboxTightWrap,omitempty"` + //Set of Custom Tab Stops Tabs ctypes.Tabs `xml:"tabs,omitempty"` - DivID *string - Justification *formatting.Justification `xml:"jc,omitempty"` - RunProperty *docxrun.RunProperty `xml:"rPr,omitempty"` - NumberingProperty *liststyle.NumberingProperty `xml:"numPr,omitempty"` + //Associated Outline Level + OutlineLvl *ctypes.DecimalNum `xml:"outlineLvl,omitempty"` + + //Associated HTML div ID + DivID *ctypes.DecimalNum `xml:"divId,omitempty"` + + //Paragraph Conditional Formatting + CnfStyle *ctypes.Cnf `xml:"cnfStyle,omitempty"` + + //Run Properties for the Paragraph Mark + RunProperty *docxrun.RunProperty `xml:"rPr,omitempty"` + + //Paragraph Alignment + Justification *formatting.Justification `xml:"jc,omitempty"` + + //Revision Information for Paragraph Properties + PPrChange *PPrChange `xml:"pPrChange,omitempty"` - // TODO: Implement this // Text Frame Properties - // FrameProp *FrameProp `xml:"framePr,omitempty"` - // This element specifies that the current paragraph references a numbering definition instance in the current document. - // NumPr *NumPr `xml:"numpr,omitempty"` - //This element specifies the borders for the parent paragraph. Each child element shall specify a specific type of border (left, right, bottom, top, and between). - // Border *Border `xml:"pBdr,omitempty"` - // Spacing Spacing - //Indent Indent + FrameProp *FrameProp `xml:"framePr,omitempty"` + + //Numbering Definition Instance Reference + NumProp *NumProp `xml:"numPr,omitempty"` + + //Paragraph Borders + Border *ParaBorder `xml:"pBdr,omitempty"` + + //Spacing Between Lines and Above/Below Paragraph + Spacing *Spacing `xml:"spacing,omitempty"` + + Indent *Indent `xml:"ind,omitempty"` + SectPr sections.SectionProp `xml:"sectPr,omitempty"` } -type onOffElems struct { - elem *elemtypes.OptOnOffElem +type binElems struct { + elem *elemtypes.OptBinFlagElem XMLName string } -func (pp *ParagraphProperty) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { +func (pp *ParagraphProp) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { elem := xml.StartElement{Name: xml.Name{Local: "w:pPr"}} // Opening element @@ -102,7 +132,7 @@ func (pp *ParagraphProperty) MarshalXML(e *xml.Encoder, start xml.StartElement) return err } - ooElems := []onOffElems{ + bElems := []binElems{ {pp.KeepNext, "w:keepNext"}, {pp.KeepLines, "w:keepLines"}, {pp.KeepLines, "w:keepLines"}, @@ -122,9 +152,10 @@ func (pp *ParagraphProperty) MarshalXML(e *xml.Encoder, start xml.StartElement) {pp.CtxlSpacing, "w:contextualSpacing"}, {pp.MirrorIndents, "w:mirrorIndents"}, {pp.WordWrap, "w:wordWrap"}, + {pp.SuppressOverlap, "w:suppressOverlap"}, } - for _, entry := range ooElems { + for _, entry := range bElems { if entry.elem == nil { continue } @@ -156,17 +187,81 @@ func (pp *ParagraphProperty) MarshalXML(e *xml.Encoder, start xml.StartElement) } } - if pp.NumberingProperty != nil { - if err = pp.NumberingProperty.MarshalXML(e, xml.StartElement{}); err != nil { + if pp.TextDirection != nil { + if err = pp.TextDirection.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("TextDirection: %w", err) + } + } + + if pp.TextAlignment != nil { + if err = pp.TextAlignment.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("TextAlignment: %w", err) + } + } + + if pp.TextboxTightWrap != nil { + if err = pp.TextboxTightWrap.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("TextboxTightWrap: %w", err) + } + } + + if pp.OutlineLvl != nil { + if err = pp.OutlineLvl.MarshalXML(e, xml.StartElement{ + Name: xml.Name{Local: "w:outlineLvl"}, + }); err != nil { + return fmt.Errorf("OutlineLvl: %w", err) + } + } + + if pp.DivID != nil { + if err = pp.DivID.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("DivID: %w", err) + } + } + + if pp.CnfStyle != nil { + if err = pp.CnfStyle.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("CnfStyle: %w", err) + } + } + + if pp.NumProp != nil { + if err = pp.NumProp.MarshalXML(e, xml.StartElement{}); err != nil { return fmt.Errorf("NumberingProperty: %w", err) } } - if err = e.EncodeToken(elem.End()); err != nil { - return err + if pp.PPrChange != nil { + if err = pp.PPrChange.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("PPrChange: %w", err) + } + } + + if pp.FrameProp != nil { + if err = pp.FrameProp.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("FrameProp: %w", err) + } + } + + if pp.Border != nil { + if err = pp.Border.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("Border: %w", err) + } + } + + if pp.Spacing != nil { + if err = pp.Spacing.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("Spacing: %w", err) + } + } + + if pp.Indent != nil { + if err = pp.Indent.MarshalXML(e, xml.StartElement{}); err != nil { + return fmt.Errorf("Indent: %w", err) + } } - return nil + return e.EncodeToken(elem.End()) } // NewParagraphStyle creates a new ParagraphStyle. @@ -179,6 +274,6 @@ func DefaultParagraphStyle() *elemtypes.SingleStrVal { return &elemtypes.SingleStrVal{Val: "Normal"} } -func DefaultParaProperty() *ParagraphProperty { - return &ParagraphProperty{} +func DefaultParaProperty() *ParagraphProp { + return &ParagraphProp{} } diff --git a/wml/docxpara/props_test.go b/wml/docxpara/props_test.go index bc066e4..fa2bd96 100644 --- a/wml/docxpara/props_test.go +++ b/wml/docxpara/props_test.go @@ -2,35 +2,43 @@ package docxpara import ( "encoding/xml" + "strings" "testing" + "github.com/gomutex/godocx/elemtypes" + "github.com/gomutex/godocx/internal" + "github.com/gomutex/godocx/wml/ctypes" + "github.com/gomutex/godocx/wml/docxrun" "github.com/gomutex/godocx/wml/formatting" + "github.com/gomutex/godocx/wml/hdrftr" + "github.com/gomutex/godocx/wml/sections" + "github.com/gomutex/godocx/wml/stypes" ) -func areParagraphPropertiesEqual(p1, p2 ParagraphProperty) bool { +func areParagraphPropertiesEqual(p1, p2 ParagraphProp) bool { return p1.Style.Val == p2.Style.Val && - p1.Justification.Value == p2.Justification.Value + p1.Justification.Val == p2.Justification.Val } -func TestParagraphProperty(t *testing.T) { +func TestParagraphProp(t *testing.T) { xmlString := ` ` - var parsedParagraphProperty ParagraphProperty - err := xml.Unmarshal([]byte(xmlString), &parsedParagraphProperty) + var parsedParagraphProp ParagraphProp + err := xml.Unmarshal([]byte(xmlString), &parsedParagraphProp) if err != nil { - t.Fatalf("Error unmarshaling XML to ParagraphProperty: %v", err) + t.Fatalf("Error unmarshaling XML to ParagraphProp: %v", err) } - expectedParagraphProperty := ParagraphProperty{ + expectedParagraphProp := ParagraphProp{ Style: NewParagraphStyle("Heading1"), Justification: formatting.NewJustification("center"), } - if !areParagraphPropertiesEqual(expectedParagraphProperty, parsedParagraphProperty) { - t.Errorf("Expected ParagraphProperty %v, got %v", expectedParagraphProperty, parsedParagraphProperty) + if !areParagraphPropertiesEqual(expectedParagraphProp, parsedParagraphProp) { + t.Errorf("Expected ParagraphProp %v, got %v", expectedParagraphProp, parsedParagraphProp) } } @@ -51,3 +59,218 @@ func TestDefaultParagraphStyle(t *testing.T) { t.Errorf("DefaultParagraphStyle() = %s; want %s", style.Val, expected) } } +func TestParagraphProp_MarshalUnmarshal(t *testing.T) { + paraProp := ParagraphProp{ + Style: &elemtypes.SingleStrVal{ + Val: "teststyle", + }, + NumProp: &NumProp{ + ILvl: ctypes.NewDecimalNum(10), + }, + Indent: &Indent{ + Left: internal.ToPtr(10), + }, + Spacing: &Spacing{ + After: internal.ToPtr(uint64(10)), + }, + Border: &ParaBorder{ + Top: &ctypes.Border{ + Val: "single", + }, + }, + FrameProp: &FrameProp{ + Width: internal.ToPtr(int64(10)), + }, + KeepNext: &elemtypes.OptBinFlagElem{}, + KeepLines: &elemtypes.OptBinFlagElem{}, + PageBreakBefore: &elemtypes.OptBinFlagElem{}, + WindowControl: &elemtypes.OptBinFlagElem{}, + SuppressLineNmbrs: &elemtypes.OptBinFlagElem{}, + SuppressAutoHyphens: &elemtypes.OptBinFlagElem{}, + Kinsoku: &elemtypes.OptBinFlagElem{}, + WordWrap: &elemtypes.OptBinFlagElem{}, + OverflowPunct: &elemtypes.OptBinFlagElem{}, + TopLinePunct: &elemtypes.OptBinFlagElem{}, + AutoSpaceDE: &elemtypes.OptBinFlagElem{}, + AutoSpaceDN: &elemtypes.OptBinFlagElem{}, + Bidi: &elemtypes.OptBinFlagElem{}, + AdjustRightInd: &elemtypes.OptBinFlagElem{}, + SnapToGrid: &elemtypes.OptBinFlagElem{}, + CtxlSpacing: &elemtypes.OptBinFlagElem{}, + MirrorIndents: &elemtypes.OptBinFlagElem{}, + SuppressOverlap: &elemtypes.OptBinFlagElem{}, + Shading: &ctypes.Shading{ + Val: stypes.ShdDiagStripe, + }, + TextDirection: &ctypes.TextDirection{ + Val: stypes.TextDirectionBtLr, + }, + TextAlignment: &ctypes.TextAlign{ + Val: stypes.TextAlignAuto, + }, + TextboxTightWrap: &ctypes.TextboxTightWrap{ + Val: stypes.TextboxTightWrapAllLines, + }, + Tabs: ctypes.Tabs{ + Tab: []ctypes.Tab{ + { + Val: internal.ToPtr(stypes.CustTabStopBar), + }, + }, + }, + OutlineLvl: &ctypes.DecimalNum{ + Val: 10000, + }, + DivID: &ctypes.DecimalNum{ + Val: 2222222, + }, + CnfStyle: &ctypes.Cnf{ + Val: "cnftest", + }, + RunProperty: &docxrun.RunProperty{ + Shading: ctypes.DefaultShading(), + }, + Justification: &formatting.Justification{ + Val: "center", + }, + PPrChange: &PPrChange{ + ID: 1, + Author: "authortest", + }, + SectPr: sections.SectionProp{ + TitlePg: &hdrftr.TitlePg{}, + }, + } + + marshaledXML, err := xml.Marshal(paraProp) + if err != nil { + t.Fatalf("Error marshaling XML: %v", err) + } + + // Unmarshal the marshaled XML back into a new ParagraphProp instance + var unmarshaledProp ParagraphProp + err = xml.Unmarshal(marshaledXML, &unmarshaledProp) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + // Compare using the custom equality function + if err := isEqualParagraphProps(paraProp, unmarshaledProp); err != nil { + t.Error(err) + } + + // For further validation, you can convert the XML back to string and compare + var result strings.Builder + encoder := xml.NewEncoder(&result) + err = encoder.Encode(paraProp) + if err != nil { + t.Fatalf("Error encoding XML: %v", err) + } + + if result.String() != string(marshaledXML) { + t.Errorf("Expected XML:\n%s\nGot:\n%s", string(marshaledXML), result.String()) + } +} + +// Define your helper function to compare two ParagraphProp structs +func isEqualParagraphProps(a, b ParagraphProp) error { + // Compare each field using internal.ComparePtr for pointers and direct comparison for non-pointers + if err := internal.ComparePtr("Style", a.Style, b.Style); err != nil { + return err + } + if err := internal.ComparePtr("NumProp", a.NumProp, b.NumProp); err != nil { + return err + } + if err := internal.ComparePtr("Indent", a.Indent, b.Indent); err != nil { + return err + } + if err := internal.ComparePtr("Spacing", a.Spacing, b.Spacing); err != nil { + return err + } + if err := internal.ComparePtr("Border", a.Border, b.Border); err != nil { + return err + } + if err := internal.ComparePtr("FrameProp", a.FrameProp, b.FrameProp); err != nil { + return err + } + if err := internal.ComparePtr("KeepNext", a.KeepNext, b.KeepNext); err != nil { + return err + } + if err := internal.ComparePtr("KeepLines", a.KeepLines, b.KeepLines); err != nil { + return err + } + if err := internal.ComparePtr("PageBreakBefore", a.PageBreakBefore, b.PageBreakBefore); err != nil { + return err + } + if err := internal.ComparePtr("WindowControl", a.WindowControl, b.WindowControl); err != nil { + return err + } + if err := internal.ComparePtr("SuppressLineNmbrs", a.SuppressLineNmbrs, b.SuppressLineNmbrs); err != nil { + return err + } + if err := internal.ComparePtr("SuppressAutoHyphens", a.SuppressAutoHyphens, b.SuppressAutoHyphens); err != nil { + return err + } + if err := internal.ComparePtr("Kinsoku", a.Kinsoku, b.Kinsoku); err != nil { + return err + } + if err := internal.ComparePtr("WordWrap", a.WordWrap, b.WordWrap); err != nil { + return err + } + if err := internal.ComparePtr("OverflowPunct", a.OverflowPunct, b.OverflowPunct); err != nil { + return err + } + if err := internal.ComparePtr("TopLinePunct", a.TopLinePunct, b.TopLinePunct); err != nil { + return err + } + if err := internal.ComparePtr("AutoSpaceDE", a.AutoSpaceDE, b.AutoSpaceDE); err != nil { + return err + } + if err := internal.ComparePtr("AutoSpaceDN", a.AutoSpaceDN, b.AutoSpaceDN); err != nil { + return err + } + if err := internal.ComparePtr("Bidi", a.Bidi, b.Bidi); err != nil { + return err + } + if err := internal.ComparePtr("AdjustRightInd", a.AdjustRightInd, b.AdjustRightInd); err != nil { + return err + } + if err := internal.ComparePtr("SnapToGrid", a.SnapToGrid, b.SnapToGrid); err != nil { + return err + } + if err := internal.ComparePtr("CtxlSpacing", a.CtxlSpacing, b.CtxlSpacing); err != nil { + return err + } + if err := internal.ComparePtr("MirrorIndents", a.MirrorIndents, b.MirrorIndents); err != nil { + return err + } + if err := internal.ComparePtr("SuppressOverlap", a.SuppressOverlap, b.SuppressOverlap); err != nil { + return err + } + + if err := internal.ComparePtr("Shading", a.Shading, b.Shading); err != nil { + return err + } + + if err := internal.ComparePtr("TextDirection", a.TextDirection, b.TextDirection); err != nil { + return err + } + + if err := internal.ComparePtr("TextAlignment", a.TextAlignment, b.TextAlignment); err != nil { + return err + } + + if err := internal.ComparePtr("TextboxTightWrap", a.TextboxTightWrap, b.TextboxTightWrap); err != nil { + return err + } + + if err := internal.ComparePtr("OutlineLvl", a.OutlineLvl, b.OutlineLvl); err != nil { + return err + } + + if err := internal.ComparePtr("DivID", a.DivID, b.DivID); err != nil { + return err + } + + return nil +} diff --git a/wml/docxpara/spacing.go b/wml/docxpara/spacing.go new file mode 100644 index 0000000..9cf193c --- /dev/null +++ b/wml/docxpara/spacing.go @@ -0,0 +1,68 @@ +package docxpara + +import ( + "encoding/xml" + "strconv" + + "github.com/gomutex/godocx/wml/stypes" +) + +// Spacing Between Lines and Above/Below Paragraph +type Spacing struct { + //Spacing Above Paragraph + Before *uint64 `xml:"before,attr,omitempty"` + + //Spacing Above Paragraph IN Line Units + BeforeLines *int `xml:"beforeLines,attr,omitempty"` + + //Spacing Below Paragraph + After *uint64 `xml:"after,attr,omitempty"` + + // Automatically Determine Spacing Above Paragraph + BeforeAutospacing *stypes.BinFlag `xml:"beforeAutospacing,attr,omitempty"` + + // Automatically Determine Spacing Below Paragraph + AfterAutospacing *stypes.BinFlag `xml:"afterAutospacing,attr,omitempty"` + + //Spacing Between Lines in Paragraph + Line *int `xml:"line,omitempty"` + + //Type of Spacing Between Lines + LineRule *stypes.LineSpacingRule `xml:"lineRule,attr,omitempty"` +} + +func (s *Spacing) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name.Local = "w:spacing" + + start.Attr = []xml.Attr{} + + if s.Before != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:before"}, Value: strconv.FormatUint(*s.Before, 10)}) + } + + if s.After != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:after"}, Value: strconv.FormatUint(*s.After, 10)}) + } + + if s.BeforeLines != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:beforeLines"}, Value: strconv.Itoa(*s.BeforeLines)}) + } + + if s.BeforeAutospacing != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:beforeAutospacing"}, Value: string(*s.BeforeAutospacing)}) + } + + if s.AfterAutospacing != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:afterAutospacing"}, Value: string(*s.AfterAutospacing)}) + } + + if s.Line != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:line"}, Value: strconv.Itoa(*s.Line)}) + } + + if s.LineRule != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:lineRule"}, Value: string(*s.LineRule)}) + } + + return e.EncodeElement("", start) +} diff --git a/wml/docxpara/spacing_test.go b/wml/docxpara/spacing_test.go new file mode 100644 index 0000000..d8b3909 --- /dev/null +++ b/wml/docxpara/spacing_test.go @@ -0,0 +1,67 @@ +package docxpara + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/gomutex/godocx/internal" + "github.com/gomutex/godocx/wml/stypes" +) + +func TestSpacing_MarshalXML(t *testing.T) { + tests := []struct { + name string + input Spacing + expected string + }{ + { + name: "All fields set", + input: Spacing{ + Before: internal.ToPtr(uint64(120)), + After: internal.ToPtr(uint64(240)), + BeforeLines: internal.ToPtr(10), + BeforeAutospacing: internal.ToPtr(stypes.BinFlagOn), + AfterAutospacing: internal.ToPtr(stypes.BinFlagOff), + Line: internal.ToPtr(360), + LineRule: internal.ToPtr(stypes.LineSpacingRuleExact), + }, + expected: ``, + }, + { + name: "Some fields set", + input: Spacing{ + Before: internal.ToPtr(uint64(120)), + After: internal.ToPtr(uint64(240)), + Line: internal.ToPtr(360), + LineRule: internal.ToPtr(stypes.LineSpacingRuleAuto), + }, + expected: ``, + }, + { + name: "No fields set", + input: Spacing{}, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result strings.Builder + encoder := xml.NewEncoder(&result) + + start := xml.StartElement{Name: xml.Name{Local: "w:spacing"}} + if err := tt.input.MarshalXML(encoder, start); err != nil { + t.Fatalf("Error marshaling XML: %v", err) + } + + // Finalize encoding + encoder.Flush() + + got := strings.TrimSpace(result.String()) + if got != tt.expected { + t.Errorf("Expected XML:\n%s\nGot:\n%s", tt.expected, got) + } + }) + } +} diff --git a/wml/docxrun/eALayout.go b/wml/docxrun/eALayout.go index 6eb42b7..1f4099a 100644 --- a/wml/docxrun/eALayout.go +++ b/wml/docxrun/eALayout.go @@ -10,10 +10,10 @@ import ( // East Asian Typography Settings type EALayout struct { ID *int `xml:"id,attr,omitempty"` - Combine *stypes.OnOff `xml:"combine,attr,omitempty"` + Combine *stypes.BinFlag `xml:"combine,attr,omitempty"` CombineBrkts *stypes.CombineBrackets `xml:"combineBrackets,attr,omitempty"` - Vert *stypes.OnOff `xml:"vert,attr,omitempty"` - VertCompress *stypes.OnOff `xml:"vertCompress,attr,omitempty"` + Vert *stypes.BinFlag `xml:"vert,attr,omitempty"` + VertCompress *stypes.BinFlag `xml:"vertCompress,attr,omitempty"` } func (ea *EALayout) MarshalXML(e *xml.Encoder, start xml.StartElement) error { diff --git a/wml/docxrun/eALayout_test.go b/wml/docxrun/eALayout_test.go index 89d32cc..c3e1aaf 100644 --- a/wml/docxrun/eALayout_test.go +++ b/wml/docxrun/eALayout_test.go @@ -5,22 +5,10 @@ import ( "strings" "testing" + "github.com/gomutex/godocx/internal" "github.com/gomutex/godocx/wml/stypes" ) -// Helper functions to create pointers -func intPtr(i int) *int { - return &i -} - -func onOffPtr(value stypes.OnOff) *stypes.OnOff { - return &value -} - -func combineBracketsPtr(value stypes.CombineBrackets) *stypes.CombineBrackets { - return &value -} - func TestEALayout_MarshalXML(t *testing.T) { tests := []struct { name string @@ -30,46 +18,46 @@ func TestEALayout_MarshalXML(t *testing.T) { { name: "All attributes set", layout: EALayout{ - ID: intPtr(1), - Combine: onOffPtr(stypes.OnOffOn), - CombineBrkts: combineBracketsPtr(stypes.CombineBracketsRound), - Vert: onOffPtr(stypes.OnOffOff), - VertCompress: onOffPtr(stypes.OnOffOn), + ID: internal.ToPtr(1), + Combine: internal.ToPtr(stypes.BinFlagOn), + CombineBrkts: internal.ToPtr(stypes.CombineBracketsRound), + Vert: internal.ToPtr(stypes.BinFlagOff), + VertCompress: internal.ToPtr(stypes.BinFlagOn), }, expected: ``, }, { name: "Only ID set", layout: EALayout{ - ID: intPtr(2), + ID: internal.ToPtr(2), }, expected: ``, }, { name: "Only Combine set", layout: EALayout{ - Combine: onOffPtr(stypes.OnOffOn), + Combine: internal.ToPtr(stypes.BinFlagOn), }, expected: ``, }, { name: "Only CombineBrkts set", layout: EALayout{ - CombineBrkts: combineBracketsPtr(stypes.CombineBracketsSquare), + CombineBrkts: internal.ToPtr(stypes.CombineBracketsSquare), }, expected: ``, }, { name: "Only Vert set", layout: EALayout{ - Vert: onOffPtr(stypes.OnOffOff), + Vert: internal.ToPtr(stypes.BinFlagOff), }, expected: ``, }, { name: "Only VertCompress set", layout: EALayout{ - VertCompress: onOffPtr(stypes.OnOffOn), + VertCompress: internal.ToPtr(stypes.BinFlagOn), }, expected: ``, }, @@ -108,46 +96,46 @@ func TestEALayout_UnmarshalXML(t *testing.T) { name: "All attributes set", inputXML: ``, expected: EALayout{ - ID: intPtr(1), - Combine: onOffPtr(stypes.OnOffOn), - CombineBrkts: combineBracketsPtr(stypes.CombineBracketsRound), - Vert: onOffPtr(stypes.OnOffOff), - VertCompress: onOffPtr(stypes.OnOffOn), + ID: internal.ToPtr(1), + Combine: internal.ToPtr(stypes.BinFlagOn), + CombineBrkts: internal.ToPtr(stypes.CombineBracketsRound), + Vert: internal.ToPtr(stypes.BinFlagOff), + VertCompress: internal.ToPtr(stypes.BinFlagOn), }, }, { name: "Only ID set", inputXML: ``, expected: EALayout{ - ID: intPtr(2), + ID: internal.ToPtr(2), }, }, { name: "Only Combine set", inputXML: ``, expected: EALayout{ - Combine: onOffPtr(stypes.OnOffOn), + Combine: internal.ToPtr(stypes.BinFlagOn), }, }, { name: "Only CombineBrkts set", inputXML: ``, expected: EALayout{ - CombineBrkts: combineBracketsPtr(stypes.CombineBracketsSquare), + CombineBrkts: internal.ToPtr(stypes.CombineBracketsSquare), }, }, { name: "Only Vert set", inputXML: ``, expected: EALayout{ - Vert: onOffPtr(stypes.OnOffOff), + Vert: internal.ToPtr(stypes.BinFlagOff), }, }, { name: "Only VertCompress set", inputXML: ``, expected: EALayout{ - VertCompress: onOffPtr(stypes.OnOffOn), + VertCompress: internal.ToPtr(stypes.BinFlagOn), }, }, { diff --git a/wml/docxrun/run.go b/wml/docxrun/run.go index 20d509d..45b6a8d 100644 --- a/wml/docxrun/run.go +++ b/wml/docxrun/run.go @@ -152,7 +152,7 @@ func (r *Run) HideText(value bool) *Run { } func (r *Run) Spacing(value int) *Run { - r.RunProperty.Spacing = elemtypes.NewSingleIntVal(value) + r.RunProperty.Spacing = ctypes.NewDecimalNum(value) return r } diff --git a/wml/docxrun/runprop.go b/wml/docxrun/runprop.go index a371cc7..b4a9448 100644 --- a/wml/docxrun/runprop.go +++ b/wml/docxrun/runprop.go @@ -20,7 +20,7 @@ type RunProperty struct { Underline *formatting.Underline `xml:"u,omitempty"` Effect *Effect `xml:"effect,omitempty"` ExpaComp *ExpaComp `xml:"w,omitempty"` - Border *TextBorder `xml:"bdr,omitempty"` + Border *ctypes.Border `xml:"bdr,omitempty"` FitText *FitText `xml:"fitText,omitempty"` VertAlign *VertAlign `xml:"vertAlign,omitempty"` Em *Em `xml:"em,omitempty"` @@ -48,9 +48,9 @@ type RunProperty struct { SpecVanish *elemtypes.OptBoolElem `xml:"specVanish,omitempty"` OMath *elemtypes.OptBoolElem `xml:"oMath,omitempty"` Kern *elemtypes.SingleUint64Val `xml:"kern,omitempty"` - Spacing *elemtypes.SingleIntVal `xml:"spacing,omitempty"` Style *elemtypes.SingleStrVal `xml:"rStyle,omitempty"` - Position *elemtypes.SingleIntVal `xml:"position,omitempty"` + Position *ctypes.DecimalNum `xml:"position,omitempty"` + Spacing *ctypes.DecimalNum `xml:"spacing,omitempty"` } // NewRunProperty creates a new RunProperty with default values. diff --git a/wml/docxrun/runprop_test.go b/wml/docxrun/runprop_test.go index ebaa1d9..6e7eacd 100644 --- a/wml/docxrun/runprop_test.go +++ b/wml/docxrun/runprop_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gomutex/godocx/elemtypes" + "github.com/gomutex/godocx/wml/ctypes" ) func optBoolElemPtr(value elemtypes.OptBoolElem) *elemtypes.OptBoolElem { @@ -17,7 +18,7 @@ func singleUint64ValPtr(value elemtypes.SingleUint64Val) *elemtypes.SingleUint64 return &value } -func singleIntValPtr(value elemtypes.SingleIntVal) *elemtypes.SingleIntVal { +func singleIntValPtr(value ctypes.DecimalNum) *ctypes.DecimalNum { return &value } @@ -56,9 +57,9 @@ func TestRunProperty_MarshalXML(t *testing.T) { SpecVanish: optBoolElemPtr(elemtypes.OptBoolElem{}), OMath: optBoolElemPtr(elemtypes.OptBoolElem{}), Kern: singleUint64ValPtr(elemtypes.SingleUint64Val{Val: 20}), - Spacing: singleIntValPtr(elemtypes.SingleIntVal{Val: 100}), + Spacing: singleIntValPtr(ctypes.DecimalNum{Val: 100}), Style: singleStrValPtr(elemtypes.SingleStrVal{Val: "Heading1"}), - Position: singleIntValPtr(elemtypes.SingleIntVal{Val: 10}), + Position: singleIntValPtr(ctypes.DecimalNum{Val: 10}), }, expected: ``, }, @@ -125,9 +126,9 @@ func TestRunProperty_UnmarshalXML(t *testing.T) { SpecVanish: optBoolElemPtr(elemtypes.OptBoolElem{}), OMath: optBoolElemPtr(elemtypes.OptBoolElem{}), Kern: singleUint64ValPtr(elemtypes.SingleUint64Val{Val: 20}), - Spacing: singleIntValPtr(elemtypes.SingleIntVal{Val: 100}), + Spacing: singleIntValPtr(ctypes.DecimalNum{Val: 100}), Style: singleStrValPtr(elemtypes.SingleStrVal{Val: "Heading1"}), - Position: singleIntValPtr(elemtypes.SingleIntVal{Val: 10}), + Position: singleIntValPtr(ctypes.DecimalNum{Val: 10}), }, }, { diff --git a/wml/formatting/justification.go b/wml/formatting/justification.go index 1d4a848..0244ee6 100644 --- a/wml/formatting/justification.go +++ b/wml/formatting/justification.go @@ -4,22 +4,22 @@ import "encoding/xml" // Justification represents the justification of a paragraph. type Justification struct { - Value string + Val string } // NewJustification creates a new Justification. func NewJustification(val string) *Justification { - return &Justification{Value: val} + return &Justification{Val: val} } // DefaultJustification creates the default Justification with the value "centerGroup". func DefaultJustification() *Justification { - return &Justification{Value: "centerGroup"} + return &Justification{Val: "centerGroup"} } func (j *Justification) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Name.Local = "w:jc" - start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: j.Value}) + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: j.Val}) return e.EncodeElement("", start) } @@ -32,6 +32,6 @@ func (j *Justification) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err } } - j.Value = attr + j.Val = attr return d.Skip() // Skipping the inner content } diff --git a/wml/formatting/justification_test.go b/wml/formatting/justification_test.go index f0b3247..16b7bb1 100644 --- a/wml/formatting/justification_test.go +++ b/wml/formatting/justification_test.go @@ -20,8 +20,8 @@ func TestJustification(t *testing.T) { t.Fatalf("Error unmarshaling XML to Justification: %v", err) } - if testJustification.Value != unmarshaledJustification.Value { - t.Errorf("Expected justification value %s, got %s", testJustification.Value, unmarshaledJustification.Value) + if testJustification.Val != unmarshaledJustification.Val { + t.Errorf("Expected justification value %s, got %s", testJustification.Val, unmarshaledJustification.Val) } expectedXMLString := `` diff --git a/wml/hdrftr/titlePg.go b/wml/hdrftr/titlePg.go index c28b351..d32c238 100644 --- a/wml/hdrftr/titlePg.go +++ b/wml/hdrftr/titlePg.go @@ -8,7 +8,7 @@ import ( // Different First Page Headers and Footers type TitlePg struct { - Val stypes.OnOff `xml:"val,attr,omitempty"` + Val stypes.BinFlag `xml:"val,attr,omitempty"` } func (t *TitlePg) MarshalXML(e *xml.Encoder, start xml.StartElement) error { diff --git a/wml/hdrftr/titlePg_test.go b/wml/hdrftr/titlePg_test.go index ad83927..b56607f 100644 --- a/wml/hdrftr/titlePg_test.go +++ b/wml/hdrftr/titlePg_test.go @@ -16,19 +16,19 @@ func TestTitlePg_MarshalXML(t *testing.T) { }{ { titlePg: TitlePg{ - Val: stypes.OnOff("on"), + Val: stypes.BinFlag("on"), }, expectedXML: ``, }, { titlePg: TitlePg{ - Val: stypes.OnOff("1"), + Val: stypes.BinFlag("1"), }, expectedXML: ``, }, { titlePg: TitlePg{ - Val: stypes.OnOff(""), + Val: stypes.BinFlag(""), }, expectedXML: ``, }, @@ -67,26 +67,26 @@ func TestTitlePg_UnmarshalXML(t *testing.T) { { xmlInput: ``, expectedTitlePg: TitlePg{ - Val: stypes.OnOff("on"), + Val: stypes.BinFlag("on"), }, }, { xmlInput: ``, expectedTitlePg: TitlePg{ - Val: stypes.OnOff("off"), + Val: stypes.BinFlag("off"), }, }, { xmlInput: ``, expectedTitlePg: TitlePg{ - Val: stypes.OnOff(""), + Val: stypes.BinFlag(""), }, - err: errors.New("invalid OnOff string"), + err: errors.New("invalid BinFlag string"), }, { xmlInput: ``, expectedTitlePg: TitlePg{ - Val: stypes.OnOff(""), + Val: stypes.BinFlag(""), }, }, } diff --git a/wml/liststyle/indent_level.go b/wml/liststyle/indent_level.go deleted file mode 100644 index bc2a0fd..0000000 --- a/wml/liststyle/indent_level.go +++ /dev/null @@ -1,39 +0,0 @@ -package liststyle - -import ( - "encoding/xml" - "strconv" -) - -// IndentLevel represents the indentation level of a numbering in a document. -type IndentLevel struct { - Val int -} - -// NewIndentLevel creates a new IndentLevel instance. -func NewIndentLevel(val int) *IndentLevel { - return &IndentLevel{Val: val} -} - -// MarshalXML implements the xml.Marshaler interface for IndentLevel. -func (i *IndentLevel) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - start.Name.Local = "w:ilvl" - start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: strconv.Itoa(i.Val)}) - return e.EncodeElement("", start) -} - -// UnmarshalXML implements the xml.Unmarshaler interface for IndentLevel. -func (i *IndentLevel) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - for _, a := range start.Attr { - if a.Name.Local == "val" { - val, err := strconv.Atoi(a.Value) - if err != nil { - return err - } - i.Val = val - break - } - } - - return d.Skip() // Skipping the inner content -} diff --git a/wml/liststyle/numbering_id.go b/wml/liststyle/numbering_id.go deleted file mode 100644 index dda9815..0000000 --- a/wml/liststyle/numbering_id.go +++ /dev/null @@ -1,39 +0,0 @@ -package liststyle - -import ( - "encoding/xml" - "strconv" -) - -// NumberingId represents the ID of a numbering in a document. -type NumberingID struct { - Val int -} - -// NewNumberingId creates a new NumberingId instance. -func NewNumberingID(val int) *NumberingID { - return &NumberingID{Val: val} -} - -// MarshalXML implements the xml.Marshaler interface for NumberingId. -func (n *NumberingID) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - start.Name.Local = "w:numId" - start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "w:val"}, Value: strconv.Itoa(n.Val)}) - return e.EncodeElement("", start) -} - -// UnmarshalXML implements the xml.Unmarshaler interface for NumberingId. -func (n *NumberingID) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - for _, a := range start.Attr { - if a.Name.Local == "val" { - val, err := strconv.Atoi(a.Value) - if err != nil { - return err - } - n.Val = val - break - } - } - - return d.Skip() // Skipping the inner content -} diff --git a/wml/liststyle/numbering_property.go b/wml/liststyle/numbering_property.go deleted file mode 100644 index e5234b8..0000000 --- a/wml/liststyle/numbering_property.go +++ /dev/null @@ -1,75 +0,0 @@ -package liststyle - -import ( - "encoding/xml" -) - -// NumberingProperty represents the properties of a numbering in a document. -type NumberingProperty struct { - ID *NumberingID - Level *IndentLevel -} - -// NewNumberingProperty creates a new NumberingProperty instance. -func NewNumberingProperty() *NumberingProperty { - return &NumberingProperty{} -} - -// AddNumber adds the numbering ID and indent level to the NumberingProperty. -func (n *NumberingProperty) AddNumber(id *NumberingID, level *IndentLevel) *NumberingProperty { - n.ID = id - n.Level = level - return n -} - -// MarshalXML implements the xml.Marshaler interface for NumberingProperty. -func (n *NumberingProperty) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - start.Name.Local = "w:numPr" - if err := e.EncodeToken(start); err != nil { - return err - } - - if n.ID != nil { - if err := e.EncodeElement(n.ID, xml.StartElement{Name: xml.Name{Local: "w:numId"}}); err != nil { - return err - } - } - - if n.Level != nil { - if err := e.EncodeElement(n.Level, xml.StartElement{Name: xml.Name{Local: "w:ilvl"}}); err != nil { - return err - } - } - - return e.EncodeToken(start.End()) -} - -// UnmarshalXML implements the xml.Unmarshaler interface for NumberingProperty. -func (n *NumberingProperty) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - for { - t, err := d.Token() - if err != nil { - return err - } - - switch tok := t.(type) { - case xml.StartElement: - switch tok.Name.Local { - case "numId": - n.ID = &NumberingID{} - if err := d.DecodeElement(n.ID, &tok); err != nil { - return err - } - case "ilvl": - n.Level = &IndentLevel{} - if err := d.DecodeElement(n.Level, &tok); err != nil { - return err - } - } - case xml.EndElement: - if tok == start.End() { - return nil - } - } - } -} diff --git a/wml/sections/formProt.go b/wml/sections/formProt.go index abfdf50..9a2f6c7 100644 --- a/wml/sections/formProt.go +++ b/wml/sections/formProt.go @@ -8,7 +8,7 @@ import ( // FormProt represents the text direction settings in a Word document. type FormProt struct { - Val stypes.OnOff `xml:"val,attr,omitempty"` + Val stypes.BinFlag `xml:"val,attr,omitempty"` } // MarshalXML implements the xml.Marshaler interface for the FormProt type. diff --git a/wml/sections/formProt_test.go b/wml/sections/formProt_test.go index 175cf8b..8c79351 100644 --- a/wml/sections/formProt_test.go +++ b/wml/sections/formProt_test.go @@ -15,7 +15,7 @@ func TestFormProt_MarshalXML(t *testing.T) { }{ { name: "WithVal", - formProt: FormProt{Val: stypes.OnOff("on")}, + formProt: FormProt{Val: stypes.BinFlag("on")}, expected: ``, }, { @@ -47,7 +47,7 @@ func TestFormProt_UnmarshalXML(t *testing.T) { }{ { xmlStr: ``, - expected: FormProt{Val: stypes.OnOff("on")}, + expected: FormProt{Val: stypes.BinFlag("on")}, }, { xmlStr: ``, diff --git a/wml/sections/sectionProp.go b/wml/sections/sectionProp.go index 411913c..17b5f0b 100644 --- a/wml/sections/sectionProp.go +++ b/wml/sections/sectionProp.go @@ -3,10 +3,11 @@ package sections import ( "encoding/xml" + "github.com/gomutex/godocx/wml/ctypes" "github.com/gomutex/godocx/wml/hdrftr" ) -// Section Properties : w:sectPr +// Document Final Section Properties : w:sectPr type SectionProp struct { HeaderReference *hdrftr.HeaderReference `xml:"headerReference,omitempty"` FooterReference *hdrftr.FooterReference `xml:"footerReference,omitempty"` @@ -16,7 +17,7 @@ type SectionProp struct { PageNum *PageNumbering `xml:"pgNumType,omitempty"` FormProt *FormProt `xml:"formProt,omitempty"` TitlePg *hdrftr.TitlePg `xml:"titlePg,omitempty"` - TextDir *TextDirection `xml:"textDirection,omitempty"` + TextDir *ctypes.TextDirection `xml:"textDirection,omitempty"` DocGrid *DocGrid `xml:"docGrid,omitempty"` } diff --git a/wml/sections/sectionProp_test.go b/wml/sections/sectionProp_test.go index 57a42f7..95cf04c 100644 --- a/wml/sections/sectionProp_test.go +++ b/wml/sections/sectionProp_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/gomutex/godocx/wml/ctypes" "github.com/gomutex/godocx/wml/hdrftr" "github.com/gomutex/godocx/wml/stypes" ) @@ -30,7 +31,7 @@ func TestSectionProp_MarshalXML(t *testing.T) { PageNum: &PageNumbering{Format: stypes.NumFmtDecimal}, FormProt: &FormProt{Val: "true"}, TitlePg: &hdrftr.TitlePg{Val: "true"}, - TextDir: &TextDirection{Val: "lrTb"}, + TextDir: &ctypes.TextDirection{Val: "lrTb"}, DocGrid: &DocGrid{Type: "default", LinePitch: intPtr(360)}, }, expected: ``, @@ -121,7 +122,7 @@ func TestSectionProp_UnmarshalXML(t *testing.T) { PageNum: &PageNumbering{Format: stypes.NumFmtDecimal}, FormProt: &FormProt{Val: "true"}, TitlePg: &hdrftr.TitlePg{Val: "true"}, - TextDir: &TextDirection{Val: "lrTb"}, + TextDir: &ctypes.TextDirection{Val: "lrTb"}, DocGrid: &DocGrid{Type: "default", LinePitch: intPtr(360)}, }, }, diff --git a/wml/stypes/align.go b/wml/stypes/align.go new file mode 100644 index 0000000..edc7f33 --- /dev/null +++ b/wml/stypes/align.go @@ -0,0 +1,46 @@ +package stypes + +import ( + "encoding/xml" + "errors" +) + +// Align Type +type Align string + +// Constants for valid values +const ( + AlignLeft Align = "left" // Left Aligned Horizontally + AlignCenter Align = "center" // Centered Horizontally + AlignRight Align = "right" // Right Aligned Horizontally + AlignInside Align = "inside" // Inside + AlignOutside Align = "outside" // Outside +) + +// AlignFromStr converts a string to Align type. +func AlignFromStr(value string) (Align, error) { + switch value { + case "left": + return AlignLeft, nil + case "center": + return AlignCenter, nil + case "right": + return AlignRight, nil + case "inside": + return AlignInside, nil + case "outside": + return AlignOutside, nil + default: + return "", errors.New("Invalid Align value") + } +} + +// UnmarshalXMLAttr unmarshals XML attribute into Align. +func (x *Align) UnmarshalXMLAttr(attr xml.Attr) error { + val, err := AlignFromStr(attr.Value) + if err != nil { + return err + } + *x = val + return nil +} diff --git a/wml/stypes/align_test.go b/wml/stypes/align_test.go new file mode 100644 index 0000000..c3733cc --- /dev/null +++ b/wml/stypes/align_test.go @@ -0,0 +1,102 @@ +package stypes + +import ( + "encoding/xml" + "testing" +) + +func TestAlignFromStr_ValidValues(t *testing.T) { + tests := []struct { + input string + expected Align + }{ + {"left", AlignLeft}, + {"center", AlignCenter}, + {"right", AlignRight}, + {"inside", AlignInside}, + {"outside", AlignOutside}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result, err := AlignFromStr(tt.input) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if result != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, result) + } + }) + } +} + +func TestAlignFromStr_InvalidValue(t *testing.T) { + input := "invalidValue" + + result, err := AlignFromStr(input) + + if err == nil { + t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) + } + + expectedError := "Invalid Align value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} + +func TestAlign_UnmarshalXMLAttr_ValidValues(t *testing.T) { + tests := []struct { + inputXML string + expected Align + }{ + {``, AlignLeft}, + {``, AlignCenter}, + {``, AlignRight}, + {``, AlignInside}, + {``, AlignOutside}, + } + + for _, tt := range tests { + t.Run(tt.inputXML, func(t *testing.T) { + type Element struct { + XMLName xml.Name `xml:"element"` + Val Align `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(tt.inputXML), &elem) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if elem.Val != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, elem.Val) + } + }) + } +} + +func TestAlign_UnmarshalXMLAttr_InvalidValue(t *testing.T) { + inputXML := `` + + type Element struct { + XMLName xml.Name `xml:"element"` + Val Align `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(inputXML), &elem) + + if err == nil { + t.Fatalf("Expected error for invalid value, but got none") + } + + expectedError := "Invalid Align value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} diff --git a/wml/stypes/anchor.go b/wml/stypes/anchor.go new file mode 100644 index 0000000..a8926e7 --- /dev/null +++ b/wml/stypes/anchor.go @@ -0,0 +1,40 @@ +package stypes + +import ( + "encoding/xml" + "errors" +) + +// Anchor Type +type Anchor string + +// Constants for valid values +const ( + AnchorText Anchor = "text" // Relative to Text Extents + AnchorMargin Anchor = "margin" // Relative To Margin + AnchorPage Anchor = "page" // Relative to Page +) + +// AnchorFromStr converts a string to Anchor type. +func AnchorFromStr(value string) (Anchor, error) { + switch value { + case "text": + return AnchorText, nil + case "margin": + return AnchorMargin, nil + case "page": + return AnchorPage, nil + default: + return "", errors.New("Invalid Anchor value") + } +} + +// UnmarshalXMLAttr unmarshals XML attribute into Anchor. +func (h *Anchor) UnmarshalXMLAttr(attr xml.Attr) error { + val, err := AnchorFromStr(attr.Value) + if err != nil { + return err + } + *h = val + return nil +} diff --git a/wml/stypes/anchor_test.go b/wml/stypes/anchor_test.go new file mode 100644 index 0000000..9d50d04 --- /dev/null +++ b/wml/stypes/anchor_test.go @@ -0,0 +1,98 @@ +package stypes + +import ( + "encoding/xml" + "testing" +) + +func TestAnchorFromStr_ValidValues(t *testing.T) { + tests := []struct { + input string + expected Anchor + }{ + {"text", AnchorText}, + {"margin", AnchorMargin}, + {"page", AnchorPage}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result, err := AnchorFromStr(tt.input) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if result != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, result) + } + }) + } +} + +func TestAnchorFromStr_InvalidValue(t *testing.T) { + input := "invalidValue" + + result, err := AnchorFromStr(input) + + if err == nil { + t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) + } + + expectedError := "Invalid Anchor value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} + +func TestAnchor_UnmarshalXMLAttr_ValidValues(t *testing.T) { + tests := []struct { + inputXML string + expected Anchor + }{ + {``, AnchorText}, + {``, AnchorMargin}, + {``, AnchorPage}, + } + + for _, tt := range tests { + t.Run(tt.inputXML, func(t *testing.T) { + type Element struct { + XMLName xml.Name `xml:"element"` + Val Anchor `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(tt.inputXML), &elem) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if elem.Val != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, elem.Val) + } + }) + } +} + +func TestAnchor_UnmarshalXMLAttr_InvalidValue(t *testing.T) { + inputXML := `` + + type Element struct { + XMLName xml.Name `xml:"element"` + Val Anchor `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(inputXML), &elem) + + if err == nil { + t.Fatalf("Expected error for invalid value, but got none") + } + + expectedError := "Invalid Anchor value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} diff --git a/wml/stypes/binFlag.go b/wml/stypes/binFlag.go new file mode 100644 index 0000000..bb650c4 --- /dev/null +++ b/wml/stypes/binFlag.go @@ -0,0 +1,52 @@ +package stypes + +import ( + "encoding/xml" + "errors" +) + +// This simple type specifies a set of values for any binary (on or off) property defined in a WordprocessingML document. +// 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. +// +// A value of off, 0, or false specifies that the property shall be explicitly turned off. +type BinFlag string + +const ( + BinFlagZero BinFlag = "0" + BinFlagOne BinFlag = "1" + BinFlagFalse BinFlag = "false" + BinFlagTrue BinFlag = "true" + BinFlagOff BinFlag = "off" + BinFlagOn BinFlag = "on" +) + +func BinFlagFromStr(s string) (BinFlag, error) { + switch s { + case "0": + return BinFlagZero, nil + case "1": + return BinFlagOne, nil + case "false": + return BinFlagFalse, nil + case "true": + return BinFlagTrue, nil + case "off": + return BinFlagOff, nil + case "on": + return BinFlagOn, nil + default: + return "", errors.New("invalid BinFlag string") + } +} + +func (d *BinFlag) UnmarshalXMLAttr(attr xml.Attr) error { + val, err := BinFlagFromStr(attr.Value) + if err != nil { + return err + } + + *d = val + + return nil + +} diff --git a/wml/stypes/onOff_test.go b/wml/stypes/binFlag_test.go similarity index 55% rename from wml/stypes/onOff_test.go rename to wml/stypes/binFlag_test.go index 32a3fb7..55425e0 100644 --- a/wml/stypes/onOff_test.go +++ b/wml/stypes/binFlag_test.go @@ -5,22 +5,22 @@ import ( "testing" ) -func TestOnOffFromStr_ValidValues(t *testing.T) { +func TestBinFlagFromStr_ValidValues(t *testing.T) { tests := []struct { input string - expected OnOff + expected BinFlag }{ - {"0", OnOffZero}, - {"1", OnOffOne}, - {"false", OnOffFalse}, - {"true", OnOffTrue}, - {"off", OnOffOff}, - {"on", OnOffOn}, + {"0", BinFlagZero}, + {"1", BinFlagOne}, + {"false", BinFlagFalse}, + {"true", BinFlagTrue}, + {"off", BinFlagOff}, + {"on", BinFlagOn}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { - result, err := OnOffFromStr(tt.input) + result, err := BinFlagFromStr(tt.input) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -32,39 +32,39 @@ func TestOnOffFromStr_ValidValues(t *testing.T) { } } -func TestOnOffFromStr_InvalidValue(t *testing.T) { +func TestBinFlagFromStr_InvalidValue(t *testing.T) { input := "invalidValue" - result, err := OnOffFromStr(input) + result, err := BinFlagFromStr(input) if err == nil { t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) } - expectedError := "invalid OnOff string" + expectedError := "invalid BinFlag string" if err.Error() != expectedError { t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) } } -func TestOnOff_UnmarshalXMLAttr_ValidValues(t *testing.T) { +func TestBinFlag_UnmarshalXMLAttr_ValidValues(t *testing.T) { tests := []struct { inputXML string - expected OnOff + expected BinFlag }{ - {``, OnOffZero}, - {``, OnOffOne}, - {``, OnOffFalse}, - {``, OnOffTrue}, - {``, OnOffOff}, - {``, OnOffOn}, + {``, BinFlagZero}, + {``, BinFlagOne}, + {``, BinFlagFalse}, + {``, BinFlagTrue}, + {``, BinFlagOff}, + {``, BinFlagOn}, } for _, tt := range tests { t.Run(tt.inputXML, func(t *testing.T) { type Element struct { XMLName xml.Name `xml:"element"` - OnOff OnOff `xml:"onOff,attr"` + BinFlag BinFlag `xml:"onOff,attr"` } var elem Element @@ -74,19 +74,19 @@ func TestOnOff_UnmarshalXMLAttr_ValidValues(t *testing.T) { t.Fatalf("Error unmarshaling XML: %v", err) } - if elem.OnOff != tt.expected { - t.Errorf("Expected %s but got %s", tt.expected, elem.OnOff) + if elem.BinFlag != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, elem.BinFlag) } }) } } -func TestOnOff_UnmarshalXMLAttr_InvalidValue(t *testing.T) { +func TestBinFlag_UnmarshalXMLAttr_InvalidValue(t *testing.T) { inputXML := `` type Element struct { XMLName xml.Name `xml:"element"` - OnOff OnOff `xml:"onOff,attr"` + BinFlag BinFlag `xml:"onOff,attr"` } var elem Element @@ -97,7 +97,7 @@ func TestOnOff_UnmarshalXMLAttr_InvalidValue(t *testing.T) { t.Fatalf("Expected error for invalid value, but got none") } - expectedError := "invalid OnOff string" + expectedError := "invalid BinFlag string" if err.Error() != expectedError { t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) } diff --git a/wml/stypes/dropCap.go b/wml/stypes/dropCap.go new file mode 100644 index 0000000..9383530 --- /dev/null +++ b/wml/stypes/dropCap.go @@ -0,0 +1,39 @@ +package stypes + +import ( + "encoding/xml" + "errors" +) + +// Text Frame Drop Cap Location +type DropCap string + +const ( + DropCapNone DropCap = "none" // Not Drop Cap + DropCapInside DropCap = "drop" // Drop Cap Inside Margin + DropCapMargin DropCap = "margin" // Drop Cap Outside Margin +) + +// DropCapFromStr converts a string to DropCap type. +func DropCapFromStr(value string) (DropCap, error) { + switch value { + case "none": + return DropCapNone, nil + case "drop": + return DropCapInside, nil + case "margin": + return DropCapMargin, nil + default: + return "", errors.New("Invalid DropCap value") + } +} + +// UnmarshalXMLAttr unmarshals XML attribute into DropCap. +func (d *DropCap) UnmarshalXMLAttr(attr xml.Attr) error { + val, err := DropCapFromStr(attr.Value) + if err != nil { + return err + } + *d = val + return nil +} diff --git a/wml/stypes/dropCap_test.go b/wml/stypes/dropCap_test.go new file mode 100644 index 0000000..77da6cd --- /dev/null +++ b/wml/stypes/dropCap_test.go @@ -0,0 +1,98 @@ +package stypes + +import ( + "encoding/xml" + "testing" +) + +func TestDropCapFromStr_ValidValues(t *testing.T) { + tests := []struct { + input string + expected DropCap + }{ + {"none", DropCapNone}, + {"drop", DropCapInside}, + {"margin", DropCapMargin}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result, err := DropCapFromStr(tt.input) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if result != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, result) + } + }) + } +} + +func TestDropCapFromStr_InvalidValue(t *testing.T) { + input := "invalidValue" + + result, err := DropCapFromStr(input) + + if err == nil { + t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) + } + + expectedError := "Invalid DropCap value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} + +func TestDropCap_UnmarshalXMLAttr_ValidValues(t *testing.T) { + tests := []struct { + inputXML string + expected DropCap + }{ + {``, DropCapNone}, + {``, DropCapInside}, + {``, DropCapMargin}, + } + + for _, tt := range tests { + t.Run(tt.inputXML, func(t *testing.T) { + type Element struct { + XMLName xml.Name `xml:"element"` + Val DropCap `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(tt.inputXML), &elem) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if elem.Val != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, elem.Val) + } + }) + } +} + +func TestDropCap_UnmarshalXMLAttr_InvalidValue(t *testing.T) { + inputXML := `` + + type Element struct { + XMLName xml.Name `xml:"element"` + Val DropCap `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(inputXML), &elem) + + if err == nil { + t.Fatalf("Expected error for invalid value, but got none") + } + + expectedError := "Invalid DropCap value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} diff --git a/wml/stypes/heightRule.go b/wml/stypes/heightRule.go new file mode 100644 index 0000000..57ad844 --- /dev/null +++ b/wml/stypes/heightRule.go @@ -0,0 +1,40 @@ +package stypes + +import ( + "encoding/xml" + "errors" +) + +// HeightRule Type +type HeightRule string + +// Constants for valid values +const ( + HeightRuleAuto HeightRule = "auto" // Determine Height Based On Contents + HeightRuleExact HeightRule = "exact" // Exact Height + HeightRuleAtLeast HeightRule = "atLeast" // Minimum Height +) + +// HeightRuleFromStr converts a string to HeightRule type. +func HeightRuleFromStr(value string) (HeightRule, error) { + switch value { + case "auto": + return HeightRuleAuto, nil + case "exact": + return HeightRuleExact, nil + case "atLeast": + return HeightRuleAtLeast, nil + default: + return "", errors.New("Invalid HeightRule value") + } +} + +// UnmarshalXMLAttr unmarshals XML attribute into HeightRule. +func (h *HeightRule) UnmarshalXMLAttr(attr xml.Attr) error { + val, err := HeightRuleFromStr(attr.Value) + if err != nil { + return err + } + *h = val + return nil +} diff --git a/wml/stypes/heightRule_test.go b/wml/stypes/heightRule_test.go new file mode 100644 index 0000000..097448a --- /dev/null +++ b/wml/stypes/heightRule_test.go @@ -0,0 +1,98 @@ +package stypes + +import ( + "encoding/xml" + "testing" +) + +func TestHeightRuleFromStr_ValidValues(t *testing.T) { + tests := []struct { + input string + expected HeightRule + }{ + {"auto", HeightRuleAuto}, + {"exact", HeightRuleExact}, + {"atLeast", HeightRuleAtLeast}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result, err := HeightRuleFromStr(tt.input) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if result != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, result) + } + }) + } +} + +func TestHeightRuleFromStr_InvalidValue(t *testing.T) { + input := "invalidValue" + + result, err := HeightRuleFromStr(input) + + if err == nil { + t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) + } + + expectedError := "Invalid HeightRule value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} + +func TestHeightRule_UnmarshalXMLAttr_ValidValues(t *testing.T) { + tests := []struct { + inputXML string + expected HeightRule + }{ + {``, HeightRuleAuto}, + {``, HeightRuleExact}, + {``, HeightRuleAtLeast}, + } + + for _, tt := range tests { + t.Run(tt.inputXML, func(t *testing.T) { + type Element struct { + XMLName xml.Name `xml:"element"` + Val HeightRule `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(tt.inputXML), &elem) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if elem.Val != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, elem.Val) + } + }) + } +} + +func TestHeightRule_UnmarshalXMLAttr_InvalidValue(t *testing.T) { + inputXML := `` + + type Element struct { + XMLName xml.Name `xml:"element"` + Val HeightRule `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(inputXML), &elem) + + if err == nil { + t.Fatalf("Expected error for invalid value, but got none") + } + + expectedError := "Invalid HeightRule value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} diff --git a/wml/stypes/lnSpacRule.go b/wml/stypes/lnSpacRule.go new file mode 100644 index 0000000..cf5d2fc --- /dev/null +++ b/wml/stypes/lnSpacRule.go @@ -0,0 +1,38 @@ +package stypes + +import ( + "encoding/xml" + "errors" +) + +type LineSpacingRule string + +const ( + LineSpacingRuleAuto LineSpacingRule = "auto" // Automatically Determined Line Height + LineSpacingRuleExact LineSpacingRule = "exact" // Exact Line Height + LineSpacingRuleAtLeast LineSpacingRule = "atLeast" // Minimum Line Height +) + +func LineSpacingRuleFromStr(value string) (LineSpacingRule, error) { + switch value { + case "auto": + return LineSpacingRuleAuto, nil + case "exact": + return LineSpacingRuleExact, nil + case "atLeast": + return LineSpacingRuleAtLeast, nil + default: + return "", errors.New("invalid LineSpacingRule value") + } +} + +func (d *LineSpacingRule) UnmarshalXMLAttr(attr xml.Attr) error { + val, err := LineSpacingRuleFromStr(attr.Value) + if err != nil { + return err + } + + *d = val + + return nil +} diff --git a/wml/stypes/lnSpacRule_test.go b/wml/stypes/lnSpacRule_test.go new file mode 100644 index 0000000..18fd732 --- /dev/null +++ b/wml/stypes/lnSpacRule_test.go @@ -0,0 +1,98 @@ +package stypes + +import ( + "encoding/xml" + "testing" +) + +func TestLineSpacingRuleFromStr_ValidValues(t *testing.T) { + tests := []struct { + input string + expected LineSpacingRule + }{ + {"auto", LineSpacingRuleAuto}, + {"exact", LineSpacingRuleExact}, + {"atLeast", LineSpacingRuleAtLeast}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result, err := LineSpacingRuleFromStr(tt.input) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if result != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, result) + } + }) + } +} + +func TestLineSpacingRuleFromStr_InvalidValue(t *testing.T) { + input := "invalidValue" + + result, err := LineSpacingRuleFromStr(input) + + if err == nil { + t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) + } + + expectedError := "invalid LineSpacingRule value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} + +func TestLineSpacingRule_UnmarshalXMLAttr_ValidValues(t *testing.T) { + tests := []struct { + inputXML string + expected LineSpacingRule + }{ + {``, LineSpacingRuleAuto}, + {``, LineSpacingRuleExact}, + {``, LineSpacingRuleAtLeast}, + } + + for _, tt := range tests { + t.Run(tt.inputXML, func(t *testing.T) { + type Element struct { + XMLName xml.Name `xml:"element"` + LineSpacing LineSpacingRule `xml:"lineSpacing,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(tt.inputXML), &elem) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if elem.LineSpacing != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, elem.LineSpacing) + } + }) + } +} + +func TestLineSpacingRule_UnmarshalXMLAttr_InvalidValue(t *testing.T) { + inputXML := `` + + type Element struct { + XMLName xml.Name `xml:"element"` + LineSpacing LineSpacingRule `xml:"lineSpacing,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(inputXML), &elem) + + if err == nil { + t.Fatalf("Expected error for invalid value, but got none") + } + + expectedError := "invalid LineSpacingRule value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} diff --git a/wml/stypes/onOff.go b/wml/stypes/onOff.go deleted file mode 100644 index 0d8557a..0000000 --- a/wml/stypes/onOff.go +++ /dev/null @@ -1,49 +0,0 @@ -package stypes - -import ( - "encoding/xml" - "errors" -) - -// On/Off Value -type OnOff string - -const ( - OnOffZero OnOff = "0" - OnOffOne OnOff = "1" - OnOffFalse OnOff = "false" - OnOffTrue OnOff = "true" - OnOffOff OnOff = "off" - OnOffOn OnOff = "on" -) - -func OnOffFromStr(s string) (OnOff, error) { - switch s { - case "0": - return OnOffZero, nil - case "1": - return OnOffOne, nil - case "false": - return OnOffFalse, nil - case "true": - return OnOffTrue, nil - case "off": - return OnOffOff, nil - case "on": - return OnOffOn, nil - default: - return "", errors.New("invalid OnOff string") - } -} - -func (d *OnOff) UnmarshalXMLAttr(attr xml.Attr) error { - val, err := OnOffFromStr(attr.Value) - if err != nil { - return err - } - - *d = val - - return nil - -} diff --git a/wml/stypes/shd.go b/wml/stypes/shd.go index c7020b0..1510549 100644 --- a/wml/stypes/shd.go +++ b/wml/stypes/shd.go @@ -130,7 +130,7 @@ func ShadingFromStr(value string) (Shading, error) { case "pct95": return ShdPct95, nil default: - return "", errors.New("invalid Shd value") + return "", errors.New("invalid shading value") } } diff --git a/wml/stypes/textAlign.go b/wml/stypes/textAlign.go new file mode 100644 index 0000000..1885086 --- /dev/null +++ b/wml/stypes/textAlign.go @@ -0,0 +1,44 @@ +package stypes + +import ( + "encoding/xml" + "errors" +) + +type TextAlign string + +const ( + TextAlignTop TextAlign = "top" // Align Text at Top + TextAlignCenter TextAlign = "center" // Align Text at Center + TextAlignBaseline TextAlign = "baseline" // Align Text at Baseline + TextAlignBottom TextAlign = "bottom" // Align Text at Bottom + TextAlignAuto TextAlign = "auto" // Automatically Determine Alignment +) + +func TextAlignFromStr(value string) (TextAlign, error) { + switch value { + case "top": + return TextAlignTop, nil + case "center": + return TextAlignCenter, nil + case "baseline": + return TextAlignBaseline, nil + case "bottom": + return TextAlignBottom, nil + case "auto": + return TextAlignAuto, nil + default: + return "", errors.New("Invalid Text Alignment") + } +} + +func (a *TextAlign) UnmarshalXMLAttr(attr xml.Attr) error { + val, err := TextAlignFromStr(attr.Value) + if err != nil { + return err + } + + *a = val + + return nil +} diff --git a/wml/stypes/textAlign_test.go b/wml/stypes/textAlign_test.go new file mode 100644 index 0000000..5a5adad --- /dev/null +++ b/wml/stypes/textAlign_test.go @@ -0,0 +1,102 @@ +package stypes + +import ( + "encoding/xml" + "testing" +) + +func TestTextAlignFromStr_ValidValues(t *testing.T) { + tests := []struct { + input string + expected TextAlign + }{ + {"top", TextAlignTop}, + {"center", TextAlignCenter}, + {"baseline", TextAlignBaseline}, + {"bottom", TextAlignBottom}, + {"auto", TextAlignAuto}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result, err := TextAlignFromStr(tt.input) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if result != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, result) + } + }) + } +} + +func TestTextAlignFromStr_InvalidValue(t *testing.T) { + input := "invalidValue" + + result, err := TextAlignFromStr(input) + + if err == nil { + t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) + } + + expectedError := "Invalid Text Alignment" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} + +func TestTextAlign_UnmarshalXMLAttr_ValidValues(t *testing.T) { + tests := []struct { + inputXML string + expected TextAlign + }{ + {``, TextAlignTop}, + {``, TextAlignCenter}, + {``, TextAlignBaseline}, + {``, TextAlignBottom}, + {``, TextAlignAuto}, + } + + for _, tt := range tests { + t.Run(tt.inputXML, func(t *testing.T) { + type Element struct { + XMLName xml.Name `xml:"element"` + Align TextAlign `xml:"align,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(tt.inputXML), &elem) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if elem.Align != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, elem.Align) + } + }) + } +} + +func TestTextAlign_UnmarshalXMLAttr_InvalidValue(t *testing.T) { + inputXML := `` + + type Element struct { + XMLName xml.Name `xml:"element"` + Align TextAlign `xml:"align,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(inputXML), &elem) + + if err == nil { + t.Fatalf("Expected error for invalid value, but got none") + } + + expectedError := "Invalid Text Alignment" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} diff --git a/wml/stypes/textboxTightWrap.go b/wml/stypes/textboxTightWrap.go new file mode 100644 index 0000000..036f003 --- /dev/null +++ b/wml/stypes/textboxTightWrap.go @@ -0,0 +1,44 @@ +package stypes + +import ( + "encoding/xml" + "errors" +) + +type TextboxTightWrap string + +const ( + TextboxTightWrapNone TextboxTightWrap = "none" // Do Not Tight Wrap + TextboxTightWrapAllLines TextboxTightWrap = "allLines" // Tight Wrap All Lines + TextboxTightWrapFirstAndLastLine TextboxTightWrap = "firstAndLastLine" // Tight Wrap First and Last Lines + TextboxTightWrapFirstLineOnly TextboxTightWrap = "firstLineOnly" // Tight Wrap First Line + TextboxTightWrapLastLineOnly TextboxTightWrap = "lastLineOnly" // Tight Wrap Last Line +) + +func TextboxTightWrapFromStr(value string) (TextboxTightWrap, error) { + switch value { + case "none": + return TextboxTightWrapNone, nil + case "allLines": + return TextboxTightWrapAllLines, nil + case "firstAndLastLine": + return TextboxTightWrapFirstAndLastLine, nil + case "firstLineOnly": + return TextboxTightWrapFirstLineOnly, nil + case "lastLineOnly": + return TextboxTightWrapLastLineOnly, nil + default: + return "", errors.New("Invalid Textbox Tight Wrap value") + } +} + +func (t *TextboxTightWrap) UnmarshalXMLAttr(attr xml.Attr) error { + val, err := TextboxTightWrapFromStr(attr.Value) + if err != nil { + return err + } + + *t = val + + return nil +} diff --git a/wml/stypes/textboxTightWrap_test.go b/wml/stypes/textboxTightWrap_test.go new file mode 100644 index 0000000..bd7daa2 --- /dev/null +++ b/wml/stypes/textboxTightWrap_test.go @@ -0,0 +1,102 @@ +package stypes + +import ( + "encoding/xml" + "testing" +) + +func TestTextboxTightWrapFromStr_ValidValues(t *testing.T) { + tests := []struct { + input string + expected TextboxTightWrap + }{ + {"none", TextboxTightWrapNone}, + {"allLines", TextboxTightWrapAllLines}, + {"firstAndLastLine", TextboxTightWrapFirstAndLastLine}, + {"firstLineOnly", TextboxTightWrapFirstLineOnly}, + {"lastLineOnly", TextboxTightWrapLastLineOnly}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result, err := TextboxTightWrapFromStr(tt.input) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if result != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, result) + } + }) + } +} + +func TestTextboxTightWrapFromStr_InvalidValue(t *testing.T) { + input := "invalidValue" + + result, err := TextboxTightWrapFromStr(input) + + if err == nil { + t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) + } + + expectedError := "Invalid Textbox Tight Wrap value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} + +func TestTextboxTightWrap_UnmarshalXMLAttr_ValidValues(t *testing.T) { + tests := []struct { + inputXML string + expected TextboxTightWrap + }{ + {``, TextboxTightWrapNone}, + {``, TextboxTightWrapAllLines}, + {``, TextboxTightWrapFirstAndLastLine}, + {``, TextboxTightWrapFirstLineOnly}, + {``, TextboxTightWrapLastLineOnly}, + } + + for _, tt := range tests { + t.Run(tt.inputXML, func(t *testing.T) { + type Element struct { + XMLName xml.Name `xml:"element"` + Val TextboxTightWrap `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(tt.inputXML), &elem) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if elem.Val != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, elem.Val) + } + }) + } +} + +func TestTextboxTightWrap_UnmarshalXMLAttr_InvalidValue(t *testing.T) { + inputXML := `` + + type Element struct { + XMLName xml.Name `xml:"element"` + Val TextboxTightWrap `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(inputXML), &elem) + + if err == nil { + t.Fatalf("Expected error for invalid value, but got none") + } + + expectedError := "Invalid Textbox Tight Wrap value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} diff --git a/wml/stypes/wrap.go b/wml/stypes/wrap.go new file mode 100644 index 0000000..31e529d --- /dev/null +++ b/wml/stypes/wrap.go @@ -0,0 +1,49 @@ +package stypes + +import ( + "encoding/xml" + "errors" +) + +// Wrap Type +type Wrap string + +// Constants for valid values +const ( + WrapAuto Wrap = "auto" // Default Text Wrapping Around Frame + WrapNotBeside Wrap = "notBeside" // No Text Wrapping Beside Frame + WrapAround Wrap = "around" // Allow Text Wrapping Around Frame + WrapTight Wrap = "tight" // Tight Text Wrapping Around Frame + WrapThrough Wrap = "through" // Through Text Wrapping Around Frame + WrapNone Wrap = "none" // No Text Wrapping Around Frame +) + +// WrapFromStr converts a string to Wrap type. +func WrapFromStr(value string) (Wrap, error) { + switch value { + case "auto": + return WrapAuto, nil + case "notBeside": + return WrapNotBeside, nil + case "around": + return WrapAround, nil + case "tight": + return WrapTight, nil + case "through": + return WrapThrough, nil + case "none": + return WrapNone, nil + default: + return "", errors.New("Invalid Wrap value") + } +} + +// UnmarshalXMLAttr unmarshals XML attribute into Wrap. +func (w *Wrap) UnmarshalXMLAttr(attr xml.Attr) error { + val, err := WrapFromStr(attr.Value) + if err != nil { + return err + } + *w = val + return nil +} diff --git a/wml/stypes/wrap_test.go b/wml/stypes/wrap_test.go new file mode 100644 index 0000000..1ad5b5f --- /dev/null +++ b/wml/stypes/wrap_test.go @@ -0,0 +1,104 @@ +package stypes + +import ( + "encoding/xml" + "testing" +) + +func TestWrapFromStr_ValidValues(t *testing.T) { + tests := []struct { + input string + expected Wrap + }{ + {"auto", WrapAuto}, + {"notBeside", WrapNotBeside}, + {"around", WrapAround}, + {"tight", WrapTight}, + {"through", WrapThrough}, + {"none", WrapNone}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result, err := WrapFromStr(tt.input) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if result != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, result) + } + }) + } +} + +func TestWrapFromStr_InvalidValue(t *testing.T) { + input := "invalidValue" + + result, err := WrapFromStr(input) + + if err == nil { + t.Fatalf("Expected error for invalid value %s, but got none. Result: %s", input, result) + } + + expectedError := "Invalid Wrap value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +} + +func TestWrap_UnmarshalXMLAttr_ValidValues(t *testing.T) { + tests := []struct { + inputXML string + expected Wrap + }{ + {``, WrapAuto}, + {``, WrapNotBeside}, + {``, WrapAround}, + {``, WrapTight}, + {``, WrapThrough}, + {``, WrapNone}, + } + + for _, tt := range tests { + t.Run(tt.inputXML, func(t *testing.T) { + type Element struct { + XMLName xml.Name `xml:"element"` + Val Wrap `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(tt.inputXML), &elem) + if err != nil { + t.Fatalf("Error unmarshaling XML: %v", err) + } + + if elem.Val != tt.expected { + t.Errorf("Expected %s but got %s", tt.expected, elem.Val) + } + }) + } +} + +func TestWrap_UnmarshalXMLAttr_InvalidValue(t *testing.T) { + inputXML := `` + + type Element struct { + XMLName xml.Name `xml:"element"` + Val Wrap `xml:"val,attr"` + } + + var elem Element + + err := xml.Unmarshal([]byte(inputXML), &elem) + + if err == nil { + t.Fatalf("Expected error for invalid value, but got none") + } + + expectedError := "Invalid Wrap value" + if err.Error() != expectedError { + t.Errorf("Expected error message '%s' but got '%s'", expectedError, err.Error()) + } +}