sheet.go 7.4 KB
Newer Older
xurime's avatar
xurime 已提交
1 2 3 4 5 6 7 8 9
package excelize

import (
	"encoding/xml"
	"fmt"
	"strconv"
	"strings"
)

xurime's avatar
xurime 已提交
10 11 12
// NewSheet provice function to greate a new sheet by given index, when
//  creating a new XLSX file, the default sheet will be create, when you
//  create a new file, you need to ensure that the index is continuous.
xurime's avatar
xurime 已提交
13 14
func NewSheet(file []FileList, index int, name string) []FileList {
	// Update docProps/app.xml
xurime's avatar
xurime 已提交
15
	file = setAppXML(file)
xurime's avatar
xurime 已提交
16 17 18 19 20 21 22 23 24 25 26 27 28 29
	// Update [Content_Types].xml
	file = setContentTypes(file, index)
	// Create new sheet /xl/worksheets/sheet%d.xml
	file = setSheet(file, index)
	// Update xl/_rels/workbook.xml.rels
	file = addXlsxWorkbookRels(file, index)
	// Update xl/workbook.xml
	file = setWorkbook(file, index, name)
	return file
}

// Read and update property of contents type of XLSX
func setContentTypes(file []FileList, index int) []FileList {
	var content xlsxTypes
xurime's avatar
xurime 已提交
30
	xml.Unmarshal([]byte(readXML(file, `[Content_Types].xml`)), &content)
xurime's avatar
xurime 已提交
31
	content.Overrides = append(content.Overrides, xlsxOverride{
xurime's avatar
xurime 已提交
32
		PartName:    `/xl/worksheets/sheet` + strconv.Itoa(index) + `.xml`,
xurime's avatar
xurime 已提交
33 34 35 36 37 38 39 40 41 42 43 44 45 46
		ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
	})
	output, err := xml.MarshalIndent(content, "", "")
	if err != nil {
		fmt.Println(err)
	}
	return saveFileList(file, `[Content_Types].xml`, string(output))
}

// Update sheet property by given index
func setSheet(file []FileList, index int) []FileList {
	var xlsx xlsxWorksheet
	xlsx.Dimension.Ref = "A1"
	xlsx.SheetViews.SheetView = append(xlsx.SheetViews.SheetView, xlsxSheetView{
xurime's avatar
xurime 已提交
47
		WorkbookViewID: 0,
xurime's avatar
xurime 已提交
48 49 50 51 52
	})
	output, err := xml.MarshalIndent(xlsx, "", "")
	if err != nil {
		fmt.Println(err)
	}
xurime's avatar
xurime 已提交
53
	path := `xl/worksheets/sheet` + strconv.Itoa(index) + `.xml`
xurime's avatar
xurime 已提交
54 55 56 57 58 59
	return saveFileList(file, path, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output))))
}

// Update workbook property of XLSX
func setWorkbook(file []FileList, index int, name string) []FileList {
	var content xlsxWorkbook
xurime's avatar
xurime 已提交
60
	xml.Unmarshal([]byte(readXML(file, `xl/workbook.xml`)), &content)
xurime's avatar
xurime 已提交
61 62

	rels := readXlsxWorkbookRels(file)
xurime's avatar
xurime 已提交
63
	rID := len(rels.Relationships)
xurime's avatar
xurime 已提交
64 65
	content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{
		Name:    name,
xurime's avatar
xurime 已提交
66 67
		SheetID: strconv.Itoa(index),
		ID:      "rId" + strconv.Itoa(rID),
xurime's avatar
xurime 已提交
68 69 70 71 72 73 74 75 76 77 78
	})
	output, err := xml.MarshalIndent(content, "", "")
	if err != nil {
		fmt.Println(err)
	}
	return saveFileList(file, `xl/workbook.xml`, replaceRelationshipsNameSpace(string(output)))
}

// Read and unmarshal workbook relationships of XLSX
func readXlsxWorkbookRels(file []FileList) xlsxWorkbookRels {
	var content xlsxWorkbookRels
xurime's avatar
xurime 已提交
79
	xml.Unmarshal([]byte(readXML(file, `xl/_rels/workbook.xml.rels`)), &content)
xurime's avatar
xurime 已提交
80 81 82 83 84 85
	return content
}

