From 5eef3e040532b5a3c67aeec01be05ee795acc4ce Mon Sep 17 00:00:00 2001 From: Tony Qu Date: Wed, 20 Aug 2025 07:55:06 +0800 Subject: [PATCH 1/3] XWPFParagraph: easier way to create a link. sync poi code [github-153] --- OpenXmlFormats/Wordprocessing/wml.cs | 12 +++++++++ ooxml/XWPF/Usermodel/XWPFDocument.cs | 11 ++++++++ ooxml/XWPF/Usermodel/XWPFParagraph.cs | 22 +++++++++------- .../ooxml/XWPF/UserModel/TestXWPFParagraph.cs | 26 ++++++------------- 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/OpenXmlFormats/Wordprocessing/wml.cs b/OpenXmlFormats/Wordprocessing/wml.cs index 999cdb3df..20992ff10 100644 --- a/OpenXmlFormats/Wordprocessing/wml.cs +++ b/OpenXmlFormats/Wordprocessing/wml.cs @@ -2658,6 +2658,18 @@ public ArrayList Items } } + public CT_R AddNewR() + { + CT_R r = new CT_R(); + this.Items.Add(r); + return r; + } + + public CT_R GetRArray(int index) + { + return this.itemsField[index] as CT_R; + } + [XmlElement("ItemsElementName", Order = 1)] [XmlIgnore] public List ItemsElementName diff --git a/ooxml/XWPF/Usermodel/XWPFDocument.cs b/ooxml/XWPF/Usermodel/XWPFDocument.cs index aeb796732..c6b9533a2 100644 --- a/ooxml/XWPF/Usermodel/XWPFDocument.cs +++ b/ooxml/XWPF/Usermodel/XWPFDocument.cs @@ -459,6 +459,17 @@ public XWPFHyperlink GetHyperlinkByID(String id) return link; } + // If the link was not found, rebuild the list (maybe a new link was added into the document) and check again. + InitHyperlinks(); + foreach(XWPFHyperlink link in hyperlinks) + { + if(link.Id.Equals(id)) + { + return link; + } + } + // Link still not there? Giving up. + return null; } diff --git a/ooxml/XWPF/Usermodel/XWPFParagraph.cs b/ooxml/XWPF/Usermodel/XWPFParagraph.cs index 7c331f666..2d50132dc 100644 --- a/ooxml/XWPF/Usermodel/XWPFParagraph.cs +++ b/ooxml/XWPF/Usermodel/XWPFParagraph.cs @@ -1688,19 +1688,21 @@ public XWPFRun GetRun(CT_R r) /// /// a new hyperlink run /// - public XWPFHyperlinkRun CreateHyperlinkRun(string rId) + public XWPFHyperlinkRun CreateHyperlinkRun(string uri) { - CT_R r = new CT_R(); - r.AddNewRPr().rStyle = new CT_String() { val = "Hyperlink" }; + // Create a relationship ID for this link. + string rId = Part.GetPackagePart().AddExternalRelationship( + uri, XWPFRelation.HYPERLINK.Relation).Id; - CT_Hyperlink1 hl = paragraph.AddNewHyperlink(); - hl.history = ST_OnOff.on; + // Create the run. + CT_Hyperlink1 hl = GetCTP().AddNewHyperlink(); hl.id = rId; - hl.Items.Add(r); - XWPFHyperlinkRun xwpfRun = new XWPFHyperlinkRun(hl, r, this); - runs.Add(xwpfRun); - iRuns.Add(xwpfRun); - return xwpfRun; + hl.AddNewR(); + + XWPFHyperlinkRun link = new XWPFHyperlinkRun(hl, hl.GetRArray(0), this); + runs.Add(link); + iRuns.Add(link); + return link; } /// /// insert a new hyperlink run in RunArray diff --git a/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs b/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs index 34bef1e61..bd7fda834 100644 --- a/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs +++ b/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs @@ -588,27 +588,17 @@ public void TestRuns() } [Test] - public void TestAddingHyperlinks() + public void TestAddHyperlink() { - XWPFDocument doc = XWPFTestDataSamples.OpenSampleDocument("sample.docx"); - - XWPFParagraph p = doc.Paragraphs[0]; - - ClassicAssert.AreEqual(2, p.Runs.Count); + XWPFDocument doc = XWPFTestDataSamples.OpenSampleDocument("SampleDoc.docx"); - string rId = p.Part.GetPackagePart().AddExternalRelationship("https://www.google.com", XWPFRelation.HYPERLINK.Relation).Id; - - XWPFHyperlinkRun hr1 = p.CreateHyperlinkRun(rId); - hr1.SetText("link1"); - ClassicAssert.AreEqual(3, p.Runs.Count); - ClassicAssert.AreEqual(2, p.Runs.IndexOf(hr1)); - ClassicAssert.AreEqual(2, p.GetCTP().Items.IndexOf(hr1.GetCTHyperlink())); + XWPFParagraph p = doc.CreateParagraph(); + XWPFHyperlinkRun h = p.CreateHyperlinkRun("http://poi.apache.org/"); + h.SetText("Apache POI"); - XWPFHyperlinkRun hr2 = p.InsertNewHyperlinkRun(1, rId); - hr2.SetText("link2"); - ClassicAssert.AreEqual(4, p.Runs.Count); - ClassicAssert.AreEqual(1, p.Runs.IndexOf(hr2)); - ClassicAssert.AreEqual(1, p.GetCTP().Items.IndexOf(hr2.GetCTHyperlink())); + ClassicAssert.AreEqual("http://poi.apache.org/", h.GetHyperlink(doc).URL); + ClassicAssert.AreEqual(1, p.Runs.Count); + ClassicAssert.AreEqual(h, p.Runs[0]); } [Test] From 7848484b11ae35d732c7b892837bdc313acb37f7 Mon Sep 17 00:00:00 2001 From: Tony Qu Date: Wed, 20 Aug 2025 10:02:26 +0800 Subject: [PATCH 2/3] allow add and remove a HyperlinkRun or a FieldRun https://github.com/apache/poi/commit/ce544b85503b695eff0ceb96d67e239962bcb06c --- OpenXmlFormats/Wordprocessing/Paragraph.cs | 44 +++++- OpenXmlFormats/Wordprocessing/wml.cs | 31 ++++ ooxml/XWPF/Usermodel/XWPFParagraph.cs | 140 +++++++++++++----- .../ooxml/XWPF/UserModel/TestXWPFParagraph.cs | 134 ++++++++++++++++- 4 files changed, 306 insertions(+), 43 deletions(-) diff --git a/OpenXmlFormats/Wordprocessing/Paragraph.cs b/OpenXmlFormats/Wordprocessing/Paragraph.cs index 824895a0c..ee98e9284 100644 --- a/OpenXmlFormats/Wordprocessing/Paragraph.cs +++ b/OpenXmlFormats/Wordprocessing/Paragraph.cs @@ -6,6 +6,7 @@ using System.IO; using NPOI.OpenXml4Net.Util; using System.Collections; +using System.CodeDom; namespace NPOI.OpenXmlFormats.Wordprocessing { @@ -171,12 +172,16 @@ public static CT_P Parse(XmlNode node, XmlNamespaceManager namespaceManager) } else if (childNode.LocalName == "fldSimple") { - ctObj.Items.Add(CT_SimpleField.Parse(childNode, namespaceManager)); + var sf = CT_SimpleField.Parse(childNode, namespaceManager); + sf.parent = ctObj; + ctObj.Items.Add(sf); ctObj.ItemsElementName.Add(ParagraphItemsChoiceType.fldSimple); } else if (childNode.LocalName == "hyperlink") { - ctObj.Items.Add(CT_Hyperlink1.Parse(childNode, namespaceManager)); + var hl=CT_Hyperlink1.Parse(childNode, namespaceManager); + hl.parent = ctObj; + ctObj.Items.Add(hl); ctObj.ItemsElementName.Add(ParagraphItemsChoiceType.hyperlink); } else if (childNode.LocalName == "ins") @@ -247,7 +252,40 @@ public bool IsSetRsidR() { return this.rsidRField != null && rsidRField.Length > 0; } - + public void RemoveHyperlink(CT_Hyperlink1 hl) + { + int index=-1; + for(int i = 0; i=0) + { + Items.RemoveAt(index); + ItemsElementName.RemoveAt(index); + } + } + public void RemoveSimpleField(CT_SimpleField sf) + { + int index=-1; + for(int i = 0; i=0) + { + Items.RemoveAt(index); + ItemsElementName.RemoveAt(index); + } + } internal void Write(StreamWriter sw, string nodeName) { sw.Write(string.Format("(); this.itemsField = new ArrayList(); } + public CT_P Parent + { + get + { + return parent; + } + set { + parent=value; + } + } public static CT_Hyperlink1 Parse(XmlNode node, XmlNamespaceManager namespaceManager) { if (node == null) diff --git a/ooxml/XWPF/Usermodel/XWPFParagraph.cs b/ooxml/XWPF/Usermodel/XWPFParagraph.cs index 2d50132dc..c82a58b9c 100644 --- a/ooxml/XWPF/Usermodel/XWPFParagraph.cs +++ b/ooxml/XWPF/Usermodel/XWPFParagraph.cs @@ -20,11 +20,14 @@ namespace NPOI.XWPF.UserModel using System.Collections.Generic; using NPOI.OpenXmlFormats.Wordprocessing; using System.Text; -using Cysharp.Text; + using Cysharp.Text; using NPOI.Util; using System.Collections; using NPOI.WP.UserModel; + using System.Linq; using S=NPOI.OpenXmlFormats.Shared; + using NPOI.POIFS.Properties; + using System.Data; /** *

