控制 Open XML WordprocessingML 文档中的文本
??
|
元素 |
元素名称 |
Open XML SDK 2.0 类名称 命名空间:DocumentFormat.OpenXml.Wordprocessing |
---|---|---|
正文 |
w:body |
Body |
表格单元格 |
w:tc |
TableCell |
文本框内容 |
w:txbxContent |
TextBoxContent |
我说过,WordprocessingML 中还存在包含段落的其他块级内容容器,例如注释部分的 w:comment 元素和标题部分的 w:hdr 元素。但是,它们不位于文档的正文部分。因此,它们相对而言比较好处理。
块级内容
块级内容元素是 WordprocessingML 元素,它会占据布局界面的整个宽度。它们在顶部和底部处绑定,然后占据可用空间从左到右的整个宽度。结果,在文档的正常布局版面上,您不会在同一物理行中看到两个段落,也不会看到段落与表格并排显示。
此规则存在例外情况,但实际上这些明显的例外情况并不是真正的例外情况。如果使用多列页面布局,则您可以看到段落并排显示。在此情况下,段落或表格的布局的可用宽度是列,而不是整个页面。另一种情况是页面包含文本框,但在这种情况下,块级内容的布局的可用宽度不包括为文本框保留的空间。另外,文本框自身也有布局界面。
接受修订之后,仅存在两种块级内容元素。
元素 |
元素名称 |
Open XML SDK 2.0 类名称 命名空间:DocumentFormat.OpenXml.Wordprocessing |
---|---|---|
段落 |
w:p |
Paragraph |
表格 |
w:tbl |
Table |
本文中我未提及的另外两个块级内容元素用于数学公式。处理 MathML 文本内容并不是常见的需求。收集公式文本并将其汇总为一个字符串(与对待段落的方式相同)的需求量不大。相反,公式中的文本必须来自公式的上下文。在本文中,我不阐述如何处理 MathML 公式。
运行级内容容器
接受修订后,只有一个元素是运行级内容容器,即段落 (w:p) 元素。运行级内容容器定义运行级内容从左到右(在适当情况下,从右到左)的布局空间。例如,适当时可通过文字环绕对段落中字体不同的多个文本进行水平布局。请注意,段落既是块级内容元素,又是运行级内容容器元素,而表格只是块级内容元素,不是运行级内容容器元素。
元素 |
元素名称 |
Open XML SDK 2.0 类名称 命名空间:DocumentFormat.OpenXml.Wordprocessing |
---|---|---|
段落 |
w:p |
Paragraph |
运行级内容
运行级内容是段落(具有特定于段落子节的格式)中的内容。例如,运行具有特定的字体。接受修订之后,仅存在三种运行级内容元素。
元素 |
元素名称 |
Open XML SDK 2.0 类名称 命名空间:DocumentFormat.OpenXml.Wordprocessing |
---|---|---|
文本运行 |
w:r |
Run |
VML 绘图 |
w:pict |
Picture |
DrawingML 对象 |
w:drawing |
Drawing |
此元素列表的一个非直观的方面就是矢量标记语言 (VML) 图形对象或 DrawingML 对象不是运行级内容就是子运行级内容。它们都还可以作为后代 w:txbxContent 元素(也是块级内容容器)包含。
子运行级内容
子运行级内容包含作为运行一部分的那些 WordprocessingML 元素。例如,运行可以包含多个文本元素 (w:t)。
元素 |
元素名称 |
Open XML SDK 2.0 类名称 命名空间:DocumentFormat.OpenXml.Wordprocessing |
---|---|---|
中断 |
w:br |
Break |
回车符 |
w:cr |
CarriageReturnPicture |
日期块 – 长日期格式 |
w:daylong |
DayLong |
日期块 – 长日期格式 |
w:daylong |
DayLong |
日期块 – 短日期格式 |
w:dayShort |
DayShort |
DrawingML 对象 |
w:drawing |
Drawing |
日期块 – 长月份格式 |
w:monthLong |
MonthLong |
日期块 – 短月份格式 |
w:monthShort |
MonthShort |
不中断连字符 |
w:noBreakHyphen |
NoBreakHyphen |
页码块 |
w:pgNum |
PageNumber |
VML 绘图 |
w:pict |
Drawing |
绝对位置制表符 |
w:pTab |
PositionalTab |
可选连字符 |
w:softHyphen |
SoftHyphen |
符号字符 |
w:sym |
SymbolChar |
文本 |
w:t |
Text |
制表符 |
w:tab |
TabChar |
日期块 – 长年份格式 |
w:yearlong |
YearLong |
日期块 – 短年份格式 |
w:yearShort |
YearShort |
此列表还包含 VML 绘图和 DrawingML 对象,这些对象可以包含 w:txbxContent 元素(块级内容容器)作为后代。
层次结构级别的变化如何增加处理过程的复杂性
一个简单的示例便可演示我们正尝试解决的问题。以下文档的第一个段落中包含一个内容控件和一个文本框:
下面的代码示例显示此段落的标记。有关此标记的详细信息,请参阅 ISO/IEC 29500-1:2008(该链接可能指向英文页面) 或标准 ECMA-376:Office Open XML 文件格式第二版本(ECMA-376 第二版)(该链接可能指向英文页面)。
注释 |
---|
已省略无关标记以便更好地展示问题。 |
<w:p> <w:pPr> <w:ind w:right="3600"/> </w:pPr> <w:r> <w:rPr> <w:noProof/> </w:rPr> <mc:AlternateContent> <mc:Choice Requires="wps"> <w:drawing> <!-- . . . --> <wps:txbx> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </wps:txbx> <!-- . . . --> </w:drawing> </mc:Choice> <mc:Fallback> <w:pict> <!-- . . . --> <v:textbox> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </v:textbox> <w10:wrap type="square"/> <!-- . . . --> </w:pict> </mc:Fallback> </mc:AlternateContent> </w:r> <w:sdt> <w:sdtContent> <w:r> <w:t>Text in content control.</w:t> </w:r> </w:sdtContent> </w:sdt> <w:r> <w:t xml:space="preserve"> Text following the content control.</w:t> </w:r> </w:p>
在该示例中,文本框中的文本与内容控件内的文本位于相同的段落。同时也与内容控件外的文本位于同一段落中。内容控件导致文本元素出现在不同级别的层次结构中。您必须编写处理层次结构中这种差别的代码。这是展示此问题的一个示例。有多个 WordprocessingML 抽象化内容可以导致文本内容出现不同级别的缩进。因此,我们需要开发解决此问题的通用解决方案。
注释 |
---|
使用 Value 属性检索段落 (w:p) 元素的文本是不正确的。 |
using (WordprocessingDocument doc = WordprocessingDocument.Open("Test.docx",false)) { XElement root = doc.MainDocumentPart.GetXDocument().Root; XElement paragraph = root.Descendants(W.p).First(); Console.WriteLine(paragraph.Value); }
返回的文本不正确。
问题不在于我们看到了两次文本框中的内容。问题在于我们看到了文本框。文本框中的文本实际上不属于段落的一部分。它是独立存在的。
您不能循环访问段落的子运行,因为内容控件导致文本运行出现在不同级别的标记层次结构中。
using (WordprocessingDocument doc = WordprocessingDocument.Open("Test.docx",false)) { XElement root = doc.MainDocumentPart.GetXDocument().Root; XElement paragraph = root.Descendants(W.p).First(); StringBuilder sb = new StringBuilder(); foreach (XElement t in paragraph.Elements(W.r).Elements(W.t)) sb.Append((string)t); Console.WriteLine(sb.ToString()); }
这不包括内容控件中的文本。
您可以编写代码将此问题处理为特殊情况。然而,这不会为任何其他导致文本内容出现在不同级别层次结构的构造返回正确的结果。相反,我们需要通用的抽象化内容以便处理文档内容。
标准 ECMA-376:Office Open XML 文件格式第一版本 (ECMA-376)(该链接可能指向英文页面) 具有与 XML 层次结构中内容所处位置关联的相同问题。作为后代包含文本框的元素与段落中其他子运行级内容同级。本文中介绍的抽象化内容也同样适用于 ECMA-376 标记。
<w:p> <w:pPr> <w:ind w:right="3600"/> </w:pPr> <w:r> <w:pict> <v:shape . . .> <v:textbox> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </v:textbox> <w10:wrap type="square"/> </v:shape> </w:pict> </w:r> <w:sdt> <w:sdtContent> <w:r w:rsidR="00C578DC"> <w:t>Text in content control.</w:t> </w:r> </w:sdtContent> </w:sdt> <w:r> <w:t xml:space="preserve"> Text following the content control.</w:t> </w:r> </w:p>
引入 LogicalChildrenContent 轴方法
为了解决此问题,我编写了返回元素的逻辑子内容的轴方法。逻辑子项包括包含在其他元素(可提升内容层次结构的级别)中的内容,例如控件。因此,该逻辑子内容轴与 LINQ to XML(或 XPath)子轴不同。提升层次结构级别的实际元素(w:sdt、w:fldsimple、w:hyperlink)不包括在返回的集合中。我们需要实际内容,而不是那些包含内容的其他元素。
提示 |
---|
我借用了 LINQ to XML 中的术语轴方法。在 XML 文档的上下文中,轴是适用于任何给定元素的概念,有一组特定的相关元素,而轴方法返回这些相关元素的集合。例如,对于给定的 XML 元素,有一组特定的子元素,一组特定的后代以及一组特定的上级。后代、子元素和上级是某些 LINQ to XML 轴方法的基础。 |
下面列示的内容在您检索正文元素的逻辑子内容时,突出显示返回集合中的元素。文本框内的段落元素不包括在逻辑子项中。这是因为段落是包含它的文本框内容元素 (w:txbxContent) 的逻辑子项。文本框内容元素是 VML 像素 (w:pict) 的逻辑子项,而该元素是包含它的运行的逻辑后代。
<w:body> <w:sdt> <w:sdtPr> <w:id w:val="172579038"/> <w:placeholder> <w:docPart w:val="DefaultPlaceholder_22675703"/> </w:placeholder> </w:sdtPr> <w:sdtEndPr/> <w:sdtContent> <w:p> <w:r> <w:t>Paragraph in content control.</w:t> </w:r> </w:p> </w:sdtContent> </w:sdt> <w:p> <w:pPr> <w:ind w:right="3600"/> </w:pPr> <w:r> <w:rPr> <w:noProof/> </w:rPr> <mc:AlternateContent> <mc:Choice Requires="wps"> <w:drawing> . . . <wps:txbx> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </wps:txbx> . . . </w:drawing> </mc:Choice> <mc:Fallback> <w:pict> . . . <v:textbox> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </v:textbox> <w10:wrap type="square"/> . . . </w:pict> </mc:Fallback> </mc:AlternateContent> </w:r> <w:sdt> <w:sdtContent> <w:r> <w:t>Text in content control.</w:t> </w:r> </w:sdtContent> </w:sdt> <w:r> <w:t xml:space="preserve"> Text following the content control.</w:t> </w:r> </w:p> <w:p> <w:r> <w:t>Text in a following paragraph.</w:t> </w:r> </w:p> </w:body>
下面列示的内容突出显示第二个段落的逻辑子内容。逻辑子项中不包含第一个运行的任何后代。
. . . <w:p> <w:pPr> <w:ind w:right="3600"/> </w:pPr> <w:r> <w:rPr> <w:noProof/> </w:rPr> <mc:AlternateContent> <mc:Choice Requires="wps"> <w:drawing> . . . <wps:txbx> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </wps:txbx> . . . </w:drawing> </mc:Choice> <mc:Fallback> <w:pict> . . . <v:textbox> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </v:textbox> <w10:wrap type="square"/> . . . </w:pict> </mc:Fallback> </mc:AlternateContent> </w:r> <w:sdt> <w:sdtContent> <w:r> <w:t>Text in content control.</w:t> </w:r> </w:sdtContent> </w:sdt> <w:r> <w:t xml:space="preserve"> Text following the content control.</w:t> </w:r> </w:p>
该段落中第一个运行的逻辑子元素是 mc:AlternateContent 元素。
<w:r> <w:rPr> <w:noProof/> </w:rPr> <mc:AlternateContent> <mc:Choice Requires="wps"> <w:drawing> . . . <wps:txbx> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </wps:txbx> . . . </w:drawing> </mc:Choice> <mc:Fallback> <w:pict> . . . <v:textbox> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </v:textbox> <w10:wrap type="square"/> . . . </w:pict> </mc:Fallback> </mc:AlternateContent> </w:r>
将 mc:AlternateContent 作为逻辑子内容元素之一非常有用,因为它可能包含有关处理内容的替代方法的信息。mc:AlternateContent 元素的逻辑子项为其包含的绘图:
<w:r> <w:rPr> <w:noProof/> </w:rPr> <mc:AlternateContent> <mc:Choice Requires="wps"> <w:drawing> . . . <wps:txbx> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </wps:txbx> . . . </w:drawing> </mc:Choice> <mc:Fallback> <w:pict> . . . <v:textbox> <w:txbxContent> <w:p> <w:r> <w:t>Text in text box</w:t> </w:r> </w:p> </w:txbxContent> </v:textbox> <w10:wrap type="square"/> . . . </w:pict> </mc:Fallback> </mc:AlternateContent> </w:r>
DrawingML 对象的逻辑子项是文本框内容 (w:txbxContents)。其子项是所含的段落。通过以这种方式定义逻辑子轴,可方便地针对任何段落精确组合文本。
实现 DescendantsTrimmed 轴方法
实现逻辑子轴方法的第一步是实现返回后代元素(其中,后代为已修整)集合的方法。任何为指定标记后代的元素都不包括在返回的集合中。另一个 DescendantsTrimmed 方法的重载将委托用作参数,它允许您指定将 lambda 表达式作为谓词,以便您可以根据多个标记进行修整。我定义此方法的语义时,将已修整元素本身包括在返回的集合中。
下面的代码示例演示 DescendantsTrimmed 轴方法的语义。在此轴方法中,修整作为 txbxContent 元素后代的元素。显示每个元素的元素名称的代码示例对上级进行计数,以便正确地缩进元素名称。
XElement doc = XElement.Parse( @"<body> <p> <r> <t>Text before the text box.</t> </r> <r> <pict> <txbxContent> <p> <r> <t>Text in a text box.</t> </r> </p> </txbxContent> </pict> </r> <r> <t>Text after the text box.</t> </r> </p> </body>"); foreach (XElement c in doc.DescendantsTrimmed("txbxContent")) Console.WriteLine("{0}{1}","".PadRight(c.Ancestors().Count() * 2),c.Name);
此示例显示返回的集合中每个元素名称的缩进列表。
p r t r pict txbxContent r t
定义逻辑子项
使用 DescendantsTrimmed 轴方法,您可以实现仅返回一组特定元素的逻辑子项的轴方法。以下是我定义逻辑子项的方法:
-
w:document 元素的唯一逻辑子项是 w:body 元素。
-
块级内容容器(w:body、w:tc、w:txbxContent)的逻辑子项是块级内容(w:p、w:tbl)。
-
表格 (w:tbl) 的逻辑子项是它的行 (w:tr)。
-
行 (w:tr) 的逻辑子项是它的单元格 (w:tc)。
-
段落 (w:p) 的逻辑子项是它的运行 (w:r)。
-
运行 (w:r) 的逻辑子项是子运行级内容(w:t、w:pict、w:drawing 等等)。请参阅文本前面的列表。此外,为符合 Office 2010 和 ISO/IEC 29500,mc:AlternateContent 元素也是运行的子项。我实现了随附的代码以使其同时符合 ECMA-376 第一版和 ISO/IEC 29500(ECMA-376 第二版)。
-
替换内容元素的逻辑子项是 mc:Choice 元素中的绘图或图片。您需要处理 mc:Choice 元素的内容,而不是 mc.Fallback 元素。
-
VML 图形对象 (w:pict) 或 DrawingML 对象 (w:drawing) 的逻辑子项是任何包含的文本框内容元素 (w:txbxContent)。如果您必须处理 VML 对象或 DrawingML 对象的其他特定部分,则可以重新定义 LogicalChildrenContent 方法以包括返回集合中必须处理的元素。
使用 LogicalChildrenContent 轴方法
在学习 LogicalChildrenContent 方法的实现之前,了解其使用方法非常有用。
下图显示存在问题的示例文档。
ExamineDocumentContent 示例
此第一个示例以递归方式循环访问文档中的所有逻辑内容,并使用正确的缩进显示每个元素的名称。如果元素为文本元素 (w:t),则该功能会打印元素的文本内容。
请注意,此示例首先通过调用 RevisionAccepter.AcceptRevisions 方法接受修订。此示例通过先将文档读入字节数组,然后从字节数组初始化大小可调的内存流来使用打开字处理文档的方法。这样可以允许示例打开可编辑参数设置为 true 的文档,继而允许示例接受修订。如果示例直接打开文档进行编辑,则示例会通过接受修订而修改现有文档,这也是运行时不希望出现的一个副作用。如果示例是在只读模式下打开文档,则接受修订将失败(引发异常)。
static void IterateContent(XElement element,int depth) { if (element.Name == W.t) Console.WriteLine("{0}{1} >{2}<",21)">"".PadRight(depth * 2),element.Name.LocalName,(string)element); else Console.WriteLine(foreach (XElement item in element.LogicalChildrenContent()) IterateContent(item,depth + 1); } static void Main(string[] args) { byte[] docByteArray = File.ReadAllBytes("Test.docx"); using (MemoryStream memoryStream = new MemoryStream()) { memoryStream.Write(docByteArray,docByteArray.Length); using (WordprocessingDocument doc = WordprocessingDocument.Open(memoryStream,true)) { RevisionAccepter.AcceptRevisions(doc); IterateContent(doc.MainDocumentPart.GetXDocument().Root,0); } } }
当我对问题文档运行此示例时,我会看到以下内容。
document body p r t >Paragraph in < r t >content control.< p r AlternateContent drawing txbxContent p r t >Text in text box< r t >Text in content control. < r t >Text following the content control.< p r t >Text in a following< r t > paragraph.<
我们看到,经过各种编辑会话之后,各种运行均拆分成多个运行。我们可以看到文本框及其内容出现在适当的位置上。
我们可以使用 欢迎使用 Open XML SDK 2.0 for Microsoft Office 的强类型对象模型来实现相同的轴方法。使用逻辑内容轴的代码如下所示。
static void IterateContent(OpenXmlElement element,int depth) { if (element.GetType() == typeof(Text)) Console.WriteLine(else Console.WriteLine(foreach (var item in element.LogicalChildrenContent()) IterateContent(item,21)">"Test7.docx"); using (MemoryStream memoryStream = new MemoryStream()) { memoryStream.Write(docByteArray,true)) { RevisionAccepter.AcceptRevisions(doc); IterateContent(doc.MainDocumentPart.Document,0); } } }
当我对问题文档运行此示例时,我会看到以下内容。
Document Body Paragraph Run Text >Paragraph in < Run Text >content control.< Paragraph Run AlternateContent Drawing TextBoxContent Paragraph Run Text >Text in text box< Run Text >Text in content control. < Run Text >Text following the content control.< Paragraph Run Text >Text in a following< Run Text > paragraph.<
检索段落文本
您经常需要处理文档,在一次操作检索所有段落、每个段落下的所有运行以及每个运行的所有文本元素,然后组合每个段落的关联文本。
为了让此过程尽可能简单,我编写了 LogicalChildrenContent 方法的另一个重载。我将该方法编写为一个扩展方法以便以参数形式获取内容元素的集合,并作为集合返回源集合中每个元素的一组逻辑子元素,这样很有用。此扩展方法相当于 LINQ to XML 中的 Elements 扩展方法(返回源集合中每个元素的所有子元素)。该扩展方法实现起来非常简单。
public static IEnumerable<XElement> LogicalChildrenContent(this IEnumerable<XElement> source) { foreach (XElement e1 in source) foreach (XElement e2 in e1.LogicalChildrenContent()) yield return e2; }
使用 欢迎使用 Open XML SDK 2.0 for Microsoft Office 的强类型对象模型实现的相同轴方法如下所示。
public static IEnumerable<OpenXmlElement> LogicalChildrenContent( this IEnumerable<OpenXmlElement> source) { foreach (OpenXmlElement e1 in source) foreach (OpenXmlElement e2 in e1.LogicalChildrenContent()) yield return e2; }
使用另一个扩展方法(StringConcatenate 方法)也很有用,它是一个字符串聚合操作。
public static string StringConcatenate(this IEnumerable<string> source) { StringBuilder sb = new StringBuilder(); foreach (string s in source) sb.Append(s); return sb.ToString(); }
现在,我们可以编写一个小程序来检索正文元素的所有子段落,并检索每个段落的文本。通过结合使用 RevisionAccepter 方法和 LogicalChildrenContent 轴,我们知道可以正确地检索每个段落的文本。
static void Main(string[] args) { byte[] docByteArray = File.ReadAllBytes(true)) { RevisionAccepter.AcceptRevisions(doc); XElement root = doc.MainDocumentPart.GetXDocument().Root; XElement body = root.LogicalChildrenContent().First(); foreach (XElement blockLevelContentElement in body.LogicalChildrenContent()) { if (blockLevelContentElement.Name == W.p) { var text = blockLevelContentElement .LogicalChildrenContent() .Where(e => e.Name == W.r) .LogicalChildrenContent() .Where(e => e.Name == W.t) .Select(t => (string)t) .StringConcatenate(); Console.WriteLine("Paragraph text >{0}<",text); continue; } // If element is not a paragraph,it must be a table. Console.WriteLine("Table"); } } } }
当我对问题文档运行此程序时,我会看到以下内容。
Paragraph text >Paragraph in content control.< Paragraph text >Text in content control. Text following the content control.< Paragraph text >Text in a following paragraph.<
使用 欢迎使用 Open XML SDK 2.0 for Microsoft Office 的示例如下所示。
static void Main(string[] args) { byte[] docByteArray = File.ReadAllBytes(true)) { RevisionAccepter.AcceptRevisions(doc); OpenXmlElement root = doc.MainDocumentPart.Document; Body body = (Body)root.LogicalChildrenContent().First(); foreach (OpenXmlElement blockLevelContentElement in body.LogicalChildrenContent()) { if (blockLevelContentElement is Paragraph) { var text = blockLevelContentElement .LogicalChildrenContent() .OfType<Run>() .Cast<OpenXmlElement>() .LogicalChildrenContent() .OfType<Text>() .Select(t => t.Text) .StringConcatenate(); Console.WriteLine("Table"); } } } }
此示例不检查后代块级内容容器的运行,因此所设计的示例不显示文本框中的文本。
LogicalChildrenContent 轴方法的两个有用重载
您可以通过定义 LogicalChildrenContent 轴方法的两个其他重载来简化最后一个示例。常见的操作是检索段落的所有运行和检索运行的所有文本元素。因此,如果我们定义两个按指定标记名称筛选的其他扩展方法,则能进一步简化代码。
public static IEnumerable<XElement> LogicalChildrenContent(this XElement element,XName name) { return element.LogicalChildrenContent().Where(e => e.Name == name); } public static IEnumerable<XElement> LogicalChildrenContent( this IEnumerable<XElement> source,XName name) { foreach (XElement e1 in source) foreach (XElement e2 in e1.LogicalChildrenContent(name)) yield return e2; }
使用这些扩展方法时,查询简化情况如下。
var text = blockLevelContentElement .LogicalChildrenContent(W.r) .LogicalChildrenContent(W.t) .Select(t => (string)t) .StringConcatenate();
此查询生成与上一个示例相同的输出。
欢迎使用 Open XML SDK 2.0 for Microsoft Office 中实现的其他扩展方法如下所示。
public static IEnumerable<OpenXmlElement> LogicalChildrenContent( this OpenXmlElement element,System.Type typeName) { return element.LogicalChildrenContent().Where(e => e.GetType() == typeName); } public static IEnumerable<OpenXmlElement> LogicalChildrenContent( this IEnumerable<OpenXmlElement> source,Type typeName) { foreach (OpenXmlElement e1 in source) foreach (OpenXmlElement e2 in e1.LogicalChildrenContent(typeName)) yield return e2; }
简化后的查询如下所示。
var text = blockLevelContentElement .LogicalChildrenContent(typeof(Run)) .LogicalChildrenContent(typeof(Text)) .OfType<Text>() .Select(t => t.Text) .StringConcatenate();
LogicalChildrenContent 方法返回的 XML 元素的标识
有一个关于 LogicalChildrenContent 方法返回的元素的重要说明需要指出来。元素是 WordprocessingML 文档中的实际元素,不是副本也不是克隆。这意味着如果您要针对样式的各种属性进行额外的筛选,则很容易实现。
在文档中搜索文本
我们现在可以编写一个示例,以搜索文档中的某个特定字符串。如果文档包含修订跟踪、内容控件、超链接或任何组合段落文本时存在问题的其他元素,此示例也会正常运行。另外,它能够正确查找跨块级内容容器的文本。
static void IterateContentAndSearch(XElement element,string searchString) { if (element.Name == W.p) { string paragraphText = element .LogicalChildrenContent(W.r) .LogicalChildrenContent(W.t) .Select(s => (string)s) .StringConcatenate(); if (paragraphText.Contains(searchString)) Console.WriteLine("Found {0},paragraph: >{1}<",searchString,paragraphText); } foreach (XElement item in element.LogicalChildrenContent()) IterateContentAndSearch(item,searchString); } static void Main(string[] args) { byte[] docByteArray = File.ReadAllBytes(true)) { RevisionAccepter.AcceptRevisions(doc); IterateContentAndSearch(doc.MainDocumentPart.GetXDocument().Root,21)">"control"); } } }
使用 欢迎使用 Open XML SDK 2.0 for Microsoft Office 的相同示例如下所示。
static void IterateContentAndSearch(OpenXmlElement element,string searchString) { if (element is Paragraph) { string paragraphText = element .LogicalChildrenContent(typeof(Run)) .LogicalChildrenContent(typeof(Text)) .OfType<Text>() .Select(s => s.Text) .StringConcatenate(); if (paragraphText.Contains(searchString)) Console.WriteLine(foreach (OpenXmlElement item in element.LogicalChildrenContent()) IterateContentAndSearch(item,true)) { RevisionAccepter.AcceptRevisions(doc); IterateContentAndSearch(doc.MainDocumentPart.Document,21)">"control"); } } }
结论
开发处理 Open XML WordprocessingML 的程序时,只考虑文档的实际内容通常非常有用。本文定义了我认为包含文档逻辑内容的元素。我还定义了轴方法 LogicalChildrenContent 的四种重载。
若要轻松且可靠地处理 Open XML WordprocessingML 文档,接受跟踪修订很重要。这使得我们编写的代码可以忽略 40 多个用于跟踪修订的元素和属性(包括一些具有复杂语义的元素和属性)。使用这些轴方法并接受跟踪修订将使我们可以编写能够可靠地从 Open XML WordprocessingML 文档提取内容的小程序。
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!