如何根据openxmlelement获取页码
how to get page numbers based on openxmlelement
对于段落 object,我如何使用 Open XML SDK 2.5 确定它位于哪个页面?
我已经获得了文档中的所有 child 元素,并且还使用它获取了内部文本。
foreach (var i in mainPart.Document.ChildElements.FirstOrDefault().ChildElements)
{
ParagraphElements.Add(i); //openxmlelement list
}
我想获取相应段落的页码。例如,我将 "this is heading 1" 标记为样式标题 1,这将在目录中更新。所以我需要传递页码
提前致谢
OpenXML 格式的页面在由文字处理器呈现之前是不存在的。
计算给定段落应该出现在哪个页面所需的元数据可用,但这远非一个简单的操作。
验证原始 OpenXML 标记中不存在页码:
- 将以“.docx”结尾的 Word 文档副本重命名为以“.zip”结尾。
- 在此 zip 存档中,打开名为 "word" 的子目录。
- 在 "word" 内打开 "document.xml"。
此文件包含您的 mainPart.Document
调用的 XML 内容。 "document.xml" 文件有一个节点 <document>...</document>
,它又有一个子节点 <body>...</body>
,它又包含您感兴趣的内容。
在使用 OpenXML 文档时,我发现 OpenXML SDK 中的抽象有时会让人分心。值得庆幸的是,使用 LINQ-to-XML 探索原始标记很简单。例如,您拨打:
var childrenFromOpenXmlSdk = mainPart.Document.ChildElements.Single().ChildElements;
等价于 LINQ-to-XML 中的以下内容:
IEnumerable<XElement> childrenFromLinqToXml =
XElement.Load("[path]/[file]/word/document.xml")
.Elements()
.Single()
.Elements();`
检查 childrenFromLinqToXml
中的元素,您会发现没有页码信息。
您可能会在 TOC 本身的原始标记中看到 缓存 页码,但这些将是先前呈现的工件,由内容标签或表单字段定义。
如果您需要以编程方式构建 TOC,请查看以下站点:
OfficeOpenXML.com's reference article for TOCs
- 这是对 OpenXML 的 ECMA-376 规范的有用参考。
Eric White's screencast "Exploring Tables-of-Contents in Open XML WordprocessingML Documents"
- Eric White 是所有开放领域的权威XML。当您发现自己处于 XML 标记和屏幕渲染的交叉点时,他的
ericwhite.com/blog
非常值得一看。
--- 跟进 Sai 的评论 ---
Hi Austin Drenski, I've created TOC and added all headings programmatically. all I need is page numbers. is there any alternative to get page number of particular paragraph ? I've gone through all the screen casts. But I'm looking for page number alone.
<w:r> <w:fldChar w:fldCharType="begin" /> </w:r> <w:r> <w:instrText xml:space="preserve"> PAGEREF _Toc481680509 \h </w:instrText> </w:r> <w:r> <w:fldChar w:fldCharType="separate" /> </w:r> <w:r> <w:t>2</w:t> </w:r> <w:r> <w:fldChar w:fldCharType="end" /> </w:r>
In that sample XML 2 "2" act as page number. That is hardcoded
now my TOC works perfectly without Pagenumber. where I also analysed default MS word functionality. First time, page numbers are literally given like above.
您可以通过编程方式将内容控件 <w:sdt>
作为 <w:body>
元素的子元素放置在文档中。
对于包含两个条目的简单目录:
<w:sdt>
<w:sdtPr>
<w:id w:val="429708664"/>
<w:docPartObj>
<w:docPartGallery w:val="Table of Contents"/>
<w:docPartUnique/>
</w:docPartObj>
</w:sdtPr>
<w:sdtContent>
<w:p>
<w:pPr>
<w:pStyle w:val="TOCHeading"/>
</w:pPr>
<w:r>
<w:t>Contents</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="TOC1"/>
<w:tabs>
<w:tab w:val="right" w:leader="dot" w:pos="9350"/>
</w:tabs>
</w:pPr>
<w:r>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:instrText xml:space="preserve"> TOC \o "1-3" \h \z \u </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:hyperlink w:anchor="_Toc481654079" w:history="1">
<w:r>
<w:rPr>
<w:rStyle w:val="Hyperlink"/>
</w:rPr>
<w:t>Testing 1</w:t>
</w:r>
<w:r>
<w:tab/>
</w:r>
<w:r>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:instrText xml:space="preserve"> PAGEREF _Toc481654079 \h </w:instrText>
</w:r>
<w:r>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:t>0</w:t>
</w:r>
<w:r>
<w:fldChar w:fldCharType="end"/>
</w:r>
</w:hyperlink>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="TOC1"/>
<w:tabs>
<w:tab w:val="right" w:leader="dot" w:pos="9350"/>
</w:tabs>
</w:pPr>
<w:hyperlink w:anchor="_Toc481654080" w:history="1">
<w:r>
<w:rPr>
<w:rStyle w:val="Hyperlink"/>
</w:rPr>
<w:t>Testing 2</w:t>
</w:r>
<w:r>
<w:tab/>
</w:r>
<w:r>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:instrText xml:space="preserve"> PAGEREF _Toc481654080 \h </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:t>0</w:t>
</w:r>
<w:r>
<w:fldChar w:fldCharType="end"/>
</w:r>
</w:hyperlink>
</w:p>
<w:p>
<w:r>
<w:fldChar w:fldCharType="end"/>
</w:r>
</w:p>
</w:sdtContent>
</w:sdt>
请注意使用 PAGEREF
字段代码指向书签。还要注意后续标记 <w:t>0</w:t>
。打开文档并更新域代码时,此零将替换为书签当前呈现的页码。
每次文档分页时,书签的确切位置 可能 会发生变化。
一旦将零替换为实例编号,您将在标记中观察到这些实例编号。但是,这些数字只是这些字段代码的最后呈现值。
在文档设置中,您可以在打开时提示用户更新域代码,这样 TOC 编号将准确反映 当前 屏幕呈现。为此,您的设置文件应类似于:
<w:settings ...namespaces ommitted...>
<w:updateFields w:val="true"/>
...other settings ommitted...
</w:settings>
最后,您仍然需要使用文字处理器来呈现 OpenXML 文档,但您避免了计算页面位置的复杂性。
经过大量的基础工作,我发现,无法使用 openxml 元素检索页码。
我们可以近似。但我们不能确定。因为页码是由文字处理器布局引擎呈现的。这发生在所有 OpenXML 元素都传递给文字处理器之后。
我们可以用 LastRenderedPageBreak 来计算。但是我们不能确定元素的位置是否正确。
因此,我建议使用 UpdateFieldsOnOpen 或 Macro 以获得更简单的解决方案。
获取当前页码
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Begin,
});
runs.Append(new FieldCode(@" PAGE \* MERGEFORMAT ")
{
Space = SpaceProcessingModeValues.Preserve
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Separate
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.End
});
获取总页数
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Begin,
});
runs.Append(new FieldCode(@" NUMPAGES \* MERGEFORMAT ")
{
Space = SpaceProcessingModeValues.Preserve
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Separate
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.End
});
完整代码
Run runs = new Run();
runs.Append(new Text("第"));
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Begin,
});
runs.Append(new FieldCode(@" PAGE \* MERGEFORMAT ")
{
Space = SpaceProcessingModeValues.Preserve
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Separate
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.End
});
runs.Append(new Text("页/共"));
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Begin,
});
runs.Append(new FieldCode(@" NUMPAGES \* MERGEFORMAT ")
{
Space = SpaceProcessingModeValues.Preserve
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Separate
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.End
});
runs.Append(new Text("页"));
paragraph.Append(runs);
footer1.Append(paragraph);
part.Footer = footer1;
字秀
enter image description here
对于段落 object,我如何使用 Open XML SDK 2.5 确定它位于哪个页面?
我已经获得了文档中的所有 child 元素,并且还使用它获取了内部文本。
foreach (var i in mainPart.Document.ChildElements.FirstOrDefault().ChildElements)
{
ParagraphElements.Add(i); //openxmlelement list
}
我想获取相应段落的页码。例如,我将 "this is heading 1" 标记为样式标题 1,这将在目录中更新。所以我需要传递页码
提前致谢
OpenXML 格式的页面在由文字处理器呈现之前是不存在的。
计算给定段落应该出现在哪个页面所需的元数据可用,但这远非一个简单的操作。
验证原始 OpenXML 标记中不存在页码:
- 将以“.docx”结尾的 Word 文档副本重命名为以“.zip”结尾。
- 在此 zip 存档中,打开名为 "word" 的子目录。
- 在 "word" 内打开 "document.xml"。
此文件包含您的 mainPart.Document
调用的 XML 内容。 "document.xml" 文件有一个节点 <document>...</document>
,它又有一个子节点 <body>...</body>
,它又包含您感兴趣的内容。
在使用 OpenXML 文档时,我发现 OpenXML SDK 中的抽象有时会让人分心。值得庆幸的是,使用 LINQ-to-XML 探索原始标记很简单。例如,您拨打:
var childrenFromOpenXmlSdk = mainPart.Document.ChildElements.Single().ChildElements;
等价于 LINQ-to-XML 中的以下内容:
IEnumerable<XElement> childrenFromLinqToXml =
XElement.Load("[path]/[file]/word/document.xml")
.Elements()
.Single()
.Elements();`
检查 childrenFromLinqToXml
中的元素,您会发现没有页码信息。
您可能会在 TOC 本身的原始标记中看到 缓存 页码,但这些将是先前呈现的工件,由内容标签或表单字段定义。
如果您需要以编程方式构建 TOC,请查看以下站点:
OfficeOpenXML.com's reference article for TOCs
- 这是对 OpenXML 的 ECMA-376 规范的有用参考。
Eric White's screencast "Exploring Tables-of-Contents in Open XML WordprocessingML Documents"
- Eric White 是所有开放领域的权威XML。当您发现自己处于 XML 标记和屏幕渲染的交叉点时,他的
ericwhite.com/blog
非常值得一看。
- Eric White 是所有开放领域的权威XML。当您发现自己处于 XML 标记和屏幕渲染的交叉点时,他的
--- 跟进 Sai 的评论 ---
Hi Austin Drenski, I've created TOC and added all headings programmatically. all I need is page numbers. is there any alternative to get page number of particular paragraph ? I've gone through all the screen casts. But I'm looking for page number alone.
<w:r> <w:fldChar w:fldCharType="begin" /> </w:r> <w:r> <w:instrText xml:space="preserve"> PAGEREF _Toc481680509 \h </w:instrText> </w:r> <w:r> <w:fldChar w:fldCharType="separate" /> </w:r> <w:r> <w:t>2</w:t> </w:r> <w:r> <w:fldChar w:fldCharType="end" /> </w:r>
In that sample XML 2 "2" act as page number. That is hardcoded
now my TOC works perfectly without Pagenumber. where I also analysed default MS word functionality. First time, page numbers are literally given like above.
您可以通过编程方式将内容控件 <w:sdt>
作为 <w:body>
元素的子元素放置在文档中。
对于包含两个条目的简单目录:
<w:sdt>
<w:sdtPr>
<w:id w:val="429708664"/>
<w:docPartObj>
<w:docPartGallery w:val="Table of Contents"/>
<w:docPartUnique/>
</w:docPartObj>
</w:sdtPr>
<w:sdtContent>
<w:p>
<w:pPr>
<w:pStyle w:val="TOCHeading"/>
</w:pPr>
<w:r>
<w:t>Contents</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="TOC1"/>
<w:tabs>
<w:tab w:val="right" w:leader="dot" w:pos="9350"/>
</w:tabs>
</w:pPr>
<w:r>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:instrText xml:space="preserve"> TOC \o "1-3" \h \z \u </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:hyperlink w:anchor="_Toc481654079" w:history="1">
<w:r>
<w:rPr>
<w:rStyle w:val="Hyperlink"/>
</w:rPr>
<w:t>Testing 1</w:t>
</w:r>
<w:r>
<w:tab/>
</w:r>
<w:r>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:instrText xml:space="preserve"> PAGEREF _Toc481654079 \h </w:instrText>
</w:r>
<w:r>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:t>0</w:t>
</w:r>
<w:r>
<w:fldChar w:fldCharType="end"/>
</w:r>
</w:hyperlink>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="TOC1"/>
<w:tabs>
<w:tab w:val="right" w:leader="dot" w:pos="9350"/>
</w:tabs>
</w:pPr>
<w:hyperlink w:anchor="_Toc481654080" w:history="1">
<w:r>
<w:rPr>
<w:rStyle w:val="Hyperlink"/>
</w:rPr>
<w:t>Testing 2</w:t>
</w:r>
<w:r>
<w:tab/>
</w:r>
<w:r>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:instrText xml:space="preserve"> PAGEREF _Toc481654080 \h </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:t>0</w:t>
</w:r>
<w:r>
<w:fldChar w:fldCharType="end"/>
</w:r>
</w:hyperlink>
</w:p>
<w:p>
<w:r>
<w:fldChar w:fldCharType="end"/>
</w:r>
</w:p>
</w:sdtContent>
</w:sdt>
请注意使用 PAGEREF
字段代码指向书签。还要注意后续标记 <w:t>0</w:t>
。打开文档并更新域代码时,此零将替换为书签当前呈现的页码。
每次文档分页时,书签的确切位置 可能 会发生变化。
一旦将零替换为实例编号,您将在标记中观察到这些实例编号。但是,这些数字只是这些字段代码的最后呈现值。
在文档设置中,您可以在打开时提示用户更新域代码,这样 TOC 编号将准确反映 当前 屏幕呈现。为此,您的设置文件应类似于:
<w:settings ...namespaces ommitted...>
<w:updateFields w:val="true"/>
...other settings ommitted...
</w:settings>
最后,您仍然需要使用文字处理器来呈现 OpenXML 文档,但您避免了计算页面位置的复杂性。
经过大量的基础工作,我发现,无法使用 openxml 元素检索页码。 我们可以近似。但我们不能确定。因为页码是由文字处理器布局引擎呈现的。这发生在所有 OpenXML 元素都传递给文字处理器之后。 我们可以用 LastRenderedPageBreak 来计算。但是我们不能确定元素的位置是否正确。
因此,我建议使用 UpdateFieldsOnOpen 或 Macro 以获得更简单的解决方案。
获取当前页码
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Begin,
});
runs.Append(new FieldCode(@" PAGE \* MERGEFORMAT ")
{
Space = SpaceProcessingModeValues.Preserve
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Separate
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.End
});
获取总页数
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Begin,
});
runs.Append(new FieldCode(@" NUMPAGES \* MERGEFORMAT ")
{
Space = SpaceProcessingModeValues.Preserve
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Separate
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.End
});
完整代码
Run runs = new Run();
runs.Append(new Text("第"));
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Begin,
});
runs.Append(new FieldCode(@" PAGE \* MERGEFORMAT ")
{
Space = SpaceProcessingModeValues.Preserve
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Separate
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.End
});
runs.Append(new Text("页/共"));
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Begin,
});
runs.Append(new FieldCode(@" NUMPAGES \* MERGEFORMAT ")
{
Space = SpaceProcessingModeValues.Preserve
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.Separate
});
runs.Append(new FieldChar
{
FieldCharType = FieldCharValues.End
});
runs.Append(new Text("页"));
paragraph.Append(runs);
footer1.Append(paragraph);
part.Footer = footer1;
字秀 enter image description here