package xlsx
import (
"bytes"
"encoding/xml"
"strconv"
"strings"
"testing"
)
// Test we can correctly open a XSLX file and return a xlsx.File
// struct.
func TestOpenFile(t *testing.T) {
var xlsxFile *File
var error error
xlsxFile, error = OpenFile("testfile.xlsx")
if error != nil {
t.Error(error.Error())
return
}
if xlsxFile == nil {
t.Error("OpenFile returned nil FileInterface without generating an os.Error")
return
}
}
// Test that when we open a real XLSX file we create xlsx.Sheet
// objects for the sheets inside the file and that these sheets are
// themselves correct.
func TestCreateSheet(t *testing.T) {
var xlsxFile *File
var error error
var sheet *Sheet
var row *Row
xlsxFile, error = OpenFile("testfile.xlsx")
if error != nil {
t.Error(error.Error())
return
}
if xlsxFile == nil {
t.Error("OpenFile returned a nil File pointer but did not generate an error.")
return
}
sheetLen := len(xlsxFile.Sheets)
if sheetLen == 0 {
t.Error("Expected len(xlsxFile.Sheets) > 0, but got ", sheetLen)
return
}
sheet = xlsxFile.Sheets[0]
rowLen := len(sheet.Rows)
if rowLen != 2 {
t.Error("Expected len(sheet.Rows) == 2, but got ", rowLen)
return
}
row = sheet.Rows[0]
if len(row.Cells) != 2 {
t.Error("Expected len(row.Cells) == 2")
return
}
cell := row.Cells[0]
cellstring := cell.String()
if cellstring != "Foo" {
t.Error("Expected cell.String() == 'Foo', got ", cellstring)
}
}
// Test that GetStyle correctly converts the xlsxStyle.Fonts.
func TestGetStyleWithFonts(t *testing.T) {
var cell *Cell
var style *Style
var xStyles *xlsxStyles
var fonts []xlsxFont
var cellXfs []xlsxXf
fonts = make([]xlsxFont, 1)
fonts[0] = xlsxFont{
Sz: xlsxVal{Val: "10"},
Name: xlsxVal{Val: "Calibra"}}
cellXfs = make([]xlsxXf, 1)
cellXfs[0] = xlsxXf{ApplyFont: true, FontId: 0}
xStyles = &xlsxStyles{Fonts: fonts, CellXfs: cellXfs}
cell = &Cell{Value: "123", styleIndex: 1, styles: xStyles}
style = cell.GetStyle()
if style == nil {
t.Error("No style returned by cell.GetStyle()")
}
if style.Font.Size != 10 {
t.Error("Expected style.Font.Size == 10, but got ", style.Font.Size)
}
if style.Font.Name != "Calibra" {
t.Error("Expected style.Font.Name == 'Calibra', but got ", style.Font.Name)
}
}
// Test that GetStyle correctly converts the xlsxStyle.Fills.
func TestGetStyleWithFills(t *testing.T) {
var cell *Cell
var style *Style
var xStyles *xlsxStyles
var fills []xlsxFill
var cellXfs []xlsxXf
fills = make([]xlsxFill, 1)
fills[0] = xlsxFill{
PatternFill: xlsxPatternFill{
PatternType: "solid",
FgColor: xlsxColor{RGB: "FF000000"},
BgColor: xlsxColor{RGB: "00FF0000"}}}
cellXfs = make([]xlsxXf, 1)
cellXfs[0] = xlsxXf{ApplyFill: true, FillId: 0}
xStyles = &xlsxStyles{Fills: fills, CellXfs: cellXfs}
cell = &Cell{Value: "123", styleIndex: 1, styles: xStyles}
style = cell.GetStyle()
fill := style.Fill
if fill.PatternType != "solid" {
t.Error("Expected fill.PatternType == 'solid', but got ",
fill.PatternType)
}
if fill.BgColor != "00FF0000" {
t.Error("Expected fill.BgColor == '00FF0000', but got ",
fill.BgColor)
}
if fill.FgColor != "FF000000" {
t.Error("Expected fill.FgColor == 'FF000000', but got ",
fill.FgColor)
}
}
// Test that GetStyle correctly converts the xlsxStyle.Borders.
func TestGetStyleWithBorders(t *testing.T) {
var cell *Cell
var style *Style
var xStyles *xlsxStyles
var borders []xlsxBorder
var cellXfs []xlsxXf
borders = make([]xlsxBorder, 1)
borders[0] = xlsxBorder{
Left: xlsxLine{Style: "thin"},
Right: xlsxLine{Style: "thin"},
Top: xlsxLine{Style: "thin"},
Bottom: xlsxLine{Style: "thin"}}
cellXfs = make([]xlsxXf, 1)
cellXfs[0] = xlsxXf{ApplyBorder: true, BorderId: 0}
xStyles = &xlsxStyles{Borders: borders, CellXfs: cellXfs}
cell = &Cell{Value: "123", styleIndex: 1, styles: xStyles}
style = cell.GetStyle()
border := style.Border
if border.Left != "thin" {
t.Error("Expected border.Left == 'thin', but got ",
border.Left)
}
if border.Right != "thin" {
t.Error("Expected border.Right == 'thin', but got ",
border.Right)
}
if border.Top != "thin" {
t.Error("Expected border.Top == 'thin', but got ",
border.Top)
}
if border.Bottom != "thin" {
t.Error("Expected border.Bottom == 'thin', but got ",
border.Bottom)
}
}
// Test that we can correctly extract a reference table from the
// sharedStrings.xml file embedded in the XLSX file and return a
// reference table of string values from it.
func TestReadSharedStringsFromZipFile(t *testing.T) {
var xlsxFile *File
var error error
xlsxFile, error = OpenFile("testfile.xlsx")
if error != nil {
t.Error(error.Error())
return
}
if xlsxFile.referenceTable == nil {
t.Error("expected non nil xlsxFile.referenceTable")
return
}
}
func testXf(t *testing.T, result, expected *xlsxXf) {
if result.ApplyAlignment != expected.ApplyAlignment {
t.Error("Expected result.ApplyAlignment == ", expected.ApplyAlignment,
", got", result.ApplyAlignment)
return
}
if result.ApplyBorder != expected.ApplyBorder {
t.Error("Expected result.ApplyBorder == ", expected.ApplyBorder,
", got ", result.ApplyBorder)
return
}
if result.ApplyFont != expected.ApplyFont {
t.Error("Expect result.ApplyFont == ", expected.ApplyFont,
", got ", result.ApplyFont)
return
}
if result.ApplyFill != expected.ApplyFill {
t.Error("Expected result.ApplyFill == ", expected.ApplyFill,
", got ", result.ApplyFill)
return
}
if result.ApplyProtection != expected.ApplyProtection {
t.Error("Expexcted result.ApplyProtection == ", expected.ApplyProtection,
", got ", result.ApplyProtection)
return
}
if result.BorderId != expected.BorderId {
t.Error("Expected BorderId == ", expected.BorderId,
". got ", result.BorderId)
return
}
if result.FillId != expected.FillId {
t.Error("Expected result.FillId == ", expected.FillId,
", got ", result.FillId)
return
}
if result.FontId != expected.FontId {
t.Error("Expected result.FontId == ", expected.FontId,
", got ", result.FontId)
return
}
if result.NumFmtId != expected.NumFmtId {
t.Error("Expected result.NumFmtId == ", expected.NumFmtId,
", got ", result.NumFmtId)
return
}
}
// We can correctly extract a style table from the style.xml file
// embedded in the XLSX file and return a styles struct from it.
func TestReadStylesFromZipFile(t *testing.T) {
var xlsxFile *File
var error error
var fontCount, fillCount, borderCount, cellStyleXfCount, cellXfCount int
var font xlsxFont
var fill xlsxFill
var border xlsxBorder
var xf xlsxXf
xlsxFile, error = OpenFile("testfile.xlsx")
if error != nil {
t.Error(error.Error())
return
}
if xlsxFile.styles == nil {
t.Error("expected non nil xlsxFile.styles")
return
}
fontCount = len(xlsxFile.styles.Fonts)
if fontCount != 4 {
t.Error("expected exactly 4 xslxFonts, got ", fontCount)
return
}
font = xlsxFile.styles.Fonts[0]
if font.Sz.Val != "11" {
t.Error("expected font.Sz.Val == 11, got ", font.Sz.Val)
return
}
if font.Name.Val != "Calibri" {
t.Error("expected font.Name.Val == 'Calibri', got ", font.Name.Val)
return
}
fillCount = len(xlsxFile.styles.Fills)
if fillCount != 3 {
t.Error("Expected exactly 3 xlsxFills, got ", fillCount)
return
}
fill = xlsxFile.styles.Fills[2]
if fill.PatternFill.PatternType != "solid" {
t.Error("Expected PatternFill.PatternType == 'solid', but got ",
fill.PatternFill.PatternType)
return
}
borderCount = len(xlsxFile.styles.Borders)
if borderCount != 2 {
t.Error("Expected exactly 2 xlsxBorders, got ", borderCount)
return
}
border = xlsxFile.styles.Borders[1]
if border.Left.Style != "thin" {
t.Error("Expected border.Left.Style == 'thin', got ", border.Left.Style)
return
}
if border.Right.Style != "thin" {
t.Error("Expected border.Right.Style == 'thin', got ", border.Right.Style)
return
}
if border.Top.Style != "thin" {
t.Error("Expected border.Top.Style == 'thin', got ", border.Top.Style)
return
}
if border.Bottom.Style != "thin" {
t.Error("Expected border.Bottom.Style == 'thin', got ", border.Bottom.Style)
return
}
cellStyleXfCount = len(xlsxFile.styles.CellStyleXfs)
if cellStyleXfCount != 20 {
t.Error("Expected excactly 20 cellStyleXfs, got ", cellStyleXfCount)
return
}
xf = xlsxFile.styles.CellStyleXfs[0]
expectedXf := &xlsxXf{
ApplyAlignment: true,
ApplyBorder: true,
ApplyFont: true,
ApplyFill: false,
ApplyProtection: true,
BorderId: 0,
FillId: 0,
FontId: 0,
NumFmtId: 164}
testXf(t, &xf, expectedXf)
cellXfCount = len(xlsxFile.styles.CellXfs)
if cellXfCount != 3 {
t.Error("Expected excactly 3 cellXfs, got ", cellXfCount)
return
}
xf = xlsxFile.styles.CellXfs[0]
expectedXf = &xlsxXf{
ApplyAlignment: false,
ApplyBorder: false,
ApplyFont: false,
ApplyFill: false,
ApplyProtection: false,
BorderId: 0,
FillId: 0,
FontId: 0,
NumFmtId: 164}
testXf(t, &xf, expectedXf)
}
// We can correctly extract a map of relationship Ids to the worksheet files in
// which they are contained from the XLSX file.
func TestReadWorkbookRelationsFromZipFile(t *testing.T) {
var xlsxFile *File
var error error
xlsxFile, error = OpenFile("testfile.xlsx")
if error != nil {
t.Error(error.Error())
return
}
sheetCount := len(xlsxFile.Sheet)
if sheetCount != 3 {
t.Error("Expected 3 items in xlsxFile.Sheet, but found ", strconv.Itoa(sheetCount))
}
}
// We can extract a map of relationship Ids to the worksheet files in
// which they are contained from the XLSX file, even when the
// worksheet files have arbitrary, non-numeric names.
func TestReadWorkbookRelationsFromZipFileWithFunnyNames(t *testing.T) {
var xlsxFile *File
var error error
xlsxFile, error = OpenFile("testrels.xlsx")
if error != nil {
t.Error(error.Error())
return
}
sheetCount := len(xlsxFile.Sheet)
if sheetCount != 2 {
t.Error("Expected 3 items in xlsxFile.Sheet, but found ", strconv.Itoa(sheetCount))
}
bob := xlsxFile.Sheet["Bob"]
row1 := bob.Rows[0]
cell1 := row1.Cells[0]
if cell1.String() != "I am Bob" {
t.Error("Expected cell1.String() == 'I am Bob', but got '" + cell1.String() + "'")
}
}
func TestLettersToNumeric(t *testing.T) {
cases := map[string]int{"A": 0, "G": 6, "z": 25, "AA": 26, "Az": 51,
"BA": 52, "Bz": 77, "ZA": 26*26 + 0, "ZZ": 26*26 + 25,
"AAA": 26*26 + 26 + 0, "AMI": 1022}
for input, ans := range cases {
output := lettersToNumeric(input)
if output != ans {
t.Error("Expected output '"+input+"' == ", ans,
"but got ", strconv.Itoa(output))
}
}
}
func TestLetterOnlyMapFunction(t *testing.T) {
var input string = "ABC123"
var output string = strings.Map(letterOnlyMapF, input)
if output != "ABC" {
t.Error("Expected output == 'ABC' but got ", output)
}
input = "abc123"
output = strings.Map(letterOnlyMapF, input)
if output != "ABC" {
t.Error("Expected output == 'ABC' but got ", output)
}
}
func TestIntOnlyMapFunction(t *testing.T) {
var input string = "ABC123"
var output string = strings.Map(intOnlyMapF, input)
if output != "123" {
t.Error("Expected output == '123' but got ", output)
}
}
func TestGetCoordsFromCellIDString(t *testing.T) {
var cellIDString string = "A3"
var x, y int
var error error
x, y, error = getCoordsFromCellIDString(cellIDString)
if error != nil {
t.Error(error)
}
if x != 0 {
t.Error("Expected x == 0, but got ", strconv.Itoa(x))
}
if y != 2 {
t.Error("Expected y == 2, but got ", strconv.Itoa(y))
}
}
func TestGetMaxMinFromDimensionRef(t *testing.T) {
var dimensionRef string = "A1:B2"
var minx, miny, maxx, maxy int
var err error
minx, miny, maxx, maxy, err = getMaxMinFromDimensionRef(dimensionRef)
if err != nil {
t.Error(err)
}
if minx != 0 {
t.Error("Expected minx == 0, but got ", strconv.Itoa(minx))
}
if miny != 0 {
t.Error("Expected miny == 0, but got ", strconv.Itoa(miny))
}
if maxx != 1 {
t.Error("Expected maxx == 0, but got ", strconv.Itoa(maxx))
}
if maxy != 1 {
t.Error("Expected maxy == 0, but got ", strconv.Itoa(maxy))
}
}
func TestGetRangeFromString(t *testing.T) {
var rangeString string
var lower, upper int
var error error
rangeString = "1:3"
lower, upper, error = getRangeFromString(rangeString)
if error != nil {
t.Error(error)
}
if lower != 1 {
t.Error("Expected lower bound == 1, but got ", strconv.Itoa(lower))
}
if upper != 3 {
t.Error("Expected upper bound == 3, but got ", strconv.Itoa(upper))
}
}
func TestMakeRowFromSpan(t *testing.T) {
var rangeString string
var row *Row
var length int
rangeString = "1:3"
row = makeRowFromSpan(rangeString)
length = len(row.Cells)
if length != 3 {
t.Error("Expected a row with 3 cells, but got ", strconv.Itoa(length))
}
rangeString = "5:7" // Note - we ignore lower bound!
row = makeRowFromSpan(rangeString)
length = len(row.Cells)
if length != 7 {
t.Error("Expected a row with 7 cells, but got ", strconv.Itoa(length))
}
rangeString = "1:1"
row = makeRowFromSpan(rangeString)
length = len(row.Cells)
if length != 1 {
t.Error("Expected a row with 1 cells, but got ", strconv.Itoa(length))
}
}
func TestReadRowsFromSheet(t *testing.T) {
var sharedstringsXML = bytes.NewBufferString(`
Foo
Bar
Baz
Quuk
`)
var sheetxml = bytes.NewBufferString(`
0
1
2
3
`)
worksheet := new(xlsxWorksheet)
error := xml.NewDecoder(sheetxml).Decode(worksheet)
if error != nil {
t.Error(error.Error())
return
}
sst := new(xlsxSST)
error = xml.NewDecoder(sharedstringsXML).Decode(sst)
if error != nil {
t.Error(error.Error())
return
}
file := new(File)
file.referenceTable = MakeSharedStringRefTable(sst)
rows, maxCols, maxRows := readRowsFromSheet(worksheet, file)
if maxRows != 2 {
t.Error("Expected maxRows == 2")
}
if maxCols != 2 {
t.Error("Expected maxCols == 2")
}
row := rows[0]
if len(row.Cells) != 2 {
t.Error("Expected len(row.Cells) == 2, got ", strconv.Itoa(len(row.Cells)))
}
cell1 := row.Cells[0]
if cell1.String() != "Foo" {
t.Error("Expected cell1.String() == 'Foo', got ", cell1.String())
}
cell2 := row.Cells[1]
if cell2.String() != "Bar" {
t.Error("Expected cell2.String() == 'Bar', got ", cell2.String())
}
}
func TestReadRowsFromSheetWithLeadingEmptyRows(t *testing.T) {
var sharedstringsXML = bytes.NewBufferString(`
ABCDEF`)
var sheetxml = bytes.NewBufferString(`
0
1
`)
worksheet := new(xlsxWorksheet)
error := xml.NewDecoder(sheetxml).Decode(worksheet)
if error != nil {
t.Error(error.Error())
return
}
sst := new(xlsxSST)
error = xml.NewDecoder(sharedstringsXML).Decode(sst)
if error != nil {
t.Error(error.Error())
return
}
file := new(File)
file.referenceTable = MakeSharedStringRefTable(sst)
rows, maxCols, maxRows := readRowsFromSheet(worksheet, file)
if maxRows != 2 {
t.Error("Expected maxRows == 2, got ", strconv.Itoa(len(rows)))
}
if maxCols != 1 {
t.Error("Expected maxCols == 1, got ", strconv.Itoa(maxCols))
}
}
func TestReadRowsFromSheetWithEmptyCells(t *testing.T) {
var sharedstringsXML = bytes.NewBufferString(`
BobAliceSueYesNo`)
var sheetxml = bytes.NewBufferString(`
0
1
2
3
4
3
4
3
`)
worksheet := new(xlsxWorksheet)
error := xml.NewDecoder(sheetxml).Decode(worksheet)
if error != nil {
t.Error(error.Error())
return
}
sst := new(xlsxSST)
error = xml.NewDecoder(sharedstringsXML).Decode(sst)
if error != nil {
t.Error(error.Error())
return
}
file := new(File)
file.referenceTable = MakeSharedStringRefTable(sst)
rows, maxCols, maxRows := readRowsFromSheet(worksheet, file)
if maxRows != 3 {
t.Error("Expected maxRows == 3, got ", strconv.Itoa(len(rows)))
}
if maxCols != 3 {
t.Error("Expected maxCols == 3, got ", strconv.Itoa(maxCols))
}
row := rows[2]
if len(row.Cells) != 3 {
t.Error("Expected len(row.Cells) == 3, got ", strconv.Itoa(len(row.Cells)))
}
cell1 := row.Cells[0]
if cell1.String() != "No" {
t.Error("Expected cell1.String() == 'No', got ", cell1.String())
}
cell2 := row.Cells[1]
if cell2.String() != "" {
t.Error("Expected cell2.String() == '', got ", cell2.String())
}
cell3 := row.Cells[2]
if cell3.String() != "Yes" {
t.Error("Expected cell3.String() == 'Yes', got ", cell3.String())
}
}
func TestReadRowsFromSheetWithTrailingEmptyCells(t *testing.T) {
var row *Row
var cell1, cell2, cell3, cell4 *Cell
var sharedstringsXML = bytes.NewBufferString(`
ABCD`)
var sheetxml = bytes.NewBufferString(`
0123
1
1
1
1
1
1
1
`)
worksheet := new(xlsxWorksheet)
error := xml.NewDecoder(sheetxml).Decode(worksheet)
if error != nil {
t.Error(error.Error())
return
}
sst := new(xlsxSST)
error = xml.NewDecoder(sharedstringsXML).Decode(sst)
if error != nil {
t.Error(error.Error())
return
}
file := new(File)
file.referenceTable = MakeSharedStringRefTable(sst)
rows, maxCol, maxRow := readRowsFromSheet(worksheet, file)
if maxCol != 4 {
t.Error("Expected maxCol == 4, got ", strconv.Itoa(maxCol))
}
if maxRow != 8 {
t.Error("Expected maxRow == 8, got ", strconv.Itoa(maxRow))
}
row = rows[0]
if len(row.Cells) != 4 {
t.Error("Expected len(row.Cells) == 4, got ", strconv.Itoa(len(row.Cells)))
}
cell1 = row.Cells[0]
if cell1.String() != "A" {
t.Error("Expected cell1.String() == 'A', got ", cell1.String())
}
cell2 = row.Cells[1]
if cell2.String() != "B" {
t.Error("Expected cell2.String() == 'B', got ", cell2.String())
}
cell3 = row.Cells[2]
if cell3.String() != "C" {
t.Error("Expected cell3.String() == 'C', got ", cell3.String())
}
cell4 = row.Cells[3]
if cell4.String() != "D" {
t.Error("Expected cell4.String() == 'D', got ", cell4.String())
}
row = rows[1]
if len(row.Cells) != 4 {
t.Error("Expected len(row.Cells) == 4, got ", strconv.Itoa(len(row.Cells)))
}
cell1 = row.Cells[0]
if cell1.String() != "1" {
t.Error("Expected cell1.String() == '1', got ", cell1.String())
}
cell2 = row.Cells[1]
if cell2.String() != "" {
t.Error("Expected cell2.String() == '', got ", cell2.String())
}
cell3 = row.Cells[2]
if cell3.String() != "" {
t.Error("Expected cell3.String() == '', got ", cell3.String())
}
cell4 = row.Cells[3]
if cell4.String() != "" {
t.Error("Expected cell4.String() == '', got ", cell4.String())
}
}