// Update workbook relationships property of XLSX
func addXlsxWorkbookRels(file []FileList, sheet int) []FileList {
	content := readXlsxWorkbookRels(file)
xurime's avatar
xurime 已提交
86
	rID := len(content.Relationships) + 1
xurime's avatar
xurime 已提交
87
	content.Relationships = append(content.Relationships, xlsxWorkbookRelation{
xurime's avatar
xurime 已提交
88
		ID:     "rId" + strconv.Itoa(rID),
xurime's avatar
xurime 已提交
89
		Target: `worksheets/sheet` + strconv.Itoa(sheet) + `.xml`,
xurime's avatar
xurime 已提交
90 91 92 93 94 95 96 97 98 99
		Type:   "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
	})
	output, err := xml.MarshalIndent(content, "", "")
	if err != nil {
		fmt.Println(err)
	}
	return saveFileList(file, `xl/_rels/workbook.xml.rels`, string(output))
}

// Update docProps/app.xml file of XML
xurime's avatar
xurime 已提交
100 101
func setAppXML(file []FileList) []FileList {
	return saveFileList(file, `docProps/app.xml`, templateDocpropsApp)
xurime's avatar
xurime 已提交
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
}

// Some tools that read XLSX files have very strict requirements about
// the structure of the input XML.  In particular both Numbers on the Mac
// and SAS dislike inline XML namespace declarations, or namespace
// prefixes that don't match the ones that Excel itself uses.  This is a
// problem because the Go XML library doesn't multiple namespace
// declarations in a single element of a document.  This function is a
// horrible hack to fix that after the XML marshalling is completed.
func replaceRelationshipsNameSpace(workbookMarshal string) string {
	// newWorkbook := strings.Replace(workbookMarshal, `xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id`, `r:id`, -1)
	// Dirty hack to fix issues #63 and #91; encoding/xml currently
	// "doesn't allow for additional namespaces to be defined in the
	// root element of the document," as described by @tealeg in the
	// comments for #63.
	oldXmlns := `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
	newXmlns := `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">`
	return strings.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
}

// replace relationships ID in worksheets/sheet%d.xml
func replaceRelationshipsID(workbookMarshal string) string {
	rids := strings.Replace(workbookMarshal, `<drawing rid="" />`, ``, -1)
	return strings.Replace(rids, `<drawing rid="`, `<drawing r:id="`, -1)
}

xurime's avatar
xurime 已提交
128
// SetActiveSheet provide function to set default active sheet of XLSX by given index
xurime's avatar
xurime 已提交
129 130 131 132 133
func SetActiveSheet(file []FileList, index int) []FileList {
	var content xlsxWorkbook
	if index < 1 {
		index = 1
	}
xurime's avatar
xurime 已提交
134 135
	index--
	xml.Unmarshal([]byte(readXML(file, `xl/workbook.xml`)), &content)
xurime's avatar
xurime 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148
	if len(content.BookViews.WorkBookView) > 0 {
		content.BookViews.WorkBookView[0].ActiveTab = index
	} else {
		content.BookViews.WorkBookView = append(content.BookViews.WorkBookView, xlsxWorkBookView{
			ActiveTab: index,
		})
	}
	sheets := len(content.Sheets.Sheet)
	output, err := xml.MarshalIndent(content, "", "")
	if err != nil {
		fmt.Println(err)
	}
	file = saveFileList(file, `xl/workbook.xml`, workBookCompatibility(replaceRelationshipsNameSpace(string(output))))
xurime's avatar
xurime 已提交
149
	index++
xurime's avatar
xurime 已提交
150 151 152
	for i := 0; i < sheets; i++ {
		xlsx := xlsxWorksheet{}
		sheetIndex := i + 1
xurime's avatar
xurime 已提交
153
		path := `xl/worksheets/sheet` + strconv.Itoa(sheetIndex) + `.xml`
xurime's avatar
xurime 已提交
154
		xml.Unmarshal([]byte(readXML(file, path)), &xlsx)
xurime's avatar
xurime 已提交
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
		if index == sheetIndex {
			if len(xlsx.SheetViews.SheetView) > 0 {
				xlsx.SheetViews.SheetView[0].TabSelected = true
			} else {
				xlsx.SheetViews.SheetView = append(xlsx.SheetViews.SheetView, xlsxSheetView{
					TabSelected: true,
				})
			}
		} else {
			if len(xlsx.SheetViews.SheetView) > 0 {
				xlsx.SheetViews.SheetView[0].TabSelected = false
			}
		}
		sheet, err := xml.MarshalIndent(xlsx, "", "")
		if err != nil {
			fmt.Println(err)
		}
		file = saveFileList(file, path, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(sheet))))
	}
	return file
}

// Replace xl/workbook.xml XML tags to self-closing for compatible Office Excel 2007
func workBookCompatibility(workbookMarshal string) string {
	workbookMarshal = strings.Replace(workbookMarshal, `xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="`, `r:id="`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></sheet>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></workbookView>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></fileVersion>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></workbookPr>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></definedNames>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></calcPr>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></workbookProtection>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></fileRecoveryPr>`, ` />`, -1)
	return workbookMarshal
}