Как работать с узлами SmartTag с помощью OpenXML - PullRequest
0 голосов
/ 11 марта 2020

У меня есть приложение в C#, которое читает текст из файла слова (.docx), используя Open XML.

В общем, есть набор параграфов (p), которые содержат элементы Run (р). Я могу перебрать узлы Run с помощью

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

. В одном конкретном документе c есть текст «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>

Насколько я могу судить, Open XML не поддерживает узел SmartTag. В результате он просто генерирует узлы OpenXmlUnknownElement.

Что делает это трудным, так это то, что он генерирует узлы OpenXmlUnknownElement для всех дочерних узлов smartTag. Это означает, что я не могу просто получить первый дочерний узел и привести его к Run.

Получить текст (через свойство InnerText) легко, но мне также нужно получить информацию о форматировании.

Есть ли достаточно простой способ справиться с этим?

В настоящее время моя лучшая идея - написать препроцессор, который удаляет узлы смарт-тегов.


Редактировать

В продолжение комментария от Синди Мейстер.

Я использую Open Xml версия 2.7.2. Как указала Синди, в Open XML 2.0 есть класс SmartTagRun. Я не знал об этом классе.

Я нашел следующую информацию на странице Что нового в Open XML SDK 2.5 для Office

Смарт-теги

Поскольку смарт-теги в Office 2010 устарели, Open XML SDK 2.5 не поддерживает элементы Open XML, связанные со смарт-тегами. Open XML SDK 2.5 по-прежнему может обрабатывать элементы смарт-тегов как неизвестные элементы, однако средство повышения производительности Open XML SDK 2.5 для Office проверяет эти элементы (см. Следующий список) в файлах документов Office как недействительные теги.

Похоже, что возможным решением было бы использование Open XML 2.0.

1 Ответ

1 голос
/ 13 марта 2020

Решение состоит в том, чтобы использовать Linq для 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
}

You можно также использовать PowerTools для Open XML, который обеспечивает упрощенный разметка, которая напрямую поддерживает удаление w:smartTag элементов.

...