From 4cdd50a79f6154033dc947b31d9aa0a3efadad35 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 23 Sep 2026 15:50:13 +0000 Subject: [PATCH] Refactor to streaming BinaryPackage/XmlPackage parsers (#33) Replace the inverted container-as-package layer with the intended four-group model: BinaryPackage + BinaryPackageParser and XmlPackage + XmlPackageParser stream semantic units; readers consume those streams to build DataSet. CSV stays on CsvReader. Co-authored-by: Fan Feilong --- src/Exceltk/Exceltk_vs.csproj | 1 - src/Exceltk/Reader/Binary/XlsBiffStream.cs | 111 ------ src/Exceltk/Reader/ExcelBinaryReader.cs | 41 ++- src/Exceltk/Reader/ExcelOpenXmlReader.cs | 331 +++++++----------- src/Exceltk/Reader/Package/BinaryPackage.cs | 23 ++ src/Exceltk/Reader/Package/IExcelPackage.cs | 17 - src/Exceltk/Reader/Package/IOpenXmlPackage.cs | 15 - src/Exceltk/Reader/Package/IPackage.cs | 8 + src/Exceltk/Reader/Package/IPathPackage.cs | 10 - src/Exceltk/Reader/Package/IStreamPackage.cs | 12 - src/Exceltk/Reader/Package/PathPackage.cs | 42 --- src/Exceltk/Reader/Package/StreamPackage.cs | 48 --- src/Exceltk/Reader/Package/XmlPackage.cs | 99 ++++++ .../Reader/Parser/BinaryPackageParser.cs | 107 +++++- src/Exceltk/Reader/Parser/CsvPackageParser.cs | 20 -- .../Parser/ExcelPackageParserFactory.cs | 49 --- .../Reader/Parser/IExcelPackageParser.cs | 10 - src/Exceltk/Reader/Parser/IPackageParser.cs | 15 + .../Reader/Parser/OpenXmlPackageParser.cs | 27 -- src/Exceltk/Reader/Parser/XmlPackageParser.cs | 203 +++++++++++ src/Exceltk/Reader/WorkbookLoader.cs | 31 +- src/Exceltk/Reader/ZipWorker.cs | 7 +- 22 files changed, 615 insertions(+), 612 deletions(-) delete mode 100644 src/Exceltk/Reader/Binary/XlsBiffStream.cs create mode 100644 src/Exceltk/Reader/Package/BinaryPackage.cs delete mode 100644 src/Exceltk/Reader/Package/IExcelPackage.cs delete mode 100644 src/Exceltk/Reader/Package/IOpenXmlPackage.cs create mode 100644 src/Exceltk/Reader/Package/IPackage.cs delete mode 100644 src/Exceltk/Reader/Package/IPathPackage.cs delete mode 100644 src/Exceltk/Reader/Package/IStreamPackage.cs delete mode 100644 src/Exceltk/Reader/Package/PathPackage.cs delete mode 100644 src/Exceltk/Reader/Package/StreamPackage.cs create mode 100644 src/Exceltk/Reader/Package/XmlPackage.cs delete mode 100644 src/Exceltk/Reader/Parser/CsvPackageParser.cs delete mode 100644 src/Exceltk/Reader/Parser/ExcelPackageParserFactory.cs delete mode 100644 src/Exceltk/Reader/Parser/IExcelPackageParser.cs create mode 100644 src/Exceltk/Reader/Parser/IPackageParser.cs delete mode 100644 src/Exceltk/Reader/Parser/OpenXmlPackageParser.cs create mode 100644 src/Exceltk/Reader/Parser/XmlPackageParser.cs diff --git a/src/Exceltk/Exceltk_vs.csproj b/src/Exceltk/Exceltk_vs.csproj index 2337718..9d6d5a1 100644 --- a/src/Exceltk/Exceltk_vs.csproj +++ b/src/Exceltk/Exceltk_vs.csproj @@ -84,7 +84,6 @@ - diff --git a/src/Exceltk/Reader/Binary/XlsBiffStream.cs b/src/Exceltk/Reader/Binary/XlsBiffStream.cs deleted file mode 100644 index 93ac428..0000000 --- a/src/Exceltk/Reader/Binary/XlsBiffStream.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.IO; - -namespace Exceltk.Reader.Binary { - /// - /// Represents a BIFF stream - /// - internal class XlsBiffStream : XlsStream { - private readonly byte[] bytes; - private readonly int m_size; - private readonly ExcelBinaryReader reader; - private int m_offset; - - public XlsBiffStream(XlsHeader hdr, uint streamStart, bool isMini, XlsRootDirectory rootDir, - ExcelBinaryReader reader) - : base(hdr, streamStart, isMini, rootDir) { - this.reader=reader; - bytes=base.ReadStream(); - m_size=bytes.Length; - m_offset=0; - } - - /// - /// Returns size of BIFF stream in bytes - /// - public int Size { - get { - return m_size; - } - } - - /// - /// Returns current position in BIFF stream - /// - public int Position { - get { - return m_offset; - } - } - - /// - /// Sets stream pointer to the specified offset - /// - /// Offset value - /// Offset origin - public void Seek(int offset, SeekOrigin origin) { - switch (origin) { - case SeekOrigin.Begin: - m_offset=offset; - break; - case SeekOrigin.Current: - m_offset+=offset; - break; - case SeekOrigin.End: - m_offset=m_size-offset; - break; - } - - if (m_offset<0){ - string message = string.Format("{0} On offset={1}", Errors.ErrorBIFFIlegalBefore, offset); - throw new ArgumentOutOfRangeException(message); - } - - if (m_offset>m_size){ - string message = string.Format("{0} On offset={1}", Errors.ErrorBIFFIlegalAfter,offset); - throw new ArgumentOutOfRangeException(message); - } - } - - /// - /// Reads record under cursor and advances cursor position to next record - /// - /// - public XlsBiffRecord Read() { - if ((uint)m_offset>=bytes.Length){ - return null; - } - - XlsBiffRecord rec=XlsBiffRecord.GetRecord(bytes, (uint)m_offset, reader); - m_offset+=rec.Size; - - if (m_offset>m_size){ - return null; - } - - return rec; - } - - /// - /// Reads record at specified offset, does not change cursor position - /// - /// - /// - public XlsBiffRecord ReadAt(int offset) { - if ((uint)offset>=bytes.Length){ - return null; - } - - XlsBiffRecord rec=XlsBiffRecord.GetRecord(bytes, (uint)offset, reader); - - //choose ReadOption.Loose to skip this check (e.g. sql reporting services) - if (reader.ReadOption==ReadOption.Strict) { - if (offset+rec.Size>m_size){ - return null; - } - } - - return rec; - } - } -} \ No newline at end of file diff --git a/src/Exceltk/Reader/ExcelBinaryReader.cs b/src/Exceltk/Reader/ExcelBinaryReader.cs index bd1fa5d..6cc42c9 100644 --- a/src/Exceltk/Reader/ExcelBinaryReader.cs +++ b/src/Exceltk/Reader/ExcelBinaryReader.cs @@ -5,6 +5,8 @@ using System.IO; using System.Text; using Exceltk.Reader.Binary; +using Exceltk.Reader.Package; +using Exceltk.Reader.Parser; namespace Exceltk.Reader { /// @@ -49,7 +51,7 @@ public class ExcelBinaryReader : IExcelDataReader { private int m_maxRow; private bool m_noIndex; private List m_sheets; - private XlsBiffStream m_stream; + private BinaryPackageParser m_stream; private ushort m_version; private DataSet m_workbookData; @@ -128,6 +130,11 @@ public void Close() { #endregion + + private static XlsBiffRecord RecordOf(BinaryPackage package) { + return package==null ? null : package.Record; + } + #region Private methods private int ResultsCount { @@ -150,7 +157,7 @@ private bool Read() { private int findFirstDataCellOffset(int startOffset) { //seek to the first dbcell record - XlsBiffRecord record=m_stream.ReadAt(startOffset); + XlsBiffRecord record=RecordOf(m_stream.ReadAt(startOffset)); while (!(record is XlsBiffDbCell)) { if (m_stream.Position >= m_stream.Size) { return -1; @@ -161,7 +168,7 @@ private int findFirstDataCellOffset(int startOffset) { } try { - record=m_stream.Read(); + record=RecordOf(m_stream.Read()); } catch { return -2; } @@ -173,7 +180,7 @@ private int findFirstDataCellOffset(int startOffset) { int offs=startCell.RowAddress; do { - row=m_stream.ReadAt(offs) as XlsBiffRow; + row=RecordOf(m_stream.ReadAt(offs)) as XlsBiffRow; if (row==null){ break; } @@ -205,7 +212,7 @@ private void readWorkBookGlobals() { return; } - m_stream=new XlsBiffStream(m_hdr, + m_stream=new BinaryPackageParser(m_hdr, workbookEntry.StreamFirstSector, workbookEntry.IsEntryMiniStream, dir,this); @@ -214,7 +221,7 @@ private void readWorkBookGlobals() { m_stream.Seek(0, SeekOrigin.Begin); - XlsBiffRecord rec=m_stream.Read(); + XlsBiffRecord rec=RecordOf(m_stream.Read()); var bof=rec as XlsBiffBOF; if (bof==null||bof.Type!=BIFFTYPE.WorkbookGlobals) { @@ -227,7 +234,7 @@ private void readWorkBookGlobals() { m_version=bof.Version; m_sheets=new List(); - while (null!=(rec=m_stream.Read())) { + while (null!=(rec=RecordOf(m_stream.Read()))) { switch (rec.ID) { case BIFFRECORDTYPE.INTERFACEHDR: m_globals.InterfaceHdr=(XlsBiffInterfaceHdr)rec; @@ -321,20 +328,20 @@ private bool readWorkSheetGlobals(XlsWorksheet sheet, out XlsBiffIndex idx, out m_stream.Seek((int)sheet.DataOffset, SeekOrigin.Begin); // Read BOF - var bof=m_stream.Read() as XlsBiffBOF; + var bof=RecordOf(m_stream.Read()) as XlsBiffBOF; if (bof==null||bof.Type!=BIFFTYPE.Worksheet) { return false; } // Read Index - XlsBiffRecord rec=m_stream.Read(); + XlsBiffRecord rec=RecordOf(m_stream.Read()); if (rec==null) return false; if (rec is XlsBiffIndex) { idx=rec as XlsBiffIndex; } else if (rec is XlsBiffUncalced) { // Sometimes this come before the index... - idx=m_stream.Read() as XlsBiffIndex; + idx=RecordOf(m_stream.Read()) as XlsBiffIndex; } if (idx!=null) { @@ -346,7 +353,7 @@ private bool readWorkSheetGlobals(XlsWorksheet sheet, out XlsBiffIndex idx, out XlsBiffDimensions dims=null; do { - trec=m_stream.Read(); + trec=RecordOf(m_stream.Read()); if (trec.ID==BIFFRECORDTYPE.DIMENSIONS) { dims=(XlsBiffDimensions)trec; break; @@ -363,7 +370,7 @@ private bool readWorkSheetGlobals(XlsWorksheet sheet, out XlsBiffIndex idx, out while (rowRecord==null) { if (m_stream.Position>=m_stream.Size) break; - XlsBiffRecord thisRec=m_stream.Read(); + XlsBiffRecord thisRec=RecordOf(m_stream.Read()); if (thisRec is XlsBiffEOF) break; @@ -402,7 +409,7 @@ private bool readWorkSheetGlobals(XlsWorksheet sheet, out XlsBiffIndex idx, out while (true) { if (m_stream.Position>=m_stream.Size) break; - XlsBiffRecord thisRecord=m_stream.Read(); + XlsBiffRecord thisRecord=RecordOf(m_stream.Read()); if (thisRecord is XlsBiffEOF) { break; @@ -427,7 +434,7 @@ private void DumpBiffRecords() { int startPos=m_stream.Position; do { - rec=m_stream.Read(); + rec=RecordOf(m_stream.Read()); } while (rec!=null&&m_stream.Position m_rowPackages; #endregion @@ -47,25 +49,13 @@ internal ExcelOpenXmlReader() { #region IExcelDataReader Members public void Open(Stream fileStream) { - var zipWorker = new ZipWorker(); - zipWorker.Extract(fileStream); - Open(zipWorker, ownsPackage: true); - } - - /// - /// Open an already-extracted OpenXML package (package / package-parser path). - /// - public void Open(IOpenXmlPackage package, bool ownsPackage = false) { - if (package == null) { - throw new ArgumentNullException("package"); - } - - m_package = package; - m_ownsPackage = ownsPackage; + m_zipWorker = new ZipWorker(); + m_zipWorker.Extract(fileStream); + m_ownsZipWorker = true; - if (!m_package.IsValid) { + if (!m_zipWorker.IsValid) { m_isValid = false; - m_exceptionMessage = m_package.ExceptionMessage; + m_exceptionMessage = m_zipWorker.ExceptionMessage; Dispose(); } else { m_isValid = true; @@ -98,11 +88,16 @@ public void Close() { m_sheetStream = null; } - if (m_package != null) { - if (m_ownsPackage) { - m_package.Dispose(); + if (m_rowPackages != null) { + m_rowPackages.Dispose(); + m_rowPackages = null; + } + + if (m_zipWorker != null) { + if (m_ownsZipWorker) { + m_zipWorker.Dispose(); } - m_package = null; + m_zipWorker = null; } } @@ -112,10 +107,10 @@ public void Close() { private void ReadGlobals() { m_workbook = new XlsxWorkbook( - m_package.GetWorkbookStream(), - m_package.GetWorkbookRelsStream(), - m_package.GetSharedStringsStream(), - m_package.GetStylesStream()); + m_zipWorker.GetWorkbookStream(), + m_zipWorker.GetWorkbookRelsStream(), + m_zipWorker.GetSharedStringsStream(), + m_zipWorker.GetStylesStream()); // Some workbooks omit styles.xml; treat that as empty styles instead of NRE (#10). if (m_workbook.Styles == null) { @@ -228,6 +223,14 @@ private void ReadSheetGlobals(XlsxWorksheet sheet) { m_xmlReader.ReadToFollowing(XlsxWorksheet.N_sheetData, m_namespaceUri); if (m_xmlReader.IsEmptyElement) { sheet.IsEmpty=true; + m_rowPackages = null; + } else { + // Stream rows as XmlRowPackage entities via XmlPackageParser. + if (m_rowPackages != null) { + m_rowPackages.Dispose(); + } + var parser = new XmlPackageParser(m_xmlReader, m_namespaceUri); + m_rowPackages = parser.ParseRows().GetEnumerator(); } } @@ -242,7 +245,7 @@ private bool ResetSheetReader(XlsxWorksheet sheet) { m_xmlReader = null; } - m_sheetStream = m_package.GetWorksheetStream(sheet.Path); + m_sheetStream = m_zipWorker.GetWorksheetStream(sheet.Path); if (null == m_sheetStream) { return false; } @@ -312,10 +315,6 @@ private bool ReadSheetRow(XlsxWorksheet sheet) { return false; } - if (null == m_xmlReader) { - return false; - } - if (m_emptyRowCount != 0) { m_cellsValues = new object[sheet.ColumnsCount]; m_emptyRowCount--; @@ -332,147 +331,25 @@ private bool ReadSheetRow(XlsxWorksheet sheet) { return true; } - bool isRow = false; - bool isSheetData = (m_xmlReader.NodeType == XmlNodeType.Element && - m_xmlReader.LocalName == XlsxWorksheet.N_sheetData); - if (isSheetData) { - isRow = m_xmlReader.ReadToFollowing(XlsxWorksheet.N_row, m_namespaceUri); - } else { - if (m_xmlReader.LocalName == XlsxWorksheet.N_row && m_xmlReader.NodeType == XmlNodeType.EndElement) { - //Console.WriteLine("read"); - m_xmlReader.Read(); - } - isRow = (m_xmlReader.NodeType == XmlNodeType.Element && m_xmlReader.LocalName == XlsxWorksheet.N_row); - } - - if (!isRow) { + if (m_rowPackages == null || !m_rowPackages.MoveNext()) { return false; } - //Console.WriteLine("New Row"); - + XmlRowPackage rowPackage = m_rowPackages.Current; m_cellsValues = new object[sheet.ColumnsCount]; - if (sheet.ColumnsCount > 13) { - int i = sheet.ColumnsCount; - } - var rowIndexText = m_xmlReader.GetAttribute(XlsxWorksheet.A_r); - Debug.Assert(rowIndexText!=null); - int rowIndex=int.Parse(rowIndexText); + Debug.Assert(rowPackage.RowIndexAttribute != null); + int rowIndex = int.Parse(rowPackage.RowIndexAttribute); if (rowIndex != (m_depth + 1)) { m_emptyRowCount = rowIndex - m_depth - 1; } - bool hasValue = false; - bool hasFormula = false; - HyperLinkIndex hyperlinkIndex = null; - string a_s = String.Empty; - string a_t = String.Empty; - string a_r = String.Empty; - string f = String.Empty; - int col = 0; - int row = 0; - - while (m_xmlReader.Read()) { - - //Console.WriteLine("m_xmlReader.LocalName:{0}",m_xmlReader.LocalName); - //Console.WriteLine("m_xmlReader.Value:{0}",m_xmlReader.Value); - if (m_xmlReader.Depth == 2) { - break; - } - - if (m_xmlReader.NodeType == XmlNodeType.Element) { - hasValue = false; - - if (m_xmlReader.LocalName == XlsxWorksheet.N_c) { - a_s = m_xmlReader.GetAttribute(XlsxWorksheet.A_s); - a_t = m_xmlReader.GetAttribute(XlsxWorksheet.A_t); - a_r = m_xmlReader.GetAttribute(XlsxWorksheet.A_r); - XlsxDimension.XlsxDim(a_r, out col, out row); - } else if(m_xmlReader.LocalName == XlsxWorksheet.N_f){ - hasFormula = true; - } else if (m_xmlReader.LocalName == XlsxWorksheet.N_v || m_xmlReader.LocalName == XlsxWorksheet.N_t) { - hasValue = true; - hasFormula = false; - } else { - //Console.WriteLine("m_xmlReader.LocalName:{0}",m_xmlReader.LocalName); - // Ignore - } - } - - bool hasHyperLinkFormula = false; - if(m_xmlReader.NodeType == XmlNodeType.Text && hasFormula){ - string formula = m_xmlReader.Value.ToString(); - if(formula.StartsWith("HYPERLINK(")){ - hyperlinkIndex = this.ReadHyperLinkFormula(sheet.Name, formula); - } - } - - - if (m_xmlReader.NodeType == XmlNodeType.Text && hasValue) { - double number; - object o = m_xmlReader.Value; - - //Console.WriteLine("O:{0}", o); - - if (double.TryParse(o.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out number)) { - // numeric - o=number; - } - - if (null!=a_t&&a_t==XlsxWorksheet.A_s) { - // string - if (m_workbook.SST!=null) { - var sstStr = m_workbook.SST[int.Parse(o.ToString())]; - //Console.WriteLine(sstStr); - o=sstStr.ConvertEscapeChars(); - } - } else if (null!=a_t&&a_t==XlsxWorksheet.N_inlineStr) { - // string inline - o=o.ToString().ConvertEscapeChars(); - } else if (a_t=="b") { - // boolean - o=m_xmlReader.Value=="1"; - } else if (a_t=="str") { - // string - o=m_xmlReader.Value; - } else if (null!=a_s && m_workbook.Styles!=null && m_workbook.Styles.CellXfs!=null) { - // something else — apply number/date format when style exists - int styleIndex; - if (int.TryParse(a_s, out styleIndex) - && styleIndex>=0 - && styleIndex= 1) { - EnsureRowCapacity(sheet, col); - if(hyperlinkIndex!=null){ - var co = new XlsCell(o); - co.HyperLinkIndex = hyperlinkIndex; - m_cellsValues[col - 1] = co; - hyperlinkIndex = null; - }else{ - m_cellsValues[col - 1] = o; - } - } - }else{ - //Console.WriteLine(m_xmlReader.Value.ToString()); - } + foreach (XmlCellPackage cellPackage in rowPackage.Cells) { + ApplyCellPackage(sheet, cellPackage); } if (m_emptyRowCount > 0) { - //Console.WriteLine("Again"); m_savedCellsValues = m_cellsValues; return ReadSheetRow(sheet); } @@ -481,37 +358,80 @@ private bool ReadSheetRow(XlsxWorksheet sheet) { return true; } - private bool ReadMergeCells(XlsxWorksheet sheet, DataTable table) { - // Restart the sheet stream so we can locate mergeCells independently of row reading. - if (!ResetSheetReader(sheet)) { - return false; - } - - if (!m_xmlReader.ReadToFollowing(XlsxWorksheet.N_mergeCells)) { - return false; - } - if (m_xmlReader.IsEmptyElement) { - return false; + private void ApplyCellPackage(XlsxWorksheet sheet, XmlCellPackage cellPackage) { + string a_s = cellPackage.StyleId; + string a_t = cellPackage.CellType; + string a_r = cellPackage.Reference; + int col; + int row; + if (string.IsNullOrEmpty(a_r)) { + return; } + XlsxDimension.XlsxDim(a_r, out col, out row); - while (m_xmlReader.Read()) { - if (m_xmlReader.NodeType != XmlNodeType.Element) { - if (m_xmlReader.NodeType == XmlNodeType.EndElement && - m_xmlReader.LocalName == XlsxWorksheet.N_mergeCells) { - break; + HyperLinkIndex hyperlinkIndex = null; + if (!string.IsNullOrEmpty(cellPackage.Formula) && cellPackage.Formula.StartsWith("HYPERLINK(")) { + hyperlinkIndex = this.ReadHyperLinkFormula(sheet.Name, cellPackage.Formula); + } + + double number; + object o = cellPackage.ValueText; + + if (double.TryParse(o.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out number)) { + o = number; + } + + if (null != a_t && a_t == XlsxWorksheet.A_s) { + if (m_workbook.SST != null) { + var sstStr = m_workbook.SST[int.Parse(o.ToString())]; + o = sstStr.ConvertEscapeChars(); + } + } else if (null != a_t && a_t == XlsxWorksheet.N_inlineStr) { + o = o.ToString().ConvertEscapeChars(); + } else if (a_t == "b") { + o = cellPackage.ValueText == "1"; + } else if (a_t == "str") { + o = cellPackage.ValueText; + } else if (null != a_s && m_workbook.Styles != null && m_workbook.Styles.CellXfs != null) { + int styleIndex; + if (int.TryParse(a_s, out styleIndex) + && styleIndex >= 0 + && styleIndex < m_workbook.Styles.CellXfs.Count) { + XlsxXf xf = m_workbook.Styles.CellXfs[styleIndex]; + if (xf.ApplyNumberFormat && o != null && o.ToString() != string.Empty && + IsDateTimeStyle(xf.NumFmtId)) { + o = number.ConvertFromOATime(); + } else if (xf.NumFmtId == 49) { + o = o.ToString(); } - continue; } - if (m_xmlReader.LocalName != XlsxWorksheet.N_mergeCell) { - break; + } + + if (col >= 1) { + EnsureRowCapacity(sheet, col); + if (hyperlinkIndex != null) { + var co = new XlsCell(o); + co.HyperLinkIndex = hyperlinkIndex; + m_cellsValues[col - 1] = co; + } else { + m_cellsValues[col - 1] = o; } + } + } - string aref = m_xmlReader.GetAttribute(XlsxWorksheet.A_ref); + private bool ReadMergeCells(XlsxWorksheet sheet, DataTable table) { + // Restart the sheet stream so we can locate mergeCells independently of row reading. + if (!ResetSheetReader(sheet)) { + return false; + } + + var parser = new XmlPackageParser(m_xmlReader, m_namespaceUri); + foreach (XmlMergePackage mergePackage in parser.ParseMerges()) { + string aref = mergePackage.Ref; if (string.IsNullOrEmpty(aref)) { continue; } - // ref may be "A2:A8" or a single cell "A2" string[] parts = aref.Split(':'); int c1, r1, c2, r2; XlsxDimension.XlsxDim(parts[0], out c1, out r1); @@ -522,7 +442,6 @@ private bool ReadMergeCells(XlsxWorksheet sheet, DataTable table) { r2 = r1; } - // Convert to 0-based c1--; r1--; c2--; r2--; if (c1 < 0 || r1 < 0) { continue; @@ -551,27 +470,19 @@ private bool ReadHyperLinks(XlsxWorksheet sheet, DataTable table) { return false; } - // ReadTo HyperLinks Node if (m_xmlReader == null) { return false; } - if (!m_xmlReader.ReadToFollowing(XlsxWorksheet.N_hyperlinks)) { - return false; - } - if (m_xmlReader.IsEmptyElement) { - return false; - } - - // Read Realtionship Table - Stream sheetRelStream = m_package.GetWorksheetRelsStream(sheet.Path); + // Read Relationship Table + Stream sheetRelStream = m_zipWorker.GetWorksheetRelsStream(sheet.Path); var hyperDict = new Dictionary(); if (sheetRelStream != null) { using (XmlReader reader = XmlReader.Create(sheetRelStream)) { while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.LocalName == XlsxWorkbook.N_rel) { string rid = reader.GetAttribute(XlsxWorkbook.A_id); - Debug.Assert(rid!=null); + Debug.Assert(rid != null); hyperDict[rid] = reader.GetAttribute(XlsxWorkbook.A_target); } } @@ -579,21 +490,14 @@ private bool ReadHyperLinks(XlsxWorksheet sheet, DataTable table) { } } - - // Read All HyperLink Node - while (m_xmlReader.Read()) { - if (m_xmlReader.NodeType != XmlNodeType.Element) { - break; - } - - if (m_xmlReader.LocalName != XlsxWorksheet.N_hyperlink) { - break; - } - - string aref = m_xmlReader.GetAttribute(XlsxWorksheet.A_ref); - string display = m_xmlReader.GetAttribute(XlsxWorksheet.A_display); - string rid = m_xmlReader.GetAttribute(XlsxWorksheet.A_rid); - string location = m_xmlReader.GetAttribute("location"); // fragment identifier + var parser = new XmlPackageParser(m_xmlReader, m_namespaceUri); + bool any = false; + foreach (XmlHyperlinkPackage linkPackage in parser.ParseHyperlinks()) { + any = true; + string aref = linkPackage.Ref; + string display = linkPackage.Display; + string rid = linkPackage.RelationshipId; + string location = linkPackage.Location; string hyperlink = display; if (!string.IsNullOrEmpty(rid) && hyperDict.ContainsKey(rid)) { @@ -604,7 +508,6 @@ private bool ReadHyperLinks(XlsxWorksheet sheet, DataTable table) { continue; } - // ref may be a single cell "A2" or a range "A3:A4" var dim = new XlsxDimension(aref); int c1 = dim.FirstCol - 1; int r1 = dim.FirstRow - 1; @@ -633,13 +536,12 @@ private bool ReadHyperLinks(XlsxWorksheet sheet, DataTable table) { } } - // Close m_xmlReader.Close(); if (m_sheetStream != null) { m_sheetStream.Close(); } - return true; + return any; } private bool IsDateTimeStyle(int styleId) { @@ -805,11 +707,12 @@ private void Dispose(bool disposing) { ((IDisposable)m_xmlReader).Dispose(); if (m_sheetStream != null) m_sheetStream.Dispose(); - if (m_package != null && m_ownsPackage) - m_package.Dispose(); + if (m_zipWorker != null && m_ownsZipWorker) + m_zipWorker.Dispose(); } - m_package = null; + m_zipWorker = null; + m_rowPackages = null; m_xmlReader = null; m_sheetStream = null; diff --git a/src/Exceltk/Reader/Package/BinaryPackage.cs b/src/Exceltk/Reader/Package/BinaryPackage.cs new file mode 100644 index 0000000..49780bb --- /dev/null +++ b/src/Exceltk/Reader/Package/BinaryPackage.cs @@ -0,0 +1,23 @@ +using Exceltk.Reader.Binary; + +namespace Exceltk.Reader.Package { + /// + /// One BIFF record as a streamed package entity. + /// + internal sealed class BinaryPackage : IPackage { + public BinaryPackage(XlsBiffRecord record) { + Record = record; + } + + public XlsBiffRecord Record { + get; + private set; + } + + public BIFFRECORDTYPE Id { + get { + return Record.ID; + } + } + } +} diff --git a/src/Exceltk/Reader/Package/IExcelPackage.cs b/src/Exceltk/Reader/Package/IExcelPackage.cs deleted file mode 100644 index e79bbe8..0000000 --- a/src/Exceltk/Reader/Package/IExcelPackage.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Exceltk.Reader.Package { - /// - /// Opened workbook container (ZIP package, OLE compound file, or plain file). - /// Parsers read workbook content through this abstraction. - /// - public interface IExcelPackage : IDisposable { - bool IsValid { - get; - } - - string ExceptionMessage { - get; - } - } -} diff --git a/src/Exceltk/Reader/Package/IOpenXmlPackage.cs b/src/Exceltk/Reader/Package/IOpenXmlPackage.cs deleted file mode 100644 index f52768c..0000000 --- a/src/Exceltk/Reader/Package/IOpenXmlPackage.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.IO; - -namespace Exceltk.Reader.Package { - /// - /// OpenXML (.xlsx) package: ZIP parts for workbook, sheets, shared strings, and styles. - /// - public interface IOpenXmlPackage : IExcelPackage { - Stream GetSharedStringsStream(); - Stream GetStylesStream(); - Stream GetWorkbookStream(); - Stream GetWorkbookRelsStream(); - Stream GetWorksheetStream(string sheetPath); - Stream GetWorksheetRelsStream(string sheetPath); - } -} diff --git a/src/Exceltk/Reader/Package/IPackage.cs b/src/Exceltk/Reader/Package/IPackage.cs new file mode 100644 index 0000000..2fa1b30 --- /dev/null +++ b/src/Exceltk/Reader/Package/IPackage.cs @@ -0,0 +1,8 @@ +namespace Exceltk.Reader.Package { + /// + /// A parsed semantic unit emitted by a streaming package parser. + /// Not a file container — binary records and XML elements are packages. + /// + public interface IPackage { + } +} diff --git a/src/Exceltk/Reader/Package/IPathPackage.cs b/src/Exceltk/Reader/Package/IPathPackage.cs deleted file mode 100644 index 368d2fe..0000000 --- a/src/Exceltk/Reader/Package/IPathPackage.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Exceltk.Reader.Package { - /// - /// Package identified by a filesystem path (e.g. CSV). - /// - public interface IPathPackage : IExcelPackage { - string Path { - get; - } - } -} diff --git a/src/Exceltk/Reader/Package/IStreamPackage.cs b/src/Exceltk/Reader/Package/IStreamPackage.cs deleted file mode 100644 index 9fb178c..0000000 --- a/src/Exceltk/Reader/Package/IStreamPackage.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.IO; - -namespace Exceltk.Reader.Package { - /// - /// Package that exposes a single content stream (binary .xls or similar). - /// - public interface IStreamPackage : IExcelPackage { - Stream Content { - get; - } - } -} diff --git a/src/Exceltk/Reader/Package/PathPackage.cs b/src/Exceltk/Reader/Package/PathPackage.cs deleted file mode 100644 index 165905c..0000000 --- a/src/Exceltk/Reader/Package/PathPackage.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.IO; - -namespace Exceltk.Reader.Package { - /// - /// Package that points at a file path without loading the whole file into memory. - /// - public sealed class PathPackage : IPathPackage { - public PathPackage(string path) { - if (string.IsNullOrEmpty(path)) { - throw new ArgumentException("path"); - } - Path = path; - if (File.Exists(path)) { - IsValid = true; - ExceptionMessage = null; - } else { - IsValid = false; - ExceptionMessage = "File not found: " + path; - } - } - - public string Path { - get; - private set; - } - - public bool IsValid { - get; - private set; - } - - public string ExceptionMessage { - get; - private set; - } - - public void Dispose() { - // Nothing to release; path is metadata only. - } - } -} diff --git a/src/Exceltk/Reader/Package/StreamPackage.cs b/src/Exceltk/Reader/Package/StreamPackage.cs deleted file mode 100644 index 84b8d9a..0000000 --- a/src/Exceltk/Reader/Package/StreamPackage.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; - -namespace Exceltk.Reader.Package { - /// - /// Thin package wrapper around an already-opened content stream. - /// - public sealed class StreamPackage : IStreamPackage { - private readonly bool m_ownsStream; - private bool m_disposed; - - public StreamPackage(Stream content, bool ownsStream = true) { - if (content == null) { - throw new ArgumentNullException("content"); - } - Content = content; - m_ownsStream = ownsStream; - IsValid = true; - ExceptionMessage = null; - } - - public Stream Content { - get; - private set; - } - - public bool IsValid { - get; - private set; - } - - public string ExceptionMessage { - get; - private set; - } - - public void Dispose() { - if (m_disposed) { - return; - } - m_disposed = true; - if (m_ownsStream && Content != null) { - Content.Dispose(); - Content = null; - } - } - } -} diff --git a/src/Exceltk/Reader/Package/XmlPackage.cs b/src/Exceltk/Reader/Package/XmlPackage.cs new file mode 100644 index 0000000..1dccd0b --- /dev/null +++ b/src/Exceltk/Reader/Package/XmlPackage.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; + +namespace Exceltk.Reader.Package { + /// + /// Base type for OpenXML worksheet semantic units streamed by XmlPackageParser. + /// + internal abstract class XmlPackage : IPackage { + } + + internal sealed class XmlWorksheetStartPackage : XmlPackage { + public string NamespaceUri { + get; + set; + } + } + + internal sealed class XmlDimensionPackage : XmlPackage { + public string Ref { + get; + set; + } + } + + internal sealed class XmlCellPackage : XmlPackage { + public string Reference { + get; + set; + } + + public string StyleId { + get; + set; + } + + public string CellType { + get; + set; + } + + public string Formula { + get; + set; + } + + public string ValueText { + get; + set; + } + } + + internal sealed class XmlRowPackage : XmlPackage { + public XmlRowPackage() { + Cells = new List(); + } + + /// 1-based row index from the r attribute, when present. + public string RowIndexAttribute { + get; + set; + } + + public List Cells { + get; + private set; + } + } + + internal sealed class XmlMergePackage : XmlPackage { + public string Ref { + get; + set; + } + } + + internal sealed class XmlHyperlinkPackage : XmlPackage { + public string Ref { + get; + set; + } + + public string Display { + get; + set; + } + + public string RelationshipId { + get; + set; + } + + public string Location { + get; + set; + } + } + + internal sealed class XmlSheetDataEndPackage : XmlPackage { + } +} diff --git a/src/Exceltk/Reader/Parser/BinaryPackageParser.cs b/src/Exceltk/Reader/Parser/BinaryPackageParser.cs index b505521..c50601d 100644 --- a/src/Exceltk/Reader/Parser/BinaryPackageParser.cs +++ b/src/Exceltk/Reader/Parser/BinaryPackageParser.cs @@ -1,24 +1,109 @@ using System; +using System.Collections.Generic; +using System.IO; +using Exceltk.Reader.Binary; using Exceltk.Reader.Package; namespace Exceltk.Reader.Parser { /// - /// Parses a binary (.xls) package via . + /// Streams entities (one BIFF record each) from a workbook stream. /// - public sealed class BinaryPackageParser : IExcelPackageParser { - public DataSet Parse(IExcelPackage package) { - var streamPackage = package as IStreamPackage; - if (streamPackage == null) { - throw new ArgumentException("BinaryPackageParser requires IStreamPackage", "package"); + internal sealed class BinaryPackageParser : XlsStream, IPackageParser { + private readonly byte[] m_bytes; + private readonly int m_size; + private readonly ExcelBinaryReader m_reader; + private int m_offset; + + public BinaryPackageParser(XlsHeader hdr, uint streamStart, bool isMini, XlsRootDirectory rootDir, + ExcelBinaryReader reader) + : base(hdr, streamStart, isMini, rootDir) { + m_reader = reader; + m_bytes = base.ReadStream(); + m_size = m_bytes.Length; + m_offset = 0; + } + + public int Size { + get { + return m_size; + } + } + + public int Position { + get { + return m_offset; + } + } + + public void Seek(int offset, SeekOrigin origin) { + switch (origin) { + case SeekOrigin.Begin: + m_offset = offset; + break; + case SeekOrigin.Current: + m_offset += offset; + break; + case SeekOrigin.End: + m_offset = m_size - offset; + break; + } + + if (m_offset < 0) { + string message = string.Format("{0} On offset={1}", Errors.ErrorBIFFIlegalBefore, offset); + throw new ArgumentOutOfRangeException(message); + } + + if (m_offset > m_size) { + string message = string.Format("{0} On offset={1}", Errors.ErrorBIFFIlegalAfter, offset); + throw new ArgumentOutOfRangeException(message); + } + } + + /// + /// Read the next BIFF record as a and advance the cursor. + /// + public BinaryPackage Read() { + if ((uint)m_offset >= m_bytes.Length) { + return null; } - if (!streamPackage.IsValid) { + + XlsBiffRecord rec = XlsBiffRecord.GetRecord(m_bytes, (uint)m_offset, m_reader); + m_offset += rec.Size; + + if (m_offset > m_size) { return null; } - // ExcelBinaryReader.AsDataSet/Close dispose the stream; do not dispose again here. - var reader = new ExcelBinaryReader(); - reader.Open(streamPackage.Content); - return reader.AsDataSet(); + return new BinaryPackage(rec); + } + + /// + /// Read a record package at an absolute offset without moving the cursor. + /// + public BinaryPackage ReadAt(int offset) { + if ((uint)offset >= m_bytes.Length) { + return null; + } + + XlsBiffRecord rec = XlsBiffRecord.GetRecord(m_bytes, (uint)offset, m_reader); + + if (m_reader.ReadOption == ReadOption.Strict) { + if (offset + rec.Size > m_size) { + return null; + } + } + + return new BinaryPackage(rec); + } + + /// + /// Stream all remaining packages from the current cursor to the end. + /// + public IEnumerable Parse() { + BinaryPackage package; + while ((package = Read()) != null) { + yield return package; + } } } } diff --git a/src/Exceltk/Reader/Parser/CsvPackageParser.cs b/src/Exceltk/Reader/Parser/CsvPackageParser.cs deleted file mode 100644 index d571b0b..0000000 --- a/src/Exceltk/Reader/Parser/CsvPackageParser.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Exceltk.Reader.Package; - -namespace Exceltk.Reader.Parser { - /// - /// Parses a CSV path package via . - /// - public sealed class CsvPackageParser : IExcelPackageParser { - public DataSet Parse(IExcelPackage package) { - var pathPackage = package as IPathPackage; - if (pathPackage == null) { - throw new ArgumentException("CsvPackageParser requires IPathPackage", "package"); - } - if (!pathPackage.IsValid) { - return null; - } - return CsvReader.AsDataSet(pathPackage.Path); - } - } -} diff --git a/src/Exceltk/Reader/Parser/ExcelPackageParserFactory.cs b/src/Exceltk/Reader/Parser/ExcelPackageParserFactory.cs deleted file mode 100644 index 565bb43..0000000 --- a/src/Exceltk/Reader/Parser/ExcelPackageParserFactory.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.IO; -using Exceltk.Reader.Package; - -namespace Exceltk.Reader.Parser { - /// - /// Creates the matching package + parser pair for a workbook path. - /// - public static class ExcelPackageParserFactory { - public static IExcelPackage OpenPackage(string path) { - string ext = Path.GetExtension(path); - if (string.Equals(ext, ".csv", StringComparison.OrdinalIgnoreCase)) { - return new PathPackage(path); - } - if (string.Equals(ext, ".xls", StringComparison.OrdinalIgnoreCase)) { - Stream stream = File.Open(path, FileMode.Open, FileAccess.Read); - return new StreamPackage(stream, ownsStream: true); - } - if (string.Equals(ext, ".xlsx", StringComparison.OrdinalIgnoreCase)) { - Stream stream = File.Open(path, FileMode.Open, FileAccess.Read); - var package = new ZipWorker(); - package.Extract(stream); - return package; - } - throw new ArgumentException("Not Support Format: " + ext); - } - - public static IExcelPackageParser CreateParser(string path) { - string ext = Path.GetExtension(path); - if (string.Equals(ext, ".csv", StringComparison.OrdinalIgnoreCase)) { - return new CsvPackageParser(); - } - if (string.Equals(ext, ".xls", StringComparison.OrdinalIgnoreCase)) { - return new BinaryPackageParser(); - } - if (string.Equals(ext, ".xlsx", StringComparison.OrdinalIgnoreCase)) { - return new OpenXmlPackageParser(); - } - throw new ArgumentException("Not Support Format: " + ext); - } - - public static DataSet Load(string path) { - using (IExcelPackage package = OpenPackage(path)) { - IExcelPackageParser parser = CreateParser(path); - return parser.Parse(package); - } - } - } -} diff --git a/src/Exceltk/Reader/Parser/IExcelPackageParser.cs b/src/Exceltk/Reader/Parser/IExcelPackageParser.cs deleted file mode 100644 index 8f1422d..0000000 --- a/src/Exceltk/Reader/Parser/IExcelPackageParser.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Exceltk.Reader.Package; - -namespace Exceltk.Reader.Parser { - /// - /// Parses an into the shared model. - /// - public interface IExcelPackageParser { - DataSet Parse(IExcelPackage package); - } -} diff --git a/src/Exceltk/Reader/Parser/IPackageParser.cs b/src/Exceltk/Reader/Parser/IPackageParser.cs new file mode 100644 index 0000000..0e2609f --- /dev/null +++ b/src/Exceltk/Reader/Parser/IPackageParser.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.IO; +using Exceltk.Reader.Package; + +namespace Exceltk.Reader.Parser { + /// + /// Streaming parser: reads an input and yields package entities incrementally. + /// + public interface IPackageParser where TPackage : IPackage { + /// + /// Parse from the current position, yielding packages one at a time. + /// + IEnumerable Parse(); + } +} diff --git a/src/Exceltk/Reader/Parser/OpenXmlPackageParser.cs b/src/Exceltk/Reader/Parser/OpenXmlPackageParser.cs deleted file mode 100644 index d6dc315..0000000 --- a/src/Exceltk/Reader/Parser/OpenXmlPackageParser.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Exceltk.Reader.Package; - -namespace Exceltk.Reader.Parser { - /// - /// Parses an OpenXML (.xlsx) package via . - /// - public sealed class OpenXmlPackageParser : IExcelPackageParser { - public DataSet Parse(IExcelPackage package) { - var openXml = package as IOpenXmlPackage; - if (openXml == null) { - throw new ArgumentException("OpenXmlPackageParser requires IOpenXmlPackage", "package"); - } - if (!openXml.IsValid) { - return null; - } - - var reader = new ExcelOpenXmlReader(); - try { - reader.Open(openXml); - return reader.AsDataSet(); - } finally { - reader.Close(); - } - } - } -} diff --git a/src/Exceltk/Reader/Parser/XmlPackageParser.cs b/src/Exceltk/Reader/Parser/XmlPackageParser.cs new file mode 100644 index 0000000..e4c33b5 --- /dev/null +++ b/src/Exceltk/Reader/Parser/XmlPackageParser.cs @@ -0,0 +1,203 @@ +using System.Collections.Generic; +using System.Xml; +using Exceltk.Reader.Package; +using Exceltk.Reader.Xml; + +namespace Exceltk.Reader.Parser { + /// + /// Streams entities from an OpenXML worksheet XmlReader. + /// + internal sealed class XmlPackageParser : IPackageParser { + private readonly XmlReader m_reader; + private readonly string m_namespaceUri; + + public XmlPackageParser(XmlReader reader, string namespaceUri = null) { + m_reader = reader; + m_namespaceUri = namespaceUri; + } + + /// + /// Walk the worksheet document, yielding dimension / row / sheetData-end packages. + /// Caller should position at the start of the worksheet document. + /// + public IEnumerable Parse() { + while (m_reader.Read()) { + if (m_reader.NodeType != XmlNodeType.Element) { + continue; + } + + if (m_reader.LocalName == XlsxWorksheet.N_worksheet) { + yield return new XmlWorksheetStartPackage { + NamespaceUri = m_reader.NamespaceURI + }; + } else if (m_reader.LocalName == XlsxWorksheet.N_dimension) { + yield return new XmlDimensionPackage { + Ref = m_reader.GetAttribute(XlsxWorksheet.A_ref) + }; + } else if (m_reader.LocalName == XlsxWorksheet.N_sheetData) { + if (m_reader.IsEmptyElement) { + yield return new XmlSheetDataEndPackage(); + continue; + } + foreach (XmlPackage rowPackage in ParseRows()) { + yield return rowPackage; + } + yield return new XmlSheetDataEndPackage(); + } else if (m_reader.LocalName == XlsxWorksheet.N_mergeCells) { + foreach (XmlMergePackage merge in ParseMergesFromCurrent()) { + yield return merge; + } + } else if (m_reader.LocalName == XlsxWorksheet.N_hyperlinks) { + foreach (XmlHyperlinkPackage link in ParseHyperlinksFromCurrent()) { + yield return link; + } + } + } + } + + /// + /// Stream row packages from the current sheetData element (or subsequent rows). + /// + public IEnumerable ParseRows() { + bool first = true; + while (true) { + bool isRow; + if (first && m_reader.NodeType == XmlNodeType.Element && + m_reader.LocalName == XlsxWorksheet.N_sheetData) { + if (m_namespaceUri != null) { + isRow = m_reader.ReadToFollowing(XlsxWorksheet.N_row, m_namespaceUri); + } else { + isRow = m_reader.ReadToFollowing(XlsxWorksheet.N_row); + } + first = false; + } else { + if (m_reader.LocalName == XlsxWorksheet.N_row && m_reader.NodeType == XmlNodeType.EndElement) { + m_reader.Read(); + } + isRow = (m_reader.NodeType == XmlNodeType.Element && m_reader.LocalName == XlsxWorksheet.N_row); + first = false; + } + + if (!isRow) { + yield break; + } + + yield return ReadCurrentRow(); + } + } + + public IEnumerable ParseMerges() { + if (!m_reader.ReadToFollowing(XlsxWorksheet.N_mergeCells)) { + yield break; + } + foreach (XmlMergePackage merge in ParseMergesFromCurrent()) { + yield return merge; + } + } + + public IEnumerable ParseHyperlinks() { + if (!m_reader.ReadToFollowing(XlsxWorksheet.N_hyperlinks)) { + yield break; + } + foreach (XmlHyperlinkPackage link in ParseHyperlinksFromCurrent()) { + yield return link; + } + } + + private IEnumerable ParseMergesFromCurrent() { + if (m_reader.IsEmptyElement) { + yield break; + } + + while (m_reader.Read()) { + if (m_reader.NodeType != XmlNodeType.Element) { + if (m_reader.NodeType == XmlNodeType.EndElement && + m_reader.LocalName == XlsxWorksheet.N_mergeCells) { + yield break; + } + continue; + } + if (m_reader.LocalName != XlsxWorksheet.N_mergeCell) { + yield break; + } + yield return new XmlMergePackage { + Ref = m_reader.GetAttribute(XlsxWorksheet.A_ref) + }; + } + } + + private IEnumerable ParseHyperlinksFromCurrent() { + if (m_reader.IsEmptyElement) { + yield break; + } + + while (m_reader.Read()) { + if (m_reader.NodeType != XmlNodeType.Element) { + yield break; + } + if (m_reader.LocalName != XlsxWorksheet.N_hyperlink) { + yield break; + } + yield return new XmlHyperlinkPackage { + Ref = m_reader.GetAttribute(XlsxWorksheet.A_ref), + Display = m_reader.GetAttribute(XlsxWorksheet.A_display), + RelationshipId = m_reader.GetAttribute(XlsxWorksheet.A_rid), + Location = m_reader.GetAttribute("location") + }; + } + } + + private XmlRowPackage ReadCurrentRow() { + var row = new XmlRowPackage { + RowIndexAttribute = m_reader.GetAttribute(XlsxWorksheet.A_r) + }; + + string a_s = string.Empty; + string a_t = string.Empty; + string a_r = string.Empty; + string formula = null; + bool hasValue = false; + bool hasFormula = false; + + while (m_reader.Read()) { + if (m_reader.Depth == 2) { + break; + } + + if (m_reader.NodeType == XmlNodeType.Element) { + hasValue = false; + if (m_reader.LocalName == XlsxWorksheet.N_c) { + a_s = m_reader.GetAttribute(XlsxWorksheet.A_s); + a_t = m_reader.GetAttribute(XlsxWorksheet.A_t); + a_r = m_reader.GetAttribute(XlsxWorksheet.A_r); + formula = null; + } else if (m_reader.LocalName == XlsxWorksheet.N_f) { + hasFormula = true; + } else if (m_reader.LocalName == XlsxWorksheet.N_v || m_reader.LocalName == XlsxWorksheet.N_t) { + hasValue = true; + hasFormula = false; + } + } + + if (m_reader.NodeType == XmlNodeType.Text && hasFormula) { + formula = m_reader.Value; + hasFormula = false; + } + + if (m_reader.NodeType == XmlNodeType.Text && hasValue) { + row.Cells.Add(new XmlCellPackage { + Reference = a_r, + StyleId = a_s, + CellType = a_t, + Formula = formula, + ValueText = m_reader.Value + }); + formula = null; + hasValue = false; + } + } + + return row; + } + } +} diff --git a/src/Exceltk/Reader/WorkbookLoader.cs b/src/Exceltk/Reader/WorkbookLoader.cs index f1d4d4a..5c1ad35 100644 --- a/src/Exceltk/Reader/WorkbookLoader.cs +++ b/src/Exceltk/Reader/WorkbookLoader.cs @@ -1,15 +1,38 @@ using System; using System.IO; -using Exceltk.Reader.Parser; namespace Exceltk.Reader { /// - /// Loads a workbook-like DataSet from .xls, .xlsx, or .csv via the - /// package / package-parser pipeline. + /// Loads a workbook-like DataSet from .xls, .xlsx, or .csv. + /// Binary/XML formats are read via streaming package parsers internally. /// public static class WorkbookLoader { public static DataSet Load(string path) { - return ExcelPackageParserFactory.Load(path); + string ext=Path.GetExtension(path); + if (string.Equals(ext, ".csv", StringComparison.OrdinalIgnoreCase)) { + return CsvReader.AsDataSet(path); + } + + FileStream stream=File.Open(path, FileMode.Open, FileAccess.Read); + IExcelDataReader excelReader=null; + try { + if (string.Equals(ext, ".xls", StringComparison.OrdinalIgnoreCase)) { + excelReader=ExcelReaderFactory.CreateBinaryReader(stream); + } else if (string.Equals(ext, ".xlsx", StringComparison.OrdinalIgnoreCase)) { + excelReader=ExcelReaderFactory.CreateOpenXmlReader(stream); + } else { + stream.Close(); + throw new ArgumentException("Not Support Format: "+ext); + } + DataSet dataSet=excelReader.AsDataSet(); + return dataSet; + } finally { + if (excelReader!=null) { + excelReader.Close(); + } else { + stream.Close(); + } + } } public static bool IsSupportedExtension(string path) { diff --git a/src/Exceltk/Reader/ZipWorker.cs b/src/Exceltk/Reader/ZipWorker.cs index 5874b6d..f53e2e1 100644 --- a/src/Exceltk/Reader/ZipWorker.cs +++ b/src/Exceltk/Reader/ZipWorker.cs @@ -3,15 +3,14 @@ using System.Diagnostics; using System.Globalization; using System.IO; -using Exceltk.Reader.Package; using ICSharpCode.SharpZipLib.Zip; namespace Exceltk.Reader { /// - /// OpenXML ZIP package worker. Implements for the - /// package / package-parser architecture. + /// OpenXML ZIP archive IO helper (file container). Not a semantic Package entity — + /// worksheet XML is streamed as XmlPackage by XmlPackageParser. /// - public class ZipWorker : IOpenXmlPackage { + public class ZipWorker : IDisposable { #region Members and Properties private const string TMP="TMP_Z";