Explorar el Código

Fix for merge-cell borders

Colin Fox hace 10 años
padre
commit
27f0f707d4
Se han modificado 7 ficheros con 134 adiciones y 49 borrados
  1. 4 1
      file_test.go
  2. 79 3
      sheet.go
  3. 9 3
      sheet_test.go
  4. 1 1
      style.go
  5. 34 35
      xmlStyle.go
  6. 4 3
      xmlStyle_test.go
  7. 3 3
      xmlWorksheet.go

+ 4 - 1
file_test.go

@@ -303,10 +303,12 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	// sheets
 	expectedSheet1 := `<?xml version="1.0" encoding="UTF-8"?>
 <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="true" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" style="1" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" s="1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
+
 	c.Assert(parts["xl/worksheets/sheet1.xml"], Equals, expectedSheet1)
 
 	expectedSheet2 := `<?xml version="1.0" encoding="UTF-8"?>
 <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="false" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" style="1" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" s="1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
+
 	c.Assert(parts["xl/worksheets/sheet2.xml"], Equals, expectedSheet2)
 
 	// .rels.xml
@@ -680,7 +682,8 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	// For now we only allow simple string data in the
 	// spreadsheet.  Style support will follow.
 	expectedStyles := `<?xml version="1.0" encoding="UTF-8"?>
-<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="12"/><name val="Verdana"/><family val="0"/><charset val="0"/></font></fonts><fills count="2"><fill><patternFill patternType="none"><fgColor rgb="FFFFFFFF"/><bgColor rgb="00000000"/></patternFill></fill><fill><patternFill patternType="lightGray"/></fill></fills><borders count="1"><border><left style="none"></left><right style="none"></right><top style="none"></top><bottom style="none"></bottom></border></borders><cellStyleXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf></cellStyleXfs><cellXfs count="2"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf></cellXfs></styleSheet>`
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="12"/><name val="Verdana"/><family val="0"/><charset val="0"/></font></fonts><fills count="2"><fill><patternFill patternType="none"><fgColor rgb="FFFFFFFF"/><bgColor rgb="00000000"/></patternFill></fill><fill><patternFill patternType="lightGray"/></fill></fills><borders count="1"><border><left style=""></left><right style=""></right><top style=""></top><bottom style=""></bottom></border></borders><cellStyleXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf></cellStyleXfs><cellXfs count="2"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf></cellXfs></styleSheet>`
+
 	c.Assert(parts["xl/styles.xml"], Equals, expectedStyles)
 }
 

+ 79 - 3
sheet.go

@@ -78,10 +78,17 @@ func (s *Sheet) Col(idx int) *Col {
 // containing the data from the field "A1" on the spreadsheet.
 func (sh *Sheet) Cell(row, col int) *Cell {
 
-	if len(sh.Rows) > row && sh.Rows[row] != nil && len(sh.Rows[row].Cells) > col {
-		return sh.Rows[row].Cells[col]
+	// If the user requests a row beyond what we have, then extend.
+	for len(sh.Rows) <= row {
+		sh.AddRow()
 	}
-	return new(Cell)
+
+	r := sh.Rows[row]
+	for len(r.Cells) <= col {
+		r.AddCell()
+	}
+
+	return r.Cells[col]
 }
 
 //Set the width of a single column or multiple columns.
@@ -103,6 +110,68 @@ func (s *Sheet) SetColWidth(startcol, endcol int, width float64) error {
 	return nil
 }
 
+// When merging cells, the cell may be the 'original' or the 'covered'.
+// First, figure out which cells are merge starting points. Then create
+// the necessary cells underlying the merge area.
+// Then go through all the underlying cells and apply the appropriate
+// border, based on the original cell.
+func (s *Sheet) handleMerged() {
+	merged := make(map[string]*Cell)
+
+	for r, row := range s.Rows {
+		for c, cell := range row.Cells {
+			if cell.HMerge > 0 || cell.VMerge > 0 {
+				coord := fmt.Sprintf("%s%d", numericToLetters(c), r+1)
+				merged[coord] = cell
+			}
+		}
+	}
+
+	// This loop iterates over all cells that should be merged and applies the correct
+	// borders to them depending on their position. If any cells required by the merge
+	// are missing, they will be allocated by s.Cell().
+	for key, cell := range merged {
+		mainstyle := cell.GetStyle()
+
+		top := mainstyle.Border.Top
+		left := mainstyle.Border.Left
+		right := mainstyle.Border.Right
+		bottom := mainstyle.Border.Bottom
+
+		// When merging cells, the upper left cell does not maintain
+		// the original borders
+		mainstyle.Border.Top = ""
+		mainstyle.Border.Left = ""
+		mainstyle.Border.Right = ""
+		mainstyle.Border.Bottom = ""
+
+		maincol, mainrow, _ := getCoordsFromCellIDString(key)
+		for rownum := 0; rownum <= cell.VMerge; rownum++ {
+			for colnum := 0; colnum <= cell.HMerge; colnum++ {
+				tmpcell := s.Cell(mainrow+rownum, maincol+colnum)
+				style := tmpcell.GetStyle()
+				style.ApplyBorder = true
+
+				if rownum == 0 {
+					style.Border.Top = top
+				}
+
+				if rownum == (cell.VMerge) {
+					style.Border.Bottom = bottom
+				}
+
+				if colnum == 0 {
+					style.Border.Left = left
+				}
+
+				if colnum == (cell.HMerge) {
+					style.Border.Right = right
+				}
+			}
+		}
+	}
+}
+
 // Dump sheet to its XML representation, intended for internal use only
 func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxWorksheet {
 	worksheet := newXlsxWorksheet()
@@ -110,9 +179,15 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 	maxRow := 0
 	maxCell := 0
 
+	// Scan through the sheet and see if there are any merged cells. If there
+	// are, we may need to extend the size of the sheet. There needs to be
+	// phantom cells underlying the area covered by the merged cell
+	s.handleMerged()
+
 	if s.Selected {
 		worksheet.SheetViews.SheetView[0].TabSelected = true
 	}
+
 	if s.SheetFormat.DefaultRowHeight != 0 {
 		worksheet.SheetFormatPr.DefaultRowHeight = s.SheetFormat.DefaultRowHeight
 	}
@@ -211,6 +286,7 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 				xC.V = cell.Value
 				xC.S = XfId
 			}
+
 			xRow.C = append(xRow.C, xC)
 
 			if cell.HMerge > 0 || cell.VMerge > 0 {

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 9 - 3
sheet_test.go


+ 1 - 1
style.go

@@ -183,7 +183,7 @@ func DefaultFill() *Fill {
 }
 
 func DefaultBorder() *Border {
-	return NewBorder("none", "none", "none", "none")
+	return NewBorder("", "", "", "")
 }
 
 func DefaultAlignment() *Alignment {

+ 34 - 35
xmlStyle.go

@@ -97,7 +97,12 @@ func (styles *xlsxStyleSheet) reset() {
 	styles.Fonts = xlsxFonts{}
 	styles.Fills = xlsxFills{}
 	styles.Borders = xlsxBorders{}
+
+	// Microsoft seems to want an emtpy border to start with
+	styles.addBorder(xlsxBorder{})
+
 	styles.CellStyleXfs = xlsxCellStyleXfs{}
+
 	// add default xf
 	styles.CellXfs = xlsxCellXfs{Count: 1, Xf: []xlsxXf{xlsxXf{}}}
 	styles.NumFmts = xlsxNumFmts{}
@@ -256,6 +261,7 @@ func (styles *xlsxStyleSheet) addBorder(xBorder xlsxBorder) (index int) {
 	}
 	styles.Borders.Border = append(styles.Borders.Border, xBorder)
 	index = styles.Borders.Count
+
 	styles.Borders.Count += 1
 	return
 }
@@ -655,7 +661,7 @@ func (color *xlsxColor) Equals(other xlsxColor) bool {
 // as I need.
 type xlsxBorders struct {
 	Count  int          `xml:"count,attr"`
-	Border []xlsxBorder `xml:"border,omitempty"`
+	Border []xlsxBorder `xml:"border"`
 }
 
 func (borders *xlsxBorders) Marshal(outputBorderMap map[int]int) (result string, err error) {
@@ -697,46 +703,39 @@ func (border *xlsxBorder) Equals(other xlsxBorder) bool {
 	return border.Left.Equals(other.Left) && border.Right.Equals(other.Right) && border.Top.Equals(other.Top) && border.Bottom.Equals(other.Bottom)
 }
 
+// To get borders to work correctly in Excel, you have to always start with an
+// empty set of borders. There was logic in this function that would strip out
+// empty elements, but unfortunately that would cause the border to fail.
+
 func (border *xlsxBorder) Marshal() (result string, err error) {
-	emit := false
 	subparts := ""
-	if border.Left.Style != "" {
-		emit = true
-		subparts += fmt.Sprintf(`<left style="%s">`, border.Left.Style)
-		if border.Left.Color.RGB != "" {
-			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Left.Color.RGB)
-		}
-		subparts += `</left>`
-	}
-	if border.Right.Style != "" {
-		emit = true
-		subparts += fmt.Sprintf(`<right style="%s">`, border.Right.Style)
-		if border.Right.Color.RGB != "" {
-			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Right.Color.RGB)
-		}
-		subparts += `</right>`
+	subparts += fmt.Sprintf(`<left style="%s">`, border.Left.Style)
+	if border.Left.Color.RGB != "" {
+		subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Left.Color.RGB)
 	}
-	if border.Top.Style != "" {
-		emit = true
-		subparts += fmt.Sprintf(`<top style="%s">`, border.Top.Style)
-		if border.Top.Color.RGB != "" {
-			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Top.Color.RGB)
-		}
-		subparts += `</top>`
+	subparts += `</left>`
+
+	subparts += fmt.Sprintf(`<right style="%s">`, border.Right.Style)
+	if border.Right.Color.RGB != "" {
+		subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Right.Color.RGB)
 	}
-	if border.Bottom.Style != "" {
-		emit = true
-		subparts += fmt.Sprintf(`<bottom style="%s">`, border.Bottom.Style)
-		if border.Bottom.Color.RGB != "" {
-			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Bottom.Color.RGB)
-		}
-		subparts += `</bottom>`
+	subparts += `</right>`
+
+	subparts += fmt.Sprintf(`<top style="%s">`, border.Top.Style)
+	if border.Top.Color.RGB != "" {
+		subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Top.Color.RGB)
 	}
-	if emit {
-		result += `<border>`
-		result += subparts
-		result += `</border>`
+	subparts += `</top>`
+
+	subparts += fmt.Sprintf(`<bottom style="%s">`, border.Bottom.Style)
+	if border.Bottom.Color.RGB != "" {
+		subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Bottom.Color.RGB)
 	}
+	subparts += `</bottom>`
+
+	result += `<border>`
+	result += subparts
+	result += `</border>`
 	return
 }
 

+ 4 - 3
xmlStyle_test.go

@@ -60,6 +60,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFill(c *C) {
 }
 
 // Test we produce valid output for a style file with one border definition.
+// Empty elements are required to accommodate for Excel quirks.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
 	styles := newXlsxStyleSheet(nil)
 	styles.Borders = xlsxBorders{}
@@ -67,11 +68,11 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
 	styles.Borders.Border = make([]xlsxBorder, 1)
 	border := xlsxBorder{}
 	border.Left.Style = "solid"
-	border.Top.Style = "none"
+	border.Top.Style = ""
 	styles.Borders.Border[0] = border
-
 	expected := `<?xml version="1.0" encoding="UTF-8"?>
-<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><borders count="1"><border><left style="solid"></left><top style="none"></top></border></borders></styleSheet>`
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><borders count="1"><border><left style="solid"></left><right style=""></right><top style=""></top><bottom style=""></bottom></border></borders></styleSheet>`
+
 	result, err := styles.Marshal()
 	c.Assert(err, IsNil)
 	c.Assert(string(result), Equals, expected)

+ 3 - 3
xmlWorksheet.go

@@ -266,7 +266,7 @@ func (mc *xlsxMergeCells) getExtent(cellRef string) (int, int, error) {
 }
 
 // xlsxC directly maps the c element in the namespace
-// http://schemas.openxmlformats.org/sprceadsheetml/2006/main -
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxC struct {
@@ -277,8 +277,8 @@ type xlsxC struct {
 	F *xlsxF `xml:"f,omitempty"`      // Formula
 }
 
-// xlsxC directly maps the f element in the namespace
-// http://schemas.openxmlformats.org/sprceadsheetml/2006/main -
+// xlsxF directly maps the f element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxF struct {

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio