From a534937fcc5444ed1cf82b1e3a536b5d316bf4c6 Mon Sep 17 00:00:00 2001 From: Tony Qu Date: Sun, 1 May 2022 07:33:11 +0800 Subject: [PATCH] POI Github 114 https://github.com/apache/poi/pull/114 Enhancement to XWPFFootnote and related APIs --- OpenXmlFormats/Wordprocessing/HdrFtr.cs | 17 ++- OpenXmlFormats/Wordprocessing/Run.cs | 28 +++- ooxml/XWPF/Usermodel/XWPFDocument.cs | 37 ++++- ooxml/XWPF/Usermodel/XWPFFootnote.cs | 103 +++++++++++++ ooxml/XWPF/Usermodel/XWPFFootnotes.cs | 41 +++++- ooxml/XWPF/Usermodel/XWPFParagraph.cs | 13 ++ .../ooxml/XWPF/UserModel/TestXWPFFootnote.cs | 136 ++++++++++++++++++ .../ooxml/XWPF/UserModel/TestXWPFFootnotes.cs | 25 ++-- 8 files changed, 383 insertions(+), 17 deletions(-) create mode 100644 testcases/ooxml/XWPF/UserModel/TestXWPFFootnote.cs diff --git a/OpenXmlFormats/Wordprocessing/HdrFtr.cs b/OpenXmlFormats/Wordprocessing/HdrFtr.cs index 425c71a1d..23e287052 100644 --- a/OpenXmlFormats/Wordprocessing/HdrFtr.cs +++ b/OpenXmlFormats/Wordprocessing/HdrFtr.cs @@ -832,6 +832,17 @@ public CT_FtnEdn AddNewFootnote() footnoteField.Add(f); return f; } + public void RemoveFootnote(int pos) + { + this.footnoteField.RemoveAt(pos); + } + public int SizeOfFootnoteArray + { + get + { + return this.footnoteField.Count; + } + } } @@ -849,7 +860,7 @@ public class CT_FtnEdn private bool typeFieldSpecified; - private string idField = string.Empty; + private int idField = -1; public static CT_FtnEdn Parse(XmlNode node, XmlNamespaceManager namespaceManager) { @@ -858,7 +869,7 @@ public static CT_FtnEdn Parse(XmlNode node, XmlNamespaceManager namespaceManager CT_FtnEdn ctObj = new CT_FtnEdn(); if (node.Attributes["w:type"] != null) ctObj.type = (ST_FtnEdn)Enum.Parse(typeof(ST_FtnEdn), node.Attributes["w:type"].Value); - ctObj.id = XmlHelper.ReadString(node.Attributes["w:id"]); + ctObj.id = XmlHelper.ReadInt(node.Attributes["w:id"]); foreach (XmlNode childNode in node.ChildNodes) { @@ -1175,7 +1186,7 @@ public bool typeSpecified } [XmlAttribute(Form = XmlSchemaForm.Qualified, DataType = "integer", Namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main")] - public string id + public int id { get { diff --git a/OpenXmlFormats/Wordprocessing/Run.cs b/OpenXmlFormats/Wordprocessing/Run.cs index 62107c544..a3de32cd6 100644 --- a/OpenXmlFormats/Wordprocessing/Run.cs +++ b/OpenXmlFormats/Wordprocessing/Run.cs @@ -20,7 +20,6 @@ public class CT_R { private CT_RPr rPrField; - private ArrayList itemsField; private List itemsElementNameField; @@ -178,6 +177,23 @@ public CT_RPr AddNewRPr() return this.rPrField; } + + public CT_Empty AddNewFootnoteRef() + { + return AddNewObject(RunItemsChoiceType.footnoteRef); + } + public IList GetFootnoteRefList() + { + return GetObjectList(RunItemsChoiceType.footnoteRef); + } + public CT_Empty GetFootnoteRefArray(int pos) + { + return GetObjectArray(pos, RunItemsChoiceType.footnoteRef); + } + public CT_FtnEdnRef AddNewFootnoteReference() + { + return AddNewObject(RunItemsChoiceType.footnoteReference); + } public CT_Empty AddNewTab() { return AddNewObject(RunItemsChoiceType.tab); @@ -390,7 +406,9 @@ public static CT_R Parse(XmlNode node, XmlNamespaceManager namespaceManager) foreach (XmlNode childNode in node.ChildNodes) { if (childNode.LocalName == "rPr") + { ctObj.rPr = CT_RPr.Parse(childNode, namespaceManager); + } else if (childNode.LocalName == "instrText") { ctObj.Items.Add(CT_Text.Parse(childNode, namespaceManager)); @@ -568,9 +586,8 @@ internal void Write(StreamWriter sw, string nodeName) sw.Write(">"); if (this.rPr != null) this.rPr.Write(sw, "rPr"); - int i = 0; - + int i = 0; foreach (object o in this.Items) { if ((o is CT_Text) && this.ItemsElementName[i] == RunItemsChoiceType.instrText) @@ -656,6 +673,11 @@ public IList GetTabList() { return GetObjectList(RunItemsChoiceType.tab); } + + public IList GetFootnoteReferenceList() + { + return GetObjectList(RunItemsChoiceType.footnoteReference); + } } diff --git a/ooxml/XWPF/Usermodel/XWPFDocument.cs b/ooxml/XWPF/Usermodel/XWPFDocument.cs index c0663e87c..01f80717e 100644 --- a/ooxml/XWPF/Usermodel/XWPFDocument.cs +++ b/ooxml/XWPF/Usermodel/XWPFDocument.cs @@ -238,7 +238,7 @@ private void InitFootnotes() EndnotesDocument endnotesDocument = EndnotesDocument.Parse(xmldoc, NamespaceManager); foreach (CT_FtnEdn ctFtnEdn in endnotesDocument.Endnotes.endnote) { - endnotes.Add(Int32.Parse(ctFtnEdn.id), new XWPFFootnote(this, ctFtnEdn)); + endnotes.Add(ctFtnEdn.id, new XWPFFootnote(this, ctFtnEdn)); } } } @@ -966,10 +966,43 @@ public XWPFFootnote AddFootnote(CT_FtnEdn note) public XWPFFootnote AddEndnote(CT_FtnEdn note) { XWPFFootnote endnote = new XWPFFootnote(this, note); - endnotes.Add(int.Parse(note.id), endnote); + endnotes.Add(note.id, endnote); return endnote; } + /// + /// Create a new footnote and add it to the document. + /// + /// + /// The new note will have one paragraph with the style "FootnoteText" + /// and one run containing the required footnote reference with the + /// style "FootnoteReference". + /// + /// New XWPFFootnote. + public XWPFFootnote CreateFootnote() + { + XWPFFootnotes footnotes = this.CreateFootnotes(); + + XWPFFootnote footnote = footnotes.CreateFootnote(); + return footnote; + } + /// + /// Remove the specified footnote if present. + /// + /// + /// + public bool RemoveFootnote(int pos) + { + if (null != footnotes) + { + return footnotes.RemoveFootnote(pos); + } + else + { + return false; + } + } + /** * remove a BodyElement from bodyElements array list * @param pos diff --git a/ooxml/XWPF/Usermodel/XWPFFootnote.cs b/ooxml/XWPF/Usermodel/XWPFFootnote.cs index 977c8e7a1..4ffa6e21e 100644 --- a/ooxml/XWPF/Usermodel/XWPFFootnote.cs +++ b/ooxml/XWPF/Usermodel/XWPFFootnote.cs @@ -249,7 +249,110 @@ public POIXMLDocumentPart Owner return footnotes; } } + public int Id + { + get + { + return this.ctFtnEdn.id; + } + } + public List GetParagraphs() + { + return paragraphs; + } + /** + * Appends a new {@link XWPFParagraph} to this footnote. + * + * @return The new {@link XWPFParagraph} + */ + public XWPFParagraph CreateParagraph() + { + XWPFParagraph p = new XWPFParagraph(this.ctFtnEdn.AddNewP(), this); + paragraphs.Add(p); + bodyElements.Add(p); + + // If the paragraph is the first paragraph in the footnote, + // ensure that it has a footnote reference run. + if (p.Equals(GetParagraphs()[0])) + { + EnsureFootnoteRef(p); + } + return p; + } + + /** + * Ensure that the specified paragraph has a reference marker for this + * footnote by adding a footnote reference if one is not found. + *

