簡體   English   中英

如何使用 OpenXML 處理 smartTag 節點

[英]How to handle smartTag nodes using OpenXML

我在 C# 中有一個應用程序,它使用 OpenXML 從 word (.docx) 文件中讀取文本。

通常,有一組段落 (p) 包含運行元素 (r)。 我可以迭代運行節點

foreach ( var run in para.Descendants<Run>() )
{
  ...
}

在一個特定的文檔中,有一個文本“START”,它分為三個部分,“ST”、“AR”和“T”。 它們中的每一個都由一個 Run 節點定義,但在兩種情況下,Run 節點包含在“smartTag”節點中。

<w:smartTag w:uri="urn:schemas-microsoft-com:office:smarttags" w:element="PersonName">
    <w:r w:rsidRPr="00BF444F">
        <w:rPr>
            <w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/>
            <w:b/>
            <w:bCs/>
            <w:sz w:val="40"/>
            <w:szCs w:val="40"/>
        </w:rPr>
        <w:t>ST</w:t>
    </w:r>
</w:smartTag>
<w:smartTag w:uri="urn:schemas-microsoft-com:office:smarttags" w:element="PersonName">
    <w:r w:rsidRPr="00BF444F">
        <w:rPr>
            <w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/>
            <w:b/>
            <w:bCs/>
            <w:sz w:val="40"/>
            <w:szCs w:val="40"/>
        </w:rPr>
        <w:t>AR</w:t>
    </w:r>
</w:smartTag>
<w:r w:rsidRPr="00BF444F">
    <w:rPr>
        <w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/>
        <w:b/>
        <w:bCs/>
        <w:sz w:val="40"/>
        <w:szCs w:val="40"/>
    </w:rPr>
    <w:t xml:space="preserve">T</w:t>
</w:r>

據我所知,OpenXML 不支持 smartTag 節點。 因此,它只生成 OpenXmlUnknownElement 節點。

困難在於它會為 smartTag 的所有后代節點生成 OpenXmlUnknownElement 節點。 這意味着我不能簡單地獲取第一個子節點並將其轉換為運行。

獲取文本(通過 InnerText 屬性)很容易,但我還需要獲取格式信息。

有沒有任何合理簡單的方法來處理這個問題?

目前,我最好的想法是編寫一個刪除智能標記節點的預處理器。


編輯

跟進 Cindy Meister 的評論。

我使用的是 OpenXml 2.7.2 版。 正如 Cindy 指出的那樣,OpenXML 2.0 中有一個 class SmartTagRun。 我不知道那個 class。

在 Open XML SDK 2.5 for Office頁面上找到了以下信息

智能標簽

由於智能標記在 Office 2010 中已棄用,Open XML SDK 2.5 不支持與智能標記相關的 Open XML 元素。 Open XML SDK 2.5 仍然可以將智能標記元素作為未知元素處理,但是 Open XML SDK 2.5 Productivity Tool for Office 將 Office 文檔文件中的這些元素(參見下表)驗證為無效標記。

所以聽起來可能的解決方案是使用 OpenXML 2.0。

解決方案是使用 Linq to XML(或System.Xml類,如果您更喜歡這些類)來刪除w:smartTag元素,如以下代碼所示:

public class SmartTagTests
{
    private const string Xml =
        @"<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
<w:body>
    <w:p>
        <w:smartTag w:uri=""urn:schemas-microsoft-com:office:smarttags"" w:element=""PersonName"">
            <w:r w:rsidRPr=""00BF444F"">
                <w:rPr>
                    <w:rFonts w:ascii=""Arial"" w:hAnsi=""Arial"" w:cs=""Arial""/>
                    <w:b/>
                    <w:bCs/>
                    <w:sz w:val=""40""/>
                    <w:szCs w:val=""40""/>
                </w:rPr>
                <w:t>ST</w:t>
            </w:r>
        </w:smartTag>
        <w:smartTag w:uri=""urn:schemas-microsoft-com:office:smarttags"" w:element=""PersonName"">
            <w:r w:rsidRPr=""00BF444F"">
                <w:rPr>
                    <w:rFonts w:ascii=""Arial"" w:hAnsi=""Arial"" w:cs=""Arial""/>
                    <w:b/>
                    <w:bCs/>
                    <w:sz w:val=""40""/>
                    <w:szCs w:val=""40""/>
                </w:rPr>
                <w:t>AR</w:t>
            </w:r>
        </w:smartTag>
        <w:r w:rsidRPr=""00BF444F"">
            <w:rPr>
                <w:rFonts w:ascii=""Arial"" w:hAnsi=""Arial"" w:cs=""Arial""/>
                <w:b/>
                <w:bCs/>
                <w:sz w:val=""40""/>
                <w:szCs w:val=""40""/>
            </w:rPr>
            <w:t xml:space=""preserve"">T</w:t>
        </w:r>
    </w:p>
</w:body>
</w:document>";

