summaryrefslogtreecommitdiff
path: root/sheet.go
diff options
context:
space:
mode:
Diffstat (limited to 'sheet.go')
-rw-r--r--sheet.go239
1 files changed, 172 insertions, 67 deletions
diff --git a/sheet.go b/sheet.go
index 011ecb8..f546561 100644
--- a/sheet.go
+++ b/sheet.go
@@ -1,3 +1,12 @@
+// Copyright 2016 - 2018 The excelize Authors. All rights reserved. Use of
+// this source code is governed by a BSD-style license that can be found in
+// the LICENSE file.
+//
+// Package excelize providing a set of functions that allow you to write to
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excelâ„¢ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.8 or later.
+
package excelize
import (
@@ -5,6 +14,8 @@ import (
"encoding/json"
"encoding/xml"
"errors"
+ "fmt"
+ "io/ioutil"
"os"
"path"
"strconv"
@@ -14,26 +25,35 @@ import (
"github.com/mohae/deepcopy"
)
-// NewSheet provides a function to create a new sheet by given worksheet name,
-// when creating a new XLSX file, the default sheet will be create, when you
-// create a new file.
+// NewSheet provides function to create a new sheet by given worksheet name.
+// When creating a new XLSX file, the default sheet will be created. Returns
+// the number of sheets in the workbook (file) after appending the new sheet.
func (f *File) NewSheet(name string) int {
// Check if the worksheet already exists
if f.GetSheetIndex(name) != 0 {
return f.SheetCount
}
+ f.DeleteSheet(name)
f.SheetCount++
+ wb := f.workbookReader()
+ sheetID := 0
+ for _, v := range wb.Sheets.Sheet {
+ if v.SheetID > sheetID {
+ sheetID = v.SheetID
+ }
+ }
+ sheetID++
// Update docProps/app.xml
f.setAppXML()
// Update [Content_Types].xml
- f.setContentTypes(f.SheetCount)
+ f.setContentTypes(sheetID)
// Create new sheet /xl/worksheets/sheet%d.xml
- f.setSheet(f.SheetCount, name)
+ f.setSheet(sheetID, name)
// Update xl/_rels/workbook.xml.rels
- rID := f.addXlsxWorkbookRels(f.SheetCount)
+ rID := f.addXlsxWorkbookRels(sheetID)
// Update xl/workbook.xml
- f.setWorkbook(name, rID)
- return f.SheetCount
+ f.setWorkbook(name, sheetID, rID)
+ return sheetID
}
// contentTypesReader provides a function to get the pointer to the
@@ -41,7 +61,7 @@ func (f *File) NewSheet(name string) int {
func (f *File) contentTypesReader() *xlsxTypes {
if f.ContentTypes == nil {
var content xlsxTypes
- _ = xml.Unmarshal([]byte(f.readXML("[Content_Types].xml")), &content)
+ _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("[Content_Types].xml")), &content)
f.ContentTypes = &content
}
return f.ContentTypes
@@ -61,7 +81,7 @@ func (f *File) contentTypesWriter() {
func (f *File) workbookReader() *xlsxWorkbook {
if f.WorkBook == nil {
var content xlsxWorkbook
- _ = xml.Unmarshal([]byte(f.readXML("xl/workbook.xml")), &content)
+ _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/workbook.xml")), &content)
f.WorkBook = &content
}
return f.WorkBook
@@ -107,7 +127,8 @@ func trimCell(column []xlsxC) []xlsxC {
return col[0:i]
}
-// Read and update property of contents type of XLSX.
+// setContentTypes provides a function to read and update property of contents
+// type of XLSX.
func (f *File) setContentTypes(index int) {
content := f.contentTypesReader()
content.Overrides = append(content.Overrides, xlsxOverride{
@@ -116,7 +137,7 @@ func (f *File) setContentTypes(index int) {
})
}
-// Update sheet property by given index.
+// setSheet provides a function to update sheet property by given index.
func (f *File) setSheet(index int, name string) {
var xlsx xlsxWorksheet
xlsx.Dimension.Ref = "A1"
@@ -130,19 +151,11 @@ func (f *File) setSheet(index int, name string) {
// setWorkbook update workbook property of XLSX. Maximum 31 characters are
// allowed in sheet title.
-func (f *File) setWorkbook(name string, rid int) {
+func (f *File) setWorkbook(name string, sheetID, rid int) {
content := f.workbookReader()
- rID := 0
- for _, v := range content.Sheets.Sheet {
- t, _ := strconv.Atoi(v.SheetID)
- if t > rID {
- rID = t
- }
- }
- rID++
content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{
Name: trimSheetName(name),
- SheetID: strconv.Itoa(rID),
+ SheetID: sheetID,
ID: "rId" + strconv.Itoa(rid),
})
}
@@ -152,7 +165,7 @@ func (f *File) setWorkbook(name string, rid int) {
func (f *File) workbookRelsReader() *xlsxWorkbookRels {
if f.WorkBookRels == nil {
var content xlsxWorkbookRels
- _ = xml.Unmarshal([]byte(f.readXML("xl/_rels/workbook.xml.rels")), &content)
+ _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/_rels/workbook.xml.rels")), &content)
f.WorkBookRels = &content
}
return f.WorkBookRels
@@ -198,39 +211,44 @@ func (f *File) setAppXML() {
f.saveFileList("docProps/app.xml", []byte(templateDocpropsApp))
}
-// 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.
+// replaceRelationshipsNameSpaceBytes; 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 replaceRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte {
oldXmlns := []byte(`<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
newXmlns := []byte(`<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">`)
return bytes.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
}
-// SetActiveSheet provides a function to set default active worksheet of XLSX by
-// given index. Note that active index is different with the index that got by
-// function GetSheetMap, and it should be greater than 0 and less than total
+// SetActiveSheet provides function to set default active worksheet of XLSX by
+// given index. Note that active index is different from the index returned by
+// function GetSheetMap(). It should be greater than 0 and less than total
// worksheet numbers.
func (f *File) SetActiveSheet(index int) {
if index < 1 {
index = 1
}
- index--
- content := f.workbookReader()
- if len(content.BookViews.WorkBookView) > 0 {
- content.BookViews.WorkBookView[0].ActiveTab = index
- } else {
- content.BookViews.WorkBookView = append(content.BookViews.WorkBookView, xlsxWorkBookView{
- ActiveTab: index,
- })
+ wb := f.workbookReader()
+ for activeTab, sheet := range wb.Sheets.Sheet {
+ if sheet.SheetID == index {
+ if len(wb.BookViews.WorkBookView) > 0 {
+ wb.BookViews.WorkBookView[0].ActiveTab = activeTab
+ } else {
+ wb.BookViews.WorkBookView = append(wb.BookViews.WorkBookView, xlsxWorkBookView{
+ ActiveTab: activeTab,
+ })
+ }
+ }
}
- index++
for idx, name := range f.GetSheetMap() {
xlsx := f.workSheetReader(name)
+ if len(xlsx.SheetViews.SheetView) > 0 {
+ xlsx.SheetViews.SheetView[0].TabSelected = false
+ }
if index == idx {
if len(xlsx.SheetViews.SheetView) > 0 {
xlsx.SheetViews.SheetView[0].TabSelected = true
@@ -239,32 +257,20 @@ func (f *File) SetActiveSheet(index int) {
TabSelected: true,
})
}
- } else {
- if len(xlsx.SheetViews.SheetView) > 0 {
- xlsx.SheetViews.SheetView[0].TabSelected = false
- }
}
}
}
-// GetActiveSheetIndex provides a function to get active sheet of XLSX. If not
-// found the active sheet will be return integer 0.
+// GetActiveSheetIndex provides a function to get active sheet index of the
+// XLSX. If not found the active sheet will be return integer 0.
func (f *File) GetActiveSheetIndex() int {
- buffer := bytes.Buffer{}
- content := f.workbookReader()
- for _, v := range content.Sheets.Sheet {
- xlsx := xlsxWorksheet{}
- buffer.WriteString("xl/worksheets/sheet")
- buffer.WriteString(strings.TrimPrefix(v.ID, "rId"))
- buffer.WriteString(".xml")
- _ = xml.Unmarshal([]byte(f.readXML(buffer.String())), &xlsx)
+ for idx, name := range f.GetSheetMap() {
+ xlsx := f.workSheetReader(name)
for _, sheetView := range xlsx.SheetViews.SheetView {
if sheetView.TabSelected {
- ID, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId"))
- return ID
+ return idx
}
}
- buffer.Reset()
}
return 0
}
@@ -342,8 +348,9 @@ func (f *File) GetSheetMap() map[int]string {
sheetMap := map[int]string{}
for _, v := range content.Sheets.Sheet {
for _, rel := range rels.Relationships {
- if rel.ID == v.ID {
- rID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(rel.Target, "worksheets/sheet"), ".xml"))
+ relStr := strings.SplitN(rel.Target, "worksheets/sheet", 2)
+ if rel.ID == v.ID && len(relStr) == 2 {
+ rID, _ := strconv.Atoi(strings.TrimSuffix(relStr[1], ".xml"))
sheetMap[rID] = v.Name
}
}
@@ -362,7 +369,7 @@ func (f *File) getSheetMap() map[string]string {
}
// SetSheetBackground provides a function to set background picture by given
-// worksheet name.
+// worksheet name and file path.
func (f *File) SetSheetBackground(sheet, picture string) error {
var err error
// Check picture exists first.
@@ -371,12 +378,13 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
}
ext, ok := supportImageTypes[path.Ext(picture)]
if !ok {
- return errors.New("Unsupported image extension")
+ return errors.New("unsupported image extension")
}
pictureID := f.countMedia() + 1
rID := f.addSheetRelationships(sheet, SourceRelationshipImage, "../media/image"+strconv.Itoa(pictureID)+ext, "")
f.addSheetPicture(sheet, rID)
- f.addMedia(picture, ext)
+ file, _ := ioutil.ReadFile(picture)
+ f.addMedia(file, ext)
f.setContentTypePartImageExtensions()
return err
}
@@ -391,8 +399,8 @@ func (f *File) DeleteSheet(name string) {
for k, v := range content.Sheets.Sheet {
if v.Name == trimSheetName(name) && len(content.Sheets.Sheet) > 1 {
content.Sheets.Sheet = append(content.Sheets.Sheet[:k], content.Sheets.Sheet[k+1:]...)
- sheet := "xl/worksheets/sheet" + strings.TrimPrefix(v.ID, "rId") + ".xml"
- rels := "xl/worksheets/_rels/sheet" + strings.TrimPrefix(v.ID, "rId") + ".xml.rels"
+ sheet := "xl/worksheets/sheet" + strconv.Itoa(v.SheetID) + ".xml"
+ rels := "xl/worksheets/_rels/sheet" + strconv.Itoa(v.SheetID) + ".xml.rels"
target := f.deleteSheetFromWorkbookRels(v.ID)
f.deleteSheetFromContentTypes(target)
delete(f.sheetMap, name)
@@ -441,7 +449,7 @@ func (f *File) deleteSheetFromContentTypes(target string) {
//
func (f *File) CopySheet(from, to int) error {
if from < 1 || to < 1 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" {
- return errors.New("Invalid worksheet index")
+ return errors.New("invalid worksheet index")
}
f.copySheet(from, to)
return nil
@@ -649,6 +657,103 @@ func (f *File) GetSheetVisible(name string) bool {
return visible
}
+// SearchSheet provides a function to get coordinates by given worksheet name
+// and cell value. This function only supports exact match of strings and
+// numbers, doesn't support the calculated result, formatted numbers and
+// conditional lookup currently. If it is a merged cell, it will return the
+// coordinates of the upper left corner of the merged area. For example,
+// search the coordinates of the value of "100" on Sheet1:
+//
+// xlsx.SearchSheet("Sheet1", "100")
+//
+func (f *File) SearchSheet(sheet, value string) []string {
+ xlsx := f.workSheetReader(sheet)
+ result := []string{}
+ name, ok := f.sheetMap[trimSheetName(sheet)]
+ if !ok {
+ return result
+ }
+ if xlsx != nil {
+ output, _ := xml.Marshal(f.Sheet[name])
+ f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
+ }
+ xml.NewDecoder(bytes.NewReader(f.readXML(name)))
+ d := f.sharedStringsReader()
+ var inElement string
+ var r xlsxRow
+ decoder := xml.NewDecoder(bytes.NewReader(f.readXML(name)))
+ for {
+ token, _ := decoder.Token()
+ if token == nil {
+ break
+ }
+ switch startElement := token.(type) {
+ case xml.StartElement:
+ inElement = startElement.Name.Local
+ if inElement == "row" {
+ r = xlsxRow{}
+ _ = decoder.DecodeElement(&r, &startElement)
+ for _, colCell := range r.C {
+ val, _ := colCell.getValueFrom(f, d)
+ if val != value {
+ continue
+ }
+ result = append(result, fmt.Sprintf("%s%d", strings.Map(letterOnlyMapF, colCell.R), r.R))
+ }
+ }
+ default:
+ }
+ }
+ return result
+}
+
+// ProtectSheet provides a function to prevent other users from accidentally
+// or deliberately changing, moving, or deleting data in a worksheet. For
+// example, protect Sheet1 with protection settings:
+//
+// xlsx.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{
+// Password: "password",
+// EditScenarios: false,
+// })
+//
+func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) {
+ xlsx := f.workSheetReader(sheet)
+ if settings == nil {
+ settings = &FormatSheetProtection{
+ EditObjects: true,
+ EditScenarios: true,
+ SelectLockedCells: true,
+ }
+ }
+ xlsx.SheetProtection = &xlsxSheetProtection{
+ AutoFilter: settings.AutoFilter,
+ DeleteColumns: settings.DeleteColumns,
+ DeleteRows: settings.DeleteRows,
+ FormatCells: settings.FormatCells,
+ FormatColumns: settings.FormatColumns,
+ FormatRows: settings.FormatRows,
+ InsertColumns: settings.InsertColumns,
+ InsertHyperlinks: settings.InsertHyperlinks,
+ InsertRows: settings.InsertRows,
+ Objects: settings.EditObjects,
+ PivotTables: settings.PivotTables,
+ Scenarios: settings.EditScenarios,
+ SelectLockedCells: settings.SelectLockedCells,
+ SelectUnlockedCells: settings.SelectUnlockedCells,
+ Sheet: true,
+ Sort: settings.Sort,
+ }
+ if settings.Password != "" {
+ xlsx.SheetProtection.Password = genSheetPasswd(settings.Password)
+ }
+}
+
+// UnprotectSheet provides a function to unprotect an Excel worksheet.
+func (f *File) UnprotectSheet(sheet string) {
+ xlsx := f.workSheetReader(sheet)
+ xlsx.SheetProtection = nil
+}
+
// trimSheetName provides a function to trim invaild characters by given worksheet
// name.
func trimSheetName(name string) string {