一尘不染

OpenXML标签搜索

c#

我正在编写一个.NET应用程序,该应用程序应读取200页长(通过DocumentFormat.OpenXML
2.5)附近的.docx文件,以查找该文档应包含的某些标记的所有出现。为了清楚起见,我不是在寻找OpenXML标记,而是应该由文档编写者将其设置为文档中的值的占位符,我需要在第二阶段中填写这些标记。此类标签应采用以下格式:

 <!TAG!>

(其中TAG可以是任意字符序列)。正如我所说,我必须找到所有此类标签的出现,再加上(如果可能的话)将找到该标签出现的“页面”定位。我在网上发现了一些东西,但是不只一次,基本方法是将文件中的所有内容转储为字符串,然后在这样的字符串中查找,而不管.docx编码如何。这可能导致误报或完全不匹配(而测试.docx文件包含多个标签),其他示例可能只是我对OpenXML的了解而已。查找此类标签的正则表达式模式应为此类:

<!(.)*?!>

可以在整个文档中找到标签(在表,文本,段落中以及页眉和页脚中)。

我正在Visual Studio 2013 .NET 4.5中进行编码,但是如果需要的话我可以回来。PS我不希望使用Office Interop
API的代码,因为目标平台将无法运行Office。

我可以产生的最小的.docx示例将其存储在文档内部

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 wp14">
<w:body>
<w:p w:rsidR="00CA7780" w:rsidRDefault="00815E5D">
  <w:pPr>
    <w:rPr>
      <w:lang w:val="en-GB"/>
    </w:rPr>
  </w:pPr>
  <w:r>
    <w:rPr>
      <w:lang w:val="en-GB"/>
    </w:rPr>
    <w:t>TRY</w:t>
  </w:r>
</w:p>
<w:p w:rsidR="00815E5D" w:rsidRDefault="00815E5D">
  <w:pPr>
    <w:rPr>
      <w:lang w:val="en-GB"/>
    </w:rPr>
  </w:pPr>
  <w:proofErr w:type="gramStart"/>
  <w:r>
    <w:rPr>
      <w:lang w:val="en-GB"/>
    </w:rPr>
    <w:t>&lt;!TAG1</w:t>
  </w:r>
  <w:proofErr w:type="gramEnd"/>
  <w:r>
    <w:rPr>
      <w:lang w:val="en-GB"/>
    </w:rPr>
    <w:t>!&gt;</w:t>
  </w:r>
</w:p>
<w:p w:rsidR="00815E5D" w:rsidRPr="00815E5D" w:rsidRDefault="00815E5D">
  <w:pPr>
    <w:rPr>
      <w:lang w:val="en-GB"/>
    </w:rPr>
  </w:pPr>
  <w:r>
    <w:rPr>
      <w:lang w:val="en-GB"/>
    </w:rPr>
    <w:t>TRY2</w:t>
  </w:r>
  <w:bookmarkStart w:id="0" w:name="_GoBack"/>
  <w:bookmarkEnd w:id="0"/>
</w:p>
<w:sectPr w:rsidR="00815E5D" w:rsidRPr="00815E5D">
  <w:pgSz w:w="11906" w:h="16838"/>
  <w:pgMar w:top="1417" w:right="1134" w:bottom="1134" w:left="1134" w:header="708" w:footer="708" w:gutter="0"/>
  <w:cols w:space="708"/>
  <w:docGrid w:linePitch="360"/>
</w:sectPr>
</w:body>
</w:document>

最好的问候,迈克


阅读 342

收藏
2020-05-19

共1个答案

一尘不染

尝试查找标签的问题在于单词并非总是以其在Word中的格式出现在基础XML中。例如,在您的示例XML中,<!TAG1!>标记被分成多个运行,如下所示:

<w:r>
    <w:rPr>
        <w:lang w:val="en-GB"/>
    </w:rPr>
    <w:t>&lt;!TAG1</w:t>
</w:r>
<w:proofErr w:type="gramEnd"/>
    <w:r>
    <w:rPr>
        <w:lang w:val="en-GB"/>
    </w:rPr>
    <w:t>!&gt;</w:t>
</w:r>

正如评论中指出的那样,这有时是由拼写和语法检查程序引起的,但这并不是所有可能导致这种情况的原因。例如,在标签的各个部分上使用不同的样式也可能会导致它。

处理的方法之一是找到InnerTextParagraph和比较,对你的Regex。该InnerText属性将返回段落的纯文本,而不会妨碍基础文档中的任何格式或其他XML。

有了标签后,替换文本是下一个问题。由于上述原因,您不能只InnerText用一些新文本替换,因为不清楚文本的哪些部分属于哪个部分Run。解决此问题的最简单方法是删除所有现有Run的,并Run使用Text包含新文本的属性添加新的。

以下代码显示查找标签并立即替换它们,而不是按照您在问题中建议的那样使用两次传递。说实话,这只是为了简化示例。它应该显示您需要的一切。

private static void ReplaceTags(string filename)
{
    Regex regex = new Regex("<!(.)*?!>", RegexOptions.Compiled);

    using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(filename, true))
    {
        //grab the header parts and replace tags there
        foreach (HeaderPart headerPart in wordDocument.MainDocumentPart.HeaderParts)
        {
            ReplaceParagraphParts(headerPart.Header, regex);
        }
        //now do the document
        ReplaceParagraphParts(wordDocument.MainDocumentPart.Document, regex);
        //now replace the footer parts
        foreach (FooterPart footerPart in wordDocument.MainDocumentPart.FooterParts)
        {
            ReplaceParagraphParts(footerPart.Footer, regex);
        }
    }
}

private static void ReplaceParagraphParts(OpenXmlElement element, Regex regex)
{
    foreach (var paragraph in element.Descendants<Paragraph>())
    {
        Match match = regex.Match(paragraph.InnerText);
        if (match.Success)
        {
            //create a new run and set its value to the correct text
            //this must be done before the child runs are removed otherwise
            //paragraph.InnerText will be empty
            Run newRun = new Run();
            newRun.AppendChild(new Text(paragraph.InnerText.Replace(match.Value, "some new value")));
            //remove any child runs
            paragraph.RemoveAllChildren<Run>();
            //add the newly created run
            paragraph.AppendChild(newRun);
        }
    }
}

上述方法的缺点是,您可能拥有的所有样式都会丢失。这些可以从现有Run的中复制,但是如果有多个Run具有不同属性的,则需要计算出哪些属性需要复制到哪里。Run如果需要的话,没有什么可以阻止您在上面的代码中创建多个具有不同属性的。其他元素也将丢失(例如,任何符号),因此也需要考虑这些元素。

2020-05-19