From 0f2a9053246c3ae45e6c7ba911a1fb135664abdf Mon Sep 17 00:00:00 2001 From: xuri Date: Thu, 2 Apr 2020 00:41:14 +0800 Subject: Performance improvements --- adjust.go | 5 +++-- drawing.go | 2 +- excelize.go | 5 ++--- lib.go | 42 ++++++++++++++++++++++++++++++++++++++++-- lib_test.go | 5 +++++ picture.go | 2 +- rows.go | 14 ++++++++------ sheet.go | 6 +++--- sparkline.go | 2 +- stream.go | 2 +- 10 files changed, 65 insertions(+), 20 deletions(-) diff --git a/adjust.go b/adjust.go index bedeec0..5056839 100644 --- a/adjust.go +++ b/adjust.go @@ -80,9 +80,10 @@ func (f *File) adjustColDimensions(xlsx *xlsxWorksheet, col, offset int) { // adjustRowDimensions provides a function to update row dimensions when // inserting or deleting rows or columns. func (f *File) adjustRowDimensions(xlsx *xlsxWorksheet, row, offset int) { - for i, r := range xlsx.SheetData.Row { + for i := range xlsx.SheetData.Row { + r := &xlsx.SheetData.Row[i] if newRow := r.R + offset; r.R >= row && newRow > 0 { - f.ajustSingleRowDimensions(&xlsx.SheetData.Row[i], newRow) + f.ajustSingleRowDimensions(r, newRow) } } } diff --git a/drawing.go b/drawing.go index 13bdab4..e410599 100644 --- a/drawing.go +++ b/drawing.go @@ -1288,7 +1288,7 @@ func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) (err } for idx := 0; idx < len(wsDr.TwoCellAnchor); idx++ { deTwoCellAnchor = new(decodeTwoCellAnchor) - if err = f.xmlNewDecoder(bytes.NewReader([]byte("" + wsDr.TwoCellAnchor[idx].GraphicFrame + ""))). + if err = f.xmlNewDecoder(bytes.NewReader(stringToBytes("" + wsDr.TwoCellAnchor[idx].GraphicFrame + ""))). Decode(deTwoCellAnchor); err != nil && err != io.EOF { err = fmt.Errorf("xml decode error: %s", err) return diff --git a/excelize.go b/excelize.go index 520cbb7..ae011d9 100644 --- a/excelize.go +++ b/excelize.go @@ -234,10 +234,9 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int { // replaceRelationshipsNameSpaceBytes provides a function to replace // XML tags to self-closing for compatible Microsoft Office Excel 2007. func replaceRelationshipsNameSpaceBytes(contentMarshal []byte) []byte { - var oldXmlns = []byte(` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`) + var oldXmlns = stringToBytes(` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`) var newXmlns = []byte(templateNamespaceIDMap) - contentMarshal = bytes.Replace(contentMarshal, oldXmlns, newXmlns, -1) - return contentMarshal + return bytesReplace(contentMarshal, oldXmlns, newXmlns, -1) } // UpdateLinkedValue fix linked values within a spreadsheet are not updating in diff --git a/lib.go b/lib.go index 2d606fa..83cdb4a 100644 --- a/lib.go +++ b/lib.go @@ -17,6 +17,7 @@ import ( "log" "strconv" "strings" + "unsafe" ) // ReadZipReader can be used to read an XLSX in memory without touching the @@ -103,7 +104,7 @@ func JoinCellName(col string, row int) (string, error) { if row < 1 { return "", newInvalidRowNumberError(row) } - return fmt.Sprintf("%s%d", normCol, row), nil + return normCol + strconv.Itoa(row), nil } // ColumnNameToNumber provides a function to convert Excel sheet column name @@ -190,6 +191,7 @@ func CoordinatesToCellName(col, row int) (string, error) { } colname, err := ColumnNumberToName(col) if err != nil { + // Error should never happens here. return "", fmt.Errorf("invalid cell coordinates [%d, %d]: %v", col, row, err) } return fmt.Sprintf("%s%d", colname, row), nil @@ -235,11 +237,47 @@ func namespaceStrictToTransitional(content []byte) []byte { StrictNameSpaceSpreadSheet: NameSpaceSpreadSheet, } for s, n := range namespaceTranslationDic { - content = bytes.Replace(content, []byte(s), []byte(n), -1) + content = bytesReplace(content, stringToBytes(s), stringToBytes(n), -1) } return content } +// stringToBytes cast a string to bytes pointer and assign the value of this +// pointer. +func stringToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer(&s)) +} + +// bytesReplace replace old bytes with given new. +func bytesReplace(s, old, new []byte, n int) []byte { + if n == 0 { + return s + } + + if len(old) < len(new) { + return bytes.Replace(s, old, new, n) + } + + if n < 0 { + n = len(s) + } + + var wid, i, j, w int + for i, j = 0, 0; i < len(s) && j < n; j++ { + wid = bytes.Index(s[i:], old) + if wid < 0 { + break + } + + w += copy(s[w:], s[i:i+wid]) + w += copy(s[w:], new) + i += wid + len(old) + } + + w += copy(s[w:], s[i:]) + return s[0:w] +} + // genSheetPasswd provides a method to generate password for worksheet // protection by given plaintext. When an Excel sheet is being protected with // a password, a 16-bit (two byte) long hash is generated. To verify a diff --git a/lib_test.go b/lib_test.go index 1c30c0e..4605e70 100644 --- a/lib_test.go +++ b/lib_test.go @@ -203,3 +203,8 @@ func TestCoordinatesToCellName_Error(t *testing.T) { } } } + +func TestBytesReplace(t *testing.T) { + s := []byte{0x01} + assert.EqualValues(t, s, bytesReplace(s, []byte{}, []byte{}, 0)) +} diff --git a/picture.go b/picture.go index ddc0480..fcdaa07 100644 --- a/picture.go +++ b/picture.go @@ -510,7 +510,7 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) err = nil for _, anchor := range deWsDr.TwoCellAnchor { deTwoCellAnchor = new(decodeTwoCellAnchor) - if err = f.xmlNewDecoder(bytes.NewReader([]byte("" + anchor.Content + ""))). + if err = f.xmlNewDecoder(bytes.NewReader(stringToBytes("" + anchor.Content + ""))). Decode(deTwoCellAnchor); err != nil && err != io.EOF { err = fmt.Errorf("xml decode error: %s", err) return diff --git a/rows.go b/rows.go index d56c81c..8f000b3 100644 --- a/rows.go +++ b/rows.go @@ -424,14 +424,16 @@ func (f *File) RemoveRow(sheet string, row int) error { if row > len(xlsx.SheetData.Row) { return f.adjustHelper(sheet, rows, row, -1) } - for rowIdx := range xlsx.SheetData.Row { - if xlsx.SheetData.Row[rowIdx].R == row { - xlsx.SheetData.Row = append(xlsx.SheetData.Row[:rowIdx], - xlsx.SheetData.Row[rowIdx+1:]...)[:len(xlsx.SheetData.Row)-1] - return f.adjustHelper(sheet, rows, row, -1) + keep := 0 + for rowIdx := 0; rowIdx < len(xlsx.SheetData.Row); rowIdx++ { + v := &xlsx.SheetData.Row[rowIdx] + if v.R != row { + xlsx.SheetData.Row[keep] = *v + keep++ } } - return nil + xlsx.SheetData.Row = xlsx.SheetData.Row[:keep] + return f.adjustHelper(sheet, rows, row, -1) } // InsertRow provides a function to insert a new row after given Excel row diff --git a/sheet.go b/sheet.go index 6ddd629..a3276c2 100644 --- a/sheet.go +++ b/sheet.go @@ -206,9 +206,9 @@ func (f *File) setAppXML() { // requirements about the structure of the input XML. This function is a // horrible hack to fix that after the XML marshalling is completed. func replaceRelationshipsBytes(content []byte) []byte { - oldXmlns := []byte(`xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships`) - newXmlns := []byte("r") - return bytes.Replace(content, oldXmlns, newXmlns, -1) + oldXmlns := stringToBytes(`xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships`) + newXmlns := stringToBytes("r") + return bytesReplace(content, oldXmlns, newXmlns, -1) } // SetActiveSheet provides function to set default active worksheet of XLSX by diff --git a/sparkline.go b/sparkline.go index ef99da6..f1e1f40 100644 --- a/sparkline.go +++ b/sparkline.go @@ -516,7 +516,7 @@ func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, for idx, ext = range decodeExtLst.Ext { if ext.URI == ExtURISparklineGroups { decodeSparklineGroups = new(decodeX14SparklineGroups) - if err = f.xmlNewDecoder(bytes.NewReader([]byte(ext.Content))). + if err = f.xmlNewDecoder(bytes.NewReader(stringToBytes(ext.Content))). Decode(decodeSparklineGroups); err != nil && err != io.EOF { return } diff --git a/stream.go b/stream.go index 98cf828..1af0b9f 100644 --- a/stream.go +++ b/stream.go @@ -365,7 +365,7 @@ func writeCell(buf *bufferedWriter, c xlsxC) { buf.WriteString(`>`) if c.V != "" { buf.WriteString(``) - xml.EscapeText(buf, []byte(c.V)) + xml.EscapeText(buf, stringToBytes(c.V)) buf.WriteString(``) } buf.WriteString(``) -- cgit v1.2.1