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