Skip to content

Commit ab0b6f1

Browse files
author
mengzhongyuan
committed
This close qax-os#2146, support setting excel custom properties (properties in DocProps/custom.xml)
- support save some biz k:v data to excel custom properties to do such that save excel biz version etc. Change-Id: I4a524b494f09fbfca321d515b5029f4f7361311f Signed-off-by: mengzhongyuan <[email protected]>
1 parent 5bd9647 commit ab0b6f1

File tree

5 files changed

+144
-2
lines changed

5 files changed

+144
-2
lines changed

docProps.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"encoding/xml"
1717
"io"
1818
"reflect"
19+
"strconv"
1920
)
2021

2122
// SetAppProps provides a function to set document application properties. The
@@ -206,6 +207,80 @@ func (f *File) SetDocProps(docProperties *DocProperties) error {
206207
return err
207208
}
208209

210+
const (
211+
customPropertiesFMTIDMagicNumber = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"
212+
)
213+
214+
// SetDocCustomProps provides a function to set excel custom properties.
215+
func (f *File) SetDocCustomProps(name string, value string) error {
216+
customProps := new(xlsxCustomProperties)
217+
218+
// find existing custom properties
219+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCustom)))).
220+
Decode(customProps); err != nil && err != io.EOF {
221+
return err
222+
}
223+
224+
props := customProps.Props
225+
existingPropertyMap := make(map[string]xlsxCustomProperty)
226+
maxPID := 1 // pid from 2
227+
for _, prop := range props {
228+
pid, err := strconv.Atoi(prop.PID)
229+
if err == nil && pid > maxPID {
230+
maxPID = pid
231+
}
232+
233+
existingPropertyMap[prop.Name] = prop
234+
}
235+
236+
// update existing custom properties
237+
if existingProperty, ok := existingPropertyMap[name]; ok {
238+
existingProperty.V = value
239+
} else {
240+
// add new custom property
241+
newProperty := xlsxCustomProperty{
242+
FmtID: customPropertiesFMTIDMagicNumber,
243+
PID: strconv.FormatInt(int64(maxPID+1), 10), // max pid plus 1 to create a new unique pid
244+
Name: name,
245+
V: value,
246+
}
247+
248+
props = append(props, newProperty)
249+
}
250+
251+
newCustomProps := &xlsxCustomProperties{
252+
Vt: NameSpaceDocumentPropertiesVariantTypes.Value,
253+
Props: props,
254+
}
255+
256+
output, err := xml.Marshal(newCustomProps)
257+
// f.saveFileList(defaultXMLPathDocPropsCustom, output)
258+
f.Pkg.Store(defaultXMLPathDocPropsCustom, append([]byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`+"\n"), output...))
259+
260+
return err
261+
}
262+
263+
// GetDocCustomProps provides a function to get document custom properties.
264+
func (f *File) GetDocCustomProps() (kv map[string]string, err error) {
265+
custom := new(xlsxCustomProperties)
266+
267+
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCustom)))).
268+
Decode(custom); err != nil && err != io.EOF {
269+
return
270+
}
271+
272+
kv = make(map[string]string)
273+
if custom == nil || len(custom.Props) == 0 {
274+
return kv, nil
275+
}
276+
277+
for _, prop := range custom.Props {
278+
kv[prop.Name] = prop.V
279+
}
280+
281+
return kv, nil
282+
}
283+
209284
// GetDocProps provides a function to get document core properties.
210285
func (f *File) GetDocProps() (ret *DocProperties, err error) {
211286
core := new(decodeCoreProperties)

docProps_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,26 @@ func TestSetDocProps(t *testing.T) {
9797
assert.EqualError(t, f.SetDocProps(&DocProperties{}), "XML syntax error on line 1: invalid UTF-8")
9898
}
9999

100+
func TestSetDocCustomProps(t *testing.T) {
101+
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
102+
if !assert.NoError(t, err) {
103+
t.FailNow()
104+
}
105+
106+
assert.NoError(t, f.SetDocCustomProps("version", "v1.0.0"))
107+
assert.NoError(t, f.SetDocCustomProps("user", "root"))
108+
assert.NoError(t, f.SetDocCustomProps("user", "Microsoft Office User"))
109+
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocCustomProps.xlsx")))
110+
f.Pkg.Store(defaultXMLPathDocPropsCustom, nil)
111+
assert.NoError(t, f.SetDocCustomProps("version", ""))
112+
assert.NoError(t, f.Close())
113+
114+
// Test unsupported charset
115+
f = NewFile()
116+
f.Pkg.Store(defaultXMLPathDocPropsCustom, MacintoshCyrillicCharset)
117+
assert.EqualError(t, f.SetDocCustomProps("version", ""), "XML syntax error on line 1: invalid UTF-8")
118+
}
119+
100120
func TestGetDocProps(t *testing.T) {
101121
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
102122
if !assert.NoError(t, err) {
@@ -116,3 +136,27 @@ func TestGetDocProps(t *testing.T) {
116136
_, err = f.GetDocProps()
117137
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
118138
}
139+
140+
func TestFile_GetDocCustomProps(t *testing.T) {
141+
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
142+
if !assert.NoError(t, err) {
143+
t.FailNow()
144+
}
145+
146+
// now no custom properties in f
147+
props, err := f.GetDocCustomProps()
148+
assert.NoError(t, err)
149+
assert.Empty(t, props)
150+
151+
assert.NoError(t, f.SetDocCustomProps("version", "v1.0.0"))
152+
153+
props, err = f.GetDocCustomProps()
154+
assert.NoError(t, err)
155+
assert.Equal(t, "v1.0.0", props["version"])
156+
157+
// Test get workbook properties with unsupported charset
158+
f = NewFile()
159+
f.Pkg.Store(defaultXMLPathDocPropsCustom, MacintoshCyrillicCharset)
160+
_, err = f.GetDocCustomProps()
161+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
162+
}

file.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func NewFile(opts ...Options) *File {
3434
f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels))
3535
f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp))
3636
f.Pkg.Store(defaultXMLPathDocPropsCore, []byte(xml.Header+templateDocpropsCore))
37+
f.Pkg.Store(defaultXMLPathDocPropsCustom, []byte(xml.Header+templateDocpropsCustom))
3738
f.Pkg.Store(defaultXMLPathWorkbookRels, []byte(xml.Header+templateWorkbookRels))
3839
f.Pkg.Store("xl/theme/theme1.xml", []byte(xml.Header+templateTheme))
3940
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+templateSheet))

templates.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ const (
277277
defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
278278
defaultXMLPathContentTypes = "[Content_Types].xml"
279279
defaultXMLPathDocPropsApp = "docProps/app.xml"
280+
defaultXMLPathDocPropsCustom = "docProps/custom.xml"
280281
defaultXMLPathDocPropsCore = "docProps/core.xml"
281282
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
282283
defaultXMLPathStyles = "xl/styles.xml"
@@ -519,7 +520,9 @@ var builtInDefinedNames = []string{"_xlnm.Print_Area", "_xlnm.Print_Titles", "_x
519520

520521
const templateDocpropsApp = `<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"><TotalTime>0</TotalTime><Application>Go Excelize</Application></Properties>`
521522

522-
const templateContentTypes = `<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/></Types>`
523+
const templateDocpropsCustom = `<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"></Properties>`
524+
525+
const templateContentTypes = `<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/><Override PartName="/docProps/custom.xml" ContentType="application/vnd.openxmlformats-officedocument.custom-properties+xml"/></Types>`
523526

524527
const templateWorkbook = `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x15" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"><fileVersion appName="xl" lastEdited="6" lowestEdited="6" rupBuild="14420" /><workbookPr filterPrivacy="1" defaultThemeVersion="164011" /><bookViews><workbookView xWindow="0" yWindow="0" windowWidth="14805" windowHeight="8010" /></bookViews><sheets><sheet name="Sheet1" sheetId="1" r:id="rId1" /></sheets><calcPr calcId="122211" /></workbook>`
525528

@@ -531,7 +534,7 @@ const templateWorkbookRels = `<Relationships xmlns="http://schemas.openxmlformat
531534

532535
const templateDocpropsCore = `<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:creator>xuri</dc:creator><dcterms:created xsi:type="dcterms:W3CDTF">2006-09-16T00:00:00Z</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">2006-09-16T00:00:00Z</dcterms:modified></cp:coreProperties>`
533536

534-
const templateRels = `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`
537+
const templateRels = `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/><Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties" Target="docProps/custom.xml"/></Relationships>`
535538

536539
const templateTheme = `<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="44546A"/></a:dk2><a:lt2><a:srgbClr val="E7E6E6"/></a:lt2><a:accent1><a:srgbClr val="5B9BD5"/></a:accent1><a:accent2><a:srgbClr val="ED7D31"/></a:accent2><a:accent3><a:srgbClr val="A5A5A5"/></a:accent3><a:accent4><a:srgbClr val="FFC000"/></a:accent4><a:accent5><a:srgbClr val="4472C4"/></a:accent5><a:accent6><a:srgbClr val="70AD47"/></a:accent6><a:hlink><a:srgbClr val="0563C1"/></a:hlink><a:folHlink><a:srgbClr val="954F72"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin typeface="Calibri Light" panose="020F0302020204030204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/></a:majorFont><a:minorFont><a:latin typeface="Calibri" panose="020F0502020204030204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/></a:theme>`
537540

xmlCustom.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package excelize
2+
3+
import (
4+
"encoding/xml"
5+
)
6+
7+
// xlsxCustomProperties is the root element of the Custom File Properties part
8+
type xlsxCustomProperties struct {
9+
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/custom-properties Properties"`
10+
Vt string `xml:"xmlns:vt,attr"`
11+
Props []xlsxCustomProperty `xml:"property"`
12+
}
13+
14+
type xlsxCustomProperty struct {
15+
FmtID string `xml:"fmtid,attr"`
16+
PID string `xml:"pid,attr"`
17+
Name string `xml:"name,attr"`
18+
V string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes lpwstr"`
19+
}

0 commit comments

Comments
 (0)