diff --git a/src/ExcelDataReader/CellStyle.cs b/src/ExcelDataReader/CellStyle.cs new file mode 100644 index 00000000..f7cd571b --- /dev/null +++ b/src/ExcelDataReader/CellStyle.cs @@ -0,0 +1,86 @@ +using System; + +namespace ExcelDataReader +{ + /// + /// Horizontal alignment. + /// + public enum HorizontalAlignment + { + /// + /// General. + /// + General, + + /// + /// Left. + /// + Left, + + /// + /// Centered. + /// + Centered, + + /// + /// Right. + /// + Right, + + /// + /// Filled. + /// + Filled, + + /// + /// Justified. + /// + Justified, + + /// + /// Centered across selection. + /// + CenteredAcrossSelection, + + /// + /// Distributed. + /// + Distributed, + } + + /// + /// Holds style information for a cell. + /// + public class CellStyle + { + /// + /// Gets the font index. + /// + public int FontIndex { get; internal set; } + + /// + /// Gets the number format index. + /// + public int NumberFormatIndex { get; internal set; } + + /// + /// Gets the indent level. + /// + public int IndentLevel { get; internal set; } + + /// + /// Gets the horizontal alignment. + /// + public HorizontalAlignment HorizontalAlignment { get; internal set; } + + /// + /// Gets a value indicating whether the cell is hidden. + /// + public bool Hidden { get; internal set; } + + /// + /// Gets a value indicating whether the cell is locked. + /// + public bool Locked { get; internal set; } + } +} \ No newline at end of file diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffFont.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffFont.cs new file mode 100644 index 00000000..19d9db51 --- /dev/null +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffFont.cs @@ -0,0 +1,106 @@ +using System.Text; +using ExcelDataReader.Exceptions; + +namespace ExcelDataReader.Core.BinaryFormat +{ + /// + /// The font with index 4 is omitted in all BIFF versions. This means the first four fonts have zero-based indexes, and the fifth font and all following fonts are referenced with one-based indexes. + /// + internal class XlsBiffFont : XlsBiffRecord + { + private readonly IXlsString _fontName; + + internal XlsBiffFont(byte[] bytes, uint offset, int biffVersion) + : base(bytes, offset) + { + if (Id == BIFFRECORDTYPE.FONT_V34) + { + _fontName = new XlsShortByteString(bytes, offset + 4 + 6); + } + else if (Id == BIFFRECORDTYPE.FONT && biffVersion == 2) + { + _fontName = new XlsShortByteString(bytes, offset + 4 + 4); + } + else if (Id == BIFFRECORDTYPE.FONT && biffVersion == 5) + { + _fontName = new XlsShortByteString(bytes, offset + 4 + 14); + } + else if (Id == BIFFRECORDTYPE.FONT && biffVersion == 8) + { + _fontName = new XlsShortUnicodeString(bytes, offset + 4 + 14); + } + else + { + _fontName = new XlsInternalString(string.Empty); + } + + if (Id == BIFFRECORDTYPE.FONT && biffVersion >= 5) + { + // Encodings were mapped by correlating this: + // https://docs.microsoft.com/en-us/windows/desktop/intl/code-page-identifiers + // with the FONT record character set table here: + // https://www.openoffice.org/sc/excelfileformat.pdf + var byteStringCharacterSet = ReadByte(12); + switch (byteStringCharacterSet) + { + case 0: // ANSI Latin + case 1: // System default + ByteStringEncoding = EncodingHelper.GetEncoding(1252); + break; + case 77: // Apple roman + ByteStringEncoding = EncodingHelper.GetEncoding(10000); + break; + case 128: // ANSI Japanese Shift-JIS + ByteStringEncoding = EncodingHelper.GetEncoding(932); + break; + case 129: // ANSI Korean (Hangul) + ByteStringEncoding = EncodingHelper.GetEncoding(949); + break; + case 130: // ANSI Korean (Johab) + ByteStringEncoding = EncodingHelper.GetEncoding(1361); + break; + case 134: // ANSI Chinese Simplified GBK + ByteStringEncoding = EncodingHelper.GetEncoding(936); + break; + case 136: // ANSI Chinese Traditional BIG5 + ByteStringEncoding = EncodingHelper.GetEncoding(950); + break; + case 161: // ANSI Greek + ByteStringEncoding = EncodingHelper.GetEncoding(1253); + break; + case 162: // ANSI Turkish + ByteStringEncoding = EncodingHelper.GetEncoding(1254); + break; + case 163: // ANSI Vietnamese + ByteStringEncoding = EncodingHelper.GetEncoding(1258); + break; + case 177: // ANSI Hebrew + ByteStringEncoding = EncodingHelper.GetEncoding(1255); + break; + case 178: // ANSI Arabic + ByteStringEncoding = EncodingHelper.GetEncoding(1256); + break; + case 186: // ANSI Baltic + ByteStringEncoding = EncodingHelper.GetEncoding(1257); + break; + case 204: // ANSI Cyrillic + ByteStringEncoding = EncodingHelper.GetEncoding(1251); + break; + case 222: // ANSI Thai + ByteStringEncoding = EncodingHelper.GetEncoding(874); + break; + case 238: // ANSI Latin II + ByteStringEncoding = EncodingHelper.GetEncoding(1250); + break; + case 255: // OEM Latin + ByteStringEncoding = EncodingHelper.GetEncoding(850); + break; + } + } + } + + public Encoding ByteStringEncoding { get; } + + public string GetFontName(Encoding encoding) => _fontName.GetValue(encoding); + } +} diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffStream.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffStream.cs index 3cbfcbb1..52ed48fa 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffStream.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffStream.cs @@ -254,7 +254,9 @@ public XlsBiffRecord GetRecord(Stream stream) case BIFFRECORDTYPE.XF_V2: case BIFFRECORDTYPE.XF_V3: case BIFFRECORDTYPE.XF_V4: - return new XlsBiffXF(bytes, offset); + return new XlsBiffXF(bytes, offset, biffVersion); + case BIFFRECORDTYPE.FONT: + return new XlsBiffFont(bytes, offset, biffVersion); case BIFFRECORDTYPE.MERGECELLS: return new XlsBiffMergeCells(bytes, offset); case BIFFRECORDTYPE.COLINFO: diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffXF.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffXF.cs index 628df1d6..cb30c24a 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffXF.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffXF.cs @@ -1,31 +1,110 @@ using System; -using System.Collections.Generic; -using System.Text; namespace ExcelDataReader.Core.BinaryFormat { + [Flags] + internal enum XfUsedAttributes : byte + { + NumberFormat = 0x01, + Font = 0x02, + TextStyle = 0x04, + BorderLines = 0x08, + BackgroundAreaStyle = 0x10, + CellProtection = 0x20, + } + internal class XlsBiffXF : XlsBiffRecord { - internal XlsBiffXF(byte[] bytes, uint offset) + internal XlsBiffXF(byte[] bytes, uint offset, int biffVersion) : base(bytes, offset) { switch (Id) { case BIFFRECORDTYPE.XF_V2: + Font = ReadByte(0); Format = ReadByte(2) & 0x3F; + IsLocked = (ReadByte(2) & 0x40) != 0; + IsHidden = (ReadByte(2) & 0x80) != 0; + HorizontalAlignment = (HorizontalAlignment)(ReadByte(3) & 0x07); + ParentCellStyleXf = 0xfff; + UsedAttributes = + XfUsedAttributes.NumberFormat | XfUsedAttributes.Font | XfUsedAttributes.TextStyle | + XfUsedAttributes.BorderLines | XfUsedAttributes.BackgroundAreaStyle | XfUsedAttributes.CellProtection; break; case BIFFRECORDTYPE.XF_V3: + Font = ReadByte(0); Format = ReadByte(1); + UsedAttributes = (XfUsedAttributes)(ReadByte(3) >> 2); + IsLocked = (ReadByte(2) & 1) != 0; + IsHidden = (ReadByte(2) & 2) != 0; + IsCellStyleXf = (ReadByte(2) & 4) != 0; + ParentCellStyleXf = ReadUInt16(4) >> 4; + HorizontalAlignment = (HorizontalAlignment)(ReadByte(4) & 0x07); break; case BIFFRECORDTYPE.XF_V4: + Font = ReadByte(0); Format = ReadByte(1); + IsLocked = (ReadByte(2) & 1) != 0; + IsHidden = (ReadByte(2) & 2) != 0; + IsCellStyleXf = (ReadByte(2) & 4) != 0; + ParentCellStyleXf = ReadUInt16(2) >> 4; + UsedAttributes = (XfUsedAttributes)(ReadByte(5) >> 2); + HorizontalAlignment = (HorizontalAlignment)(ReadByte(4) & 0x07); break; default: + Font = ReadUInt16(0); Format = ReadUInt16(2); + IsLocked = (ReadByte(4) & 1) != 0; + IsHidden = (ReadByte(4) & 2) != 0; + IsCellStyleXf = (ReadByte(4) & 4) != 0; + ParentCellStyleXf = ReadUInt16(4) >> 4; + HorizontalAlignment = (HorizontalAlignment)(ReadByte(6) & 0x07); + if (biffVersion < 8) + { + UsedAttributes = (XfUsedAttributes)(ReadByte(7) >> 2); + } + else if (biffVersion == 8) + { + IndentLevel = ReadByte(8) & 0x0F; + UsedAttributes = (XfUsedAttributes)(ReadByte(9) >> 2); + } + break; } + + // Paren 0xfff = do not inherit any cell style XF + if (ParentCellStyleXf == 0xfff) + { + ParentCellStyleXf = -1; + } + + // The font with index 4 is omitted in all BIFF versions. This means the first four + // fonts have zero-based indexes, and the fifth font and all following fonts are + // referenced with one-based indexes. + if (Font > 4) + { + Font--; + } } + public int Font { get; } + + public XfUsedAttributes UsedAttributes { get; } + public int Format { get; } + + public int ParentCellStyleXf { get; } + + public bool IsCellStyleXf { get; } + + public bool IsLocked { get; } + + public bool IsHidden { get; } + + public bool ApplyAlignment { get; } + + public int IndentLevel { get; } + + public HorizontalAlignment HorizontalAlignment { get; } } } diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsWorkbook.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsWorkbook.cs index 369d1be8..34d03c1f 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsWorkbook.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsWorkbook.cs @@ -67,7 +67,7 @@ internal XlsWorkbook(Stream stream, string password, Encoding fallbackEncoding) public XlsBiffSimpleValueRecord Backup { get; set; } - public List Fonts { get; } = new List(); + public List Fonts { get; } = new List(); public List Sheets { get; } = new List(); @@ -138,6 +138,37 @@ public IEnumerable ReadWorksheets() } } + internal void AddXf(XlsBiffXF xf) + { + // Ignore used flags in Cell XFs + var applyFont = !xf.IsCellStyleXf || (xf.UsedAttributes & XfUsedAttributes.Font) != 0; + var applyNumberFormat = !xf.IsCellStyleXf || (xf.UsedAttributes & XfUsedAttributes.NumberFormat) != 0; + var applyAlignment = !xf.IsCellStyleXf || (xf.UsedAttributes & XfUsedAttributes.TextStyle) != 0; + var applyProtection = !xf.IsCellStyleXf || (xf.UsedAttributes & XfUsedAttributes.CellProtection) != 0; + var extendedFormat = new ExtendedFormat() + { + FontIndex = xf.Font, + NumberFormatIndex = xf.Format, + Locked = xf.IsLocked, + Hidden = xf.IsHidden, + HorizontalAlignment = xf.HorizontalAlignment, + IndentLevel = xf.IndentLevel, + ParentCellStyleXf = xf.ParentCellStyleXf, + ApplyFont = applyFont, + ApplyNumberFormat = applyNumberFormat, + ApplyTextAlignment = applyAlignment, + ApplyProtection = applyProtection, + }; + + // The workbook holds two kinds of XF records: Cell XFs, and Cell Style XFs. + // In the binary XLS format, both kinds of XF records are saved in a single list, + // whereas the XLSX format has two separate lists - like the CommonWorkbook internals. + // The Cell XFs hold indexes into the Cell Style XF list, so adding the XF in both lists + // here to keep the indexes the same. + ExtendedFormats.Add(extendedFormat); + CellStyleExtendedFormats.Add(extendedFormat); + } + private void ReadWorkbookGlobals(XlsBiffStream biffStream) { XlsBiffRecord rec; @@ -173,7 +204,7 @@ private void ReadWorkbookGlobals(XlsBiffStream biffStream) break; case BIFFRECORDTYPE.FONT: case BIFFRECORDTYPE.FONT_V34: - Fonts.Add(rec); + Fonts.Add((XlsBiffFont)rec); break; case BIFFRECORDTYPE.FORMAT_V23: { @@ -193,7 +224,7 @@ private void ReadWorkbookGlobals(XlsBiffStream biffStream) case BIFFRECORDTYPE.XF_V4: case BIFFRECORDTYPE.XF_V3: case BIFFRECORDTYPE.XF_V2: - AddExtendedFormat(GetExtendedFormatCount(), ((XlsBiffXF)rec).Format, true); + AddXf((XlsBiffXF)rec); break; case BIFFRECORDTYPE.SST: SST = (XlsBiffSST)rec; @@ -224,4 +255,4 @@ private void ReadWorkbookGlobals(XlsBiffStream biffStream) } } } -} \ No newline at end of file +} diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsWorksheet.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsWorksheet.cs index 2e23259a..593b5eea 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsWorksheet.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsWorksheet.cs @@ -118,16 +118,6 @@ public IEnumerable ReadRows() } } - public NumberFormatString GetNumberFormatString(int numberFormatIndex) - { - if (Workbook.Formats.TryGetValue(numberFormatIndex, out var fmtString)) - { - return fmtString; - } - - return null; - } - /// /// Find how many rows to read at a time and their offset in the file. /// If rows are stored sequentially in the file, returns a block size of up to 32 rows. @@ -223,8 +213,8 @@ private XlsRowBlock ReadNextBlock(XlsBiffStream biffStream, int startRow, int ro } else { - var numberFormatIndex = GetFormatIndexForCell(cell, ixfe); - var cellValue = ReadSingleCell(biffStream, cell, numberFormatIndex); + var xfIndex = GetXfIndexForCell(cell, ixfe); + var cellValue = ReadSingleCell(biffStream, cell, xfIndex); currentRow.Cells.Add(cellValue); } @@ -271,12 +261,14 @@ private List ReadMultiCell(XlsBiffBlankCell cell) ushort lastColumnIndex = rkCell.LastColumnIndex; for (ushort j = cell.ColumnIndex; j <= lastColumnIndex; j++) { - var numberFormatIndex = Workbook.GetNumberFormatFromXF(rkCell.GetXF(j)); + var xfIndex = rkCell.GetXF(j); + var effectiveStyle = Workbook.GetEffectiveCellStyle(xfIndex, cell.Format); var resultCell = new Cell() { ColumnIndex = j, - Value = TryConvertOADateTime(rkCell.GetValue(j), numberFormatIndex), - NumberFormatIndex = numberFormatIndex + Value = TryConvertOADateTime(rkCell.GetValue(j), effectiveStyle.NumberFormatIndex), + XfIndex = xfIndex, + EffectiveStyle = effectiveStyle, }; result.Add(resultCell); @@ -293,7 +285,7 @@ private List ReadMultiCell(XlsBiffBlankCell cell) /// /// Reads additional records if needed: a string record might follow a formula result /// - private Cell ReadSingleCell(XlsBiffStream biffStream, XlsBiffBlankCell cell, int numberFormatIndex) + private Cell ReadSingleCell(XlsBiffStream biffStream, XlsBiffBlankCell cell, int xfIndex) { LogManager.Log(this).Debug("ReadSingleCell {0}", cell.Id); @@ -301,11 +293,14 @@ private Cell ReadSingleCell(XlsBiffStream biffStream, XlsBiffBlankCell cell, int int intValue; object objectValue; + var effectiveStyle = Workbook.GetEffectiveCellStyle(xfIndex, cell.Format); var result = new Cell() { ColumnIndex = cell.ColumnIndex, - NumberFormatIndex = numberFormatIndex + XfIndex = xfIndex, + EffectiveStyle = effectiveStyle, }; + var numberFormatIndex = effectiveStyle.NumberFormatIndex; switch (cell.Id) { @@ -330,7 +325,7 @@ private Cell ReadSingleCell(XlsBiffStream biffStream, XlsBiffBlankCell cell, int case BIFFRECORDTYPE.LABEL: case BIFFRECORDTYPE.LABEL_OLD: case BIFFRECORDTYPE.RSTRING: - result.Value = ((XlsBiffLabelCell)cell).GetValue(Encoding); + result.Value = GetLabelString((XlsBiffLabelCell)cell, effectiveStyle); break; case BIFFRECORDTYPE.LABELSST: result.Value = Workbook.SST.GetString(((XlsBiffLabelSSTCell)cell).SSTIndex, Encoding); @@ -347,7 +342,7 @@ private Cell ReadSingleCell(XlsBiffStream biffStream, XlsBiffBlankCell cell, int case BIFFRECORDTYPE.FORMULA: case BIFFRECORDTYPE.FORMULA_V3: case BIFFRECORDTYPE.FORMULA_V4: - objectValue = TryGetFormulaValue(biffStream, (XlsBiffFormulaCell)cell, numberFormatIndex); + objectValue = TryGetFormulaValue(biffStream, (XlsBiffFormulaCell)cell, effectiveStyle); result.Value = objectValue; break; } @@ -357,7 +352,27 @@ private Cell ReadSingleCell(XlsBiffStream biffStream, XlsBiffBlankCell cell, int return result; } - private object TryGetFormulaValue(XlsBiffStream biffStream, XlsBiffFormulaCell formulaCell, int numberFormatIndex) + private string GetLabelString(XlsBiffLabelCell cell, ExtendedFormat effectiveStyle) + { + // 1. Use encoding from font's character set (BIFF5-8) + // 2. If not specified, use encoding from CODEPAGE BIFF record + // 3. If not specified, use configured fallback encoding + // Encoding is only used on BIFF2-5 byte strings. BIFF8 uses XlsUnicodeString which ignores the encoding. + var labelEncoding = GetFont(effectiveStyle.FontIndex)?.ByteStringEncoding ?? Encoding; + return cell.GetValue(labelEncoding); + } + + private XlsBiffFont GetFont(int fontIndex) + { + if (fontIndex < 0 || fontIndex >= Workbook.Fonts.Count) + { + return null; + } + + return Workbook.Fonts[fontIndex]; + } + + private object TryGetFormulaValue(XlsBiffStream biffStream, XlsBiffFormulaCell formulaCell, ExtendedFormat effectiveStyle) { switch (formulaCell.FormulaType) { @@ -368,16 +383,16 @@ private object TryGetFormulaValue(XlsBiffStream biffStream, XlsBiffFormulaCell f case XlsBiffFormulaCell.FormulaValueType.EmptyString: return string.Empty; case XlsBiffFormulaCell.FormulaValueType.Number: - return TryConvertOADateTime(formulaCell.XNumValue, numberFormatIndex); + return TryConvertOADateTime(formulaCell.XNumValue, effectiveStyle.NumberFormatIndex); case XlsBiffFormulaCell.FormulaValueType.String: - return TryGetFormulaString(biffStream); + return TryGetFormulaString(biffStream, effectiveStyle); } // Bad data or new formula value type return null; } - private string TryGetFormulaString(XlsBiffStream biffStream) + private string TryGetFormulaString(XlsBiffStream biffStream, ExtendedFormat effectiveStyle) { var rec = biffStream.Read(); if (rec != null && rec.Id == BIFFRECORDTYPE.SHAREDFMLA) @@ -388,7 +403,8 @@ private string TryGetFormulaString(XlsBiffStream biffStream) if (rec != null && rec.Id == BIFFRECORDTYPE.STRING) { var stringRecord = (XlsBiffFormulaString)rec; - return stringRecord.GetValue(Encoding); + var formulaEncoding = GetFont(effectiveStyle.FontIndex)?.ByteStringEncoding ?? Encoding; // Workbook.GetFontEncodingFromXF(xFormat) ?? Encoding; + return stringRecord.GetValue(formulaEncoding); } // Bad data - could not find a string following the formula @@ -397,7 +413,7 @@ private string TryGetFormulaString(XlsBiffStream biffStream) private object TryConvertOADateTime(double value, int numberFormatIndex) { - var format = GetNumberFormatString(numberFormatIndex); + var format = Workbook.GetNumberFormatString(numberFormatIndex); if (format != null) { if (format.IsDateTimeFormat) @@ -411,7 +427,7 @@ private object TryConvertOADateTime(double value, int numberFormatIndex) private object TryConvertOADateTime(int value, int numberFormatIndex) { - var format = GetNumberFormatString(numberFormatIndex); + var format = Workbook.GetNumberFormatString(numberFormatIndex); if (format != null) { if (format.IsDateTimeFormat) @@ -424,34 +440,34 @@ private object TryConvertOADateTime(int value, int numberFormatIndex) } /// - /// Returns an index into Workbook.Formats for the given cell and preceding ixfe record. + /// Returns an index into Workbook.ExtendedFormats for the given cell and preceding ixfe record. /// - private int GetFormatIndexForCell(XlsBiffBlankCell cell, XlsBiffRecord ixfe) + private int GetXfIndexForCell(XlsBiffBlankCell cell, XlsBiffRecord ixfe) { if (Workbook.BiffVersion == 2) { if (cell.XFormat == 63 && ixfe != null) { var xFormat = ixfe.ReadUInt16(0); - return Workbook.GetNumberFormatFromXF(xFormat); + return xFormat; } else if (cell.XFormat > 63) { - // Invalid XF ref on cell in BIFF2 stream, default to built-in "General" - return 0; + // Invalid XF ref on cell in BIFF2 stream + return -1; } - else if (cell.XFormat < Workbook.GetExtendedFormatCount()) + else if (cell.XFormat < Workbook.ExtendedFormats.Count) { - return Workbook.GetNumberFormatFromXF(cell.XFormat); + return cell.XFormat; } else { - // Either the file has no XFs, or XF was out of range. Use the cell attributes' format reference. - return Workbook.GetNumberFormatFromFileIndex(cell.Format); + // Either the file has no XFs, or XF was out of range + return -1; } } - return Workbook.GetNumberFormatFromXF(cell.XFormat); + return cell.XFormat; } private void ReadWorksheetGlobals() @@ -499,7 +515,7 @@ private void ReadWorksheetGlobals() { // NOTE: XF records should only occur in raw BIFF2-4 single worksheet documents without the workbook stream, or globally in the workbook stream. // It is undefined behavior if multiple worksheets in a workbook declare XF records. - Workbook.AddExtendedFormat(-1, ((XlsBiffXF)rec).Format, true); + Workbook.AddXf((XlsBiffXF)rec); } if (rec.Id == BIFFRECORDTYPE.MERGECELLS) diff --git a/src/ExcelDataReader/Core/Cell.cs b/src/ExcelDataReader/Core/Cell.cs index 50365b2b..4501cc67 100644 --- a/src/ExcelDataReader/Core/Cell.cs +++ b/src/ExcelDataReader/Core/Cell.cs @@ -7,7 +7,16 @@ internal class Cell /// public int ColumnIndex { get; set; } - public int NumberFormatIndex { get; set; } + /// + /// Gets or sets the index of the XF record describing the styling of this cell. + /// + public int XfIndex { get; set; } + + /// + /// Gets or sets the effective style on the cell. The effective style is determined from + /// the Cell XF, with optional overrides from a Cell Style XF. + /// + public ExtendedFormat EffectiveStyle { get; set; } public object Value { get; set; } } diff --git a/src/ExcelDataReader/Core/CommonWorkbook.cs b/src/ExcelDataReader/Core/CommonWorkbook.cs index 4f920ff6..efb9292a 100644 --- a/src/ExcelDataReader/Core/CommonWorkbook.cs +++ b/src/ExcelDataReader/Core/CommonWorkbook.cs @@ -9,19 +9,6 @@ namespace ExcelDataReader.Core /// internal class CommonWorkbook { - public CommonWorkbook() - { - const int maxBuiltInFormats = 163; - for (var i = 0; i < maxBuiltInFormats; i++) - { - var numFmt = BuiltinNumberFormat.GetBuiltinNumberFormat(i); - if (numFmt != null) - { - Formats.Add(i, numFmt); - } - } - } - /// /// Gets the dictionary of global number format strings. Always includes the built-in formats at their /// corresponding indices and any additional formats specified in the workbook file. @@ -29,95 +16,92 @@ public CommonWorkbook() public Dictionary Formats { get; } = new Dictionary(); /// - /// Gets the the dictionary of mappings between format index in the file and key in the Formats dictionary. + /// Gets the Cell XFs /// - private Dictionary FormatMappings { get; } = new Dictionary(); - - private List ExtendedFormats { get; } = new List(); - - public int GetExtendedFormatCount() => ExtendedFormats.Count; + public List ExtendedFormats { get; } = new List(); /// - /// Returns the global number format index from an XF index. + /// Gets the Cell Style XFs /// - public int GetNumberFormatFromXF(int xfIndex) + public List CellStyleExtendedFormats { get; } = new List(); + + private NumberFormatString GeneralNumberFormat { get; } = new NumberFormatString("General"); + + public ExtendedFormat GetEffectiveCellStyle(int xfIndex, int numberFormatFromCell) { - if (xfIndex < 0 || xfIndex >= ExtendedFormats.Count) + var effectiveStyle = new ExtendedFormat(); + var cellXf = xfIndex >= 0 && xfIndex < ExtendedFormats.Count + ? ExtendedFormats[xfIndex] + : null; + if (cellXf != null) { - // Invalid XF index, return built-in "General" format - return 0; + effectiveStyle.FontIndex = cellXf.FontIndex; + effectiveStyle.NumberFormatIndex = cellXf.NumberFormatIndex; + + effectiveStyle.Hidden = cellXf.Hidden; + effectiveStyle.Locked = cellXf.Locked; + effectiveStyle.IndentLevel = cellXf.IndentLevel; + effectiveStyle.HorizontalAlignment = cellXf.HorizontalAlignment; + + var cellStyleXf = cellXf.ParentCellStyleXf >= 0 && cellXf.ParentCellStyleXf < CellStyleExtendedFormats.Count + ? CellStyleExtendedFormats[cellXf.ParentCellStyleXf] + : null; + if (cellStyleXf != null) + { + if (cellStyleXf.ApplyFont) + { + effectiveStyle.FontIndex = cellStyleXf.FontIndex; + } + + if (cellStyleXf.ApplyNumberFormat) + { + effectiveStyle.NumberFormatIndex = cellStyleXf.NumberFormatIndex; + } + + if (cellStyleXf.ApplyProtection) + { + effectiveStyle.Hidden = cellStyleXf.Hidden; + effectiveStyle.Locked = cellStyleXf.Locked; + } + + if (cellStyleXf.ApplyTextAlignment) + { + effectiveStyle.IndentLevel = cellStyleXf.IndentLevel; + effectiveStyle.HorizontalAlignment = cellStyleXf.HorizontalAlignment; + } + } } - - var extendedFormat = ExtendedFormats[xfIndex]; - if (!extendedFormat.ApplyNumberFormat) + else { - return 0; + effectiveStyle.NumberFormatIndex = numberFormatFromCell; } - return GetNumberFormatFromFileIndex(ExtendedFormats[xfIndex].FormatIndex); + return effectiveStyle; } /// - /// Returns the global number format index from a file-based format index. + /// Registers a number format string in the workbook's Formats dictionary. /// - public int GetNumberFormatFromFileIndex(int formatIndexInFile) + public void AddNumberFormat(int formatIndexInFile, string formatString) { - if (FormatMappings.TryGetValue(formatIndexInFile, out var formatIndex)) - { - return formatIndex; - } - - // Format not stored in file, assume built-in format - return formatIndexInFile; + Formats.Add(formatIndexInFile, new NumberFormatString(formatString)); } - /// - /// Registers a number format string and its file-based format index in the workbook's Formats dictionary. - /// If the format string matches a built-in or previously registered format, it will be mapped to that index. - /// - public void AddNumberFormat(int formatIndexInFile, string formatString) + public NumberFormatString GetNumberFormatString(int numberFormatIndex) { - var exists = false; - int maxIndex = 163; - foreach (var format in Formats) + if (Formats.TryGetValue(numberFormatIndex, out var numberFormat)) { - if (!exists && format.Value.FormatString == formatString) - { - FormatMappings[formatIndexInFile] = format.Key; - exists = true; - } - - maxIndex = Math.Max(maxIndex, format.Key); + return numberFormat; } - if (!exists) + numberFormat = BuiltinNumberFormat.GetBuiltinNumberFormat(numberFormatIndex); + if (numberFormat != null) { - maxIndex++; - Formats.Add(maxIndex, new NumberFormatString(formatString)); - FormatMappings[formatIndexInFile] = maxIndex; + return numberFormat; } - } - - /// - /// Registers an extended format and its file based number format index. - /// - public void AddExtendedFormat(int xfId, int formatIndexInFile, bool applyNumberFormat) - { - ExtendedFormats.Add(new ExtendedFormat() - { - XfId = xfId, - FormatIndex = formatIndexInFile, - ApplyNumberFormat = applyNumberFormat - }); - } - - private class ExtendedFormat - { - public int XfId { get; set; } - - public int FormatIndex { get; set; } - public bool ApplyNumberFormat { get; set; } + // Fall back to "General" if the number format index is invalid + return GeneralNumberFormat; } } } diff --git a/src/ExcelDataReader/Core/CompoundFormat/CompoundDocument.cs b/src/ExcelDataReader/Core/CompoundFormat/CompoundDocument.cs index 404070b3..3e2b3b6b 100644 --- a/src/ExcelDataReader/Core/CompoundFormat/CompoundDocument.cs +++ b/src/ExcelDataReader/Core/CompoundFormat/CompoundDocument.cs @@ -45,12 +45,15 @@ internal static bool IsCompoundDocument(byte[] probe) return BitConverter.ToUInt64(probe, 0) == 0xE11AB1A1E011CFD0; } - internal CompoundDirectoryEntry FindEntry(string entryName) + internal CompoundDirectoryEntry FindEntry(params string[] entryNames) { foreach (var e in Entries) { - if (string.Equals(e.EntryName, entryName, StringComparison.CurrentCultureIgnoreCase)) - return e; + foreach (var entryName in entryNames) + { + if (string.Equals(e.EntryName, entryName, StringComparison.CurrentCultureIgnoreCase)) + return e; + } } return null; diff --git a/src/ExcelDataReader/Core/CsvFormat/CsvWorkbook.cs b/src/ExcelDataReader/Core/CsvFormat/CsvWorkbook.cs index f3564976..5b5c2312 100644 --- a/src/ExcelDataReader/Core/CsvFormat/CsvWorkbook.cs +++ b/src/ExcelDataReader/Core/CsvFormat/CsvWorkbook.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Text; +using ExcelDataReader.Core.NumberFormat; namespace ExcelDataReader.Core.CsvFormat { @@ -29,5 +30,10 @@ public IEnumerable ReadWorksheets() { yield return new CsvWorksheet(Stream, Encoding, AutodetectSeparators, AnalyzeInitialCsvRows); } + + public NumberFormatString GetNumberFormatString(int index) + { + return null; + } } } diff --git a/src/ExcelDataReader/Core/CsvFormat/CsvWorksheet.cs b/src/ExcelDataReader/Core/CsvFormat/CsvWorksheet.cs index 5d44bd7e..04e90222 100644 --- a/src/ExcelDataReader/Core/CsvFormat/CsvWorksheet.cs +++ b/src/ExcelDataReader/Core/CsvFormat/CsvWorksheet.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Text; -using ExcelDataReader.Core.NumberFormat; namespace ExcelDataReader.Core.CsvFormat { @@ -77,11 +76,6 @@ public int RowCount private int AnalyzedRowCount { get; } - public NumberFormatString GetNumberFormatString(int index) - { - return null; - } - public IEnumerable ReadRows() { var bufferSize = 1024; diff --git a/src/ExcelDataReader/Core/ExtendedFormat.cs b/src/ExcelDataReader/Core/ExtendedFormat.cs new file mode 100644 index 00000000..0061300a --- /dev/null +++ b/src/ExcelDataReader/Core/ExtendedFormat.cs @@ -0,0 +1,31 @@ +namespace ExcelDataReader.Core +{ + internal class ExtendedFormat + { + /// + /// Gets or sets the index to the parent Cell Style CF record with overrides for this XF. Only used with Cell XFs. + /// 0xFFF means no override + /// + public int ParentCellStyleXf { get; set; } + + public int FontIndex { get; set; } + + public int NumberFormatIndex { get; set; } + + public bool Locked { get; set; } + + public bool Hidden { get; set; } + + public int IndentLevel { get; set; } + + public HorizontalAlignment HorizontalAlignment { get; set; } + + public bool ApplyNumberFormat { get; set; } + + public bool ApplyFont { get; set; } + + public bool ApplyTextAlignment { get; set; } + + public bool ApplyProtection { get; set; } + } +} diff --git a/src/ExcelDataReader/Core/IWorkbook.cs b/src/ExcelDataReader/Core/IWorkbook.cs index f77d371f..9f594166 100644 --- a/src/ExcelDataReader/Core/IWorkbook.cs +++ b/src/ExcelDataReader/Core/IWorkbook.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.Text; +using ExcelDataReader.Core.NumberFormat; namespace ExcelDataReader.Core { @@ -13,5 +13,7 @@ internal interface IWorkbook int ResultsCount { get; } IEnumerable ReadWorksheets(); + + NumberFormatString GetNumberFormatString(int index); } } diff --git a/src/ExcelDataReader/Core/IWorksheet.cs b/src/ExcelDataReader/Core/IWorksheet.cs index 2ec9148f..d2090dea 100644 --- a/src/ExcelDataReader/Core/IWorksheet.cs +++ b/src/ExcelDataReader/Core/IWorksheet.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using ExcelDataReader.Core.NumberFormat; namespace ExcelDataReader.Core { @@ -25,7 +24,5 @@ internal interface IWorksheet Col[] ColumnWidths { get; } IEnumerable ReadRows(); - - NumberFormatString GetNumberFormatString(int index); } } diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorkbook.cs b/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorkbook.cs index 151a620d..ae57b93e 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorkbook.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorkbook.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Xml; @@ -16,6 +17,7 @@ internal class XlsxWorkbook : CommonWorkbook, IWorkbook private const string ElementStringItem = "si"; private const string ElementStyleSheet = "styleSheet"; private const string ElementCellCrossReference = "cellXfs"; + private const string ElementCellStyleCrossReference = "cellStyleXfs"; private const string ElementNumberFormats = "numFmts"; private const string ElementWorkbook = "workbook"; private const string ElementWorkbookProperties = "workbookPr"; @@ -31,13 +33,25 @@ internal class XlsxWorkbook : CommonWorkbook, IWorkbook private const string AttributeTarget = "Target"; private const string NXF = "xf"; + private const string AFontId = "fontId"; private const string ANumFmtId = "numFmtId"; private const string AXFId = "xfId"; + private const string AApplyFont = "applyFont"; private const string AApplyNumberFormat = "applyNumberFormat"; + private const string AApplyAlignment = "applyAlignment"; + private const string AApplyProtection = "applyProtection"; private const string NNumFmt = "numFmt"; private const string AFormatCode = "formatCode"; + private const string NAlignment = "alignment"; + private const string AIndent = "indent"; + private const string AHorizontal = "horizontal"; + + private const string NProtection = "protection"; + private const string AHidden = "hidden"; + private const string ALocked = "locked"; + private ZipWorker _zipWorker; public XlsxWorkbook(ZipWorker zipWorker) @@ -310,7 +324,11 @@ private void ReadStyles(XmlReader reader) { if (reader.IsStartElement(ElementCellCrossReference, NsSpreadsheetMl)) { - ReadCellXfs(reader); + ReadCellXfs(reader, false); + } + else if (reader.IsStartElement(ElementCellStyleCrossReference, NsSpreadsheetMl)) + { + ReadCellXfs(reader, true); } else if (reader.IsStartElement(ElementNumberFormats, NsSpreadsheetMl)) { @@ -323,7 +341,7 @@ private void ReadStyles(XmlReader reader) } } - private void ReadCellXfs(XmlReader reader) + private void ReadCellXfs(XmlReader reader, bool isCellStyleXF) { if (!XmlReaderHelper.ReadFirstContent(reader)) { @@ -336,8 +354,80 @@ private void ReadCellXfs(XmlReader reader) { int.TryParse(reader.GetAttribute(AXFId), out var xfId); int.TryParse(reader.GetAttribute(ANumFmtId), out var numFmtId); - var applyNumberFormat = reader.GetAttribute(AApplyNumberFormat) != "0"; - AddExtendedFormat(xfId, numFmtId, applyNumberFormat); + int.TryParse(reader.GetAttribute(AFontId), out var fontId); + var applyFont = reader.GetAttribute(AApplyFont) == "1"; + var applyNumberFormat = reader.GetAttribute(AApplyNumberFormat) == "1"; + var applyAlignment = reader.GetAttribute(AApplyAlignment) == "1"; + var applyProtection = reader.GetAttribute(AApplyProtection) == "1"; + ReadAlignment(reader, out int indentLevel, out HorizontalAlignment horizontalAlignment, out var hidden, out var locked); + + var extendedFormat = new ExtendedFormat() + { + FontIndex = fontId, + ParentCellStyleXf = xfId, + NumberFormatIndex = numFmtId, + HorizontalAlignment = horizontalAlignment, + IndentLevel = indentLevel, + Hidden = hidden, + Locked = locked, + ApplyFont = applyFont, + ApplyNumberFormat = applyNumberFormat, + ApplyProtection = applyProtection, + ApplyTextAlignment = applyAlignment, + }; + + if (!isCellStyleXF) + { + ExtendedFormats.Add(extendedFormat); + } + else + { + CellStyleExtendedFormats.Add(extendedFormat); + } + + // reader.Skip(); + } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; + } + } + } + + private void ReadAlignment(XmlReader reader, out int indentLevel, out HorizontalAlignment horizontalAlignment, out bool hidden, out bool locked) + { + indentLevel = 0; + horizontalAlignment = HorizontalAlignment.General; + hidden = false; + locked = false; + + if (!XmlReaderHelper.ReadFirstContent(reader)) + { + return; + } + + while (!reader.EOF) + { + if (reader.IsStartElement(NAlignment, NsSpreadsheetMl)) + { + int.TryParse(reader.GetAttribute(AIndent), out indentLevel); + try + { + horizontalAlignment = (HorizontalAlignment)Enum.Parse(typeof(HorizontalAlignment), reader.GetAttribute(AHorizontal), true); + } + catch (ArgumentException) + { + } + catch (OverflowException) + { + } + + reader.Skip(); + } + else if (reader.IsStartElement(NProtection, NsSpreadsheetMl)) + { + locked = reader.GetAttribute(ALocked) == "1"; + hidden = reader.GetAttribute(AHidden) == "1"; reader.Skip(); } else if (!XmlReaderHelper.SkipContent(reader)) diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorksheet.cs b/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorksheet.cs index 072bdd83..b0d67834 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorksheet.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorksheet.cs @@ -119,16 +119,6 @@ public IEnumerable ReadRows() } } - public NumberFormatString GetNumberFormatString(int numberFormatIndex) - { - if (Workbook.Formats.TryGetValue(numberFormatIndex, out var result)) - { - return result; - } - - return null; - } - private void ReadWorksheetGlobals() { if (string.IsNullOrEmpty(Path)) @@ -513,14 +503,19 @@ private Cell ReadCell(XmlReader xmlReader, int nextColumnIndex) else result.ColumnIndex = nextColumnIndex; + int styleIndex = -1; if (aS != null) { - if (int.TryParse(aS, NumberStyles.Any, CultureInfo.InvariantCulture, out var styleIndex)) + if (!int.TryParse(aS, NumberStyles.Any, CultureInfo.InvariantCulture, out styleIndex)) { - result.NumberFormatIndex = Workbook.GetNumberFormatFromXF(styleIndex); + styleIndex = -1; } } + var effectiveStyle = Workbook.GetEffectiveCellStyle(styleIndex, 0); + result.XfIndex = styleIndex; + result.EffectiveStyle = effectiveStyle; + if (!XmlReaderHelper.ReadFirstContent(xmlReader)) { return result; @@ -532,13 +527,13 @@ private Cell ReadCell(XmlReader xmlReader, int nextColumnIndex) { var rawValue = xmlReader.ReadElementContentAsString(); if (!string.IsNullOrEmpty(rawValue)) - result.Value = ConvertCellValue(rawValue, aT, result.NumberFormatIndex); + result.Value = ConvertCellValue(rawValue, aT, effectiveStyle.NumberFormatIndex); } else if (xmlReader.IsStartElement(NIs, NsSpreadsheetMl)) { var rawValue = XlsxWorkbook.ReadStringItem(xmlReader); if (!string.IsNullOrEmpty(rawValue)) - result.Value = ConvertCellValue(rawValue, aT, result.NumberFormatIndex); + result.Value = ConvertCellValue(rawValue, aT, effectiveStyle.NumberFormatIndex); } else if (!XmlReaderHelper.SkipContent(xmlReader)) { @@ -581,7 +576,7 @@ private object ConvertCellValue(string rawValue, string aT, int numberFormatInde default: if (double.TryParse(rawValue, style, invariantCulture, out double number)) { - var format = GetNumberFormatString(numberFormatIndex); + var format = Workbook.GetNumberFormatString(numberFormatIndex); if (format != null) { if (format.IsDateTimeFormat) diff --git a/src/ExcelDataReader/ExcelDataReader.cs b/src/ExcelDataReader/ExcelDataReader.cs index 4de6476d..e27f0282 100644 --- a/src/ExcelDataReader/ExcelDataReader.cs +++ b/src/ExcelDataReader/ExcelDataReader.cs @@ -116,7 +116,9 @@ public string GetNumberFormatString(int i) throw new InvalidOperationException("No data exists for the row/column."); if (RowCells[i] == null) return null; - return _worksheetIterator?.Current?.GetNumberFormatString(RowCells[i].NumberFormatIndex)?.FormatString; + if (RowCells[i].EffectiveStyle == null) + return null; + return Workbook.GetNumberFormatString(RowCells[i].EffectiveStyle.NumberFormatIndex)?.FormatString; } public int GetNumberFormatIndex(int i) @@ -125,7 +127,9 @@ public int GetNumberFormatIndex(int i) throw new InvalidOperationException("No data exists for the row/column."); if (RowCells[i] == null) return -1; - return RowCells[i].NumberFormatIndex; + if (RowCells[i].EffectiveStyle == null) + return -1; + return RowCells[i].EffectiveStyle.NumberFormatIndex; } public double GetColumnWidth(int i) @@ -161,6 +165,32 @@ public double GetColumnWidth(int i) return retWidth ?? DefaultColumnWidth; } + public CellStyle GetCellStyle(int i) + { + if (RowCells == null) + throw new InvalidOperationException("No data exists for the row/column."); + + var result = new CellStyle(); + if (RowCells[i] == null) + { + return result; + } + + var effectiveStyle = RowCells[i].EffectiveStyle; + if (effectiveStyle == null) + { + return result; + } + + result.FontIndex = effectiveStyle.FontIndex; + result.NumberFormatIndex = effectiveStyle.NumberFormatIndex; + result.IndentLevel = effectiveStyle.IndentLevel; + result.HorizontalAlignment = effectiveStyle.HorizontalAlignment; + result.Hidden = effectiveStyle.Hidden; + result.Locked = effectiveStyle.Locked; + return result; + } + /// public void Reset() { diff --git a/src/ExcelDataReader/ExcelReaderFactory.cs b/src/ExcelDataReader/ExcelReaderFactory.cs index 50c74a2a..b246ff20 100644 --- a/src/ExcelDataReader/ExcelReaderFactory.cs +++ b/src/ExcelDataReader/ExcelReaderFactory.cs @@ -187,7 +187,7 @@ public static IExcelDataReader CreateCsvReader(Stream fileStream, ExcelReaderCon private static bool TryGetWorkbook(Stream fileStream, CompoundDocument document, out Stream stream) { - var workbookEntry = document.FindEntry(DirectoryEntryWorkbook) ?? document.FindEntry(DirectoryEntryBook); + var workbookEntry = document.FindEntry(DirectoryEntryWorkbook, DirectoryEntryBook); if (workbookEntry != null) { if (workbookEntry.EntryType != STGTY.STGTY_STREAM) diff --git a/src/ExcelDataReader/IExcelDataReader.cs b/src/ExcelDataReader/IExcelDataReader.cs index 124bd4fa..14db0f69 100644 --- a/src/ExcelDataReader/IExcelDataReader.cs +++ b/src/ExcelDataReader/IExcelDataReader.cs @@ -72,5 +72,12 @@ public interface IExcelDataReader : IDataReader /// The index of the column to find. /// The width of the specified column. double GetColumnWidth(int i); + + /// + /// Gets the cell style. + /// + /// The index of the column to find. + /// The cell style. + CellStyle GetCellStyle(int i); } } \ No newline at end of file diff --git a/test/ExcelDataReader.Tests/Configuration.cs b/test/ExcelDataReader.Tests/Configuration.cs index d6da39bc..1170e81d 100644 --- a/test/ExcelDataReader.Tests/Configuration.cs +++ b/test/ExcelDataReader.Tests/Configuration.cs @@ -189,6 +189,11 @@ internal static class Configuration { "Test_git_issue_382_oom.xls", "Test_git_issue_382_oom.xls" }, { "Test_git_issue_385_backslash.xlsx", "Test_git_issue_385_backslash.xlsx" }, { "Test_git_issue_392_oob.xls", "Test_git_issue_392_oob.xls" }, + { "Test_git_issue_341.xls", "Test_git_issue_341.xls" }, + { "Test_git_issue_341_style.xls", "Test_git_issue_341_style.xls" }, + { "Test_git_issue_341.xlsx", "Test_git_issue_341.xlsx" }, + { "customformat_notdate.xls", "customformat_notdate.xls" }, + { "Test_git_issue_411.xls", "Test_git_issue_411.xls" }, }; public static Stream GetTestWorkbook(string key) diff --git a/test/ExcelDataReader.Tests/ExcelBinaryReaderTest.cs b/test/ExcelDataReader.Tests/ExcelBinaryReaderTest.cs index d6e6e8d8..2e2efa48 100644 --- a/test/ExcelDataReader.Tests/ExcelBinaryReaderTest.cs +++ b/test/ExcelDataReader.Tests/ExcelBinaryReaderTest.cs @@ -1966,5 +1966,115 @@ public void GitIssue_392_OOB() Assert.AreEqual("10x27", result.Rows[9][9]); } } + + [TestMethod] + public void GitIssue_341_Indent() + { + int[][] expected = + { + new[] { 2, 0, 0 }, + new[] { 2, 0, 0 }, + new[] { 3, 3, 4 }, + new[] { 1, 1, 0 }, // Merged cell + new[] { 2, 0, 0 }, + }; + + int index = 0; + using (var reader = ExcelReaderFactory.CreateBinaryReader(Configuration.GetTestWorkbook("Test_git_issue_341.xls"))) + { + while (reader.Read()) + { + int[] expectedRow = expected[index]; + int[] actualRow = new int[reader.FieldCount]; + for (int i = 0; i < reader.FieldCount; i++) + { + actualRow[i] = reader.GetCellStyle(i).IndentLevel; + } + + Assert.AreEqual(expectedRow, actualRow, "Indent level on row '{0}'.", index); + + index++; + } + } + } + + [TestMethod] + public void GitIssue_341_HorizontalAlignment() + { + HorizontalAlignment[][] expected = + { + new[] { HorizontalAlignment.Left, HorizontalAlignment.General, HorizontalAlignment.General }, + new[] { HorizontalAlignment.Distributed, HorizontalAlignment.General, HorizontalAlignment.General }, + new[] { HorizontalAlignment.Left, HorizontalAlignment.Left, HorizontalAlignment.Left }, + new[] { HorizontalAlignment.Left, HorizontalAlignment.Left, HorizontalAlignment.General }, // Merged cell + new[] { HorizontalAlignment.Left, HorizontalAlignment.General, HorizontalAlignment.General }, + }; + + int index = 0; + using (var reader = ExcelReaderFactory.CreateBinaryReader(Configuration.GetTestWorkbook("Test_git_issue_341.xls"))) + { + while (reader.Read()) + { + HorizontalAlignment[] expectedRow = expected[index]; + HorizontalAlignment[] actualRow = new HorizontalAlignment[reader.FieldCount]; + for (int i = 0; i < reader.FieldCount; i++) + { + actualRow[i] = reader.GetCellStyle(i).HorizontalAlignment; + } + + Assert.AreEqual(expectedRow, actualRow, "Horizontal alignment on row '{0}'.", index); + + index++; + } + } + } + + [TestMethod(Description = "XF_USED_ATTRIB is not set correctly")] + public void GitIssue_341_HorizontalAlignment2() + { + using (var reader = ExcelReaderFactory.CreateBinaryReader(Configuration.GetTestWorkbook("Test_Git_Issue_51"))) + { + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(HorizontalAlignment.Right, reader.GetCellStyle(1).HorizontalAlignment); + } + } + + [TestMethod(Description = "Indent is from a style")] + public void GitIssue_341_FromStyle() + { + using (var reader = ExcelReaderFactory.CreateBinaryReader(Configuration.GetTestWorkbook("Test_git_issue_341_style.xls"))) + { + Assert.IsTrue(reader.Read()); + Assert.AreEqual(2, reader.GetCellStyle(0).IndentLevel); + } + } + + [TestMethod] + public void MultiCellCustomFormatNotDate() + { + using (var reader = ExcelReaderFactory.CreateBinaryReader(Configuration.GetTestWorkbook("customformat_notdate.xls"))) + { + Assert.IsTrue(reader.Read()); + Assert.AreEqual(60.8, reader.GetValue(1)); + Assert.AreEqual("#,##0.0;\\–#,##0.0;\"–\"", reader.GetNumberFormatString(1)); + } + } + + [TestMethod] + public void Test_git_issue_411() + { + // This file has two problems: + // - has both Book and Workbook compound streams + // - has no codepage record, encoding specified in font records + using (var reader = ExcelReaderFactory.CreateBinaryReader(Configuration.GetTestWorkbook("Test_git_issue_411.xls"))) + { + Assert.AreEqual(1, reader.ResultsCount); + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.AreEqual("Универсальный передаточный\nдокумент", reader.GetValue(1)); + } + } } } diff --git a/test/ExcelDataReader.Tests/ExcelOpenXmlReaderTest.cs b/test/ExcelDataReader.Tests/ExcelOpenXmlReaderTest.cs index 9b353cb5..301c3c92 100644 --- a/test/ExcelDataReader.Tests/ExcelOpenXmlReaderTest.cs +++ b/test/ExcelDataReader.Tests/ExcelOpenXmlReaderTest.cs @@ -1598,5 +1598,67 @@ public void GitIssue_385_Backslash() Assert.AreEqual("10x27", result.Rows[9][9]); } } + + [TestMethod] + public void GitIssue_341_Indent() + { + int[][] expected = + { + new[] { 2, 0, 0 }, + new[] { 2, 0, 0 }, + new[] { 3, 3, 4 }, + new[] { 1, 1, 1 }, // Merged cell + new[] { 2, 0, 0 }, + }; + + int index = 0; + using (var reader = ExcelReaderFactory.CreateOpenXmlReader(Configuration.GetTestWorkbook("Test_git_issue_341.xlsx"))) + { + while (reader.Read()) + { + int[] expectedRow = expected[index]; + int[] actualRow = new int[reader.FieldCount]; + for (int i = 0; i < reader.FieldCount; i++) + { + actualRow[i] = reader.GetCellStyle(i).IndentLevel; + } + + Assert.AreEqual(expectedRow, actualRow, "Indent level on row '{0}'.", index); + + index++; + } + } + } + + [TestMethod] + public void GitIssue_341_HorizontalAlignment() + { + HorizontalAlignment[][] expected = + { + new[] { HorizontalAlignment.Left, HorizontalAlignment.General, HorizontalAlignment.General }, + new[] { HorizontalAlignment.Distributed, HorizontalAlignment.General, HorizontalAlignment.General }, + new[] { HorizontalAlignment.Left, HorizontalAlignment.Left, HorizontalAlignment.Left }, + new[] { HorizontalAlignment.Left, HorizontalAlignment.Left, HorizontalAlignment.Left }, // Merged cell + new[] { HorizontalAlignment.Left, HorizontalAlignment.General, HorizontalAlignment.General }, + }; + + int index = 0; + using (var reader = ExcelReaderFactory.CreateOpenXmlReader(Configuration.GetTestWorkbook("Test_git_issue_341.xlsx"))) + { + while (reader.Read()) + { + HorizontalAlignment[] expectedRow = expected[index]; + HorizontalAlignment[] actualRow = new HorizontalAlignment[reader.FieldCount]; + for (int i = 0; i < reader.FieldCount; i++) + { + actualRow[i] = reader.GetCellStyle(i).HorizontalAlignment; + } + + Assert.AreEqual(expectedRow, actualRow, "Horizontal alignment on row '{0}'.", index); + + index++; + } + } + } } } diff --git a/test/Resources/Test_git_issue_341.xls b/test/Resources/Test_git_issue_341.xls new file mode 100644 index 00000000..99955e13 Binary files /dev/null and b/test/Resources/Test_git_issue_341.xls differ diff --git a/test/Resources/Test_git_issue_341.xlsx b/test/Resources/Test_git_issue_341.xlsx new file mode 100644 index 00000000..a6730713 Binary files /dev/null and b/test/Resources/Test_git_issue_341.xlsx differ diff --git a/test/Resources/Test_git_issue_341_style.xls b/test/Resources/Test_git_issue_341_style.xls new file mode 100644 index 00000000..62be0fea Binary files /dev/null and b/test/Resources/Test_git_issue_341_style.xls differ diff --git a/test/Resources/Test_git_issue_411.xls b/test/Resources/Test_git_issue_411.xls new file mode 100644 index 00000000..3d39b24c Binary files /dev/null and b/test/Resources/Test_git_issue_411.xls differ diff --git a/test/Resources/customformat_notdate.xls b/test/Resources/customformat_notdate.xls new file mode 100644 index 00000000..0cdbdbb6 Binary files /dev/null and b/test/Resources/customformat_notdate.xls differ