Skip to content

Commit

Permalink
support parse and generate XML element namespace dynamic, fix qax-os#651
Browse files Browse the repository at this point in the history
  • Loading branch information
xuri committed Jul 18, 2020
1 parent a50e8aa commit dd29d3d
Show file tree
Hide file tree
Showing 16 changed files with 171 additions and 45 deletions.
1 change: 1 addition & 0 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) error {
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipHyperLink, link, linkType)
linkData.RID = "rId" + strconv.Itoa(rID)
f.addSheetNameSpace(sheet, SourceRelationship)
case "Location":
linkData = xlsxHyperlink{
Ref: axis,
Expand Down
4 changes: 3 additions & 1 deletion chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ func (f *File) AddChart(sheet, cell, format string, combo ...string) error {
f.addChart(formatSet, comboCharts)
f.addContentTypePart(chartID, "chart")
f.addContentTypePart(drawingID, "drawings")
f.addSheetNameSpace(sheet, SourceRelationship)
return err
}

Expand Down Expand Up @@ -804,7 +805,8 @@ func (f *File) AddChartSheet(sheet, format string, combo ...string) error {
// Update xl/workbook.xml
f.setWorkbook(sheet, sheetID, rID)
chartsheet, _ := xml.Marshal(cs)
f.saveFileList(path, replaceRelationshipsBytes(replaceRelationshipsNameSpaceBytes(chartsheet)))
f.addSheetNameSpace(sheet, NameSpaceSpreadSheet)
f.saveFileList(path, replaceRelationshipsBytes(f.replaceNameSpaceBytes(path, chartsheet)))
return err
}

Expand Down
2 changes: 1 addition & 1 deletion col.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (f *File) Cols(sheet string) (*Cols, error) {
}
if f.Sheet[name] != nil {
output, _ := xml.Marshal(f.Sheet[name])
f.saveFileList(name, replaceRelationshipsNameSpaceBytes(output))
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
}
var (
inElement string
Expand Down
1 change: 1 addition & 0 deletions comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (f *File) AddComment(sheet, cell, format string) error {
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
f.addSheetNameSpace(sheet, SourceRelationship)
f.addSheetLegacyDrawing(sheet, rID)
}
commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
Expand Down
7 changes: 4 additions & 3 deletions drawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func (f *File) prepareChartSheetDrawing(xlsx *xlsxChartsheet, drawingID int, she
// Only allow one chart in a chartsheet.
sheetRels := "xl/chartsheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/chartsheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
f.addSheetNameSpace(sheet, SourceRelationship)
xlsx.Drawing = &xlsxDrawing{
RID: "rId" + strconv.Itoa(rID),
}
Expand All @@ -60,7 +61,7 @@ func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) {
xlsxChartSpace := xlsxChartSpace{
XMLNSc: NameSpaceDrawingMLChart,
XMLNSa: NameSpaceDrawingML,
XMLNSr: SourceRelationship,
XMLNSr: SourceRelationship.Value,
XMLNSc16r2: SourceRelationshipChart201506,
Date1904: &attrValBool{Val: boolPtr(false)},
Lang: &attrValString{Val: stringPtr("en-US")},
Expand Down Expand Up @@ -1212,7 +1213,7 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
URI: NameSpaceDrawingMLChart,
Chart: &xlsxChart{
C: NameSpaceDrawingMLChart,
R: SourceRelationship,
R: SourceRelationship.Value,
RID: "rId" + strconv.Itoa(rID),
},
},
Expand Down Expand Up @@ -1252,7 +1253,7 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, formatSet *forma
URI: NameSpaceDrawingMLChart,
Chart: &xlsxChart{
C: NameSpaceDrawingMLChart,
R: SourceRelationship,
R: SourceRelationship.Value,
RID: "rId" + strconv.Itoa(rID),
},
},
Expand Down
14 changes: 6 additions & 8 deletions excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

// File define a populated spreadsheet file struct.
type File struct {
xmlAttr map[string][]xml.Attr
checked map[string]bool
sheetMap map[string]string
CalcChain *xlsxCalcChain
Expand Down Expand Up @@ -72,6 +73,7 @@ func OpenFile(filename string) (*File, error) {
// newFile is object builder
func newFile() *File {
return &File{
xmlAttr: make(map[string][]xml.Attr),
checked: make(map[string]bool),
sheetMap: make(map[string]string),
Comments: make(map[string]*xlsxComments),
Expand Down Expand Up @@ -166,6 +168,10 @@ func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
return
}
xlsx = new(xlsxWorksheet)
if _, ok := f.xmlAttr[name]; !ok {
d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name))))
f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...)
}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))).
Decode(xlsx); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
Expand Down Expand Up @@ -254,14 +260,6 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int {
return rID
}

