Browse Source

Performance improvements

xuri 5 years ago
parent
commit
0f2a905324
10 changed files with 65 additions and 20 deletions
  1. 3 2
      adjust.go
  2. 1 1
      drawing.go
  3. 2 3
      excelize.go
  4. 40 2
      lib.go
  5. 5 0
      lib_test.go
  6. 1 1
      picture.go
  7. 8 6
      rows.go
  8. 3 3
      sheet.go
  9. 1 1
      sparkline.go
  10. 1 1
      stream.go

+ 3 - 2
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)
 		}
 	}
 }

+ 1 - 1
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("<decodeTwoCellAnchor>" + wsDr.TwoCellAnchor[idx].GraphicFrame + "</decodeTwoCellAnchor>"))).
+		if err = f.xmlNewDecoder(bytes.NewReader(stringToBytes("<decodeTwoCellAnchor>" + wsDr.TwoCellAnchor[idx].GraphicFrame + "</decodeTwoCellAnchor>"))).
 			Decode(deTwoCellAnchor); err != nil && err != io.EOF {
 			err = fmt.Errorf("xml decode error: %s", err)
 			return

+ 2 - 3
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

+ 40 - 2
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

+ 5 - 0
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))
+}

+ 1 - 1
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("<decodeTwoCellAnchor>" + anchor.Content + "</decodeTwoCellAnchor>"))).
+		if err = f.xmlNewDecoder(bytes.NewReader(stringToBytes("<decodeTwoCellAnchor>" + anchor.Content + "</decodeTwoCellAnchor>"))).
 			Decode(deTwoCellAnchor); err != nil && err != io.EOF {
 			err = fmt.Errorf("xml decode error: %s", err)
 			return

+ 8 - 6
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

+ 3 - 3
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

+ 1 - 1
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
 			}

+ 1 - 1
stream.go

@@ -365,7 +365,7 @@ func writeCell(buf *bufferedWriter, c xlsxC) {
 	buf.WriteString(`>`)
 	if c.V != "" {
 		buf.WriteString(`<v>`)
-		xml.EscapeText(buf, []byte(c.V))
+		xml.EscapeText(buf, stringToBytes(c.V))
 		buf.WriteString(`</v>`)
 	}
 	buf.WriteString(`</c>`)