    [Fact]
    public void CanStripSmartTags()
    {
        // Say you have a WordprocessingDocument stored on a stream (e.g., read
        // from a file).
        using Stream stream = CreateTestWordprocessingDocument();

        // Open the WordprocessingDocument and inspect it using the strongly-
        // typed classes. This shows that we find OpenXmlUnknownElement instances
        // are found and only a single Run instance is recognized.
        using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, false))
        {
            MainDocumentPart part = wordDocument.MainDocumentPart;
            Document document = part.Document;

            Assert.Single(document.Descendants<Run>());
            Assert.NotEmpty(document.Descendants<OpenXmlUnknownElement>());
        }

        // Now, open that WordprocessingDocument to make edits, using Linq to XML.
        // Do NOT use the strongly typed classes in this context.
        using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true))
        {
            // Get the w:document as an XElement and demonstrate that this
            // w:document contains w:smartTag elements.
            MainDocumentPart part = wordDocument.MainDocumentPart;
            string xml = ReadString(part);
            XElement document = XElement.Parse(xml);

            Assert.NotEmpty(document.Descendants().Where(d => d.Name.LocalName == "smartTag"));

            // Transform the w:document, stripping all w:smartTag elements and
            // demonstrate that the transformed w:document no longer contains
            // w:smartTag elements.
            var transformedDocument = (XElement) StripSmartTags(document);

            Assert.Empty(transformedDocument.Descendants().Where(d => d.Name.LocalName == "smartTag"));

            // Write the transformed document back to the part.
            WriteString(part, transformedDocument.ToString(SaveOptions.DisableFormatting));
        }

        // Open the WordprocessingDocument again and inspect it using the 
        // strongly-typed classes. This demonstrates that all Run instances
        // are now recognized.
        using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, false))
        {
            MainDocumentPart part = wordDocument.MainDocumentPart;
            Document document = part.Document;

            Assert.Equal(3, document.Descendants<Run>().Count());
            Assert.Empty(document.Descendants<OpenXmlUnknownElement>());
        }
    }

    /// <summary>
    /// Recursive, pure functional transform that removes all w:smartTag elements.
    /// </summary>
    /// <param name="node">The <see cref="XNode" /> to be transformed.</param>
    /// <returns>The transformed <see cref="XNode" />.</returns>
    private static object StripSmartTags(XNode node)
    {
        // We only consider elements (not text nodes, for example).
        if (!(node is XElement element))
        {
            return node;
        }

        // Strip w:smartTag elements by only returning their children.
        if (element.Name.LocalName == "smartTag")
        {
            return element.Elements();
        }

        // Perform the identity transform.
        return new XElement(element.Name, element.Attributes(),
            element.Nodes().Select(StripSmartTags));
    }

    private static Stream CreateTestWordprocessingDocument()
    {
        var stream = new MemoryStream();

        using var wordDocument = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document);
        MainDocumentPart part = wordDocument.AddMainDocumentPart();
        WriteString(part, Xml);

        return stream;
    }

    #region Generic Open XML Utilities

    private static string ReadString(OpenXmlPart part)
    {
        using Stream stream = part.GetStream(FileMode.Open, FileAccess.Read);
        using var streamReader = new StreamReader(stream);
        return streamReader.ReadToEnd();
    }

    private static void WriteString(OpenXmlPart part, string text)
    {
        using Stream stream = part.GetStream(FileMode.Create, FileAccess.Write);
        using var streamWriter = new StreamWriter(stream);
        streamWriter.Write(text);
    }

    #endregion
}

您還可以使用PowerTools for Open XML ,它提供了一個直接支持刪除w:smartTag元素的標記簡化器。

你可以使用 e.LocalName == "smartTag" 檢查 smartTag,並使用 e.LocalName == "r" 檢查運行。

if (child.LocalName == "smartTag") {
  void f(OpenXmlElement e)
  {
    if (e == null) return;
    if (e.LocalName == "r") {
      var r = new Run();
      r.InnerXml = e.InnerXml;
      // process the run
      ProcessRun(r);
      return;
    }
    if (!e.HasChildren) return;
    foreach (var ele in e.ChildElements)
    {
      f(ele);
    }
  }
  f(child);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM