sheet.go 7.3 KB
Newer Older
xurime's avatar
xurime 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
package excelize

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

// Create 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.
func NewSheet(file []FileList, index int, name string) []FileList {
	// Update docProps/app.xml
	file = setAppXml(file)
	// 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
	xml.Unmarshal([]byte(readXml(file, `[Content_Types].xml`)), &content)
	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 47 48 49 50 51 52
		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{
		WorkbookViewId: 0,
	})
	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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
	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
	xml.Unmarshal([]byte(readXml(file, `xl/workbook.xml`)), &content)

	rels := readXlsxWorkbookRels(file)
	rId := len(rels.Relationships)
	content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{
		Name:    name,
		SheetId: strconv.Itoa(index),
		Id:      "rId" + strconv.Itoa(rId),
	})
	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
	xml.Unmarshal([]byte(readXml(file, `xl/_rels/workbook.xml.rels`)), &content)
	return content
}

// Update workbook relationships property of XLSX
func addXlsxWorkbookRels(file []FileList, sheet int) []FileList {
	content := readXlsxWorkbookRels(file)
	rId := len(content.Relationships) + 1
	content.Relationships = append(content.Relationships, xlsxWorkbookRelation{
		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 100 101 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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
		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
func setAppXml(file []FileList) []FileList {
	return saveFileList(file, `docProps/app.xml`, TEMPLATE_DOCPROPS_APP)
}

// 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)
}

// Set default active sheet of XLSX by given index
func SetActiveSheet(file []FileList, index int) []FileList {
	var content xlsxWorkbook
	if index < 1 {
		index = 1
	}
	index -= 1
	xml.Unmarshal([]byte(readXml(file, `xl/workbook.xml`)), &content)
	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))))
	index += 1
	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 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
		xml.Unmarshal([]byte(readXml(file, path)), &xlsx)
		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
}