OpenXML标记搜索

wei*_*gyn 10 .net c# ms-word openxml

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

 <!TAG!>
Run Code Online (Sandbox Code Playgroud)

(其中TAG可以是任意字符序列).正如我所说,我必须找到所有这些标签的出现加上(如果可能的话)找到已找到标签出现的"页面".我在Web上发现了一些东西,但不止一次基本方法是将文件的所有内容转储到字符串中,然后查看这样的字符串,无论.docx编码如何.这或者导致误报或者根本没有匹配(虽然测试.docx文件包含多个标签),其他示例可能与我对OpenXML的了解有点差异.找到这样的标签的正则表达式模式应该是这样的:

<!(.)*?!>
Run Code Online (Sandbox Code Playgroud)

标签可以在整个文档中找到(在表格,文本,段落内,也可以在页眉和页脚中).

我在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>
Run Code Online (Sandbox Code Playgroud)

最诚挚的问候,迈克

pet*_*ids 8

尝试查找标记的问题是,单词并不总是在它们看起来在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>
Run Code Online (Sandbox Code Playgroud)

正如评论中所指出的,这有时是由拼写和语法检查引起的,但并不是所有这些都可能导致它.例如,在标签的部分上具有不同的样式也可能导致它.

处理此问题的一种方法是找到InnerTexta Paragraph并将其与您的比较Regex.该InnerText属性将返回段落的纯文本,而基础文档中的任何格式或其他XML都不会妨碍.

获得标签后,替换文本是下一个问题.由于上述原因,您不能仅仅InnerText用一些新文本替换它,因为不清楚文本的哪些部分属于哪个部分Run.最简单的方法是删除任何现有Run的并添加一个包含新文本RunText属性.

以下代码显示了找到标记并立即替换它们,而不是像您在问题中建议的那样使用两遍.这只是为了让事实更简单.它应该显示你需要的一切.

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 Code Online (Sandbox Code Playgroud)

上述方法的唯一缺点是你可能拥有的任何款式都将丢失.这些可以从现有Run的复制,但如果有多个Run具有不同的属性,你需要找出你需要复制的地方.Run如果需要的话,没有什么可以阻止你在上面的代码中创建多个具有不同属性的代码.