// replaceRelationshipsNameSpaceBytes provides a function to replace
// XML tags to self-closing for compatible Microsoft Office Excel 2007.
func replaceRelationshipsNameSpaceBytes(contentMarshal []byte) []byte {
var oldXmlns = stringToBytes(` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
var newXmlns = []byte(templateNamespaceIDMap)
return bytesReplace(contentMarshal, oldXmlns, newXmlns, -1)
}

// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
// Office Excel 2007 and 2010. This function will be remove value tag when met a
// cell have a linked value. Reference
Expand Down
101 changes: 99 additions & 2 deletions lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"archive/zip"
"bytes"
"container/list"
"encoding/xml"
"fmt"
"io"
"strconv"
Expand Down Expand Up @@ -243,11 +244,11 @@ func parseFormatSet(formatSet string) []byte {
// Transitional namespaces.
func namespaceStrictToTransitional(content []byte) []byte {
var namespaceTranslationDic = map[string]string{
StrictSourceRelationship: SourceRelationship,
StrictSourceRelationship: SourceRelationship.Value,
StrictSourceRelationshipChart: SourceRelationshipChart,
StrictSourceRelationshipComments: SourceRelationshipComments,
StrictSourceRelationshipImage: SourceRelationshipImage,
StrictNameSpaceSpreadSheet: NameSpaceSpreadSheet,
StrictNameSpaceSpreadSheet: NameSpaceSpreadSheet.Value,
}
for s, n := range namespaceTranslationDic {
content = bytesReplace(content, stringToBytes(s), stringToBytes(n), -1)
Expand Down Expand Up @@ -319,6 +320,102 @@ func genSheetPasswd(plaintext string) string {
return strings.ToUpper(strconv.FormatInt(password, 16))
}

// getRootElement extract root element attributes by given XML decoder.
func getRootElement(d *xml.Decoder) []xml.Attr {
tokenIdx := 0
for {
token, _ := d.Token()
if token == nil {
break
}
switch startElement := token.(type) {
case xml.StartElement:
tokenIdx++
if tokenIdx == 1 {
return startElement.Attr
}
}
}
return nil
}

// genXMLNamespace generate serialized XML attributes with a multi namespace
// by given element attributes.
func genXMLNamespace(attr []xml.Attr) string {
var rootElement string
for _, v := range attr {
if lastSpace := getXMLNamespace(v.Name.Space, attr); lastSpace != "" {
rootElement += fmt.Sprintf("%s:%s=\"%s\" ", lastSpace, v.Name.Local, v.Value)
continue
}
rootElement += fmt.Sprintf("%s=\"%s\" ", v.Name.Local, v.Value)
}
return strings.TrimSpace(rootElement) + ">"
}

// getXMLNamespace extract XML namespace from specified element name and attributes.
func getXMLNamespace(space string, attr []xml.Attr) string {
for _, attribute := range attr {
if attribute.Value == space {
return attribute.Name.Local
}
}
return space
}

// replaceNameSpaceBytes provides a function to replace the XML root element
// attribute by the given component part path and XML content.
func (f *File) replaceNameSpaceBytes(path string, contentMarshal []byte) []byte {
var oldXmlns = stringToBytes(`xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
var newXmlns = []byte(templateNamespaceIDMap)
if attr, ok := f.xmlAttr[path]; ok {
newXmlns = []byte(genXMLNamespace(attr))
}
return bytesReplace(contentMarshal, oldXmlns, newXmlns, -1)
}

// addNameSpaces provides a function to add a XML attribute by the given
// component part path.
func (f *File) addNameSpaces(path string, ns xml.Attr) {
exist := false
mc := false
ignore := false
if attr, ok := f.xmlAttr[path]; ok {
for _, attribute := range attr {
if attribute.Name.Local == ns.Name.Local && attribute.Name.Space == ns.Name.Space {
exist = true
}
if attribute.Name.Local == "Ignorable" && attribute.Name.Space == "mc" {
ignore = true
}
if attribute.Name.Local == "mc" && attribute.Name.Space == "xmlns" {
mc = true
}
}
}
if !exist {
f.xmlAttr[path] = append(f.xmlAttr[path], ns)
if !mc {
f.xmlAttr[path] = append(f.xmlAttr[path], xml.Attr{
Name: xml.Name{Local: "mc", Space: "xmlns"},
Value: SourceRelationshipCompatibility,
})
}
if !ignore {
f.xmlAttr[path] = append(f.xmlAttr[path], xml.Attr{
Name: xml.Name{Local: "Ignorable", Space: "mc"},
Value: ns.Name.Local,
})
}
}
}

// addSheetNameSpace add XML attribute for worksheet.
func (f *File) addSheetNameSpace(sheet string, ns xml.Attr) {
name, _ := f.sheetMap[trimSheetName(sheet)]
f.addNameSpaces(name, ns)
}

// Stack defined an abstract data type that serves as a collection of elements.
type Stack struct {
list *list.List
Expand Down
5 changes: 3 additions & 2 deletions picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
return err
}
f.addContentTypePart(drawingID, "drawings")
f.addSheetNameSpace(sheet, SourceRelationship)
return err
}