This method is for the first paragraph in the footnote, not + * paragraphs that will refer to the footnote. For references to + * the footnote, use {@link XWPFParagraph#addFootnoteReference(XWPFFootnote)}. + *

+ *

The first run of the first paragraph in a footnote should + * contain a {@link CTFtnEdnRef} object.

+ * + * @param p The {@link XWPFParagraph} to ensure + */ + public void EnsureFootnoteRef(XWPFParagraph p) + { + + XWPFRun r = null; + if (p.Runs.Count > 0) + { + r = p.Runs[0]; + } + if (r == null) + { + r = p.CreateRun(); + } + CT_R ctr = r.GetCTR(); + bool foundRef = false; + foreach (CT_FtnEdnRef reference in ctr.GetFootnoteReferenceList()) + { + if (Id.ToString().Equals(reference.id)) + { + foundRef = true; + break; + } + } + if (!foundRef) + { + ctr.AddNewRPr().AddNewRStyle().val="FootnoteReference"; + ctr.AddNewFootnoteRef(); + } + } + + /** + * Appends a new {@link XWPFTable} to this footnote + * + * @return The new {@link XWPFTable} + */ + public XWPFTable CreateTable() + { + XWPFTable table = new XWPFTable(ctFtnEdn.AddNewTbl(), this); + if (bodyElements.Count == 0) + { + XWPFParagraph p = CreateParagraph(); + EnsureFootnoteRef(p); + } + bodyElements.Add(table); + tables.Add(table); + return table; + } + + /** + * Appends a new {@link XWPFTable} to this footnote + * @param rows Number of rows to initialize the table with + * @param cols Number of columns to initialize the table with + * @return the new {@link XWPFTable} with the specified number of rows and columns + */ + public XWPFTable CreateTable(int rows, int cols) + { + XWPFTable table = new XWPFTable(ctFtnEdn.AddNewTbl(), this, rows, cols); + bodyElements.Add(table); + tables.Add(table); + return table; + } /** * * @param cursor diff --git a/ooxml/XWPF/Usermodel/XWPFFootnotes.cs b/ooxml/XWPF/Usermodel/XWPFFootnotes.cs index 01ed32009..4aba717ab 100644 --- a/ooxml/XWPF/Usermodel/XWPFFootnotes.cs +++ b/ooxml/XWPF/Usermodel/XWPFFootnotes.cs @@ -123,7 +123,7 @@ public List GetFootnotesList() public XWPFFootnote GetFootnoteById(int id) { foreach(XWPFFootnote note in listFootnote) { - if(note.GetCTFtnEdn().id == id.ToString()) + if(note.GetCTFtnEdn().id == id) return note; } return null; @@ -167,7 +167,44 @@ public void SetXWPFDocument(XWPFDocument doc) { document = doc; } + /// + /// Create a new footnote and add it to the document. + /// + /// + /// The new note will have one paragraph with the style "FootnoteText" + /// and one run containing the required footnote reference with the + /// style "FootnoteReference". + /// + /// New XWPFFootnote + public XWPFFootnote CreateFootnote() + { + CT_FtnEdn newNote = new CT_FtnEdn(); + newNote.type = ST_FtnEdn.normal; + + XWPFFootnote footnote = AddFootnote(newNote); + int id = ctFootnotes.SizeOfFootnoteArray; + footnote.GetCTFtnEdn().id = id; + return footnote; + } + /// + /// Remove the specified footnote if present. + /// + /// + /// + public bool RemoveFootnote(int pos) + { + if (ctFootnotes.SizeOfFootnoteArray >= pos - 1) + { + ctFootnotes.RemoveFootnote(pos); + listFootnote.RemoveAt(pos); + return true; + } + else + { + return false; + } + } /** * @see NPOI.XWPF.UserModel.IBody#getPart() */ @@ -182,6 +219,8 @@ public XWPFDocument GetXWPFDocument() return (XWPFDocument)GetParent(); } } + + }//end class diff --git a/ooxml/XWPF/Usermodel/XWPFParagraph.cs b/ooxml/XWPF/Usermodel/XWPFParagraph.cs index a3ee5349b..d5b393792 100644 --- a/ooxml/XWPF/Usermodel/XWPFParagraph.cs +++ b/ooxml/XWPF/Usermodel/XWPFParagraph.cs @@ -1647,6 +1647,19 @@ public XWPFHyperlinkRun CreateHyperlinkRun(string rId) iRuns.Add(xwpfRun); return xwpfRun; } + /// + /// Add a new run with a reference to the specified footnote. The footnote reference run will have the style name "FootnoteReference". + /// + /// Footnote to which to add a reference. + public void AddFootnoteReference(XWPFFootnote footnote) + { + XWPFRun run = CreateRun(); + CT_R ctRun = run.GetCTR(); + var rstyle=ctRun.AddNewRPr().AddNewRStyle(); + rstyle.val="FootnoteReference"; + var footnoteRef = ctRun.AddNewFootnoteReference(); + footnoteRef.id= footnote.Id.ToString(); + } } } \ No newline at end of file diff --git a/testcases/ooxml/XWPF/UserModel/TestXWPFFootnote.cs b/testcases/ooxml/XWPF/UserModel/TestXWPFFootnote.cs new file mode 100644 index 000000000..306262e9b --- /dev/null +++ b/testcases/ooxml/XWPF/UserModel/TestXWPFFootnote.cs @@ -0,0 +1,136 @@ +using NPOI.OpenXmlFormats.Wordprocessing; +using NPOI.XWPF.UserModel; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; + +namespace TestCases.XWPF.UserModel +{ + [TestFixture] + public class TestXWPFFootnote + { + private XWPFDocument docOut; + private String p1Text; + private String p2Text; + private int footnoteId; + private XWPFFootnote footnote; + + [SetUp] + public void SetUp() + { + docOut = new XWPFDocument(); + p1Text = "First paragraph in footnote"; + p2Text = "Second paragraph in footnote"; + + // NOTE: XWPFDocument.CreateFootnote() delegates directly + // to XWPFFootnotes.CreateFootnote() so this tests + // both creation of new XWPFFootnotes in document + // and XWPFFootnotes.CreateFootnote(); + + // NOTE: Creating the footnote does not automatically + // create a first paragraph. + footnote = docOut.CreateFootnote(); + footnoteId = footnote.Id; + } + [Test] + public void TestAddParagraphsToFootnote() + { + + // Add a run to the first paragraph: + + XWPFParagraph p1 = footnote.CreateParagraph(); + p1.CreateRun().SetText(p1Text); + + // Create a second paragraph: + + XWPFParagraph p = footnote.CreateParagraph(); + Assert.IsNotNull(p, "Paragraph is null"); + p.CreateRun().SetText(p2Text); + + XWPFDocument docIn = XWPFTestDataSamples.WriteOutAndReadBack(docOut); + + XWPFFootnote testFootnote = docIn.GetFootnoteByID(footnoteId); + Assert.IsNotNull(testFootnote); + + Assert.AreEqual(2, testFootnote.GetParagraphs().Count); + XWPFParagraph testP1 = testFootnote.GetParagraphs()[0]; + Assert.AreEqual(p1Text, testP1.Text); + + XWPFParagraph testP2 = testFootnote.GetParagraphs()[1]; + Assert.AreEqual(p2Text, testP2.Text); + + // The first paragraph added using CreateParagraph() should + // have the required footnote reference added to the first + // run. + + // Verify that we have a footnote reference in the first paragraph and not + // in the second paragraph. + + XWPFRun r1 = testP1.Runs[0]; + Assert.IsNotNull(r1); + Assert.IsTrue(r1.GetCTR().GetFootnoteRefList().Count > 0, "No footnote reference in testP1"); + Assert.IsNotNull(r1.GetCTR().GetFootnoteRefArray(0), "No footnote reference in testP1"); + + XWPFRun r2 = testP2.Runs[0]; + Assert.IsNotNull(r2, "Expected a run in testP2"); + Assert.IsTrue(r2.GetCTR().GetFootnoteRefList().Count == 0, "Found a footnote reference in testP2"); + + } + [Test] + public void TestAddTableToFootnote() + { + XWPFTable table = footnote.CreateTable(); + Assert.IsNotNull(table); + + XWPFDocument docIn = XWPFTestDataSamples.WriteOutAndReadBack(docOut); + + XWPFFootnote testFootnote = docIn.GetFootnoteByID(footnoteId); + XWPFTable testTable = testFootnote.GetTableArray(0); + Assert.IsNotNull(testTable); + + table = footnote.CreateTable(2, 3); + Assert.AreEqual(2, table.NumberOfRows); + Assert.AreEqual(3, table.GetRow(0).GetTableCells().Count); + + // If the table is the first body element of the footnote then + // a paragraph with the footnote reference should have been + // added automatically. + + Assert.AreEqual(3, footnote.BodyElements.Count, "Expected 3 body elements"); + IBodyElement testP1 = footnote.BodyElements[0]; + Assert.IsTrue(testP1 is XWPFParagraph, "Expected a paragraph, got " + testP1.GetType().Name); + XWPFRun r1 = ((XWPFParagraph)testP1).Runs[0]; + Assert.IsNotNull(r1); + Assert.IsTrue(r1.GetCTR().GetFootnoteRefList().Count > 0, "No footnote reference in testP1"); + Assert.IsNotNull(r1.GetCTR().GetFootnoteRefArray(0), "No footnote reference in testP1"); + + } + [Test] + public void TestRemoveFootnote() + { + // NOTE: XWPFDocument.removeFootnote() delegates directly to + // XWPFFootnotes. + docOut.CreateFootnote(); + Assert.AreEqual(2, docOut.GetFootnotes().Count, "Expected 2 footnotes"); + Assert.IsNotNull(docOut.GetFootnotes()[1], "Didn't get second footnote"); + bool result = docOut.RemoveFootnote(0); + Assert.IsTrue(result, "Remove footnote did not return true"); + Assert.AreEqual(1, docOut.GetFootnotes().Count, "Expected 1 footnote after removal"); + } + [Test] + public void TestAddFootnoteRefToParagraph() + { + XWPFParagraph p = docOut.CreateParagraph(); + var runs = p.Runs; + Assert.AreEqual(0, runs.Count, "Expected no runs in new paragraph"); + p.AddFootnoteReference(footnote); + XWPFRun run = p.Runs[0]; + CT_R ctr = run.GetCTR(); + Assert.IsNotNull(run, "Expected a run"); + CT_FtnEdnRef ref1 = ctr.GetFootnoteReferenceList()[0]; + Assert.IsNotNull(ref1); + Assert.AreEqual(footnote.Id.ToString(), ref1.id, "Footnote ID and reference ID did not match"); + } + } +} \ No newline at end of file diff --git a/testcases/ooxml/XWPF/UserModel/TestXWPFFootnotes.cs b/testcases/ooxml/XWPF/UserModel/TestXWPFFootnotes.cs index a1451b0d6..ebc8c2ab3 100644 --- a/testcases/ooxml/XWPF/UserModel/TestXWPFFootnotes.cs +++ b/testcases/ooxml/XWPF/UserModel/TestXWPFFootnotes.cs @@ -27,23 +27,32 @@ namespace TestCases.XWPF.UserModel [TestFixture] public class TestXWPFFootnotes { - + [Test] + public void TestCreateFootnotes() + { + XWPFDocument docOut = new XWPFDocument(); + XWPFFootnotes footnotes = docOut.CreateFootnotes(); + Assert.IsNotNull(footnotes); + XWPFFootnotes secondFootnotes = docOut.CreateFootnotes(); + Assert.AreSame(footnotes, secondFootnotes); + docOut.Close(); + } [Test] public void TestAddFootnotesToDocument() { XWPFDocument docOut = new XWPFDocument(); - int noteId = 1; - - XWPFFootnotes footnotes = docOut.CreateFootnotes(); - CT_FtnEdn ctNote = new CT_FtnEdn(); - ctNote.id = (noteId.ToString()); - ctNote.type = (ST_FtnEdn.normal); - footnotes.AddFootnote(ctNote); + // NOTE: XWPFDocument.createFootnote() delegates directly + // to XWPFFootnotes.createFootnote() so this tests + // both creation of new XWPFFootnotes in document + // and XWPFFootnotes.createFootnote(); + XWPFFootnote footnote = docOut.CreateFootnote(); + int noteId = footnote.Id; XWPFDocument docIn = XWPFTestDataSamples.WriteOutAndReadBack(docOut); XWPFFootnote note = docIn.GetFootnoteByID(noteId); + Assert.IsNotNull(note); Assert.AreEqual(note.GetCTFtnEdn().type, ST_FtnEdn.normal); }