【发布时间】:2010-07-22 11:27:01
【问题描述】:
我认为 v2.0 更好...他们有一些不错的“如何:...”examples 但书签的行为似乎不像表格那样明显...书签由两个 XML 元素BookmarkStart 和BookmarkEnd。我们有一些带有文本作为书签的模板,我们只是想用一些其他文本替换书签......没有奇怪的格式发生,但我如何选择/替换书签文本?
【问题讨论】:
标签: c# ms-word openxml openxml-sdk
我认为 v2.0 更好...他们有一些不错的“如何:...”examples 但书签的行为似乎不像表格那样明显...书签由两个 XML 元素BookmarkStart 和BookmarkEnd。我们有一些带有文本作为书签的模板,我们只是想用一些其他文本替换书签......没有奇怪的格式发生,但我如何选择/替换书签文本?
【问题讨论】:
标签: c# ms-word openxml openxml-sdk
以下是我以你们为灵感的方法:
IDictionary<String, BookmarkStart> bookmarkMap =
new Dictionary<String, BookmarkStart>();
foreach (BookmarkStart bookmarkStart in file.MainDocumentPart.RootElement.Descendants<BookmarkStart>())
{
bookmarkMap[bookmarkStart.Name] = bookmarkStart;
}
foreach (BookmarkStart bookmarkStart in bookmarkMap.Values)
{
Run bookmarkText = bookmarkStart.NextSibling<Run>();
if (bookmarkText != null)
{
bookmarkText.GetFirstChild<Text>().Text = "blah";
}
}
【讨论】:
用单个内容(可能是多个文本块)替换书签。
public static void InsertIntoBookmark(BookmarkStart bookmarkStart, string text)
{
OpenXmlElement elem = bookmarkStart.NextSibling();
while (elem != null && !(elem is BookmarkEnd))
{
OpenXmlElement nextElem = elem.NextSibling();
elem.Remove();
elem = nextElem;
}
bookmarkStart.Parent.InsertAfter<Run>(new Run(new Text(text)), bookmarkStart);
}
首先,移除 start 和 end 之间的现有内容。然后直接在开始之后(结束之前)添加一个新的运行。
但是,不确定书签在打开时是否在另一个部分或不同的表格单元格等中关闭。..
对我来说现在已经足够了。
【讨论】:
经过很多小时,我写了这个方法:
Public static void ReplaceBookmarkParagraphs(WordprocessingDocument doc, string bookmark, string text)
{
//Find all Paragraph with 'BookmarkStart'
var t = (from el in doc.MainDocumentPart.RootElement.Descendants<BookmarkStart>()
where (el.Name == bookmark) &&
(el.NextSibling<Run>() != null)
select el).First();
//Take ID value
var val = t.Id.Value;
//Find the next sibling 'text'
OpenXmlElement next = t.NextSibling<Run>();
//Set text value
next.GetFirstChild<Text>().Text = text;
//Delete all bookmarkEnd node, until the same ID
deleteElement(next.GetFirstChild<Text>().Parent, next.GetFirstChild<Text>().NextSibling(), val, true);
}
之后,我调用:
Public static bool deleteElement(OpenXmlElement parentElement, OpenXmlElement elem, string id, bool seekParent)
{
bool found = false;
//Loop until I find BookmarkEnd or null element
while (!found && elem != null && (!(elem is BookmarkEnd) || (((BookmarkEnd)elem).Id.Value != id)))
{
if (elem.ChildElements != null && elem.ChildElements.Count > 0)
{
found = deleteElement(elem, elem.FirstChild, id, false);
}
if (!found)
{
OpenXmlElement nextElem = elem.NextSibling();
elem.Remove();
elem = nextElem;
}
}
if (!found)
{
if (elem == null)
{
if (!(parentElement is Body) && seekParent)
{
//Try to find bookmarkEnd in Sibling nodes
found = deleteElement(parentElement.Parent, parentElement.NextSibling(), id, true);
}
}
else
{
if (elem is BookmarkEnd && ((BookmarkEnd)elem).Id.Value == id)
{
found = true;
}
}
}
return found;
}
如果您没有空书签,此代码运行良好。 我希望它可以帮助某人。
【讨论】:
我在 10 分钟前才发现这一点,所以请原谅代码的骇人听闻的性质。
首先我编写了一个辅助递归辅助函数来查找所有书签:
private static Dictionary<string, BookmarkEnd> FindBookmarks(OpenXmlElement documentPart, Dictionary<string, BookmarkEnd> results = null, Dictionary<string, string> unmatched = null )
{
results = results ?? new Dictionary<string, BookmarkEnd>();
unmatched = unmatched ?? new Dictionary<string,string>();
foreach (var child in documentPart.Elements())
{
if (child is BookmarkStart)
{
var bStart = child as BookmarkStart;
unmatched.Add(bStart.Id, bStart.Name);
}
if (child is BookmarkEnd)
{
var bEnd = child as BookmarkEnd;
foreach (var orphanName in unmatched)
{
if (bEnd.Id == orphanName.Key)
results.Add(orphanName.Value, bEnd);
}
}
FindBookmarks(child, results, unmatched);
}
return results;
}
这会返回给我一个字典,我可以使用它来分隔我的替换列表并在书签后添加文本:
var bookMarks = FindBookmarks(doc.MainDocumentPart.Document);
foreach( var end in bookMarks )
{
var textElement = new Text("asdfasdf");
var runElement = new Run(textElement);
end.Value.InsertAfterSelf(runElement);
}
据我所知,插入和替换书签看起来更难。当我使用 InsertAt 而不是 InsertIntoSelf 时,我得到:“非复合元素没有子元素。” YMMV
【讨论】:
doc.MainDocumentPart.Document.Body.Descendants
我从答案中获取了代码,但在特殊情况下遇到了几个问题:
bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)
你可以查看我的具体实现here)
希望这对遇到同样问题的一些人有所帮助。
【讨论】:
这里的大多数解决方案都假设运行前开始和运行后结束的常规书签模式,这并不总是正确的,例如如果书签从一个段落或表格开始并在另一个段落的某个地方结束(就像其他人指出的那样)。如何使用文档顺序来处理书签未放置在常规结构中的情况 - 文档顺序仍然会找到所有相关的文本节点,然后可以替换它们之间的所有相关文本节点。只需执行 root.DescendantNodes().Where(xtext or bookmarkstart or bookmark end) ,它将按文档顺序遍历,然后可以替换在看到书签开始节点之后但在看到结束节点之前出现的文本节点。
【讨论】:
这是我如何做到这一点和 VB 在 bookmarkStart 和 BookmarkEnd 之间添加/替换文本。
<w:bookmarkStart w:name="forbund_kort" w:id="0" />
- <w:r>
<w:t>forbund_kort</w:t>
</w:r>
<w:bookmarkEnd w:id="0" />
Imports DocumentFormat.OpenXml.Packaging
Imports DocumentFormat.OpenXml.Wordprocessing
Public Class PPWordDocx
Public Sub ChangeBookmarks(ByVal path As String)
Try
Dim doc As WordprocessingDocument = WordprocessingDocument.Open(path, True)
'Read the entire document contents using the GetStream method:
Dim bookmarkMap As IDictionary(Of String, BookmarkStart) = New Dictionary(Of String, BookmarkStart)()
Dim bs As BookmarkStart
For Each bs In doc.MainDocumentPart.RootElement.Descendants(Of BookmarkStart)()
bookmarkMap(bs.Name) = bs
Next
For Each bs In bookmarkMap.Values
Dim bsText As DocumentFormat.OpenXml.OpenXmlElement = bs.NextSibling
If Not bsText Is Nothing Then
If TypeOf bsText Is BookmarkEnd Then
'Add Text element after start bookmark
bs.Parent.InsertAfter(New Run(New Text(bs.Name)), bs)
Else
'Change Bookmark Text
If TypeOf bsText Is Run Then
If bsText.GetFirstChild(Of Text)() Is Nothing Then
bsText.InsertAt(New Text(bs.Name), 0)
End If
bsText.GetFirstChild(Of Text)().Text = bs.Name
End If
End If
End If
Next
doc.MainDocumentPart.RootElement.Save()
doc.Close()
Catch ex As Exception
Throw ex
End Try
End Sub
End Class
【讨论】:
我需要用表格替换书签的文本(书签名称为“表格”)。这是我的方法:
public void ReplaceBookmark( DatasetToTable( ds ) )
{
MainDocumentPart mainPart = myDoc.MainDocumentPart;
Body body = mainPart.Document.GetFirstChild<Body>();
var bookmark = body.Descendants<BookmarkStart>()
.Where( o => o.Name == "Table" )
.FirstOrDefault();
var parent = bookmark.Parent; //bookmark's parent element
if (ds!=null)
{
parent.InsertAfterSelf( DatasetToTable( ds ) );
parent.Remove();
}
mainPart.Document.Save();
}
public Table DatasetToTable( DataSet ds )
{
Table table = new Table();
//creating table;
return table;
}
希望对你有帮助
【讨论】:
这是我在 VB.NET 中的做法:
For Each curBookMark In contractBookMarkStarts
''# Get the "Run" immediately following the bookmark and then
''# get the Run's "Text" field
runAfterBookmark = curBookMark.NextSibling(Of Wordprocessing.Run)()
textInRun = runAfterBookmark.LastChild
''# Decode the bookmark to a contract attribute
lines = DecodeContractDataToContractDocFields(curBookMark.Name, curContract).Split(vbCrLf)
''# If there are multiple lines returned then some work needs to be done to create
''# the necessary Run/Text fields to hold lines 2 thru n. If just one line then set the
''# Text field to the attribute from the contract
For ptr = 0 To lines.Count - 1
line = lines(ptr)
If ptr = 0 Then
textInRun.Text = line.Trim()
Else
''# Add a <br> run/text component then add next line
newRunForLf = New Run(runAfterBookmark.OuterXml)
newRunForLf.LastChild.Remove()
newBreak = New Break()
newRunForLf.Append(newBreak)
newRunForText = New Run(runAfterBookmark.OuterXml)
DirectCast(newRunForText.LastChild, Text).Text = line.Trim
curBookMark.Parent.Append(newRunForLf)
curBookMark.Parent.Append(newRunForText)
End If
Next
Next
【讨论】:
接受的答案和其他一些答案对书签在文档结构中的位置进行了假设。这是我的 C# 代码,它可以处理替换跨越多个段落的书签和正确替换不在段落边界开始和结束的书签。仍然不完美,但更接近......希望它有用。如果您发现更多改进方法,请编辑!
private static void ReplaceBookmarkParagraphs(MainDocumentPart doc, string bookmark, IEnumerable<OpenXmlElement> paras) {
var start = doc.Document.Descendants<BookmarkStart>().Where(x => x.Name == bookmark).First();
var end = doc.Document.Descendants<BookmarkEnd>().Where(x => x.Id.Value == start.Id.Value).First();
OpenXmlElement current = start;
var done = false;
while ( !done && current != null ) {
OpenXmlElement next;
next = current.NextSibling();
if ( next == null ) {
var parentNext = current.Parent.NextSibling();
while ( !parentNext.HasChildren ) {
var toRemove = parentNext;
parentNext = parentNext.NextSibling();
toRemove.Remove();
}
next = current.Parent.NextSibling().FirstChild;
current.Parent.Remove();
}
if ( next is BookmarkEnd ) {
BookmarkEnd maybeEnd = (BookmarkEnd)next;
if ( maybeEnd.Id.Value == start.Id.Value ) {
done = true;
}
}
if ( current != start ) {
current.Remove();
}
current = next;
}
foreach ( var p in paras ) {
end.Parent.InsertBeforeSelf(p);
}
}
【讨论】:
这是我最终得到的结果 - 不是 100% 完美,但适用于简单的书签和要插入的简单文本:
private void FillBookmarksUsingOpenXml(string sourceDoc, string destDoc, Dictionary<string, string> bookmarkData)
{
string wordmlNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
// Make a copy of the template file.
File.Copy(sourceDoc, destDoc, true);
//Open the document as an Open XML package and extract the main document part.
using (WordprocessingDocument wordPackage = WordprocessingDocument.Open(destDoc, true))
{
MainDocumentPart part = wordPackage.MainDocumentPart;
//Setup the namespace manager so you can perform XPath queries
//to search for bookmarks in the part.
NameTable nt = new NameTable();
XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
nsManager.AddNamespace("w", wordmlNamespace);
//Load the part's XML into an XmlDocument instance.
XmlDocument xmlDoc = new XmlDocument(nt);
xmlDoc.Load(part.GetStream());
//Iterate through the bookmarks.
foreach (KeyValuePair<string, string> bookmarkDataVal in bookmarkData)
{
var bookmarks = from bm in part.Document.Body.Descendants<BookmarkStart>()
select bm;
foreach (var bookmark in bookmarks)
{
if (bookmark.Name == bookmarkDataVal.Key)
{
Run bookmarkText = bookmark.NextSibling<Run>();
if (bookmarkText != null) // if the bookmark has text replace it
{
bookmarkText.GetFirstChild<Text>().Text = bookmarkDataVal.Value;
}
else // otherwise append new text immediately after it
{
var parent = bookmark.Parent; // bookmark's parent element
Text text = new Text(bookmarkDataVal.Value);
Run run = new Run(new RunProperties());
run.Append(text);
// insert after bookmark parent
parent.Append(run);
}
//bk.Remove(); // we don't want the bookmark anymore
}
}
}
//Write the changes back to the document part.
xmlDoc.Save(wordPackage.MainDocumentPart.GetStream(FileMode.Create));
}
}
【讨论】: