web-dev-qa-db-ja.com

Open XML SDKを使用してWordファイルのブックマークテキストを置き換える

私はv2.0の方が良いと思います...彼らはいくつかの "ハウツー:..." examples を持っていますが、ブックマークはテーブルと言うほど明らかに機能していないようです...ブックマークはtwoXML要素 BookmarkStartBookmarkEnd によって定義されます。ブックマークとしてテキストを含むテンプレートがいくつかあり、ブックマークを他のテキストに置き換えたいだけです...奇妙なフォーマットは行われていませんが、ブックマークテキストを選択/置換するにはどうすればよいですか?

21
Mr. Boy

皆さんをインスピレーションとして使用した後の私のアプローチは次のとおりです。

  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";
      }
  }
18
Mr. Boy

ブックマークを単一のコンテンツ(場合によっては複数のテキストブロック)に置き換えます。

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);
}

最初に、開始と終了の間の既存のコンテンツが削除されます。次に、新しいランが開始の直後(終了の前)に追加されます。

ただし、ブックマークが開かれたときに別のセクションで閉じられているのか、別のテーブルセルで閉じられているのかなどは不明です。

今のところそれで十分です。

8
cyberblast

私はこれを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);
}

ブックマークへの挿入とブックマークの置換が難しいように見えます。 InsertIntoSelfの代わりにInsertAtを使用すると、「非複合要素には子要素がありません」と表示されました。 YMMV

4
John Farrell

多くの時間の後、私はこのメソッドを書きました:

    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;
}

空のブックマークがない場合、このコードは適切に機能します。誰かのお役に立てば幸いです。

4
gorgonzola

私は答えからコードを取り出しましたが、例外的なケースでいくつかの問題がありました:

  1. 非表示のブックマークを無視したい場合があります。名前が_(アンダースコア)で始まる場合、ブックマークは非表示になります
  2. ブックマークがもう1つのTableCellに対するものである場合、ブックマークが開始するセルの0から始まる列インデックスを参照するプロパティColumnFirstを持つ行の最初のセルのBookmarkStartにあります。 ColumnLastは、ブックマークが終了するセルを参照します。私の特別なケースでは、常にColumnFirst == ColumnLastでした(ブックマークは1つの列のみをマークしました)。この場合、BookmarkEndも見つかりません。
  3. ブックマークは空にすることができるので、BookmarkStartはBookmarkEndの直後に続きます。この場合、bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)を呼び出すだけです。
  4. また、ブックマークには多くのText要素を含めることができるため、他のすべての要素を削除したい場合があります。そうしないと、ブックマークの他の部分が置き換えられ、他の次の部分はそのまま残ります。
  5. そして、OpenXMLのすべての制限を知っているわけではないので、最後のハックが必要かどうかはわかりませんが、前の4つを発見した後、Runの兄弟が存在することを信頼できなくなりました。テキストの子。したがって、代わりに(BookmarkStartと同じIDを持つBookmarEndまで)すべての兄弟を見て、テキストが見つかるまですべての子をチェックします。 -必要に応じて、OpenXMLの経験が豊富な人が回答できるかもしれません。

あなたは私の特定の実装を見ることができます ここ

これが同じ問題を経験した一部のユーザーに役立つことを願っています。

3
peter

ここでのほとんどのソリューションは、実行前に開始し、実行後に終了するという通常のブックマークパターンを前提としています。ブックマークがパラまたはテーブルで始まり、別のパラのどこかで終わる場合(他の人が指摘したように)。ブックマークが通常の構造に配置されていない場合に対処するためにドキュメントの順序を使用するのはどうでしょうか。ドキュメントの順序は、すべての関連するテキストノードを見つけて、その間に置き換えることができます。文書の順序で移動するroot.DescendantNodes()。Where(xtextまたはBookmarkstartまたはBookmark End)を実行するだけで、ブックマーク開始ノードが表示された後、終了ノードが表示される前に表示されるテキストノードを置き換えることができます。

2
Sanorita Rm

これが私がそれを行う方法であり、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
1
LSFM

ブックマーク(ブックマーク名は「Table」)のテキストをテーブルに置き換える必要がありました。これは私のアプローチです:

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;
}

お役に立てれば

1
Gogutz

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
0
Stephen Study

これが私が最終的に得たものです-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));
        }
    }
0
Lance

受け入れられた回答とその他の回答は、ブックマークがドキュメント構造のどこにあるかを想定しています。これが複数の段落にまたがるブックマークの置き換えに対応できる私のC#コードですand段落の境界で開始および終了しないブックマークを正しく置き換えます。まだ完璧ではありませんが、近いです...役に立つことを願っています。改善する方法が他にある場合は編集してください!

    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);
        }
    }
0
Dan Fitch