Browse Source

Fix #523, add stream writer for generate new worksheet with huge amounts of data

xuri 6 years ago
parent
commit
08d1a86c3a
4 changed files with 386 additions and 80 deletions
  1. 219 0
      stream.go
  2. 66 0
      stream_test.go
  3. 1 0
      xmlTable.go
  4. 100 80
      xmlWorksheet.go

+ 219 - 0
stream.go

@@ -0,0 +1,219 @@
+// Copyright 2016 - 2019 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.10 or later.
+
+package excelize
+
+import (
+	"bytes"
+	"encoding/xml"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"reflect"
+)
+
+// StreamWriter defined the type of stream writer.
+type StreamWriter struct {
+	tmpFile   *os.File
+	File      *File
+	Sheet     string
+	SheetID   int
+	SheetData bytes.Buffer
+}
+
+// NewStreamWriter return stream writer struct by given worksheet name for
+// generate new worksheet with large amounts of data. Note that after set
+// rows, you must call the 'Flush' method to end the streaming writing
+// process and ensure that the order of line numbers is ascending. For
+// example, set data for worksheet of size 102400 rows x 50 columns with
+// numbers:
+//
+//    file := excelize.NewFile()
+//    streamWriter, err := file.NewStreamWriter("Sheet1")
+//    if err != nil {
+//        panic(err)
+//    }
+//    for rowID := 1; rowID <= 102400; rowID++ {
+//        row := make([]interface{}, 50)
+//        for colID := 0; colID < 50; colID++ {
+//            row[colID] = rand.Intn(640000)
+//        }
+//        cell, _ := excelize.CoordinatesToCellName(1, rowID)
+//        if err := streamWriter.SetRow(cell, &row); err != nil {
+//            panic(err)
+//        }
+//    }
+//    if err := streamWriter.Flush(); err != nil {
+//        panic(err)
+//    }
+//    if err := file.SaveAs("Book1.xlsx"); err != nil {
+//        panic(err)
+//    }
+//
+func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
+	sheetID := f.GetSheetIndex(sheet)
+	if sheetID == 0 {
+		return nil, fmt.Errorf("sheet %s is not exist", sheet)
+	}
+	rsw := &StreamWriter{
+		File:    f,
+		Sheet:   sheet,
+		SheetID: sheetID,
+	}
+	rsw.SheetData.WriteString("<sheetData>")
+	return rsw, nil
+}
+
+// SetRow writes an array to streaming row by given worksheet name, starting
+// coordinate and a pointer to array type 'slice'. Note that, cell settings
+// with styles are not supported currently and after set rows, you must call the
+// 'Flush' method to end the streaming writing process. The following
+// shows the supported data types:
+//
+//    int
+//    string
+//
+func (sw *StreamWriter) SetRow(axis string, slice interface{}) error {
+	col, row, err := CellNameToCoordinates(axis)
+	if err != nil {
+		return err
+	}
+	// Make sure 'slice' is a Ptr to Slice
+	v := reflect.ValueOf(slice)
+	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice {
+		return errors.New("pointer to slice expected")
+	}
+	v = v.Elem()
+	sw.SheetData.WriteString(fmt.Sprintf(`<row r="%d">`, row))
+	for i := 0; i < v.Len(); i++ {
+		axis, err := CoordinatesToCellName(col+i, row)
+		if err != nil {
+			return err
+		}
+		switch val := v.Index(i).Interface().(type) {
+		case int:
+			sw.SheetData.WriteString(fmt.Sprintf(`<c r="%s"><v>%d</v></c>`, axis, val))
+		case string:
+			sw.SheetData.WriteString(sw.setCellStr(axis, val))
+		default:
+			sw.SheetData.WriteString(sw.setCellStr(axis, fmt.Sprint(val)))
+		}
+	}
+	sw.SheetData.WriteString(`</row>`)
+	// Try to use local storage
+	chunk := 1 << 24
+	if sw.SheetData.Len() >= chunk {
+		if sw.tmpFile == nil {
+			err := sw.createTmp()
+			if err != nil {
+				// can not use local storage
+				return nil
+			}
+		}
+		// use local storage
+		_, err := sw.tmpFile.Write(sw.SheetData.Bytes())
+		if err != nil {
+			return nil
+		}
+		sw.SheetData.Reset()
+	}
+	return err
+}
+
+// Flush ending the streaming writing process.
+func (sw *StreamWriter) Flush() error {
+	sw.SheetData.WriteString(`</sheetData>`)
+
+	ws, err := sw.File.workSheetReader(sw.Sheet)
+	if err != nil {
+		return err
+	}
+	sheetXML := fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)
+	delete(sw.File.Sheet, sheetXML)
+	delete(sw.File.checked, sheetXML)
+	var sheetDataByte []byte
+	if sw.tmpFile != nil {
+		// close the local storage file
+		if err = sw.tmpFile.Close(); err != nil {
+			return err
+		}
+
+		file, err := os.Open(sw.tmpFile.Name())
+		if err != nil {
+			return err
+		}
+
+		sheetDataByte, err = ioutil.ReadAll(file)
+		if err != nil {
+			return err
+		}
+
+		if err := file.Close(); err != nil {
+			return err
+		}
+
+		err = os.Remove(sw.tmpFile.Name())
+		if err != nil {
+			return err
+		}
+	}
+
+	sheetDataByte = append(sheetDataByte, sw.SheetData.Bytes()...)
+	replaceMap := map[string][]byte{
+		"XMLName":   []byte{},
+		"SheetData": sheetDataByte,
+	}
+	sw.SheetData.Reset()
+	sw.File.XLSX[fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)] =
+		StreamMarshalSheet(ws, replaceMap)
+	return err
+}
+
+// createTmp creates a temporary file in the operating system default
+// temporary directory.
+func (sw *StreamWriter) createTmp() (err error) {
+	sw.tmpFile, err = ioutil.TempFile(os.TempDir(), "excelize-")
+	return err
+}
+
+// StreamMarshalSheet provides method to serialization worksheets by field as
+// streaming.
+func StreamMarshalSheet(ws *xlsxWorksheet, replaceMap map[string][]byte) []byte {
+	s := reflect.ValueOf(ws).Elem()
+	typeOfT := s.Type()
+	var marshalResult []byte
+	marshalResult = append(marshalResult, []byte(XMLHeader+`<worksheet`+templateNamespaceIDMap)...)
+	for i := 0; i < s.NumField(); i++ {
+		f := s.Field(i)
+		content, ok := replaceMap[typeOfT.Field(i).Name]
+		if ok {
+			marshalResult = append(marshalResult, content...)
+			continue
+		}
+		out, _ := xml.Marshal(f.Interface())
+		marshalResult = append(marshalResult, out...)
+	}
+	marshalResult = append(marshalResult, []byte(`</worksheet>`)...)
+	return marshalResult
+}
+
+// setCellStr provides a function to set string type value of a cell as
+// streaming. Total number of characters that a cell can contain 32767
+// characters.
+func (sw *StreamWriter) setCellStr(axis, value string) string {
+	if len(value) > 32767 {
+		value = value[0:32767]
+	}
+	// Leading and ending space(s) character detection.
+	if len(value) > 0 && (value[0] == 32 || value[len(value)-1] == 32) {
+		return fmt.Sprintf(`<c xml:space="preserve" r="%s" t="str"><v>%s</v></c>`, axis, value)
+	}
+	return fmt.Sprintf(`<c r="%s" t="str"><v>%s</v></c>`, axis, value)
+}

