Вот код, который работает для точно конструкции XML, которую вы показываете: закладка начинается и заканчивается в пределах абзаца, в начале и конце абзаца. Есть множество других вариантов, и каждый должен быть учтен в явном виде.
Закладка состоит из начальной и конечной точек. Вам нужно и то, и другое, чтобы получить контент.
Поскольку документ может иметь несколько закладок, а закладки могут перекрываться, необходимо получить Id
закладки, чтобы определить, какая конечная точка соответствует начальной точке. Имя присутствует только в элементе BookmarkStart
. Только Id
используется в начальных и конечных элементах.
Необходимо определить, где (в какой структуре) лежат начальная и конечная точки закладки, поскольку это дает информацию о том, какими могут быть родительские, родственные и дочерние элементы. Для этого конкретного случая использования, поскольку начало и конец закладки находятся в абзацах, родители обоих элементов являются Paragraph
элементами. Код ниже определяет это, проверяя Parent.LocalName
.
В этом случае определяются родительские абзацы начальной и конечной точек. Для редактирования содержимого всех абзацев внутри закладки создается List
; родительский абзац начальной точки добавляется к нему. Дополнительный объект Paragraph
создается для проверки следующих родственных абзацев, и это проверяется для конечной точки закладки. Пока конец закладки не находится в объекте для следующего абзаца-брата, цикл while
выполняется; следующий брат добавляется к List
.
Как только все абзацы до и включая конец с закладкой находятся в List
, List
зацикливается для замены текста в каждом абзаце. Первый Run
копируется, чтобы сохранить основное форматирование абзаца. Все элементы Run
и Text
затем удаляются, к скопированному Run
добавляется новый текст.
В конце конец закладки устанавливается на конец последнего абзаца.
private void btnReplaceBookmarkText_Click(object sender, EventArgs e)
{
string fileNameDoc = "path name";
string bkmName = "signet";
string bkmID = "";
string parentTypeStart = "";
string parentTypeEnd = "";
using (WordprocessingDocument pkgDoc = WordprocessingDocument.Open(fileNameDoc, true))
{
Body body = pkgDoc.MainDocumentPart.Document.Body;
BookmarkStart bkmStart = body.Descendants<BookmarkStart>().Where(bkm => bkm.Name == bkmName).FirstOrDefault();
bkmID = bkmStart.Id;
BookmarkEnd bkmEnd = body.Descendants<BookmarkEnd>().Where(bkm => bkm.Id == bkmID).FirstOrDefault();
parentTypeStart = bkmStart.Parent.LocalName;
parentTypeEnd = bkmEnd.Parent.LocalName;
int counter = 0;
if (parentTypeStart == "p" && parentTypeEnd == "p")
{ //bookmark starts at a paragraph and ends within a paragraph
Paragraph bkmParaStart = (Paragraph) bkmStart.Parent;
Paragraph bkmParaEnd = (Paragraph) bkmEnd.Parent;
Paragraph bkmParaNext = (Paragraph) bkmParaStart;
List<Paragraph> paras = new List<Paragraph>();
paras.Add(bkmParaStart);
BookmarkEnd x = bkmParaNext.Descendants<BookmarkEnd>().Where(bkm => bkm.Id == bkmID).FirstOrDefault();
while (x==null)
{
Paragraph nextPara = (Paragraph) bkmParaNext.NextSibling();
if (nextPara != null)
{
paras.Add(nextPara);
bkmParaNext = (Paragraph)nextPara.Clone();
x = bkmParaNext.Descendants<BookmarkEnd>().Where(bkm => bkm.Id == bkmID).FirstOrDefault();
}
}
foreach (Paragraph para in paras)
{
string t = "changed string once more " + counter;
Run firstRun = para.Descendants<Run>().FirstOrDefault();
Run newRun = (Run) firstRun.Clone();
newRun.RemoveAllChildren<Text>();
para.RemoveAllChildren<Run>();
para.RemoveAllChildren<Text>();
para.AppendChild<Run>(newRun).AppendChild<Text>(new Text(t));
}
//After replacing the runs and text the bookmark is at the beginning
//of the paragraph, we want it at the end
BookmarkEnd newBkmEnd = new BookmarkEnd() { Id = bkmID };
Paragraph p = paras.Last<Paragraph>();
p.Descendants<BookmarkEnd>().Where(bkm => bkm.Id==bkmID).FirstOrDefault().Remove();
p.Append(newBkmEnd);
}
}
}
Примечание:
Поскольку я больше чувствую себя в объектной модели Word лучше, чем XML, возможно, код мог бы быть более оптимальным, но это сработало для меня.