diff --git a/OpenXmlFormats/Wordprocessing/Numbering.cs b/OpenXmlFormats/Wordprocessing/Numbering.cs index bcf11ec1d..542d31655 100644 --- a/OpenXmlFormats/Wordprocessing/Numbering.cs +++ b/OpenXmlFormats/Wordprocessing/Numbering.cs @@ -1100,7 +1100,7 @@ public bool ValueEquals(CT_AbstractNum cT_AbstractNum) public void Set(CT_AbstractNum cT_AbstractNum) { this.abstractNumIdField = cT_AbstractNum.abstractNumIdField; - this.lvlField = new List(cT_AbstractNum.lvlField); + this.lvlField = cT_AbstractNum.lvlField == null ? new List() : new List(cT_AbstractNum.lvlField); this.multiLevelTypeField = cT_AbstractNum.multiLevelTypeField; this.nameField = cT_AbstractNum.nameField; this.nsidField = cT_AbstractNum.nsidField; diff --git a/OpenXmlFormats/Wordprocessing/Table.cs b/OpenXmlFormats/Wordprocessing/Table.cs index 9d3241851..2f682d682 100644 --- a/OpenXmlFormats/Wordprocessing/Table.cs +++ b/OpenXmlFormats/Wordprocessing/Table.cs @@ -2900,6 +2900,22 @@ public CT_TblPPr AddNewTblPPr() this.tblpPr = new CT_TblPPr(); return this.tblpPr; } + + public bool IsSetJc() + { + return this.jc != null; + } + + public CT_Jc AddNewJc() + { + this.jc = new CT_Jc(); + return this.jc; + } + + public void UnsetJc() + { + this.jc = null; + } } diff --git a/ooxml/XWPF/Usermodel/TableRowAlign.cs b/ooxml/XWPF/Usermodel/TableRowAlign.cs new file mode 100644 index 000000000..0f3b28be7 --- /dev/null +++ b/ooxml/XWPF/Usermodel/TableRowAlign.cs @@ -0,0 +1,59 @@ +/* ==================================================================== + 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; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace NPOI.XWPF.UserModel +{ + /// + /// Sets alignment values allowed for Tables and Table Rows + /// + public enum TableRowAlign + { + + LEFT = 0, + CENTER = 1, + RIGHT = 2 + } + public static class TableRowAlignExtension + { + private static readonly Dictionary imap = + new(){ + { 0, TableRowAlign.LEFT }, + { 1, TableRowAlign.CENTER }, + { 2, TableRowAlign.RIGHT }, + }; + + public static TableRowAlign ValueOf(int type) + { + if(imap.TryGetValue(type, out TableRowAlign err)) + return err; + throw new ArgumentException("Unknown table row alignment: " + type); + } + + public static int GetValue(this TableRowAlign trAlign) + { + return (int) trAlign; + } + } +} + + diff --git a/ooxml/XWPF/Usermodel/XWPFNumbering.cs b/ooxml/XWPF/Usermodel/XWPFNumbering.cs index 15fdd5155..4989122eb 100644 --- a/ooxml/XWPF/Usermodel/XWPFNumbering.cs +++ b/ooxml/XWPF/Usermodel/XWPFNumbering.cs @@ -24,6 +24,7 @@ namespace NPOI.XWPF.UserModel using System.IO; using System.Xml.Serialization; using System.Xml; + using NPOI.Util; /** @@ -302,11 +303,15 @@ public string AddAbstractNum() */ public bool RemoveAbstractNum(string abstractNumID) { - if (int.Parse(abstractNumID) < abstractNums.Count) + foreach (XWPFAbstractNum abstractNum in abstractNums) { - ctNumbering.RemoveAbstractNum(int.Parse(abstractNumID)); - abstractNums.RemoveAt(int.Parse(abstractNumID)); - return true; + string foundNumId = abstractNum.GetAbstractNum().abstractNumId; + if(abstractNumID.Equals(foundNumId)) + { + ctNumbering.RemoveAbstractNum(int.Parse(foundNumId)); + abstractNums.Remove(abstractNum); + return true; + } } return false; } diff --git a/ooxml/XWPF/Usermodel/XWPFSDTContent.cs b/ooxml/XWPF/Usermodel/XWPFSDTContent.cs index 5cddab245..357558bd6 100644 --- a/ooxml/XWPF/Usermodel/XWPFSDTContent.cs +++ b/ooxml/XWPF/Usermodel/XWPFSDTContent.cs @@ -42,6 +42,10 @@ public class XWPFSDTContent : ISDTContent public XWPFSDTContent(CT_SdtContentRun sdtRun, IBody part, IRunBody parent) { + if (sdtRun == null) + { + return; + } foreach (CT_R ctr in sdtRun.GetRList()) { XWPFRun run = new XWPFRun((CT_R)ctr, parent); @@ -51,7 +55,10 @@ public XWPFSDTContent(CT_SdtContentRun sdtRun, IBody part, IRunBody parent) } public XWPFSDTContent(CT_SdtContentBlock block, IBody part, IRunBody parent) { - + if (block == null) + { + return; + } foreach (object o in block.Items) { if (o is CT_P ctP) diff --git a/ooxml/XWPF/Usermodel/XWPFTable.cs b/ooxml/XWPF/Usermodel/XWPFTable.cs index a28682e7b..a60de4e66 100644 --- a/ooxml/XWPF/Usermodel/XWPFTable.cs +++ b/ooxml/XWPF/Usermodel/XWPFTable.cs @@ -276,6 +276,62 @@ public CT_TblPr GetTrPr() .AddNewTblPr(); } + /// + /// Returns CTTblPr object for table. Creates it if it does not exist. + /// + private CT_TblPr GetTblPr() { + return GetTblPr(true); + } + + /// + /// Returns CTTblPr object for table. If force parameter is true, will + /// create the element if necessary. If force parameter is false, returns + /// null when CTTblPr element is missing. + /// + /// - force creation of CTTblPr element if necessary + private CT_TblPr GetTblPr(bool force) + { + return (ctTbl.tblPr != null) ? ctTbl.tblPr + : (force ? ctTbl.AddNewTblPr() : null); + } + + /// + /// Get or set the current table alignment or NULL + /// + /// Table Alignment as a enum + public TableRowAlign? TableAlignment + { + get + { + CT_TblPr tPr = GetTblPr(false); + return tPr == null ? null + : tPr.IsSetJc() ? TableRowAlignExtension.ValueOf((int)tPr.jc.val) + : null; + } + set + { + if(value.HasValue) + { + CT_TblPr tPr = GetTblPr(true); + CT_Jc jc = tPr.IsSetJc() ? tPr.jc : tPr.AddNewJc(); + jc.val = (ST_Jc)value.Value.GetValue(); + } + else + { + RemoveTableAlignment(); + } + } + } + + /// + /// Removes the table alignment attribute from a table + /// + public void RemoveTableAlignment() { + CT_TblPr tPr = GetTblPr(false); + if (tPr != null && tPr.IsSetJc()) { + tPr.UnsetJc(); + } + } private static void AddColumn(XWPFTableRow tabRow, int sizeCol) { if (sizeCol > 0) diff --git a/testcases/ooxml/XWPF/TestXWPFBugs.cs b/testcases/ooxml/XWPF/TestXWPFBugs.cs index b819c2bf2..21d1ff2f0 100644 --- a/testcases/ooxml/XWPF/TestXWPFBugs.cs +++ b/testcases/ooxml/XWPF/TestXWPFBugs.cs @@ -236,6 +236,57 @@ public void Test59378() XWPFDocument docBack = XWPFTestDataSamples.WriteOutAndReadBack(doc); docBack.Close(); } + + [Test] + public void Test63788() { + using (XWPFDocument doc = new XWPFDocument()) + { + + XWPFNumbering numbering = doc.CreateNumbering(); + + for (int i = 10; i >= 0; i--) { + addNumberingWithAbstractId(numbering, i); //add numbers in reverse order + } + + for (int i = 0; i <= 10; i++) { + ClassicAssert.AreEqual(i, int.Parse(numbering.GetAbstractNum(i.ToString()).GetAbstractNum().abstractNumId)); + } + + //attempt to remove item with numId 2 + ClassicAssert.IsTrue(numbering.RemoveAbstractNum("2")); + + for (int i = 0; i <= 10; i++) { + XWPFAbstractNum abstractNum = numbering.GetAbstractNum(i.ToString()); + + // we removed id "2", so this one should be empty, all others not + if (i == 2) { + ClassicAssert.IsNull(abstractNum, "Failed for " + i); + } else { + ClassicAssert.IsNotNull(abstractNum, "Failed for " + i); + ClassicAssert.AreEqual(i, int.Parse(abstractNum.GetAbstractNum().abstractNumId)); + } + } + + // removing the same again fails + ClassicAssert.IsFalse(numbering.RemoveAbstractNum("2")); + + // removing another one works + ClassicAssert.IsTrue(numbering.RemoveAbstractNum("4")); + } + } + + private static void addNumberingWithAbstractId(XWPFNumbering documentNumbering, int id) + { + // create a numbering scheme + CT_AbstractNum cTAbstractNum = new CT_AbstractNum(); + // give the scheme an ID + cTAbstractNum.abstractNumId = id.ToString(); + + XWPFAbstractNum abstractNum = new XWPFAbstractNum(cTAbstractNum); + string abstractNumID = documentNumbering.AddAbstractNum(abstractNum); + + documentNumbering.AddNum(abstractNumID); + } } } diff --git a/testcases/ooxml/XWPF/UserModel/TestXWPFSDT.cs b/testcases/ooxml/XWPF/UserModel/TestXWPFSDT.cs index e8e6d3ab7..91ec224ff 100644 --- a/testcases/ooxml/XWPF/UserModel/TestXWPFSDT.cs +++ b/testcases/ooxml/XWPF/UserModel/TestXWPFSDT.cs @@ -150,6 +150,20 @@ public void Test60341() ClassicAssert.AreEqual("", sdts[0].GetTitle()); } + [Test] + public void Test62859() + { + //this doesn't test the exact code path for this issue, but + //it does test for a related issue, and the fix fixes both. + //We should try to add the actual triggering document + //to our test suite. + XWPFDocument doc = XWPFTestDataSamples.OpenSampleDocument("Bug62859.docx"); + List sdts = ExtractAllSDTs(doc); + ClassicAssert.AreEqual(1, sdts.Count); + ClassicAssert.AreEqual("", sdts[0].GetTag()); + ClassicAssert.AreEqual("", sdts[0].GetTitle()); + } + private List ExtractAllSDTs(XWPFDocument doc) { diff --git a/testcases/ooxml/XWPF/UserModel/TestXWPFTable.cs b/testcases/ooxml/XWPF/UserModel/TestXWPFTable.cs index 63960ed1e..de9392239 100644 --- a/testcases/ooxml/XWPF/UserModel/TestXWPFTable.cs +++ b/testcases/ooxml/XWPF/UserModel/TestXWPFTable.cs @@ -16,30 +16,21 @@ limitations under the License. ==================================================================== */ namespace TestCases.XWPF.UserModel { - using System; - using NUnit.Framework;using NUnit.Framework.Legacy; using NPOI.OpenXmlFormats.Wordprocessing; - using System.Collections.Generic; using NPOI.XWPF.UserModel; + using NUnit.Framework; + using NUnit.Framework.Legacy; + using System; + using System.Collections.Generic; + using System.IO; /** - * Tests for XWPF Run + * Tests for XWPF Tables */ [TestFixture] public class TestXWPFTable { - [SetUp] - public void SetUp() - { - /* - XWPFDocument doc = new XWPFDocument(); - p = doc.CreateParagraph(); - - this.ctRun = CTR.Factory.NewInstance(); - */ - } - [Test] public void TestConstructor() { @@ -346,5 +337,28 @@ public void TestReadTableCaptionAndDescription() ClassicAssert.AreEqual("Table Title", table.TableCaption); ClassicAssert.AreEqual("Table Description", table.TableDescription); } + + [Test] + public void TestSetGetTableAlignment() + { + XWPFDocument doc = new XWPFDocument(); + XWPFTable tbl = doc.CreateTable(1, 1); + tbl.TableAlignment = (TableRowAlign.LEFT); + ClassicAssert.AreEqual(TableRowAlign.LEFT, tbl.TableAlignment); + tbl.TableAlignment = (TableRowAlign.CENTER); + ClassicAssert.AreEqual(TableRowAlign.CENTER, tbl.TableAlignment); + tbl.TableAlignment = (TableRowAlign.RIGHT); + ClassicAssert.AreEqual(TableRowAlign.RIGHT, tbl.TableAlignment); + tbl.RemoveTableAlignment(); + ClassicAssert.IsNull(tbl.TableAlignment); + try + { + doc.Close(); + } + catch (IOException e) + { + ClassicAssert.Fail("Unable to close doc"); + } + } } } \ No newline at end of file diff --git a/testcases/test-data/document/Bug62859.docx b/testcases/test-data/document/Bug62859.docx new file mode 100644 index 000000000..e0ede4a83 Binary files /dev/null and b/testcases/test-data/document/Bug62859.docx differ