diff --git a/empty.go b/empty.go index a616dfc..7322a7e 100644 --- a/empty.go +++ b/empty.go @@ -31,9 +31,11 @@ func newEmptyA4File() *Docx { XMLName: xml.Name{ Space: "w", }, - XMLW: XMLNS_W, - XMLR: XMLNS_R, - XMLWP: XMLNS_WP, + XMLW: XMLNS_W, + XMLR: XMLNS_R, + XMLWP: XMLNS_WP, + XMLWPS: XMLNS_WPS, + // XMLMC: XMLNS_MC, // XMLWP14: XMLNS_WP14, Body: Body{ Items: make([]interface{}, 0, 64), diff --git a/structdoc.go b/structdoc.go index 6d60136..28d4ddb 100644 --- a/structdoc.go +++ b/structdoc.go @@ -28,9 +28,11 @@ import ( //nolint:revive,stylecheck const ( - XMLNS_W = `http://schemas.openxmlformats.org/wordprocessingml/2006/main` - XMLNS_R = `http://schemas.openxmlformats.org/officeDocument/2006/relationships` - XMLNS_WP = `http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing` + XMLNS_W = `http://schemas.openxmlformats.org/wordprocessingml/2006/main` + XMLNS_R = `http://schemas.openxmlformats.org/officeDocument/2006/relationships` + XMLNS_WP = `http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing` + XMLNS_WPS = `http://schemas.microsoft.com/office/word/2010/wordprocessingShape` + // XMLNS_MC = `http://schemas.openxmlformats.org/markup-compatibility/2006` // XMLNS_WP14 = `http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing` XMLNS_PICTURE = `http://schemas.openxmlformats.org/drawingml/2006/picture` @@ -95,9 +97,11 @@ func (b *Body) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { // Document type Document struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/wordprocessingml/2006/main document"` - XMLW string `xml:"xmlns:w,attr"` // cannot be unmarshalled in - XMLR string `xml:"xmlns:r,attr,omitempty"` // cannot be unmarshalled in - XMLWP string `xml:"xmlns:wp,attr,omitempty"` // cannot be unmarshalled in + XMLW string `xml:"xmlns:w,attr"` // cannot be unmarshalled in + XMLR string `xml:"xmlns:r,attr,omitempty"` // cannot be unmarshalled in + XMLWP string `xml:"xmlns:wp,attr,omitempty"` // cannot be unmarshalled in + XMLWPS string `xml:"xmlns:wps,attr,omitempty"` // cannot be unmarshalled in + // XMLMC string `xml:"xmlns:mc,attr,omitempty"` // cannot be unmarshalled in // XMLWP14 string `xml:"xmlns:wp14,attr,omitempty"` // cannot be unmarshalled in Body Body `xml:"w:body"` diff --git a/structdrawing.go b/structdrawing.go index 87651b3..b476189 100644 --- a/structdrawing.go +++ b/structdrawing.go @@ -741,10 +741,6 @@ func (p *PICSpPr) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { return err } case "prstGeom": - err = d.DecodeElement(&p.PrstGeom, &tt) - if err != nil && !strings.HasPrefix(err.Error(), "expected") { - return err - } p.PrstGeom.Prst = getAtt(tt.Attr, "prst") default: err = d.Skip() // skip unsupported tags diff --git a/structrun.go b/structrun.go index 9070a91..a21518e 100644 --- a/structrun.go +++ b/structrun.go @@ -49,56 +49,92 @@ func (r *Run) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { return err } - var child interface{} - if tt, ok := t.(xml.StartElement); ok { - switch tt.Name.Local { - case "rPr": - var value RunProperties - err = d.DecodeElement(&value, &tt) - if err != nil && !strings.HasPrefix(err.Error(), "expected") { - return err - } - r.RunProperties = &value - continue - case "instrText": - var value string - err = d.DecodeElement(&value, &tt) - if err != nil && !strings.HasPrefix(err.Error(), "expected") { - return err - } - r.InstrText = value - continue - case "t": - var value Text - err = d.DecodeElement(&value, &tt) - if err != nil && !strings.HasPrefix(err.Error(), "expected") { - return err - } - child = &value - case "drawing": - var value Drawing - err = d.DecodeElement(&value, &tt) - if err != nil && !strings.HasPrefix(err.Error(), "expected") { - return err - } - child = &value - case "tab": - child = &Tab{} - default: - err = d.Skip() // skip unsupported tags - if err != nil { - return err - } - continue + child, err := r.parse(d, tt) + if err != nil { + return err + } + if child != nil { + r.Children = append(r.Children, child) } - r.Children = append(r.Children, child) } } return nil } +func (r *Run) parse(d *xml.Decoder, tt xml.StartElement) (child interface{}, err error) { + switch tt.Name.Local { + case "rPr": + var value RunProperties + err = d.DecodeElement(&value, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return nil, err + } + r.RunProperties = &value + return nil, nil + case "instrText": + var value string + err = d.DecodeElement(&value, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return nil, err + } + r.InstrText = value + return nil, nil + case "t": + var value Text + err = d.DecodeElement(&value, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return nil, err + } + child = &value + case "drawing": + var value Drawing + err = d.DecodeElement(&value, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return nil, err + } + child = &value + case "tab": + child = &Tab{} + case "AlternateContent": + altcont: + for { + tok, err1 := d.Token() + if err1 == io.EOF { + break + } + if err1 != nil { + return nil, err1 + } + + if ttt, ok := tok.(xml.StartElement); ok && ttt.Name.Local == "Choice" { + for _, attr := range ttt.Attr { + if attr.Name.Local == "Requires" { + if attr.Value == "wps" { + child, err = r.parse(d, ttt) + break altcont + } + break + } + } + } + if et, ok := tok.(xml.EndElement); ok { + if et.Name.Local == "AlternateContent" { + break + } + } + err = d.Skip() // skip unsupported tags + if err != nil { + return nil, err + } + } + default: + err = d.Skip() // skip unsupported tags + } + return +} + // RunProperties encapsulates visual properties of a run type RunProperties struct { XMLName xml.Name `xml:"w:rPr,omitempty"` diff --git a/structshape.go b/structshape.go new file mode 100644 index 0000000..5695ca2 --- /dev/null +++ b/structshape.go @@ -0,0 +1,263 @@ +package docx + +import ( + "encoding/xml" + "io" + "strconv" + "strings" +) + +// WPSWordprocessingShape is a container for a WordprocessingML DrawingML shape. +type WPSWordprocessingShape struct { + XMLName xml.Name `xml:"wps:wsp,omitempty"` + CNvCnPr *WPSCNvCnPr + SpPr *WPSSpPr + BodyPr *WPSBodyPr +} + +// UnmarshalXML ... +func (w *WPSWordprocessingShape) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for { + t, err := d.Token() + if err == io.EOF { + break + } + if err != nil { + return err + } + + if tt, ok := t.(xml.StartElement); ok { + switch tt.Name.Local { + case "cNvCnPr": + w.CNvCnPr = new(WPSCNvCnPr) + err = d.DecodeElement(w.CNvCnPr, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return err + } + case "spPr": + w.SpPr = new(WPSSpPr) + err = d.DecodeElement(w.SpPr, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return err + } + case "bodyPr": + w.BodyPr = new(WPSBodyPr) + err = d.DecodeElement(w.BodyPr, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return err + } + default: + err = d.Skip() // skip unsupported tags + if err != nil { + return err + } + } + } + } + return nil +} + +// WPSCNvCnPr represents the non-visual drawing properties of a connector. +type WPSCNvCnPr struct { + XMLName xml.Name `xml:"wps:cNvCnPr,omitempty"` + ConnShapeLocks *struct{} `xml:"a:cxnSpLocks,omitempty"` +} + +// UnmarshalXML ... +func (w *WPSCNvCnPr) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for { + t, err := d.Token() + if err == io.EOF { + break + } + if err != nil { + return err + } + + if tt, ok := t.(xml.StartElement); ok { + switch tt.Name.Local { + case "cxnSpLocks": + w.ConnShapeLocks = &struct{}{} + default: + err = d.Skip() // skip unsupported tags + if err != nil { + return err + } + continue + } + } + } + return nil +} + +// WPSSpPr is a container element that represents the visual properties of a shape. +type WPSSpPr struct { + XMLName xml.Name `xml:"wps:spPr,omitempty"` + BWMode string `xml:"bwMode,attr"` + + Xfrm AXfrm + PrstGeom APrstGeom + NoFill *struct{} `xml:"a:noFill,omitempty"` + Ln *ALine +} + +// UnmarshalXML ... +func (w *WPSSpPr) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for _, attr := range start.Attr { + switch attr.Name.Local { + case "bwMode": + w.BWMode = attr.Value + default: + // ignore other attributes + } + } + for { + t, err := d.Token() + if err == io.EOF { + break + } + if err != nil { + return err + } + + if tt, ok := t.(xml.StartElement); ok { + switch tt.Name.Local { + case "xfrm": + err = d.DecodeElement(&w.Xfrm, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return err + } + case "prstGeom": + w.PrstGeom.Prst = getAtt(tt.Attr, "prst") + case "noFill": + w.NoFill = &struct{}{} + case "ln": + w.Ln = &ALine{} + err = d.DecodeElement(&w.Ln, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return err + } + default: + err = d.Skip() // skip unsupported tags + if err != nil { + return err + } + continue + } + } + } + return nil +} + +// ALine represents a line element in a Word document. +type ALine struct { + XMLName xml.Name `xml:"a:ln,omitempty"` + W int64 `xml:"w,attr"` + + SolidFill *ASolidFill + Round *struct{} `xml:"a:round,omitempty"` + HeadEnd *struct{} `xml:"a:headEnd,omitempty"` + TailEnd *struct{} `xml:"a:tailEnd,omitempty"` +} + +// UnmarshalXML ... +func (l *ALine) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) { + for _, attr := range start.Attr { + switch attr.Name.Local { + case "w": + l.W, err = strconv.ParseInt(attr.Value, 10, 64) + if err != nil { + return err + } + default: + // ignore other attributes + } + } + for { + t, err := d.Token() + if err == io.EOF { + break + } + if err != nil { + return err + } + + if tt, ok := t.(xml.StartElement); ok { + switch tt.Name.Local { + case "solidFill": + l.SolidFill = new(ASolidFill) + err = d.DecodeElement(l.SolidFill, &tt) + if err != nil && !strings.HasPrefix(err.Error(), "expected") { + return err + } + case "round": + l.Round = &struct{}{} + case "headEnd": + l.HeadEnd = &struct{}{} + case "tailEnd": + l.TailEnd = &struct{}{} + default: + err = d.Skip() // skip unsupported tags + if err != nil { + return err + } + continue + } + } + } + return nil +} + +// ASolidFill represents a solid fill of a shape or chart element. +type ASolidFill struct { + XMLName xml.Name `xml:"a:solidFill,omitempty"` + SrgbClr *ASrgbClr +} + +// UnmarshalXML ... +func (s *ASolidFill) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for { + t, err := d.Token() + if err == io.EOF { + break + } + if err != nil { + return err + } + + if tt, ok := t.(xml.StartElement); ok { + switch tt.Name.Local { + case "srgbClr": + s.SrgbClr = new(ASrgbClr) + for _, attr := range tt.Attr { + if attr.Name.Local == "val" { + s.SrgbClr.Val = attr.Value + break + } + } + err = d.Skip() // skip unsupported elements + if err != nil { + return err + } + default: + err = d.Skip() // skip unsupported elements + if err != nil { + return err + } + continue + } + } + } + return nil +} + +// ASrgbClr represents an sRGB color. +type ASrgbClr struct { + XMLName xml.Name `xml:"a:srgbClr,omitempty"` + Val string `xml:"val,attr"` +} + +// WPSBodyPr represents the body properties for a WordprocessingML DrawingML shape. +type WPSBodyPr struct { + XMLName xml.Name `xml:"wps:bodyPr,omitempty"` +} diff --git a/unpack.go b/unpack.go index 2ee83e9..dd954f3 100644 --- a/unpack.go +++ b/unpack.go @@ -81,6 +81,8 @@ func (f *Docx) parseDocument(file *zip.File) error { f.Document.XMLW = XMLNS_W f.Document.XMLR = XMLNS_R f.Document.XMLWP = XMLNS_WP + // f.Document.XMLMC = XMLNS_MC + f.Document.XMLWPS = XMLNS_WPS // f.Document.XMLWP14 = XMLNS_WP14 f.Document.XMLName.Space = XMLNS_W f.Document.XMLName.Local = "document"