已验证 提交 c922c32f 编写于 作者: xurime's avatar xurime

support parse and generate XML element namespace dynamic, fix #651

上级 820a314c
......@@ -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,
......
......@@ -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
}
......@@ -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
}
......
......@@ -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
......
......@@ -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"
......
......@@ -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),
}
......@@ -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")},
......@@ -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),
},
},
......@@ -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),
},
},
......
......@@ -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
......@@ -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),
......@@ -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)
......@@ -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
......
......@@ -15,6 +15,7 @@ import (
"archive/zip"
"bytes"
"container/list"
"encoding/xml"
"fmt"
"io"
"strconv"
......@@ -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)
......@@ -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
......
......@@ -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
}
......@@ -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"
......
......@@ -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
......@@ -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
......@@ -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
......
......@@ -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 {
......
......@@ -92,15 +92,18 @@ func (f *File) contentTypesWriter() {
// structure after deserialization.
func (f *File) workbookReader() *xlsxWorkbook {
var err error
if f.WorkBook == nil {
f.WorkBook = new(xlsxWorkbook)
if _, ok := f.xmlAttr["xl/workbook.xml"]; !ok {
d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/workbook.xml"))))
f.xmlAttr["xl/workbook.xml"] = append(f.xmlAttr["xl/workbook.xml"], getRootElement(d)...)
f.addNameSpaces("xl/workbook.xml", SourceRelationship)
}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/workbook.xml")))).
Decode(f.WorkBook); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
}
}
return f.WorkBook
}
......@@ -109,7 +112,7 @@ func (f *File) workbookReader() *xlsxWorkbook {
func (f *File) workBookWriter() {
if f.WorkBook != nil {
output, _ := xml.Marshal(f.WorkBook)
f.saveFileList("xl/workbook.xml", replaceRelationshipsBytes(replaceRelationshipsNameSpaceBytes(output)))
f.saveFileList("xl/workbook.xml", replaceRelationshipsBytes(f.replaceNameSpaceBytes("xl/workbook.xml", output)))
}
}
......@@ -122,7 +125,7 @@ func (f *File) workSheetWriter() {
f.Sheet[p].SheetData.Row[k].C = trimCell(v.C)
}
output, _ := xml.Marshal(sheet)
f.saveFileList(p, replaceRelationshipsBytes(replaceRelationshipsNameSpaceBytes(output)))
f.saveFileList(p, replaceRelationshipsBytes(f.replaceNameSpaceBytes(p, output)))
ok := f.checked[p]
if ok {
delete(f.Sheet, p)
......@@ -173,6 +176,7 @@ func (f *File) setSheet(index int, name string) {
path := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml"
f.sheetMap[trimSheetName(name)] = path
f.Sheet[path] = &xlsx
f.xmlAttr[path] = append(f.xmlAttr[path], NameSpaceSpreadSheet)
}
// setWorkbook update workbook property of the spreadsheet. Maximum 31
......@@ -193,7 +197,7 @@ func (f *File) relsWriter() {
if rel != nil {
output, _ := xml.Marshal(rel)
if strings.HasPrefix(path, "xl/worksheets/sheet/rels/sheet") {
output = replaceRelationshipsNameSpaceBytes(output)
output = f.replaceNameSpaceBytes(path, output)
}
f.saveFileList(path, replaceRelationshipsBytes(output))
}
......@@ -440,6 +444,7 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "")
f.addSheetPicture(sheet, rID)
f.addSheetNameSpace(sheet, SourceRelationship)
f.setContentTypePartImageExtensions()
return err
}
......@@ -479,6 +484,7 @@ func (f *File) DeleteSheet(name string) {
delete(f.XLSX, rels)
delete(f.Relationships, rels)
delete(f.Sheet, sheetXML)
delete(f.xmlAttr, sheetXML)
f.SheetCount--
}
}
......@@ -557,6 +563,9 @@ func (f *File) copySheet(from, to int) error {
if ok {
f.XLSX[toRels] = f.XLSX[fromRels]
}
fromSheetXMLPath, _ := f.sheetMap[trimSheetName(fromSheet)]
fromSheetAttr, _ := f.xmlAttr[fromSheetXMLPath]
f.xmlAttr[path] = fromSheetAttr
return err
}
......@@ -779,7 +788,7 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, 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))
}
return f.searchSheet(name, value, regSearch)
}
......
......@@ -455,7 +455,7 @@ func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) {
}
ws.ExtLst.Ext = string(extBytes)
}
f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
return
}
......
......@@ -151,7 +151,7 @@ func (sw *StreamWriter) AddTable(hcell, vcell, format string) error {
}
table := xlsxTable{
XMLNS: NameSpaceSpreadSheet,
XMLNS: NameSpaceSpreadSheet.Value,
ID: tableID,
Name: name,
DisplayName: name,
......
......@@ -1022,7 +1022,7 @@ func (f *File) stylesReader() *xlsxStyleSheet {
func (f *File) styleSheetWriter() {
if f.Styles != nil {
output, _ := xml.Marshal(f.Styles)
f.saveFileList("xl/styles.xml", replaceRelationshipsNameSpaceBytes(output))
f.saveFileList("xl/styles.xml", f.replaceNameSpaceBytes("xl/styles.xml", output))
}
}
......@@ -1031,7 +1031,7 @@ func (f *File) styleSheetWriter() {
func (f *File) sharedStringsWriter() {
if f.SharedStrings != nil {
output, _ := xml.Marshal(f.SharedStrings)
f.saveFileList("xl/sharedStrings.xml", replaceRelationshipsNameSpaceBytes(output))
f.saveFileList("xl/sharedStrings.xml", f.replaceNameSpaceBytes("xl/sharedStrings.xml", output))
}
}
......
......@@ -83,9 +83,11 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error {
// Add first table for given sheet.
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
f.addSheetTable(sheet, rID)
err = f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet)
if err != nil {
if err = f.addSheetTable(sheet, rID); err != nil {
return err
}
f.addSheetNameSpace(sheet, SourceRelationship)
if err = f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet); err != nil {
return err
}
f.addContentTypePart(tableID, "table")
......@@ -106,16 +108,20 @@ func (f *File) countTables() int {
// addSheetTable provides a function to add tablePart element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetTable(sheet string, rID int) {
xlsx, _ := f.workSheetReader(sheet)
func (f *File) addSheetTable(sheet string, rID int) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
table := &xlsxTablePart{
RID: "rId" + strconv.Itoa(rID),
}
if xlsx.TableParts == nil {
xlsx.TableParts = &xlsxTableParts{}
if ws.TableParts == nil {
ws.TableParts = &xlsxTableParts{}
}
xlsx.TableParts.Count++
xlsx.TableParts.TableParts = append(xlsx.TableParts.TableParts, table)
ws.TableParts.Count++
ws.TableParts.TableParts = append(ws.TableParts.TableParts, table)
return err
}
// addTable provides a function to add table by given worksheet name,
......@@ -159,7 +165,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
name = "Table" + strconv.Itoa(i)
}
t := xlsxTable{
XMLNS: NameSpaceSpreadSheet,
XMLNS: NameSpaceSpreadSheet.Value,
ID: i,
Name: name,
DisplayName: name,
......
......@@ -13,9 +13,15 @@ package excelize
import "encoding/xml"
// Source relationship and namespace.
var (
SourceRelationship = xml.Attr{Name: xml.Name{Local: "r", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"}
NameSpaceSpreadSheet = xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
NameSpaceSpreadSheetX14 = xml.Attr{Name: xml.Name{Local: "x14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"}
)
// Source relationship and namespace.
const (
SourceRelationship = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
SourceRelationshipComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
......@@ -37,8 +43,6 @@ const (
NameSpaceDrawingML = "http://schemas.openxmlformats.org/drawingml/2006/main"
NameSpaceDrawingMLChart = "http://schemas.openxmlformats.org/drawingml/2006/chart"
NameSpaceDrawingMLSpreadSheet = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
NameSpaceSpreadSheet = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
NameSpaceSpreadSheetX14 = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
NameSpaceSpreadSheetX15 = "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"
NameSpaceSpreadSheetExcel2006Main = "http://schemas.microsoft.com/office/excel/2006/main"
NameSpaceMacExcel2008Main = "http://schemas.microsoft.com/office/mac/excel/2008/main"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册