A Paragraph within a Document, Table, Header etc.

@@ -132,11 +135,9 @@ private void BuildRunsInOrderFromXml(ArrayList items) { foreach (CT_R r in link.GetRList()) { - //runs.Add(new XWPFHyperlinkRun(link, r, this)); XWPFHyperlinkRun hr = new XWPFHyperlinkRun(link, r, this); runs.Add(hr); iRuns.Add(hr); - } } if (o is CT_SimpleField field) { @@ -1578,28 +1579,38 @@ public String GetText(TextSegment segment) */ public bool RemoveRun(int pos) { - if (pos >= 0 && pos < runs.Count) + if(pos >= 0 && pos < runs.Count) { // Remove the run from our high level lists XWPFRun run = runs[pos]; - if (run is XWPFHyperlinkRun || run is XWPFFieldRun) + + // CTP -> CTHyperlink -> R array + if(run is XWPFHyperlinkRun && IsTheOnlyCTHyperlinkInRuns((XWPFHyperlinkRun) run)) { - // TODO Add support for removing these kinds of nested runs, - // which aren't on the CTP -> R array, but CTP -> XXX -> R array - throw new ArgumentException("Removing Field or Hyperlink runs not yet supported"); + var hl=((XWPFHyperlinkRun) run).GetCTHyperlink(); + hl.Parent.RemoveHyperlink(hl); + } + // CTP -> CTField -> R array + if(run is XWPFFieldRun && IsTheOnlyCTFieldInRuns((XWPFFieldRun) run)) + { + var sf=((XWPFFieldRun) run).GetCTField(); + sf.Parent.RemoveSimpleField(sf); } runs.RemoveAt(pos); iRuns.Remove(run); - // Remove the run from the low-level XML - //calculate the correct pos as our run/irun list contains hyperlinks and fields so is different to the paragraph R array. - int rPos = 0; - for (int i = 0; i < pos; i++) + if(!(run is XWPFHyperlinkRun|| run is XWPFFieldRun)) { - XWPFRun currRun = runs[i]; - if (!(currRun is XWPFHyperlinkRun || currRun is XWPFFieldRun)) - rPos++; + // Remove the run from the low-level XML + //calculate the correct pos as our run/irun list contains hyperlinks and fields so is different to the paragraph R array. + int rPos = 0; + for(int i = 0; i < pos; i++) + { + XWPFRun currRun = runs[i]; + if(!(currRun is XWPFHyperlinkRun || currRun is XWPFFieldRun)) + rPos++; + } + GetCTP().RemoveR(rPos); } - GetCTP().RemoveR(rPos); return true; } return false; @@ -1686,7 +1697,7 @@ public XWPFRun GetRun(CT_R r) /// /// Appends a new hyperlink run to this paragraph /// - /// a new hyperlink run + /// hyperlink uri /// public XWPFHyperlinkRun CreateHyperlinkRun(string uri) { @@ -1697,6 +1708,7 @@ public XWPFHyperlinkRun CreateHyperlinkRun(string uri) // Create the run. CT_Hyperlink1 hl = GetCTP().AddNewHyperlink(); hl.id = rId; + hl.Parent = paragraph; hl.AddNewR(); XWPFHyperlinkRun link = new XWPFHyperlinkRun(hl, hl.GetRArray(0), this); @@ -1708,13 +1720,13 @@ public XWPFHyperlinkRun CreateHyperlinkRun(string uri) /// insert a new hyperlink run in RunArray ///
/// The position at which the new run should be added. - /// a new hyperlink run + /// hyperlink uri /// the inserted hyperlink run or null if the given pos is out of bounds. - public XWPFHyperlinkRun InsertNewHyperlinkRun(int pos, string rId) + public XWPFHyperlinkRun InsertNewHyperlinkRun(int pos, string uri) { if(pos == runs.Count) { - return CreateHyperlinkRun(rId); + return CreateHyperlinkRun(uri); } if(pos >= 0 && pos < runs.Count) @@ -1726,39 +1738,64 @@ public XWPFHyperlinkRun InsertNewHyperlinkRun(int pos, string rId) itemPos = paragraph.Items.Count; } - CT_R r = new CT_R(); - r.AddNewRPr().rStyle = new CT_String() { val = "Hyperlink" }; + string rId = Part.GetPackagePart().AddExternalRelationship( + uri, XWPFRelation.HYPERLINK.Relation).Id; CT_Hyperlink1 hl = new CT_Hyperlink1(); + hl.Parent = paragraph; paragraph.Items.Insert(itemPos, hl); paragraph.ItemsElementName.Insert(itemPos, ParagraphItemsChoiceType.hyperlink); - hl.history = ST_OnOff.on; hl.id=rId; - hl.Items.Add(r); - XWPFHyperlinkRun newRun = new XWPFHyperlinkRun(hl, r, this); - - // To update the iRuns, find where we're going - // in the normal Runs, and go in there - int iPos = iRuns.Count; - if(pos < runs.Count) + XWPFHyperlinkRun newRun = new XWPFHyperlinkRun(hl, hl.AddNewR(), this); + UpdateRunAfterInsert(pos, newRun); + return newRun; + } + return null; + } + public XWPFFieldRun InsertNewFieldRun(int pos) + { + if(pos == runs.Count) + { + return CreateFieldRun(); + } + if(pos >= 0 && pos < runs.Count) + { + int itemPos = paragraph.Items.IndexOf(runs[pos].GetCTR()); + if(itemPos == -1) { - XWPFRun oldAtPos = runs[pos]; - int oldAt = iRuns.IndexOf(oldAtPos); - if(oldAt != -1) - { - iPos = oldAt; - } + itemPos = paragraph.Items.Count; } - iRuns.Insert(iPos, newRun); - // Runs itself is easy to update - runs.Insert(pos, newRun); + CT_SimpleField ctSimpleField = new CT_SimpleField(); + ctSimpleField.Parent = paragraph; + paragraph.Items.Insert(itemPos, ctSimpleField); + paragraph.ItemsElementName.Insert(itemPos, ParagraphItemsChoiceType.fldSimple); + XWPFFieldRun newRun = new XWPFFieldRun(ctSimpleField, ctSimpleField.AddNewR(), this); + UpdateRunAfterInsert(pos, newRun); return newRun; } return null; } + private void UpdateRunAfterInsert(int pos, XWPFRun newRun) + { + // To update the iRuns, find where we're going + // in the normal Runs, and go in there + int iPos = iRuns.Count; + if(pos < runs.Count) + { + XWPFRun oldAtPos = runs[pos]; + int oldAt = iRuns.IndexOf(oldAtPos); + if(oldAt != -1) + { + iPos = oldAt; + } + } + iRuns.Insert(iPos, newRun); + // Runs itself is easy to update + runs.Insert(pos, newRun); + } /// /// Add a new run with a reference to the specified footnote. The footnote reference run will have the style name "FootnoteReference". /// @@ -1772,6 +1809,31 @@ public void AddFootnoteReference(XWPFFootnote footnote) var footnoteRef = ctRun.AddNewFootnoteReference(); footnoteRef.id= footnote.Id.ToString(); } + public XWPFFieldRun CreateFieldRun() + { + CT_SimpleField ctSimpleField = paragraph.AddNewFldSimple(); + ctSimpleField.Parent = paragraph; + XWPFFieldRun newRun = new XWPFFieldRun(ctSimpleField, ctSimpleField.AddNewR(), this); + runs.Add(newRun); + iRuns.Add(newRun); + return newRun; + } + + private bool IsTheOnlyCTHyperlinkInRuns(XWPFHyperlinkRun run) + { + CT_Hyperlink1 ctHyperlink = run.GetCTHyperlink(); + long count = runs.Count(r=>(r is XWPFHyperlinkRun) + && ctHyperlink == ((XWPFHyperlinkRun) r).GetCTHyperlink()); + return count <= 1; + } + + private bool IsTheOnlyCTFieldInRuns(XWPFFieldRun run) + { + CT_SimpleField ctField = run.GetCTField(); + long count = runs.Count(r=>(r is XWPFFieldRun) + && ctField == ((XWPFFieldRun) r).GetCTField()); + return count <= 1; + } } } diff --git a/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs b/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs index bd7fda834..2b1299e0c 100644 --- a/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs +++ b/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs @@ -20,6 +20,7 @@ namespace TestCases.XWPF.UserModel using NPOI.OpenXmlFormats.Wordprocessing; using NPOI.Util; using NPOI.XWPF.UserModel; + using NSubstitute; using NUnit.Framework;using NUnit.Framework.Legacy; using System; using System.Collections.Generic; @@ -388,7 +389,7 @@ public void TestPictures() NPOI.OpenXmlFormats.Dml.CT_GraphicalObject go = r.GetCTR().GetDrawingArray(0).GetInlineArray(0).graphic; NPOI.OpenXmlFormats.Dml.CT_GraphicalObjectData god = r.GetCTR().GetDrawingArray(0).GetInlineArray(0).graphic.graphicData; //PicDocument pd = new PicDocumentImpl(null); - //assertTrue(pd.isNil()); + //ClassicAssert.IsTrue(pd.isNil()); } [Test] @@ -768,5 +769,136 @@ public void TestNumberedLists() // TODO Shouldn't we use XWPFNumbering or similar here? // TODO Make it easier to change } + [Test] + public void TestCreateNewRuns() + { + using(XWPFDocument doc = new XWPFDocument()) + { + XWPFParagraph p = doc.CreateParagraph(); + XWPFHyperlinkRun h = p.CreateHyperlinkRun("http://poi.apache.org"); + XWPFFieldRun fieldRun = p.CreateFieldRun(); + XWPFRun r = p.CreateRun(); + + ClassicAssert.AreEqual(3, p.Runs.Count); + ClassicAssert.AreEqual(0, p.Runs.IndexOf(h)); + ClassicAssert.AreEqual(1, p.Runs.IndexOf(fieldRun)); + ClassicAssert.AreEqual(2, p.Runs.IndexOf(r)); + + ClassicAssert.AreEqual(3, p.IRuns.Count); + ClassicAssert.AreEqual(0, p.IRuns.IndexOf(h)); + ClassicAssert.AreEqual(1, p.IRuns.IndexOf(fieldRun)); + ClassicAssert.AreEqual(2, p.IRuns.IndexOf(r)); + } + } + [Test] + public void TestInsertNewRuns() + { + using(XWPFDocument doc = new XWPFDocument()) + { + XWPFParagraph p = doc.CreateParagraph(); + XWPFRun r = p.CreateRun(); + ClassicAssert.AreEqual(1, p.Runs.Count); + ClassicAssert.AreEqual(0, p.Runs.IndexOf(r)); + + XWPFHyperlinkRun h = p.InsertNewHyperlinkRun(0, "http://poi.apache.org"); + ClassicAssert.AreEqual(2, p.Runs.Count); + ClassicAssert.AreEqual(0, p.Runs.IndexOf(h)); + ClassicAssert.AreEqual(1, p.Runs.IndexOf(r)); + + XWPFFieldRun fieldRun2 = p.InsertNewFieldRun(2); + ClassicAssert.AreEqual(3, p.Runs.Count); + ClassicAssert.AreEqual(2, p.Runs.IndexOf(fieldRun2)); + } + } + [Test] + public void TestRemoveRuns() + { + using(XWPFDocument doc = new XWPFDocument()) + { + XWPFParagraph p = doc.CreateParagraph(); + XWPFRun r = p.CreateRun(); + p.CreateRun(); + XWPFHyperlinkRun hyperlinkRun = p + .CreateHyperlinkRun("http://poi.apache.org"); + XWPFFieldRun fieldRun = p.CreateFieldRun(); + + ClassicAssert.AreEqual(4, p.Runs.Count); + ClassicAssert.AreEqual(2, p.Runs.IndexOf(hyperlinkRun)); + ClassicAssert.AreEqual(3, p.Runs.IndexOf(fieldRun)); + + p.RemoveRun(2); + ClassicAssert.AreEqual(3, p.Runs.Count); + ClassicAssert.AreEqual(-1, p.Runs.IndexOf(hyperlinkRun)); + ClassicAssert.AreEqual(2, p.Runs.IndexOf(fieldRun)); + + p.RemoveRun(0); + ClassicAssert.AreEqual(2, p.Runs.Count); + ClassicAssert.AreEqual(-1, p.Runs.IndexOf(r)); + ClassicAssert.AreEqual(1, p.Runs.IndexOf(fieldRun)); + + p.RemoveRun(1); + ClassicAssert.AreEqual(1, p.Runs.Count); + ClassicAssert.AreEqual(-1, p.Runs.IndexOf(fieldRun)); + } + } + [Test] + public void TestRemoveAndInsertRunsWithOtherIRunElement() + { + XWPFDocument doc = new XWPFDocument(); + + XWPFParagraph p = doc.CreateParagraph(); + p.CreateRun(); + // add other run element + p.GetCTP().AddNewSdt(); + // add two CTR in hyperlink + XWPFHyperlinkRun hyperlinkRun = p + .CreateHyperlinkRun("http://poi.apache.org"); + hyperlinkRun.GetCTHyperlink().AddNewR(); + p.CreateFieldRun(); + + XWPFDocument doc2 = XWPFTestDataSamples.WriteOutAndReadBack(doc); + XWPFParagraph paragraph = doc2.GetParagraphArray(0); + + ClassicAssert.AreEqual(4, paragraph.Runs.Count); + ClassicAssert.AreEqual(5, paragraph.IRuns.Count); + + ClassicAssert.IsTrue(paragraph.Runs[1] is XWPFHyperlinkRun); + ClassicAssert.IsTrue(paragraph.Runs[2] is XWPFHyperlinkRun); + ClassicAssert.IsTrue(paragraph.Runs[3] is XWPFFieldRun); + + ClassicAssert.IsTrue(paragraph.IRuns[1] is XWPFSDT); + ClassicAssert.IsTrue(paragraph.IRuns[2] is XWPFHyperlinkRun); + + paragraph.RemoveRun(1); + ClassicAssert.AreEqual(3, paragraph.Runs.Count); + ClassicAssert.IsTrue(paragraph.Runs[1] is XWPFHyperlinkRun); + ClassicAssert.IsTrue(paragraph.Runs[2] is XWPFFieldRun); + + ClassicAssert.IsTrue(paragraph.IRuns[1] is XWPFSDT); + ClassicAssert.IsTrue(paragraph.IRuns[2] is XWPFHyperlinkRun); + + paragraph.RemoveRun(1); + ClassicAssert.AreEqual(2, paragraph.Runs.Count); + ClassicAssert.IsTrue(paragraph.Runs[1] is XWPFFieldRun); + + ClassicAssert.IsTrue(paragraph.IRuns[1] is XWPFSDT); + ClassicAssert.IsTrue(paragraph.IRuns[2] is XWPFFieldRun); + + paragraph.RemoveRun(0); + ClassicAssert.AreEqual(1, paragraph.Runs.Count); + ClassicAssert.IsTrue(paragraph.Runs[0] is XWPFFieldRun); + + ClassicAssert.IsTrue(paragraph.IRuns[0] is XWPFSDT); + ClassicAssert.IsTrue(paragraph.IRuns[1] is XWPFFieldRun); + + XWPFRun newRun = paragraph.InsertNewRun(0); + ClassicAssert.AreEqual(2, paragraph.Runs.Count); + + ClassicAssert.AreEqual(3, paragraph.IRuns.Count); + ClassicAssert.AreEqual(0, paragraph.Runs.IndexOf(newRun)); + + doc.Close(); + doc2.Close(); + } } } From 21b2877cceebe3f116a81b67dfe17dbc3ed32f40 Mon Sep 17 00:00:00 2001 From: Tony Qu Date: Wed, 20 Aug 2025 10:05:01 +0800 Subject: [PATCH 3/3] remove NSubtitute --- testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs b/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs index 2b1299e0c..1947b95d0 100644 --- a/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs +++ b/testcases/ooxml/XWPF/UserModel/TestXWPFParagraph.cs @@ -20,8 +20,8 @@ namespace TestCases.XWPF.UserModel using NPOI.OpenXmlFormats.Wordprocessing; using NPOI.Util; using NPOI.XWPF.UserModel; - using NSubstitute; - using NUnit.Framework;using NUnit.Framework.Legacy; + using NUnit.Framework; + using NUnit.Framework.Legacy; using System; using System.Collections.Generic; using System.Text;