我正在编写一个.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><!TAG1</w:t> </w:r> <w:proofErr w:type="gramEnd"/> <w:r> <w:rPr> <w:lang w:val="en-GB"/> </w:rPr> <w:t>!></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>
最好的问候,迈克
尝试查找标签的问题在于单词并非总是以其在Word中的格式出现在基础XML中。例如,在您的示例XML中,<!TAG1!>标记被分成多个运行,如下所示:
<!TAG1!>
<w:r> <w:rPr> <w:lang w:val="en-GB"/> </w:rPr> <w:t><!TAG1</w:t> </w:r> <w:proofErr w:type="gramEnd"/> <w:r> <w:rPr> <w:lang w:val="en-GB"/> </w:rPr> <w:t>!></w:t> </w:r>
正如评论中指出的那样,这有时是由拼写和语法检查程序引起的,但这并不是所有可能导致这种情况的原因。例如,在标签的各个部分上使用不同的样式也可能会导致它。
处理的方法之一是找到InnerText的Paragraph和比较,对你的Regex。该InnerText属性将返回段落的纯文本,而不会妨碍基础文档中的任何格式或其他XML。
InnerText
Paragraph
Regex
有了标签后,替换文本是下一个问题。由于上述原因,您不能只InnerText用一些新文本替换,因为不清楚文本的哪些部分属于哪个部分Run。解决此问题的最简单方法是删除所有现有Run的,并Run使用Text包含新文本的属性添加新的。
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如果需要的话,没有什么可以阻止您在上面的代码中创建多个具有不同属性的。其他元素也将丢失(例如,任何符号),因此也需要考虑这些元素。