OSDN Git Service

This closes #1694, using namespace prefix in workbook theme XML
authorxuri <xuri.me@gmail.com>
Thu, 19 Oct 2023 16:04:31 +0000 (00:04 +0800)
committerxuri <xuri.me@gmail.com>
Thu, 19 Oct 2023 16:04:31 +0000 (00:04 +0800)
- Improve compatibility with the viewer which doesn't support default theme part namespace
- ref #1690, support read background color style, and conditional format with default pattern type
- Update the unit tests

excelize.go
file.go
styles.go
styles_test.go
xmlTheme.go

index 80ba1d3..79b4b68 100644 (file)
@@ -47,7 +47,7 @@ type File struct {
        Sheet            sync.Map
        SheetCount       int
        Styles           *xlsxStyleSheet
-       Theme            *xlsxTheme
+       Theme            *decodeTheme
        DecodeVMLDrawing map[string]*decodeVmlDrawing
        VMLDrawing       map[string]*vmlDrawing
        WorkBook         *xlsxWorkbook
diff --git a/file.go b/file.go
index 19333ea..7f2a036 100644 (file)
--- a/file.go
+++ b/file.go
@@ -191,13 +191,11 @@ func (f *File) writeToZip(zw *zip.Writer) error {
                        return err
                }
                var from io.Reader
-               from, err = stream.rawData.Reader()
-               if err != nil {
+               if from, err = stream.rawData.Reader(); err != nil {
                        _ = stream.rawData.Close()
                        return err
                }
-               _, err = io.Copy(fi, from)
-               if err != nil {
+               if _, err = io.Copy(fi, from); err != nil {
                        return err
                }
        }
@@ -210,8 +208,7 @@ func (f *File) writeToZip(zw *zip.Writer) error {
                        return true
                }
                var fi io.Writer
-               fi, err = zw.Create(path.(string))
-               if err != nil {
+               if fi, err = zw.Create(path.(string)); err != nil {
                        return false
                }
                _, err = fi.Write(content.([]byte))
@@ -222,8 +219,7 @@ func (f *File) writeToZip(zw *zip.Writer) error {
                        return true
                }
                var fi io.Writer
-               fi, err = zw.Create(path.(string))
-               if err != nil {
+               if fi, err = zw.Create(path.(string)); err != nil {
                        return false
                }
                _, err = fi.Write(f.readBytes(path.(string)))
index 115cad1..0ca82fd 100644 (file)
--- a/styles.go
+++ b/styles.go
@@ -129,8 +129,67 @@ func (f *File) styleSheetWriter() {
 // themeWriter provides a function to save xl/theme/theme1.xml after serialize
 // structure.
 func (f *File) themeWriter() {
+       newColor := func(c *decodeCTColor) xlsxCTColor {
+               return xlsxCTColor{
+                       ScrgbClr:  c.ScrgbClr,
+                       SrgbClr:   c.SrgbClr,
+                       HslClr:    c.HslClr,
+                       SysClr:    c.SysClr,
+                       SchemeClr: c.SchemeClr,
+                       PrstClr:   c.PrstClr,
+               }
+       }
+       newFontScheme := func(c *decodeFontCollection) xlsxFontCollection {
+               return xlsxFontCollection{
+                       Latin:  c.Latin,
+                       Ea:     c.Ea,
+                       Cs:     c.Cs,
+                       Font:   c.Font,
+                       ExtLst: c.ExtLst,
+               }
+       }
        if f.Theme != nil {
-               output, _ := xml.Marshal(f.Theme)
+               output, _ := xml.Marshal(xlsxTheme{
+                       XMLNSa: NameSpaceDrawingML.Value,
+                       XMLNSr: SourceRelationship.Value,
+                       Name:   f.Theme.Name,
+                       ThemeElements: xlsxBaseStyles{
+                               ClrScheme: xlsxColorScheme{
+                                       Name:     f.Theme.ThemeElements.ClrScheme.Name,
+                                       Dk1:      newColor(&f.Theme.ThemeElements.ClrScheme.Dk1),
+                                       Lt1:      newColor(&f.Theme.ThemeElements.ClrScheme.Lt1),
+                                       Dk2:      newColor(&f.Theme.ThemeElements.ClrScheme.Dk2),
+                                       Lt2:      newColor(&f.Theme.ThemeElements.ClrScheme.Lt2),
+                                       Accent1:  newColor(&f.Theme.ThemeElements.ClrScheme.Accent1),
+                                       Accent2:  newColor(&f.Theme.ThemeElements.ClrScheme.Accent2),
+                                       Accent3:  newColor(&f.Theme.ThemeElements.ClrScheme.Accent3),
+                                       Accent4:  newColor(&f.Theme.ThemeElements.ClrScheme.Accent4),
+                                       Accent5:  newColor(&f.Theme.ThemeElements.ClrScheme.Accent5),
+                                       Accent6:  newColor(&f.Theme.ThemeElements.ClrScheme.Accent6),
+                                       Hlink:    newColor(&f.Theme.ThemeElements.ClrScheme.Hlink),
+                                       FolHlink: newColor(&f.Theme.ThemeElements.ClrScheme.FolHlink),
+                                       ExtLst:   f.Theme.ThemeElements.ClrScheme.ExtLst,
+                               },
+                               FontScheme: xlsxFontScheme{
+                                       Name:      f.Theme.ThemeElements.FontScheme.Name,
+                                       MajorFont: newFontScheme(&f.Theme.ThemeElements.FontScheme.MajorFont),
+                                       MinorFont: newFontScheme(&f.Theme.ThemeElements.FontScheme.MinorFont),
+                                       ExtLst:    f.Theme.ThemeElements.FontScheme.ExtLst,
+                               },
+                               FmtScheme: xlsxStyleMatrix{
+                                       Name:           f.Theme.ThemeElements.FmtScheme.Name,
+                                       FillStyleLst:   f.Theme.ThemeElements.FmtScheme.FillStyleLst,
+                                       LnStyleLst:     f.Theme.ThemeElements.FmtScheme.LnStyleLst,
+                                       EffectStyleLst: f.Theme.ThemeElements.FmtScheme.EffectStyleLst,
+                                       BgFillStyleLst: f.Theme.ThemeElements.FmtScheme.BgFillStyleLst,
+                               },
+                               ExtLst: f.Theme.ThemeElements.ExtLst,
+                       },
+                       ObjectDefaults:    f.Theme.ObjectDefaults,
+                       ExtraClrSchemeLst: f.Theme.ExtraClrSchemeLst,
+                       CustClrLst:        f.Theme.CustClrLst,
+                       ExtLst:            f.Theme.ExtLst,
+               })
                f.saveFileList(defaultXMLPathTheme, f.replaceNameSpaceBytes(defaultXMLPathTheme, output))
        }
 }
@@ -1284,6 +1343,9 @@ func (f *File) extractFills(fl *xlsxFill, s *xlsxStyleSheet, style *Style) {
                if fl.PatternFill != nil {
                        fill.Type = "pattern"
                        fill.Pattern = inStrSlice(styleFillPatterns, fl.PatternFill.PatternType, false)
+                       if fl.PatternFill.BgColor != nil {
+                               fill.Color = []string{f.getThemeColor(fl.PatternFill.BgColor)}
+                       }
                        if fl.PatternFill.FgColor != nil {
                                fill.Color = []string{f.getThemeColor(fl.PatternFill.FgColor)}
                        }
@@ -1507,6 +1569,10 @@ func (f *File) GetConditionalStyle(idx int) (*Style, error) {
        }
        style = &Style{}
        xf := s.Dxfs.Dxfs[idx]
+       // The default pattern fill type of conditional format style is solid
+       if xf.Fill != nil && xf.Fill.PatternFill != nil && xf.Fill.PatternFill.PatternType == "" {
+               xf.Fill.PatternFill.PatternType = "solid"
+       }
        f.extractFills(xf.Fill, s, style)
        f.extractBorders(xf.Border, s, style)
        f.extractFont(xf.Font, s, style)
@@ -3078,11 +3144,11 @@ func getPaletteColor(color string) string {
 
 // themeReader provides a function to get the pointer to the xl/theme/theme1.xml
 // structure after deserialization.
-func (f *File) themeReader() (*xlsxTheme, error) {
+func (f *File) themeReader() (*decodeTheme, error) {
        if _, ok := f.Pkg.Load(defaultXMLPathTheme); !ok {
                return nil, nil
        }
-       theme := xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value}
+       theme := decodeTheme{}
        if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathTheme)))).
                Decode(&theme); err != nil && err != io.EOF {
                return &theme, err
index 2e91fed..a886c20 100644 (file)
@@ -391,6 +391,18 @@ func TestConditionalStyle(t *testing.T) {
        f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
        _, err = f.GetConditionalStyle(1)
        assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
+
+       f = NewFile()
+       // Test get conditional style with background color and empty pattern type
+       idx, err = f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
+       assert.NoError(t, err)
+       f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.PatternType = ""
+       f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.FgColor = nil
+       f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.BgColor = &xlsxColor{Theme: intPtr(6)}
+       style, err = f.GetConditionalStyle(idx)
+       assert.NoError(t, err)
+       assert.Equal(t, "pattern", style.Fill.Type)
+       assert.Equal(t, []string{"A5A5A5"}, style.Fill.Color)
 }
 
 func TestGetDefaultFont(t *testing.T) {
@@ -436,7 +448,7 @@ func TestThemeReader(t *testing.T) {
        f.Pkg.Store(defaultXMLPathTheme, MacintoshCyrillicCharset)
        theme, err := f.themeReader()
        assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
-       assert.EqualValues(t, &xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value}, theme)
+       assert.EqualValues(t, &decodeTheme{}, theme)
 }
 
 func TestSetCellStyle(t *testing.T) {
index 3a01221..88cc573 100644 (file)
@@ -16,15 +16,15 @@ import "encoding/xml"
 // xlsxTheme directly maps the theme element in the namespace
 // http://schemas.openxmlformats.org/drawingml/2006/main
 type xlsxTheme struct {
-       XMLName           xml.Name              `xml:"http://schemas.openxmlformats.org/drawingml/2006/main theme"`
+       XMLName           xml.Name              `xml:"a:theme"`
        XMLNSa            string                `xml:"xmlns:a,attr"`
        XMLNSr            string                `xml:"xmlns:r,attr"`
        Name              string                `xml:"name,attr"`
-       ThemeElements     xlsxBaseStyles        `xml:"themeElements"`
-       ObjectDefaults    xlsxObjectDefaults    `xml:"objectDefaults"`
-       ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"extraClrSchemeLst"`
-       CustClrLst        *xlsxInnerXML         `xml:"custClrLst"`
-       ExtLst            *xlsxExtLst           `xml:"extLst"`
+       ThemeElements     xlsxBaseStyles        `xml:"a:themeElements"`
+       ObjectDefaults    xlsxObjectDefaults    `xml:"a:objectDefaults"`
+       ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"a:extraClrSchemeLst"`
+       CustClrLst        *xlsxInnerXML         `xml:"a:custClrLst"`
+       ExtLst            *xlsxExtLst           `xml:"a:extLst"`
 }
 
 // xlsxBaseStyles defines the theme elements for a theme, and is the workhorse
@@ -33,40 +33,40 @@ type xlsxTheme struct {
 // scheme, a font scheme, and a style matrix (format scheme) that defines
 // different formatting options for different pieces of a document.
 type xlsxBaseStyles struct {
-       ClrScheme  xlsxColorScheme `xml:"clrScheme"`
-       FontScheme xlsxFontScheme  `xml:"fontScheme"`
-       FmtScheme  xlsxStyleMatrix `xml:"fmtScheme"`
-       ExtLst     *xlsxExtLst     `xml:"extLst"`
+       ClrScheme  xlsxColorScheme `xml:"a:clrScheme"`
+       FontScheme xlsxFontScheme  `xml:"a:fontScheme"`
+       FmtScheme  xlsxStyleMatrix `xml:"a:fmtScheme"`
+       ExtLst     *xlsxExtLst     `xml:"a:extLst"`
 }
 
 // xlsxCTColor holds the actual color values that are to be applied to a given
 // diagram and how those colors are to be applied.
 type xlsxCTColor struct {
-       ScrgbClr  *xlsxInnerXML  `xml:"scrgbClr"`
-       SrgbClr   *attrValString `xml:"srgbClr"`
-       HslClr    *xlsxInnerXML  `xml:"hslClr"`
-       SysClr    *xlsxSysClr    `xml:"sysClr"`
-       SchemeClr *xlsxInnerXML  `xml:"schemeClr"`
-       PrstClr   *xlsxInnerXML  `xml:"prstClr"`
+       ScrgbClr  *xlsxInnerXML  `xml:"a:scrgbClr"`
+       SrgbClr   *attrValString `xml:"a:srgbClr"`
+       HslClr    *xlsxInnerXML  `xml:"a:hslClr"`
+       SysClr    *xlsxSysClr    `xml:"a:sysClr"`
+       SchemeClr *xlsxInnerXML  `xml:"a:schemeClr"`
+       PrstClr   *xlsxInnerXML  `xml:"a:prstClr"`
 }
 
 // xlsxColorScheme defines a set of colors for the theme. The set of colors
 // consists of twelve color slots that can each hold a color of choice.
 type xlsxColorScheme struct {
        Name     string      `xml:"name,attr"`
-       Dk1      xlsxCTColor `xml:"dk1"`
-       Lt1      xlsxCTColor `xml:"lt1"`
-       Dk2      xlsxCTColor `xml:"dk2"`
-       Lt2      xlsxCTColor `xml:"lt2"`
-       Accent1  xlsxCTColor `xml:"accent1"`
-       Accent2  xlsxCTColor `xml:"accent2"`
-       Accent3  xlsxCTColor `xml:"accent3"`
-       Accent4  xlsxCTColor `xml:"accent4"`
-       Accent5  xlsxCTColor `xml:"accent5"`
-       Accent6  xlsxCTColor `xml:"accent6"`
-       Hlink    xlsxCTColor `xml:"hlink"`
-       FolHlink xlsxCTColor `xml:"folHlink"`
-       ExtLst   *xlsxExtLst `xml:"extLst"`
+       Dk1      xlsxCTColor `xml:"a:dk1"`
+       Lt1      xlsxCTColor `xml:"a:lt1"`
+       Dk2      xlsxCTColor `xml:"a:dk2"`
+       Lt2      xlsxCTColor `xml:"a:lt2"`
+       Accent1  xlsxCTColor `xml:"a:accent1"`
+       Accent2  xlsxCTColor `xml:"a:accent2"`
+       Accent3  xlsxCTColor `xml:"a:accent3"`
+       Accent4  xlsxCTColor `xml:"a:accent4"`
+       Accent5  xlsxCTColor `xml:"a:accent5"`
+       Accent6  xlsxCTColor `xml:"a:accent6"`
+       Hlink    xlsxCTColor `xml:"a:hlink"`
+       FolHlink xlsxCTColor `xml:"a:folHlink"`
+       ExtLst   *xlsxExtLst `xml:"a:extLst"`
 }
 
 // objectDefaults element allows for the definition of default shape, line,
@@ -95,11 +95,11 @@ type xlsxCTSupplementalFont struct {
 // Asian, and complex script. On top of these three definitions, one can also
 // define a font for use in a specific language or languages.
 type xlsxFontCollection struct {
-       Latin  *xlsxCTTextFont          `xml:"latin"`
-       Ea     *xlsxCTTextFont          `xml:"ea"`
-       Cs     *xlsxCTTextFont          `xml:"cs"`
-       Font   []xlsxCTSupplementalFont `xml:"font"`
-       ExtLst *xlsxExtLst              `xml:"extLst"`
+       Latin  *xlsxCTTextFont          `xml:"a:latin"`
+       Ea     *xlsxCTTextFont          `xml:"a:ea"`
+       Cs     *xlsxCTTextFont          `xml:"a:cs"`
+       Font   []xlsxCTSupplementalFont `xml:"a:font"`
+       ExtLst *xlsxExtLst              `xml:"a:extLst"`
 }
 
 // xlsxFontScheme element defines the font scheme within the theme. The font
@@ -109,9 +109,9 @@ type xlsxFontCollection struct {
 // paragraph areas.
 type xlsxFontScheme struct {
        Name      string             `xml:"name,attr"`
-       MajorFont xlsxFontCollection `xml:"majorFont"`
-       MinorFont xlsxFontCollection `xml:"minorFont"`
-       ExtLst    *xlsxExtLst        `xml:"extLst"`
+       MajorFont xlsxFontCollection `xml:"a:majorFont"`
+       MinorFont xlsxFontCollection `xml:"a:minorFont"`
+       ExtLst    *xlsxExtLst        `xml:"a:extLst"`
 }
 
 // xlsxStyleMatrix defines a set of formatting options, which can be referenced
@@ -121,10 +121,10 @@ type xlsxFontScheme struct {
 // change when the theme is changed.
 type xlsxStyleMatrix struct {
        Name           string             `xml:"name,attr,omitempty"`
-       FillStyleLst   xlsxFillStyleLst   `xml:"fillStyleLst"`
-       LnStyleLst     xlsxLnStyleLst     `xml:"lnStyleLst"`
-       EffectStyleLst xlsxEffectStyleLst `xml:"effectStyleLst"`
-       BgFillStyleLst xlsxBgFillStyleLst `xml:"bgFillStyleLst"`
+       FillStyleLst   xlsxFillStyleLst   `xml:"a:fillStyleLst"`
+       LnStyleLst     xlsxLnStyleLst     `xml:"a:lnStyleLst"`
+       EffectStyleLst xlsxEffectStyleLst `xml:"a:effectStyleLst"`
+       BgFillStyleLst xlsxBgFillStyleLst `xml:"a:bgFillStyleLst"`
 }
 
 // xlsxFillStyleLst element defines a set of three fill styles that are used
@@ -161,3 +161,85 @@ type xlsxSysClr struct {
        Val     string `xml:"val,attr"`
        LastClr string `xml:"lastClr,attr"`
 }
+
+// decodeTheme defines the structure used to parse the a:theme element for the
+// theme.
+type decodeTheme struct {
+       XMLName           xml.Name              `xml:"http://schemas.openxmlformats.org/drawingml/2006/main theme"`
+       Name              string                `xml:"name,attr"`
+       ThemeElements     decodeBaseStyles      `xml:"themeElements"`
+       ObjectDefaults    xlsxObjectDefaults    `xml:"objectDefaults"`
+       ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"extraClrSchemeLst"`
+       CustClrLst        *xlsxInnerXML         `xml:"custClrLst"`
+       ExtLst            *xlsxExtLst           `xml:"extLst"`
+}
+
+// decodeBaseStyles defines the structure used to parse the theme elements for a
+// theme, and is the workhorse of the theme.
+type decodeBaseStyles struct {
+       ClrScheme  decodeColorScheme `xml:"clrScheme"`
+       FontScheme decodeFontScheme  `xml:"fontScheme"`
+       FmtScheme  decodeStyleMatrix `xml:"fmtScheme"`
+       ExtLst     *xlsxExtLst       `xml:"extLst"`
+}
+
+// decodeColorScheme defines the structure used to parse a set of colors for the
+// theme.
+type decodeColorScheme struct {
+       Name     string        `xml:"name,attr"`
+       Dk1      decodeCTColor `xml:"dk1"`
+       Lt1      decodeCTColor `xml:"lt1"`
+       Dk2      decodeCTColor `xml:"dk2"`
+       Lt2      decodeCTColor `xml:"lt2"`
+       Accent1  decodeCTColor `xml:"accent1"`
+       Accent2  decodeCTColor `xml:"accent2"`
+       Accent3  decodeCTColor `xml:"accent3"`
+       Accent4  decodeCTColor `xml:"accent4"`
+       Accent5  decodeCTColor `xml:"accent5"`
+       Accent6  decodeCTColor `xml:"accent6"`
+       Hlink    decodeCTColor `xml:"hlink"`
+       FolHlink decodeCTColor `xml:"folHlink"`
+       ExtLst   *xlsxExtLst   `xml:"extLst"`
+}
+
+// decodeFontScheme defines the structure used to parse font scheme within the
+// theme.
+type decodeFontScheme struct {
+       Name      string               `xml:"name,attr"`
+       MajorFont decodeFontCollection `xml:"majorFont"`
+       MinorFont decodeFontCollection `xml:"minorFont"`
+       ExtLst    *xlsxExtLst          `xml:"extLst"`
+}
+
+// decodeFontCollection defines the structure used to parse a major and minor
+// font which is used in the font scheme.
+type decodeFontCollection struct {
+       Latin  *xlsxCTTextFont          `xml:"latin"`
+       Ea     *xlsxCTTextFont          `xml:"ea"`
+       Cs     *xlsxCTTextFont          `xml:"cs"`
+       Font   []xlsxCTSupplementalFont `xml:"font"`
+       ExtLst *xlsxExtLst              `xml:"extLst"`
+}
+
+// decodeCTColor defines the structure used to parse the actual color values
+// that are to be applied to a given diagram and how those colors are to be
+// applied.
+type decodeCTColor struct {
+       ScrgbClr  *xlsxInnerXML  `xml:"scrgbClr"`
+       SrgbClr   *attrValString `xml:"srgbClr"`
+       HslClr    *xlsxInnerXML  `xml:"hslClr"`
+       SysClr    *xlsxSysClr    `xml:"sysClr"`
+       SchemeClr *xlsxInnerXML  `xml:"schemeClr"`
+       PrstClr   *xlsxInnerXML  `xml:"prstClr"`
+}
+
+// decodeStyleMatrix defines the structure used to parse a set of formatting
+// options, which can be referenced by documents that apply a certain style to
+// a given part of an object.
+type decodeStyleMatrix struct {
+       Name           string             `xml:"name,attr,omitempty"`
+       FillStyleLst   xlsxFillStyleLst   `xml:"fillStyleLst"`
+       LnStyleLst     xlsxLnStyleLst     `xml:"lnStyleLst"`
+       EffectStyleLst xlsxEffectStyleLst `xml:"effectStyleLst"`
+       BgFillStyleLst xlsxBgFillStyleLst `xml:"bgFillStyleLst"`
+}