From 352fa0498f9a3b72d469c44ae5f7843eb3ddb77d Mon Sep 17 00:00:00 2001 From: Antony Liu Date: Sun, 11 May 2025 14:47:02 +0800 Subject: [PATCH 1/8] Rename test file, add license header --- .../{TestXSSFShape.cs => TestXSSFShapes.cs} | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) rename testcases/ooxml/XSSF/UserModel/{TestXSSFShape.cs => TestXSSFShapes.cs} (96%) diff --git a/testcases/ooxml/XSSF/UserModel/TestXSSFShape.cs b/testcases/ooxml/XSSF/UserModel/TestXSSFShapes.cs similarity index 96% rename from testcases/ooxml/XSSF/UserModel/TestXSSFShape.cs rename to testcases/ooxml/XSSF/UserModel/TestXSSFShapes.cs index f467b45dc..53f76dcb5 100644 --- a/testcases/ooxml/XSSF/UserModel/TestXSSFShape.cs +++ b/testcases/ooxml/XSSF/UserModel/TestXSSFShapes.cs @@ -1,5 +1,21 @@ -using NPOI.OpenXmlFormats.Dml; -using NPOI.OpenXmlFormats.Dml.Spreadsheet; +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for Additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +using NPOI.OpenXmlFormats.Dml; using NPOI.SS.UserModel; using NPOI.Util; using NPOI.XSSF; @@ -8,7 +24,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.CompilerServices; namespace TestCases.XSSF.UserModel { From 68d6d2546a4ada60377e21edbc4502f801f0f712 Mon Sep 17 00:00:00 2001 From: Antony Liu Date: Sun, 11 May 2025 15:20:32 +0800 Subject: [PATCH 2/8] bug 58325: enable TestXSSFShape unit tests. --- OpenXmlFormats/Drawing/SpreadsheetDrawing.cs | 3 +- .../ooxml/XSSF/UserModel/TestUnfixedBugs.cs | 37 ---------- .../ooxml/XSSF/UserModel/TestXSSFShape.cs | 68 +++++++++++++++++++ 3 files changed, 70 insertions(+), 38 deletions(-) create mode 100644 testcases/ooxml/XSSF/UserModel/TestXSSFShape.cs diff --git a/OpenXmlFormats/Drawing/SpreadsheetDrawing.cs b/OpenXmlFormats/Drawing/SpreadsheetDrawing.cs index c04219fab..f16fe7c38 100644 --- a/OpenXmlFormats/Drawing/SpreadsheetDrawing.cs +++ b/OpenXmlFormats/Drawing/SpreadsheetDrawing.cs @@ -1394,7 +1394,8 @@ public int SizeOfOneCellAnchorArray() public static CT_Drawing Parse(XmlDocument xmldoc, XmlNamespaceManager namespaceManager) { - XmlNodeList cellanchorNodes = xmldoc.SelectNodes("/xdr:wsDr/*", namespaceManager); + XmlNode root = xmldoc.SelectSingleNode("/xdr:wsDr", namespaceManager); + XmlNodeList cellanchorNodes = root.SelectNodes("descendant::xdr:oneCellAnchor|descendant::xdr:twoCellAnchor|descendant::xdr:absCellAnchor", namespaceManager); CT_Drawing ctDrawing = new CT_Drawing(); foreach (XmlNode node in cellanchorNodes) { diff --git a/testcases/ooxml/XSSF/UserModel/TestUnfixedBugs.cs b/testcases/ooxml/XSSF/UserModel/TestUnfixedBugs.cs index eead8663d..53a7f913c 100644 --- a/testcases/ooxml/XSSF/UserModel/TestUnfixedBugs.cs +++ b/testcases/ooxml/XSSF/UserModel/TestUnfixedBugs.cs @@ -355,43 +355,6 @@ private void checkRow57423(ISheet testSheet, int rowNum, String contents) ClassicAssert.AreEqual(contents, cell.ToString(), "Did not have expected contents at rownum " + rowNum); //ClassicAssert.AreEqual(contents + ".0", cell.ToString(), "Did not have expected contents at rownum " + rowNum); } - - [Test] - public void test58325_one() - { - check58325(XSSFTestDataSamples.OpenSampleWorkbook("58325_lt.xlsx"), 1); - } - [Test] - [Ignore("TODO FIX CI TESTS")] - public void test58325_three() - { - check58325(XSSFTestDataSamples.OpenSampleWorkbook("58325_db.xlsx"), 3); - } - private void check58325(XSSFWorkbook wb, int expectedShapes) - { - XSSFSheet sheet = wb.GetSheet("MetasNM001") as XSSFSheet; - ClassicAssert.IsNotNull(sheet); - StringBuilder str = new StringBuilder(); - str.Append("sheet " + sheet.SheetName + " - "); - XSSFDrawing drawing = sheet.GetDrawingPatriarch(); - //drawing = ((XSSFSheet)sheet).createDrawingPatriarch(); - List shapes = drawing.GetShapes(); - str.Append("drawing.Shapes.size() = " + shapes.Count); - IEnumerator it = shapes.GetEnumerator(); - while (it.MoveNext()) - { - XSSFShape shape = it.Current; - str.Append(", " + shape.ToString()); - str.Append(", Col1:" + ((XSSFClientAnchor)shape.GetAnchor()).Col1); - str.Append(", Col2:" + ((XSSFClientAnchor)shape.GetAnchor()).Col2); - str.Append(", Row1:" + ((XSSFClientAnchor)shape.GetAnchor()).Row1); - str.Append(", Row2:" + ((XSSFClientAnchor)shape.GetAnchor()).Row2); - } - - ClassicAssert.AreEqual(expectedShapes, shapes.Count, - "Having shapes: " + str); - } - } } diff --git a/testcases/ooxml/XSSF/UserModel/TestXSSFShape.cs b/testcases/ooxml/XSSF/UserModel/TestXSSFShape.cs new file mode 100644 index 000000000..74c00051f --- /dev/null +++ b/testcases/ooxml/XSSF/UserModel/TestXSSFShape.cs @@ -0,0 +1,68 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NPOI.XSSF; +using NPOI.XSSF.UserModel; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace TestCases.XSSF.UserModel +{ + [TestFixture] + internal class TestXSSFShape + { + [Test] + public void Test58325_one() + { + Check58325(XSSFTestDataSamples.OpenSampleWorkbook("58325_lt.xlsx"), 1); + } + [Test] + public void Test58325_three() + { + Check58325(XSSFTestDataSamples.OpenSampleWorkbook("58325_db.xlsx"), 3); + } + private void Check58325(XSSFWorkbook wb, int expectedShapes) + { + XSSFSheet sheet = wb.GetSheet("MetasNM001") as XSSFSheet; + ClassicAssert.IsNotNull(sheet); + StringBuilder str = new StringBuilder(); + str.Append("sheet " + sheet.SheetName + " - "); + XSSFDrawing drawing = sheet.GetDrawingPatriarch(); + //drawing = ((XSSFSheet)sheet).createDrawingPatriarch(); + List shapes = drawing.GetShapes(); + str.Append("drawing.Shapes.size() = " + shapes.Count); + IEnumerator it = shapes.GetEnumerator(); + while (it.MoveNext()) + { + XSSFShape shape = it.Current; + str.Append(", " + shape.ToString()); + str.Append(", Col1:" + ((XSSFClientAnchor)shape.GetAnchor()).Col1); + str.Append(", Col2:" + ((XSSFClientAnchor)shape.GetAnchor()).Col2); + str.Append(", Row1:" + ((XSSFClientAnchor)shape.GetAnchor()).Row1); + str.Append(", Row2:" + ((XSSFClientAnchor)shape.GetAnchor()).Row2); + } + + ClassicAssert.AreEqual(expectedShapes, shapes.Count, + "Having shapes: " + str); + } + } +} \ No newline at end of file From 9fed0e5c70b623231df3bcaf1b086f4da5e2a994 Mon Sep 17 00:00:00 2001 From: Antony Liu Date: Sun, 11 May 2025 16:04:14 +0800 Subject: [PATCH 3/8] XSSFTable should format numeric/date cells when used as Column Header names as Excel does, see https://stackoverflow.com/questions/44407111/apache-poi-cant-format-filled-cells-as-numeric --- OpenXmlFormats/Spreadsheet/Sheet/CT_Table.cs | 19 +++++ ooxml/XSSF/UserModel/XSSFTable.cs | 50 ++++++++--- .../ooxml/XSSF/UserModel/TestXSSFTable.cs | 80 ++++++++++++++++++ .../TablesWithDifferentHeaders.xlsx | Bin 0 -> 11359 bytes 4 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 testcases/test-data/spreadsheet/TablesWithDifferentHeaders.xlsx diff --git a/OpenXmlFormats/Spreadsheet/Sheet/CT_Table.cs b/OpenXmlFormats/Spreadsheet/Sheet/CT_Table.cs index 5648f3d14..95e72dc94 100644 --- a/OpenXmlFormats/Spreadsheet/Sheet/CT_Table.cs +++ b/OpenXmlFormats/Spreadsheet/Sheet/CT_Table.cs @@ -703,6 +703,25 @@ public void RemoveTableColumn(int columnIndex) { this.tableColumn.RemoveAt(columnIndex); } + + internal CT_TableColumn GetTableColumnArray(int v) + { + if (this.tableColumnField == null) + return null; + if (v < 0 || v >= this.tableColumn.Count) + throw new ArgumentOutOfRangeException(); + return tableColumnField[v]; + } + + public CT_TableColumn AddNewTableColumn() + { + if(this.tableColumnField==null) + this.tableColumnField = new List (); + var c = new CT_TableColumn(); + this.tableColumnField.Add(c); + return c; + } + [XmlElement] public List tableColumn { diff --git a/ooxml/XSSF/UserModel/XSSFTable.cs b/ooxml/XSSF/UserModel/XSSFTable.cs index 9228ed8fc..3bd265e6f 100644 --- a/ooxml/XSSF/UserModel/XSSFTable.cs +++ b/ooxml/XSSF/UserModel/XSSFTable.cs @@ -476,6 +476,27 @@ public long NumberOfMappedColumns return ctTable.tableColumns.count; } } + + /// + /// The reference for the cells of the table + /// (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref) + /// + /// Does not track updates to underlying changes to CTTable + /// To synchronize with changes to the underlying CTTable, + /// call + /// + /// The reference for the cells of the table + public AreaReference References + { + get + { + return new AreaReference( + StartCellReference, + EndCellReference + ); + } + + } public int ColumnCount { get @@ -610,15 +631,23 @@ public int RowCount } - /** - * Synchronize table headers with cell values in the parent sheet. - * Headers must be in sync, otherwise Excel will display a - * "Found unreadable content" message on startup. - * - * If calling both {@link #updateReferences()} and - * {@link #updateHeaders()}, {@link #updateReferences()} - * should be called first. - */ + /// + /// + /// Synchronize table headers with cell values in the parent sheet. + /// Headers must be in sync, otherwise Excel will display a + /// "Found unreadable content" message on startup. + /// + /// + /// If calling both and + /// , + /// should be called first. + /// + /// + /// Note that a Table must have a header. To reproduce + /// the equivalent of inserting a table in Excel without Headers, + /// manually add cells with values of "Column1", "Column2" etc first. + /// + /// public void UpdateHeaders() { XSSFSheet sheet = (XSSFSheet)GetParent(); @@ -630,6 +659,7 @@ public void UpdateHeaders() int firstHeaderColumn = ref1.Col; XSSFRow row = sheet.GetRow(headerRow) as XSSFRow; + DataFormatter formatter = new DataFormatter(); if (row != null && row.GetCTRow() != null) { @@ -642,7 +672,7 @@ public void UpdateHeaders() { if (row.GetCell(cellnum) is XSSFCell cell) { - col.name = cell.StringCellValue; + col.name = formatter.FormatCellValue(cell); } cellnum++; } diff --git a/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs b/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs index a528baa2e..e4ba33271 100644 --- a/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs +++ b/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs @@ -296,5 +296,85 @@ public void GetEndCellReferenceFromSingleCellTable() wb.Close(); } + [Test] + public void TestDifferentHeaderTypes() + { + + XSSFWorkbook wb = XSSFTestDataSamples.OpenSampleWorkbook("TablesWithDifferentHeaders.xlsx"); + ClassicAssert.AreEqual(3, wb.NumberOfSheets); + XSSFSheet s; + XSSFTable t; + + // TODO Nicer column fetching + + s = wb.GetSheet("IntHeaders") as XSSFSheet; + ClassicAssert.AreEqual(1, s.GetTables().Count); + t = s.GetTables()[0]; + ClassicAssert.AreEqual("A1:B2", t.References.FormatAsString()); + ClassicAssert.AreEqual("12", t.GetCTTable().tableColumns.GetTableColumnArray(0).name); + ClassicAssert.AreEqual("34", t.GetCTTable().tableColumns.GetTableColumnArray(1).name); + + s = wb.GetSheet("FloatHeaders") as XSSFSheet; + ClassicAssert.AreEqual(1, s.GetTables().Count); + t = s.GetTables()[0]; + ClassicAssert.AreEqual("A1:B2", t.References.FormatAsString()); + ClassicAssert.AreEqual("12.34", t.GetCTTable().tableColumns.GetTableColumnArray(0).name); + ClassicAssert.AreEqual("34.56", t.GetCTTable().tableColumns.GetTableColumnArray(1).name); + + s = wb.GetSheet("NoExplicitHeaders") as XSSFSheet; + ClassicAssert.AreEqual(1, s.GetTables().Count); + t = s.GetTables()[0]; + ClassicAssert.AreEqual("A1:B3", t.References.FormatAsString()); + ClassicAssert.AreEqual("Column1", t.GetCTTable().tableColumns.GetTableColumnArray(0).name); + ClassicAssert.AreEqual("Column2", t.GetCTTable().tableColumns.GetTableColumnArray(1).name); + } + + /// + /// See https://stackoverflow.com/questions/44407111/apache-poi-cant-format-filled-cells-as-numeric + /// + [Test] + public void TestNumericCellsInTable() + { + + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet s = wb.CreateSheet() as XSSFSheet; + + //Setting up the CTTable + XSSFTable t = s.CreateTable(); + CT_Table ctt = t.GetCTTable(); + ctt.id = (1); + ctt.name = ("CT Table Test"); + ctt.@ref =("A1:B2"); + CT_TableColumns cttcs = ctt.AddNewTableColumns(); + CT_TableColumn cttc1 = cttcs.AddNewTableColumn(); + cttc1.id=(1); + CT_TableColumn cttc2 = cttcs.AddNewTableColumn(); + cttc2.id=(2); + + //Creating the cells + ICell c1 = s.CreateRow(0).CreateCell(0); + XSSFCell c2 = s.GetRow(0).CreateCell(1) as XSSFCell; + XSSFCell c3 = s.CreateRow(1).CreateCell(0) as XSSFCell; + XSSFCell c4 = s.GetRow(1).CreateCell(1) as XSSFCell; + + // Inserting values; some numeric strings, some alphabetical strings + c1.SetCellValue(12); + c2.SetCellValue(34); + c3.SetCellValue("AB"); + c4.SetCellValue("CD"); + + // Save and re-load + wb = XSSFTestDataSamples.WriteOutAndReadBack(wb); + s = wb.GetSheetAt(0) as XSSFSheet; + + // Check + ClassicAssert.AreEqual(1, s.GetTables().Count); + t = s.GetTables()[0]; + ClassicAssert.AreEqual("A1", t.StartCellReference.FormatAsString()); + ClassicAssert.AreEqual("B2", t.EndCellReference.FormatAsString()); + + // Done + wb.Close(); + } } } diff --git a/testcases/test-data/spreadsheet/TablesWithDifferentHeaders.xlsx b/testcases/test-data/spreadsheet/TablesWithDifferentHeaders.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c9bb110adca01481083f965e6064d27657fe62e6 GIT binary patch literal 11359 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 literal 0 HcmV?d00001 From a083714cfe9e8a45d1254413c3df1ef04e570896 Mon Sep 17 00:00:00 2001 From: Antony Liu Date: Sun, 11 May 2025 16:49:30 +0800 Subject: [PATCH 4/8] Make it possible to create simple XSSF sheet tables without needing CT classes directly --- ooxml/XSSF/UserModel/XSSFSheet.cs | 2 +- ooxml/XSSF/UserModel/XSSFTable.cs | 98 +++++++++---------- .../ooxml/XSSF/UserModel/TestXSSFTable.cs | 55 ++++++----- 3 files changed, 80 insertions(+), 75 deletions(-) diff --git a/ooxml/XSSF/UserModel/XSSFSheet.cs b/ooxml/XSSF/UserModel/XSSFSheet.cs index e89169556..6341f2973 100644 --- a/ooxml/XSSF/UserModel/XSSFSheet.cs +++ b/ooxml/XSSF/UserModel/XSSFSheet.cs @@ -3742,7 +3742,7 @@ public XSSFTable CreateTable() false); XSSFTable table = rp.DocumentPart as XSSFTable; tbl.id = rp.Relationship.Id; - + table.GetCTTable().id = (uint)tableNumber; tables[tbl.id] = table; return table; diff --git a/ooxml/XSSF/UserModel/XSSFTable.cs b/ooxml/XSSF/UserModel/XSSFTable.cs index 3bd265e6f..aa060ab85 100644 --- a/ooxml/XSSF/UserModel/XSSFTable.cs +++ b/ooxml/XSSF/UserModel/XSSFTable.cs @@ -241,6 +241,27 @@ public List GetXmlColumnPrs() } return xmlColumnPrs; } + + public void AddColumn() + { + // Ensure we have Table Columns + CT_TableColumns columns = ctTable.tableColumns; + if (columns == null) + { + columns = ctTable.AddNewTableColumns(); + } + + // Add another Column, and give it a sensible ID + CT_TableColumn column = columns.AddNewTableColumn(); + int num = columns.tableColumn.Count; + columns.count = (uint)num; + column.id = (uint)num; + + // Have the Headers updated if possible + UpdateHeaders(); + } + + private string name; /** * @return the name of the Table, if set */ @@ -369,41 +390,38 @@ public XSSFTableColumn CreateColumn(String columnName, int columnIndex) return GetColumns()[columnIndex]; } - /** - * Get the area reference for the cells which this table covers. The area - * includes header rows and totals rows. - * - * Does not track updates to underlying changes to CTTable To synchronize - * with changes to the underlying CTTable, call {@link #updateReferences()}. - * - * @return the area of the table - * @see "Open Office XML Part 4: chapter 3.5.1.2, attribute ref" - */ - public AreaReference GetCellReferences() + + /// + /// + /// Get or set the area reference for the cells which this table covers. + /// The area includes header rows and totals rows. + /// Does not track updates to underlying changes to CTTable To synchronize + /// with changes to the underlying CTTable, call + /// + /// + /// The area's width should be identical to the amount of columns in + /// the table or the table may be invalid. All header rows, totals rows and + /// at least one data row must fit inside the area. Updating the area with + /// this method does not create or remove any columns and does not change any + /// cell values. + /// @see "Open Office XML Part 4: chapter 3.5.1.2, attribute ref" + /// + public AreaReference CellReferences { - return new AreaReference( + get + { + return new AreaReference( StartCellReference, EndCellReference, SpreadsheetVersion.EXCEL2007 - ); - } - /** - * Set the area reference for the cells which this table covers. The area - * includes includes header rows and totals rows. Automatically synchronizes - * any changes by calling {@link #updateHeaders()}. - * - * Note: The area's width should be identical to the amount of columns in - * the table or the table may be invalid. All header rows, totals rows and - * at least one data row must fit inside the area. Updating the area with - * this method does not create or remove any columns and does not change any - * cell values. - * - * @see "Open Office XML Part 4: chapter 3.5.1.2, attribute ref" - */ - public void SetCellReferences(AreaReference refs) - { - SetCellRef(refs); + ); + } + set + { + SetCellRef(value); + } } + protected void SetCellRef(AreaReference refs) { @@ -477,26 +495,6 @@ public long NumberOfMappedColumns } } - /// - /// The reference for the cells of the table - /// (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref) - /// - /// Does not track updates to underlying changes to CTTable - /// To synchronize with changes to the underlying CTTable, - /// call - /// - /// The reference for the cells of the table - public AreaReference References - { - get - { - return new AreaReference( - StartCellReference, - EndCellReference - ); - } - - } public int ColumnCount { get diff --git a/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs b/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs index e4ba33271..d1d3a6b60 100644 --- a/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs +++ b/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs @@ -283,7 +283,7 @@ public void FormatAsTable() row.CreateCell(2).SetCellValue("Value3"); XSSFTable table = sh.CreateTable(); - table.SetCellReferences(new AreaReference(new CellReference(0, 0), new CellReference(1, 2))); + table.CellReferences = new AreaReference(new CellReference(0, 0), new CellReference(1, 2)); wb.Close(); } @@ -310,21 +310,21 @@ public void TestDifferentHeaderTypes() s = wb.GetSheet("IntHeaders") as XSSFSheet; ClassicAssert.AreEqual(1, s.GetTables().Count); t = s.GetTables()[0]; - ClassicAssert.AreEqual("A1:B2", t.References.FormatAsString()); + ClassicAssert.AreEqual("A1:B2", t.CellReferences.FormatAsString()); ClassicAssert.AreEqual("12", t.GetCTTable().tableColumns.GetTableColumnArray(0).name); ClassicAssert.AreEqual("34", t.GetCTTable().tableColumns.GetTableColumnArray(1).name); s = wb.GetSheet("FloatHeaders") as XSSFSheet; ClassicAssert.AreEqual(1, s.GetTables().Count); t = s.GetTables()[0]; - ClassicAssert.AreEqual("A1:B2", t.References.FormatAsString()); + ClassicAssert.AreEqual("A1:B2", t.CellReferences.FormatAsString()); ClassicAssert.AreEqual("12.34", t.GetCTTable().tableColumns.GetTableColumnArray(0).name); ClassicAssert.AreEqual("34.56", t.GetCTTable().tableColumns.GetTableColumnArray(1).name); s = wb.GetSheet("NoExplicitHeaders") as XSSFSheet; ClassicAssert.AreEqual(1, s.GetTables().Count); t = s.GetTables()[0]; - ClassicAssert.AreEqual("A1:B3", t.References.FormatAsString()); + ClassicAssert.AreEqual("A1:B3", t.CellReferences.FormatAsString()); ClassicAssert.AreEqual("Column1", t.GetCTTable().tableColumns.GetTableColumnArray(0).name); ClassicAssert.AreEqual("Column2", t.GetCTTable().tableColumns.GetTableColumnArray(1).name); } @@ -339,29 +339,32 @@ public void TestNumericCellsInTable() XSSFWorkbook wb = new XSSFWorkbook(); XSSFSheet s = wb.CreateSheet() as XSSFSheet; - //Setting up the CTTable - XSSFTable t = s.CreateTable(); - CT_Table ctt = t.GetCTTable(); - ctt.id = (1); - ctt.name = ("CT Table Test"); - ctt.@ref =("A1:B2"); - CT_TableColumns cttcs = ctt.AddNewTableColumns(); - CT_TableColumn cttc1 = cttcs.AddNewTableColumn(); - cttc1.id=(1); - CT_TableColumn cttc2 = cttcs.AddNewTableColumn(); - cttc2.id=(2); - - //Creating the cells + // Create some cells, some numeric, some not ICell c1 = s.CreateRow(0).CreateCell(0); - XSSFCell c2 = s.GetRow(0).CreateCell(1) as XSSFCell; - XSSFCell c3 = s.CreateRow(1).CreateCell(0) as XSSFCell; - XSSFCell c4 = s.GetRow(1).CreateCell(1) as XSSFCell; + ICell c2 = s.GetRow(0).CreateCell(1); + ICell c3 = s.GetRow(0).CreateCell(2); + ICell c4 = s.CreateRow(1).CreateCell(0); + ICell c5 = s.GetRow(1).CreateCell(1); + ICell c6 = s.GetRow(1).CreateCell(2); // Inserting values; some numeric strings, some alphabetical strings c1.SetCellValue(12); - c2.SetCellValue(34); - c3.SetCellValue("AB"); - c4.SetCellValue("CD"); + c2.SetCellValue(34.56); + c3.SetCellValue("ABCD"); + c4.SetCellValue("AB"); + c5.SetCellValue("CD"); + c6.SetCellValue("EF"); + + // Setting up the CTTable + XSSFTable t = s.CreateTable(); + t.Name = "TableTest"; + t.DisplayName = "CT_Table_Test"; + t.AddColumn(); + t.AddColumn(); + t.AddColumn(); + t.CellReferences = (new AreaReference( + new CellReference(c1), new CellReference(c6) + )); // Save and re-load wb = XSSFTestDataSamples.WriteOutAndReadBack(wb); @@ -371,8 +374,12 @@ public void TestNumericCellsInTable() ClassicAssert.AreEqual(1, s.GetTables().Count); t = s.GetTables()[0]; ClassicAssert.AreEqual("A1", t.StartCellReference.FormatAsString()); - ClassicAssert.AreEqual("B2", t.EndCellReference.FormatAsString()); + ClassicAssert.AreEqual("C2", t.EndCellReference.FormatAsString()); + // TODO Nicer column fetching + ClassicAssert.AreEqual("12", t.GetCTTable().tableColumns.GetTableColumnArray(0).name); + ClassicAssert.AreEqual("34.56", t.GetCTTable().tableColumns.GetTableColumnArray(1).name); + ClassicAssert.AreEqual("ABCD", t.GetCTTable().tableColumns.GetTableColumnArray(2).name); // Done wb.Close(); } From ecaadec20ff12cfee5fc84769548ac37b2aa61fc Mon Sep 17 00:00:00 2001 From: Antony Liu Date: Sun, 11 May 2025 17:21:35 +0800 Subject: [PATCH 5/8] Don't report data table master cells as formula cells, since POI doesn't evaluate data table formulas. --- ooxml/XSSF/UserModel/XSSFCell.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/ooxml/XSSF/UserModel/XSSFCell.cs b/ooxml/XSSF/UserModel/XSSFCell.cs index 42b49450a..4fb5dbe14 100644 --- a/ooxml/XSSF/UserModel/XSSFCell.cs +++ b/ooxml/XSSF/UserModel/XSSFCell.cs @@ -759,12 +759,22 @@ public ICellStyle CellStyle } } } - + /// + /// POI currently supports these formula types: + /// + /// + /// + /// + /// + /// POI does not support formulas. + /// + /// true if the cell is of a formula type POI can handle + /// private bool IsFormulaCell { get { - if (_cell.f != null || ((XSSFSheet)Sheet).IsCellInArrayFormulaContext(this)) + if ((_cell.f != null && _cell.f.t != ST_CellFormulaType.dataTable) || ((XSSFSheet)Sheet).IsCellInArrayFormulaContext(this)) { return true; } @@ -773,7 +783,12 @@ private bool IsFormulaCell } /// - /// Return the cell type. + /// Return the cell type. Tables in an array formula return + /// for all cells, even though the formula is only defined + /// in the OOXML file for the top left cell of the array. + /// + /// NOTE: POI does not support data table formulas. + /// Cells in a data table appear to POI as plain cells typed from their cached value. /// public CellType CellType { From 887195779505c508b0abc0126f460484dd54de92 Mon Sep 17 00:00:00 2001 From: Antony Liu Date: Mon, 12 May 2025 19:01:46 +0800 Subject: [PATCH 6/8] fix compile error --- ooxml/XSSF/UserModel/XSSFTable.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ooxml/XSSF/UserModel/XSSFTable.cs b/ooxml/XSSF/UserModel/XSSFTable.cs index aa060ab85..8c3f7f5a5 100644 --- a/ooxml/XSSF/UserModel/XSSFTable.cs +++ b/ooxml/XSSF/UserModel/XSSFTable.cs @@ -261,7 +261,6 @@ public void AddColumn() UpdateHeaders(); } - private string name; /** * @return the name of the Table, if set */ From 9bc01ca1cf98c03d2b8332e854de40f0b0557fbf Mon Sep 17 00:00:00 2001 From: Antony Liu Date: Sat, 24 May 2025 17:51:24 +0800 Subject: [PATCH 7/8] Close workbook to prevent resource leaks --- testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs b/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs index d1d3a6b60..e0420aac5 100644 --- a/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs +++ b/testcases/ooxml/XSSF/UserModel/TestXSSFTable.cs @@ -327,6 +327,8 @@ public void TestDifferentHeaderTypes() ClassicAssert.AreEqual("A1:B3", t.CellReferences.FormatAsString()); ClassicAssert.AreEqual("Column1", t.GetCTTable().tableColumns.GetTableColumnArray(0).name); ClassicAssert.AreEqual("Column2", t.GetCTTable().tableColumns.GetTableColumnArray(1).name); + + wb.Close(); } /// From b1e7551f792a651fb0f62f382570f57348c572ec Mon Sep 17 00:00:00 2001 From: Antony Liu Date: Sat, 24 May 2025 17:51:46 +0800 Subject: [PATCH 8/8] Avoid NullReferenceException --- OpenXmlFormats/Drawing/SpreadsheetDrawing.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenXmlFormats/Drawing/SpreadsheetDrawing.cs b/OpenXmlFormats/Drawing/SpreadsheetDrawing.cs index f16fe7c38..e821e7c3b 100644 --- a/OpenXmlFormats/Drawing/SpreadsheetDrawing.cs +++ b/OpenXmlFormats/Drawing/SpreadsheetDrawing.cs @@ -1395,6 +1395,10 @@ public int SizeOfOneCellAnchorArray() public static CT_Drawing Parse(XmlDocument xmldoc, XmlNamespaceManager namespaceManager) { XmlNode root = xmldoc.SelectSingleNode("/xdr:wsDr", namespaceManager); + if (root == null) + { + return new CT_Drawing(); + } XmlNodeList cellanchorNodes = root.SelectNodes("descendant::xdr:oneCellAnchor|descendant::xdr:twoCellAnchor|descendant::xdr:absCellAnchor", namespaceManager); CT_Drawing ctDrawing = new CT_Drawing(); foreach (XmlNode node in cellanchorNodes)