diff --git a/ooxml/XWPF/Usermodel/XWPFDocument.cs b/ooxml/XWPF/Usermodel/XWPFDocument.cs index 5dfb01659..07f899064 100644 --- a/ooxml/XWPF/Usermodel/XWPFDocument.cs +++ b/ooxml/XWPF/Usermodel/XWPFDocument.cs @@ -1882,44 +1882,72 @@ public XWPFDocument GetXWPFDocument() return this; } - private static void FindAndReplaceTextInParagraph(XWPFParagraph paragraph, string oldValue, string newValue) + private static void FindAndReplaceTextInParagraph(XWPFParagraph paragraph, string oldValue, string newValue, int startPos = 0) { - var index = paragraph?.Text.IndexOf(oldValue) ?? -1; - if (index != -1) + if(paragraph == null) + return; + + string paragraphText = string.Concat(paragraph.Runs.Select(p => p.Text)); + + var startIndex = paragraphText.IndexOf(oldValue, startPos); + if(startIndex == -1) + return; + + int firstRun = -1; + int firstIndex = -1; + + int lastRun = -1; + int lastIndex = -1; + + int processedRuns = 0; + int processedChars = 0; + + for(; processedRuns < paragraph.Runs.Count; processedRuns++) { - int? firstIndex = null; - int? lastIndex = null; - for (var i = 0; i < paragraph.Runs.Count; ++i) + var text = paragraph.Runs[processedRuns].Text; + if(processedChars + text.Length > startIndex) { - if (index < paragraph.Runs[i].Text.Length) - { - if (-index <= oldValue.Length) - { - if (firstIndex == null) - { - firstIndex = i; - } - } - else - { - lastIndex = i; - break; - } - } - index -= paragraph.Runs[i].Text.Length; - } - if (lastIndex == null) - { - lastIndex = paragraph.Runs.Count - 1; + firstRun = processedRuns; + firstIndex = startIndex - processedChars; + break; } - var newText = String.Concat(paragraph.Runs.Skip(firstIndex ?? 0).Take((lastIndex ?? 0) - (firstIndex ?? 0) + 1).Select(_ => _.Text)); - newText = newText.Replace(oldValue, newValue); - paragraph.Runs[firstIndex ?? 0].SetText(newText); - for (var i = (firstIndex ?? 0) + 1; i <= lastIndex; ++i) + + processedChars += text.Length; + } + + int endIndex = startIndex + oldValue.Length; + + for(; processedRuns < paragraph.Runs.Count; processedRuns++) + { + var text = paragraph.Runs[processedRuns].Text; + if(processedChars + text.Length > endIndex) { - paragraph.RemoveRun((firstIndex ?? 0) + 1); + lastRun = processedRuns; + lastIndex = endIndex - processedChars; + break; } + + processedChars += text.Length; + } + + var initialFirstText = paragraph.Runs[firstRun].Text; + if(firstRun == lastRun) + { + paragraph.Runs[firstRun].SetText(initialFirstText.Substring(0, firstIndex) + newValue + initialFirstText.Substring(lastIndex)); + } + else + { + paragraph.Runs[firstRun].SetText(initialFirstText.Substring(0, firstIndex) + newValue); + + if(lastRun != -1) + paragraph.Runs[lastRun].SetText(paragraph.Runs[lastRun].Text.Substring(lastIndex)); } + + int removeTo = lastRun == -1 ? paragraph.Runs.Count : lastRun; + for(int i = firstRun + 1; i < removeTo; i++) + paragraph.RemoveRun(firstRun + 1); + + FindAndReplaceTextInParagraph(paragraph, oldValue, newValue, startIndex + newValue.Length); } private static void FindAndReplaceTextInTable(XWPFTable table, string oldValue, string newValue) diff --git a/testcases/ooxml/XWPF/UserModel/TestXWPFDocument.cs b/testcases/ooxml/XWPF/UserModel/TestXWPFDocument.cs index f15e2880f..41de1e600 100644 --- a/testcases/ooxml/XWPF/UserModel/TestXWPFDocument.cs +++ b/testcases/ooxml/XWPF/UserModel/TestXWPFDocument.cs @@ -150,7 +150,7 @@ public void ReplaceParagraphText() doc.FindAndReplaceText("$replace_cell_text$", "Regel1\nRegel2\nRegel3"); //Save Word Document - XWPFDocument outputDocument = outputDocument = XWPFTestDataSamples.WriteOutAndReadBack(doc); + XWPFDocument outputDocument = XWPFTestDataSamples.WriteOutAndReadBack(doc); //Combine all runs of all paragraphs StringBuilder builder = new StringBuilder(); @@ -184,6 +184,44 @@ public void ReplaceParagraphText() } + [Test] + public void FindAndReplaceTextInParagraph() + { + XWPFDocument doc = XWPFTestDataSamples.OpenSampleDocument("WordFindAndReplaceTextInParagraph.docx"); + string initialText = string.Concat(doc.Paragraphs.Select(t => t.Text)); + + Dictionary replacers = new Dictionary + { + {"$FINALQUALIFYINGWORK_QUESTION_1_ASKING_SHORT$", "Asking1" }, + {"$FINALQUALIFYINGWORK_QUESTION_1_QUESTION$", "Question1" }, + {"$FINALQUALIFYINGWORK_QUESTION_2_ASKING_SHORT$", "Asking2" }, + {"$FINALQUALIFYINGWORK_QUESTION_2_QUESTION$", "Question2" }, + {"$STUDENT_FULL$", "На русском с пробелами" }, + {"$FINALQUALIFYINGWORK_GRADE$", "5" }, + {"$SIMPLE$", "Last text" }, + {"$DOUBLE$", "Twice" }, + }; + + //This is calling FindAndReplaceTextInParagraph for each paragraph in document + foreach(var replacer in replacers) + doc.FindAndReplaceText(replacer.Key, replacer.Value); + + //Save Word Document + XWPFDocument outputDocument = XWPFTestDataSamples.WriteOutAndReadBack(doc); + + foreach(var replacer in replacers) + initialText = initialText.Replace(replacer.Key, replacer.Value); + + string savedText = string.Concat(outputDocument.Paragraphs.Select(t => t.Text)); + + //Check that at least replacing in string equal to result file + ClassicAssert.AreEqual(initialText, savedText); + + //Check + ClassicAssert.AreEqual("Some initial text на разный манер (inserted) and so on:Asking1: Question1Asking2: Question2Result on:1. Say that На русском с пробелами with a very long sentence and one more replacer in the end for (русский язык) sure 5Triple replace with TwiceTwice и ещё одним Twice повторением.Last text", + savedText); + } + [Test] public void TestAddPicture() { diff --git a/testcases/test-data/document/WordFindAndReplaceTextInParagraph.docx b/testcases/test-data/document/WordFindAndReplaceTextInParagraph.docx new file mode 100644 index 000000000..85e3d3042 Binary files /dev/null and b/testcases/test-data/document/WordFindAndReplaceTextInParagraph.docx differ