私はv2.0の方が優れていると思います...彼らにはいくつかの素晴らしい「方法:...」の例がありますが、ブックマークはテーブルと言うほど明確に機能していないようです...ブックマークは2つのXML要素で定義されていますBookmarkStart &ブックマーク終了. ブックマークとしてテキストを含むテンプレートがいくつかあり、ブックマークを他のテキストに置き換えたいだけです...奇妙な書式設定は行われていませんが、ブックマークテキストを選択/置換するにはどうすればよいですか?
11 に答える
皆さんをインスピレーションとして使用した後の私のアプローチは次のとおりです。
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 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;
}
これにより、置換リストを分割してブックマークの後にテキストを追加するために使用できる Dictionary が返されます。
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);
}
私が知る限り、ブックマークへの挿入と置換は難しく見えます。InsertIntoSelf の代わりに InsertAt を使用すると、「非複合要素には子要素がありません。」YMMV
回答からコードを取得しましたが、例外的な場合にいくつかの問題がありました。
- 非表示のブックマークを無視したい場合があります。名前が _ (アンダースコア) で始まる場合、ブックマークは非表示になります
- ブックマークがもう 1 つの TableCell のものである場合、ブックマークが開始するセルの 0 から始まる列インデックスを参照するプロパティ ColumnFirst を使用して、行の最初のセルの BookmarkStart にブックマークが見つかります。ColumnLast は、ブックマークが終了するセルを指します。私の特別なケースでは、常に ColumnFirst == ColumnLast でした (ブックマークは 1 つの列のみをマークします)。この場合、BookmarkEnd も見つかりません。
- ブックマークは空にすることができるため、BookmarkStart は BookmarkEnd の直後に続きます。この場合は、単に呼び出すことができます。
bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)
- また、ブックマークには多くのテキスト要素を含めることができるため、他のすべての要素を削除することをお勧めします。そうしないと、ブックマークの一部が置き換えられ、他の次の部分が残ります。
- そして、OpenXML のすべての制限を知っているわけではないので、最後のハックが必要かどうかはわかりませんが、前の 4 つを発見した後、Run の兄弟が存在することも信頼できなくなりました。テキストの子。代わりに、すべての兄弟を (BookmarkStart と同じ ID を持つ BookmarEnd まで) 見て、テキストが見つかるまですべての子をチェックします。- 必要に応じて、OpenXML の経験が豊富な方が回答していただけますか?
ここで私の特定の実装を表示できます)
これが同じ問題を経験した人の助けになることを願っています。
ここでのほとんどのソリューションは、実行前に開始し、実行後に終了する通常のブックマークパターンを想定しています。これは、ブックマークがパラまたはテーブルで始まり、別のパラのどこかで終わる場合など、常に正しいとは限りません(他の人が指摘しているように)。ブックマークが通常の構造に配置されていない場合に対処するためにドキュメントの順序を使用するのはどうですか?ドキュメントの順序は、その間にあるすべての関連するテキストノードを検索し、それらを置き換えることができます。root.DescendantNodes()。Where(xtextまたはbookmarkstartまたはbookmark end)を実行するだけで、ドキュメントの順序でトラバースします。次に、ブックマークの開始ノードを表示した後、終了ノードを表示する前に表示されるテキストノードを置き換えることができます。
これが、bookmarkStart と BookmarkEnd の間にテキストを追加/置換する方法と VB です。
<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
これが私が最終的に得たものです-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));
}
}
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);
}
}