+ 66 - 0
stream_test.go

@@ -0,0 +1,66 @@
+package excelize
+
+import (
+	"math/rand"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestStreamWriter(t *testing.T) {
+	file := NewFile()
+	streamWriter, err := file.NewStreamWriter("Sheet1")
+	assert.NoError(t, err)
+
+	// Test max characters in a cell.
+	row := make([]interface{}, 1)
+	row[0] = strings.Repeat("c", 32769)
+	assert.NoError(t, streamWriter.SetRow("A1", &row))
+
+	// Test leading and ending space(s) character characters in a cell.
+	row = make([]interface{}, 1)
+	row[0] = " characters"
+	assert.NoError(t, streamWriter.SetRow("A2", &row))
+
+	row = make([]interface{}, 1)
+	row[0] = []byte("Word")
+	assert.NoError(t, streamWriter.SetRow("A3", &row))
+
+	for rowID := 10; rowID <= 51200; rowID++ {
+		row := make([]interface{}, 50)
+		for colID := 0; colID < 50; colID++ {
+			row[colID] = rand.Intn(640000)
+		}
+		cell, _ := CoordinatesToCellName(1, rowID)
+		assert.NoError(t, streamWriter.SetRow(cell, &row))
+	}
+
+	err = streamWriter.Flush()
+	assert.NoError(t, err)
+	// Save xlsx file by the given path.
+	assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx")))
+
+	// Test error exceptions
+	streamWriter, err = file.NewStreamWriter("SheetN")
+	assert.EqualError(t, err, "sheet SheetN is not exist")
+}
+
+func TestFlush(t *testing.T) {
+	// Test error exceptions
+	file := NewFile()
+	streamWriter, err := file.NewStreamWriter("Sheet1")
+	assert.NoError(t, err)
+	streamWriter.Sheet = "SheetN"
+	assert.EqualError(t, streamWriter.Flush(), "sheet SheetN is not exist")
+}
+
+func TestSetRow(t *testing.T) {
+	// Test error exceptions
+	file := NewFile()
+	streamWriter, err := file.NewStreamWriter("Sheet1")
+	assert.NoError(t, err)
+	assert.EqualError(t, streamWriter.SetRow("A", &[]interface{}{}), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
+	assert.EqualError(t, streamWriter.SetRow("A1", []interface{}{}), `pointer to slice expected`)
+}

+ 1 - 0
xmlTable.go

@@ -44,6 +44,7 @@ type xlsxTable struct {
 // applied column by column to a table of data in the worksheet. This collection
 // expresses AutoFilter settings.
 type xlsxAutoFilter struct {
+	XMLName      xml.Name          `xml:"autoFilter"`
 	Ref          string            `xml:"ref,attr"`
 	FilterColumn *xlsxFilterColumn `xml:"filterColumn"`
 }

+ 100 - 80
xmlWorksheet.go

@@ -59,7 +59,8 @@ type xlsxWorksheet struct {
 
 // xlsxDrawing change r:id to rid in the namespace.
 type xlsxDrawing struct {
-	RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
+	XMLName xml.Name `xml:"drawing"`
+	RID     string   `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
 }
 
 // xlsxHeaderFooter directly maps the headerFooter element in the namespace
@@ -70,6 +71,7 @@ type xlsxDrawing struct {
 // footers on the first page can differ from those on odd- and even-numbered
 // pages. In the latter case, the first page is not considered an odd page.
 type xlsxHeaderFooter struct {
+	XMLName          xml.Name       `xml:"headerFooter"`
 	AlignWithMargins bool           `xml:"alignWithMargins,attr,omitempty"`
 	DifferentFirst   bool           `xml:"differentFirst,attr,omitempty"`
 	DifferentOddEven bool           `xml:"differentOddEven,attr,omitempty"`
@@ -91,32 +93,33 @@ type xlsxHeaderFooter struct {
 // each of the left section, center section and right section of a header and
 // a footer.
 type xlsxDrawingHF struct {
-	Content string `xml:",chardata"`
+	Content string `xml:",innerxml"`
 }
 
 // xlsxPageSetUp directly maps the pageSetup element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page setup
 // settings for the worksheet.
 type xlsxPageSetUp struct {
-	BlackAndWhite      bool    `xml:"blackAndWhite,attr,omitempty"`
-	CellComments       string  `xml:"cellComments,attr,omitempty"`
-	Copies             int     `xml:"copies,attr,omitempty"`
-	Draft              bool    `xml:"draft,attr,omitempty"`
-	Errors             string  `xml:"errors,attr,omitempty"`
-	FirstPageNumber    int     `xml:"firstPageNumber,attr,omitempty"`
-	FitToHeight        int     `xml:"fitToHeight,attr,omitempty"`
-	FitToWidth         int     `xml:"fitToWidth,attr,omitempty"`
-	HorizontalDPI      float32 `xml:"horizontalDpi,attr,omitempty"`
-	RID                string  `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
-	Orientation        string  `xml:"orientation,attr,omitempty"`
-	PageOrder          string  `xml:"pageOrder,attr,omitempty"`
-	PaperHeight        string  `xml:"paperHeight,attr,omitempty"`
-	PaperSize          int     `xml:"paperSize,attr,omitempty"`
-	PaperWidth         string  `xml:"paperWidth,attr,omitempty"`
-	Scale              int     `xml:"scale,attr,omitempty"`
-	UseFirstPageNumber bool    `xml:"useFirstPageNumber,attr,omitempty"`
-	UsePrinterDefaults bool    `xml:"usePrinterDefaults,attr,omitempty"`
-	VerticalDPI        float32 `xml:"verticalDpi,attr,omitempty"`
+	XMLName            xml.Name `xml:"pageSetup"`
+	BlackAndWhite      bool     `xml:"blackAndWhite,attr,omitempty"`
+	CellComments       string   `xml:"cellComments,attr,omitempty"`
+	Copies             int      `xml:"copies,attr,omitempty"`
+	Draft              bool     `xml:"draft,attr,omitempty"`
+	Errors             string   `xml:"errors,attr,omitempty"`
+	FirstPageNumber    int      `xml:"firstPageNumber,attr,omitempty"`
+	FitToHeight        int      `xml:"fitToHeight,attr,omitempty"`
+	FitToWidth         int      `xml:"fitToWidth,attr,omitempty"`
+	HorizontalDPI      float32  `xml:"horizontalDpi,attr,omitempty"`
+	RID                string   `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
+	Orientation        string   `xml:"orientation,attr,omitempty"`
+	PageOrder          string   `xml:"pageOrder,attr,omitempty"`
+	PaperHeight        string   `xml:"paperHeight,attr,omitempty"`
+	PaperSize          int      `xml:"paperSize,attr,omitempty"`
+	PaperWidth         string   `xml:"paperWidth,attr,omitempty"`
+	Scale              int      `xml:"scale,attr,omitempty"`
+	UseFirstPageNumber bool     `xml:"useFirstPageNumber,attr,omitempty"`
+	UsePrinterDefaults bool     `xml:"usePrinterDefaults,attr,omitempty"`
+	VerticalDPI        float32  `xml:"verticalDpi,attr,omitempty"`
 }
 
 // xlsxPrintOptions directly maps the printOptions element in the namespace
@@ -124,44 +127,48 @@ type xlsxPageSetUp struct {
 // the sheet. Printer-specific settings are stored separately in the Printer
 // Settings part.
 type xlsxPrintOptions struct {
-	GridLines          bool `xml:"gridLines,attr,omitempty"`
-	GridLinesSet       bool `xml:"gridLinesSet,attr,omitempty"`
-	Headings           bool `xml:"headings,attr,omitempty"`
-	HorizontalCentered bool `xml:"horizontalCentered,attr,omitempty"`
-	VerticalCentered   bool `xml:"verticalCentered,attr,omitempty"`
+	XMLName            xml.Name `xml:"printOptions"`
+	GridLines          bool     `xml:"gridLines,attr,omitempty"`
+	GridLinesSet       bool     `xml:"gridLinesSet,attr,omitempty"`
+	Headings           bool     `xml:"headings,attr,omitempty"`
+	HorizontalCentered bool     `xml:"horizontalCentered,attr,omitempty"`
+	VerticalCentered   bool     `xml:"verticalCentered,attr,omitempty"`
 }
 
 // xlsxPageMargins directly maps the pageMargins element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page margins for
 // a sheet or a custom sheet view.
 type xlsxPageMargins struct {
-	Bottom float64 `xml:"bottom,attr"`
-	Footer float64 `xml:"footer,attr"`
-	Header float64 `xml:"header,attr"`
-	Left   float64 `xml:"left,attr"`
-	Right  float64 `xml:"right,attr"`
-	Top    float64 `xml:"top,attr"`
+	XMLName xml.Name `xml:"pageMargins"`
+	Bottom  float64  `xml:"bottom,attr"`
+	Footer  float64  `xml:"footer,attr"`
+	Header  float64  `xml:"header,attr"`
+	Left    float64  `xml:"left,attr"`
+	Right   float64  `xml:"right,attr"`
+	Top     float64  `xml:"top,attr"`
 }
 
 // xlsxSheetFormatPr directly maps the sheetFormatPr element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main. This element
 // specifies the sheet formatting properties.
 type xlsxSheetFormatPr struct {
-	BaseColWidth     uint8   `xml:"baseColWidth,attr,omitempty"`
-	DefaultColWidth  float64 `xml:"defaultColWidth,attr,omitempty"`
-	DefaultRowHeight float64 `xml:"defaultRowHeight,attr"`
-	CustomHeight     bool    `xml:"customHeight,attr,omitempty"`
-	ZeroHeight       bool    `xml:"zeroHeight,attr,omitempty"`
-	ThickTop         bool    `xml:"thickTop,attr,omitempty"`
-	ThickBottom      bool    `xml:"thickBottom,attr,omitempty"`
-	OutlineLevelRow  uint8   `xml:"outlineLevelRow,attr,omitempty"`
-	OutlineLevelCol  uint8   `xml:"outlineLevelCol,attr,omitempty"`
+	XMLName          xml.Name `xml:"sheetFormatPr"`
+	BaseColWidth     uint8    `xml:"baseColWidth,attr,omitempty"`
+	DefaultColWidth  float64  `xml:"defaultColWidth,attr,omitempty"`
+	DefaultRowHeight float64  `xml:"defaultRowHeight,attr"`
+	CustomHeight     bool     `xml:"customHeight,attr,omitempty"`
+	ZeroHeight       bool     `xml:"zeroHeight,attr,omitempty"`
+	ThickTop         bool     `xml:"thickTop,attr,omitempty"`
+	ThickBottom      bool     `xml:"thickBottom,attr,omitempty"`
+	OutlineLevelRow  uint8    `xml:"outlineLevelRow,attr,omitempty"`
+	OutlineLevelCol  uint8    `xml:"outlineLevelCol,attr,omitempty"`
 }
 
 // xlsxSheetViews directly maps the sheetViews element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main - Worksheet views
 // collection.
 type xlsxSheetViews struct {
+	XMLName   xml.Name        `xml:"sheetViews"`
 	SheetView []xlsxSheetView `xml:"sheetView"`
 }
 
@@ -263,7 +270,8 @@ type xlsxTabColor struct {
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
 // not checked it for completeness - it does as much as I need.
 type xlsxCols struct {
-	Col []xlsxCol `xml:"col"`
+	XMLName xml.Name  `xml:"cols"`
+	Col     []xlsxCol `xml:"col"`
 }
 
 // xlsxCol directly maps the col (Column Width & Formatting). Defines column
@@ -289,7 +297,8 @@ type xlsxCol struct {
 // When an entire column is formatted, only the first cell in that column is
 // considered used.
 type xlsxDimension struct {
-	Ref string `xml:"ref,attr"`
+	XMLName xml.Name `xml:"dimension"`
+	Ref     string   `xml:"ref,attr"`
 }
 
 // xlsxSheetData directly maps the sheetData element in the namespace
@@ -322,6 +331,7 @@ type xlsxRow struct {
 // xlsxCustomSheetViews directly maps the customSheetViews element. This is a
 // collection of custom sheet views.
 type xlsxCustomSheetViews struct {
+	XMLName         xml.Name               `xml:"customSheetViews"`
 	CustomSheetView []*xlsxCustomSheetView `xml:"customSheetView"`
 }
 
@@ -384,13 +394,15 @@ type xlsxMergeCell struct {
 // xlsxMergeCells directly maps the mergeCells element. This collection
 // expresses all the merged cells in the sheet.
 type xlsxMergeCells struct {
-	Count int              `xml:"count,attr,omitempty"`
-	Cells []*xlsxMergeCell `xml:"mergeCell,omitempty"`
+	XMLName xml.Name         `xml:"mergeCells"`
+	Count   int              `xml:"count,attr,omitempty"`
+	Cells   []*xlsxMergeCell `xml:"mergeCell,omitempty"`
 }
 
 // xlsxDataValidations expresses all data validation information for cells in a
 // sheet which have data validation features applied.
 type xlsxDataValidations struct {
+	XMLName        xml.Name          `xml:"dataValidations"`
 	Count          int               `xml:"count,attr,omitempty"`
 	DisablePrompts bool              `xml:"disablePrompts,attr,omitempty"`
 	XWindow        int               `xml:"xWindow,attr,omitempty"`
@@ -434,14 +446,15 @@ type DataValidation struct {
 //      str (String)              | Cell containing a formula string.
 //
 type xlsxC struct {
-	R string `xml:"r,attr"`           // Cell ID, e.g. A1
-	S int    `xml:"s,attr,omitempty"` // Style reference.
-	// Str string `xml:"str,attr,omitempty"` // Style reference.
-	T        string   `xml:"t,attr,omitempty"` // Type.
-	F        *xlsxF   `xml:"f,omitempty"`      // Formula
-	V        string   `xml:"v,omitempty"`      // Value
-	IS       *xlsxSI  `xml:"is"`
+	XMLName  xml.Name `xml:"c"`
 	XMLSpace xml.Attr `xml:"space,attr,omitempty"`
+	R        string   `xml:"r,attr"`           // Cell ID, e.g. A1
+	S        int      `xml:"s,attr,omitempty"` // Style reference.
+	// Str string `xml:"str,attr,omitempty"` // Style reference.
+	T  string  `xml:"t,attr,omitempty"` // Type.
+	F  *xlsxF  `xml:"f,omitempty"`      // Formula
+	V  string  `xml:"v,omitempty"`      // Value
+	IS *xlsxSI `xml:"is"`
 }
 
 func (c *xlsxC) hasValue() bool {
@@ -461,27 +474,28 @@ type xlsxF struct {
 // xlsxSheetProtection collection expresses the sheet protection options to
 // enforce when the sheet is protected.
 type xlsxSheetProtection struct {
-	AlgorithmName       string `xml:"algorithmName,attr,omitempty"`
-	Password            string `xml:"password,attr,omitempty"`
-	HashValue           string `xml:"hashValue,attr,omitempty"`
-	SaltValue           string `xml:"saltValue,attr,omitempty"`
-	SpinCount           int    `xml:"spinCount,attr,omitempty"`
-	Sheet               bool   `xml:"sheet,attr"`
-	Objects             bool   `xml:"objects,attr"`
-	Scenarios           bool   `xml:"scenarios,attr"`
-	FormatCells         bool   `xml:"formatCells,attr"`
-	FormatColumns       bool   `xml:"formatColumns,attr"`
-	FormatRows          bool   `xml:"formatRows,attr"`
-	InsertColumns       bool   `xml:"insertColumns,attr"`
-	InsertRows          bool   `xml:"insertRows,attr"`
-	InsertHyperlinks    bool   `xml:"insertHyperlinks,attr"`
-	DeleteColumns       bool   `xml:"deleteColumns,attr"`
-	DeleteRows          bool   `xml:"deleteRows,attr"`
-	SelectLockedCells   bool   `xml:"selectLockedCells,attr"`
-	Sort                bool   `xml:"sort,attr"`
-	AutoFilter          bool   `xml:"autoFilter,attr"`
-	PivotTables         bool   `xml:"pivotTables,attr"`
-	SelectUnlockedCells bool   `xml:"selectUnlockedCells,attr"`
+	XMLName             xml.Name `xml:"sheetProtection"`
+	AlgorithmName       string   `xml:"algorithmName,attr,omitempty"`
+	Password            string   `xml:"password,attr,omitempty"`
+	HashValue           string   `xml:"hashValue,attr,omitempty"`
+	SaltValue           string   `xml:"saltValue,attr,omitempty"`
+	SpinCount           int      `xml:"spinCount,attr,omitempty"`
+	Sheet               bool     `xml:"sheet,attr"`
+	Objects             bool     `xml:"objects,attr"`
+	Scenarios           bool     `xml:"scenarios,attr"`
+	FormatCells         bool     `xml:"formatCells,attr"`
+	FormatColumns       bool     `xml:"formatColumns,attr"`
+	FormatRows          bool     `xml:"formatRows,attr"`
+	InsertColumns       bool     `xml:"insertColumns,attr"`
+	InsertRows          bool     `xml:"insertRows,attr"`
+	InsertHyperlinks    bool     `xml:"insertHyperlinks,attr"`
+	DeleteColumns       bool     `xml:"deleteColumns,attr"`
+	DeleteRows          bool     `xml:"deleteRows,attr"`
+	SelectLockedCells   bool     `xml:"selectLockedCells,attr"`
+	Sort                bool     `xml:"sort,attr"`
+	AutoFilter          bool     `xml:"autoFilter,attr"`
+	PivotTables         bool     `xml:"pivotTables,attr"`
+	SelectUnlockedCells bool     `xml:"selectUnlockedCells,attr"`
 }
 
 // xlsxPhoneticPr (Phonetic Properties) represents a collection of phonetic
@@ -492,9 +506,10 @@ type xlsxSheetProtection struct {
 // every phonetic hint is expressed as a phonetic run (rPh), and these
 // properties specify how to display that phonetic run.
 type xlsxPhoneticPr struct {
-	Alignment string `xml:"alignment,attr,omitempty"`
-	FontID    *int   `xml:"fontId,attr"`
-	Type      string `xml:"type,attr,omitempty"`
+	XMLName   xml.Name `xml:"phoneticPr"`
+	Alignment string   `xml:"alignment,attr,omitempty"`
+	FontID    *int     `xml:"fontId,attr"`
+	Type      string   `xml:"type,attr,omitempty"`
 }
 
 // A Conditional Format is a format, such as cell shading or font color, that a
@@ -502,8 +517,9 @@ type xlsxPhoneticPr struct {
 // condition is true. This collection expresses conditional formatting rules
 // applied to a particular cell or range.
 type xlsxConditionalFormatting struct {
-	SQRef  string        `xml:"sqref,attr,omitempty"`
-	CfRule []*xlsxCfRule `xml:"cfRule"`
+	XMLName xml.Name      `xml:"conditionalFormatting"`
+	SQRef   string        `xml:"sqref,attr,omitempty"`
+	CfRule  []*xlsxCfRule `xml:"cfRule"`
 }
 
 // xlsxCfRule (Conditional Formatting Rule) represents a description of a
@@ -568,6 +584,7 @@ type xlsxCfvo struct {
 // be stored in a package as a relationship. Hyperlinks shall be identified by
 // containing a target which specifies the destination of the given hyperlink.
 type xlsxHyperlinks struct {
+	XMLName   xml.Name        `xml:"hyperlinks"`
 	Hyperlink []xlsxHyperlink `xml:"hyperlink"`
 }
 
@@ -612,6 +629,7 @@ type xlsxHyperlink struct {
 //    </worksheet>
 //
 type xlsxTableParts struct {
+	XMLName    xml.Name         `xml:"tableParts"`
 	Count      int              `xml:"count,attr,omitempty"`
 	TableParts []*xlsxTablePart `xml:"tablePart"`
 }
@@ -629,7 +647,8 @@ type xlsxTablePart struct {
 //    <picture r:id="rId1"/>
 //
 type xlsxPicture struct {
-	RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
+	XMLName xml.Name `xml:"picture"`
+	RID     string   `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
 }
 
 // xlsxLegacyDrawing directly maps the legacyDrawing element in the namespace
@@ -642,7 +661,8 @@ type xlsxPicture struct {
 // can also be used to explain assumptions made in a formula or to call out
 // something special about the cell.
 type xlsxLegacyDrawing struct {
-	RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
+	XMLName xml.Name `xml:"legacyDrawing"`
+	RID     string   `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
 }
 
 type xlsxInnerXML struct {