【问题标题】:How to handle smartTag nodes using OpenXML如何使用 OpenXML 处理 smartTag 节点
【发布时间】:2020-06-23 08:20:24
【问题描述】:

我有一个 C# 应用程序,它使用 OpenXML 从单词 (.docx) 文件中读取文本。

一般来说,有一组段落 (p),其中包含运行元素 (r)。 我可以使用

遍历 Run 节点
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 节点。这意味着我不能简单地获取第一个子节点并将其转换为 Run。

获取文本(通过 InnerText 属性)很容易,但我还需要获取格式信息。

有没有比较简单的方法来处理这个问题?

目前,我最好的想法是编写一个去除智能标签节点的预处理器。


编辑

跟进 Cindy Meister 的评论。

我使用的是 OpenXml 版本 2.7.2。正如 Cindy 所指出的,OpenXML 2.0 中有一个 SmartTagRun 类。我不知道那门课。

我在What's new in the Open XML SDK 2.5 for Office页面找到了以下信息

智能标签

由于 Office 2010 中已弃用智能标记,因此 Open XML SDK 2.5 不支持与智能标记相关​​的 Open XML 元素。 Open XML SDK 2.5 仍然可以将智能标记元素视为未知元素, 但是适用于 Office 的 Open XML SDK 2.5 Productivity Tool 验证 Office 文档文件中的那些元素(请参见以下列表)为 无效标签。

所以听起来可能的解决方案是使用 OpenXML 2.0。

【问题讨论】:

  • 一些想法...我认为para.Descendants&lt;Run&gt; 也应该在smartTag 中选择运行?您是说 SDK 区分嵌套在 w:smartTag 中的 w:r? (我无法测试,因为 Word 不再支持创建 SmartTags - 有一个法庭案件裁定 MS 使用了另一家公司的专利技术,因此必须剥离该功能。)
  • 假设是 ^^ 那么是否可以检查 document.xml 中的这些元素(不使用 SDK)并在使用 SDK 之前将其删除?
  • para.Descendents&lt;Run&gt; 不会获取 smartTag 中的 Runs。 smartTag 和所有后代节点都创建为 OpenXmlUnknownElement 节点。
  • 去除smartTag 元素是个好主意,但我不完全确定如何去做。在使用 OpenXML 之前,我是否必须解压缩 docx 文件、编辑 document.xml 文件并重新打包,或者是否有一些支持 OpenXML 来预处理文件。
  • 我已经有一段时间没有真正使用 SDK 进行编码了,这就是为什么我如此犹豫......在 VBA 中我知道我可以做到这一点;我认为应该可以使用 SDK,因为(我记得)可以获得零件或树的一部分的“纯”XML?操作它,然后再写回去?当然,.NET Framework Packaging 命名空间将允许直接访问 document.xml(这就是 SDK 在后台所做的事情)。可能值得提出一个新问题,专门关于阅读/编辑 Office ZIP 包的 XML(IOW 不强调 SmartTag 部分)。

标签: c# openxml openxml-sdk


【解决方案1】:

解决方案是使用 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 元素的标记简化器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-25
    • 2018-06-14
    • 1970-01-01
    • 2020-11-10
    • 2015-10-29
    • 1970-01-01
    • 2018-06-09
    相关资源
    最近更新 更多