Expand Down Expand Up @@ -279,11 +280,11 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he
pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID)
if hyperlinkRID != 0 {
pic.NvPicPr.CNvPr.HlinkClick = &xlsxHlinkClick{
R: SourceRelationship,
R: SourceRelationship.Value,
RID: "rId" + strconv.Itoa(hyperlinkRID),
}
}
pic.BlipFill.Blip.R = SourceRelationship
pic.BlipFill.Blip.R = SourceRelationship.Value
pic.BlipFill.Blip.Embed = "rId" + strconv.Itoa(rID)
pic.SpPr.PrstGeom.Prst = "rect"

Expand Down
15 changes: 10 additions & 5 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,8 @@ func (rows *Rows) Columns() ([]string, error) {
}
}
blank := cellCol - len(columns)
for i := 1; i < blank; i++ {
columns = append(columns, "")
}
val, _ := colCell.getValueFrom(rows.f, d)
columns = append(columns, val)
columns = append(appendSpace(blank, columns), val)
}
case xml.EndElement:
inElement = startElement.Name.Local
Expand All @@ -137,6 +134,14 @@ func (rows *Rows) Columns() ([]string, error) {
return columns, err
}

// appendSpace append blank characters to slice by given length and source slice.
func appendSpace(l int, s []string) []string {
for i := 1; i < l; i++ {
s = append(s, "")
}
return s
}

// ErrSheetNotExist defines an error of sheet is not exist
type ErrSheetNotExist struct {
SheetName string
Expand Down Expand Up @@ -173,7 +178,7 @@ func (f *File) Rows(sheet string) (*Rows, error) {
if f.Sheet[name] != nil {
// flush data
output, _ := xml.Marshal(f.Sheet[name])
f.saveFileList(name, replaceRelationshipsNameSpaceBytes(output))
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
}
var (
err error
Expand Down
1 change: 1 addition & 0 deletions shape.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ func (f *File) AddShape(sheet, cell, format string) error {
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
f.addSheetDrawing(sheet, rID)
f.addSheetNameSpace(sheet, SourceRelationship)
}
err = f.addDrawingShape(sheet, drawingXML, cell, formatSet)
if err != nil {
Expand Down
Loading

0 comments on commit dd29d3d

Please sign in to comment.