OpenXml编程--修正Word目录页码错误
2011-06-16 20:42
204 查看
场景描述
图1
图1是一个PDF文件生成的简单流程,事先做好的Word模板和数据源进行匹配以生成新的Word文档,然后再将Word文档转换为PDF文档。由Word文档和数据源产生新的Word文档我们采用的是FlexDoc组件(
图2
图3
生成的Word的页码是不会自动更新的,但是会在转PDF的时候更新,这时候我们遇到了一个FlexDoc的Bug,转换后的目录产生了“未定义书签的错误”。如图4。
图4
本文从Word目录的原理出发,探寻页码转换出错的原因,继而提出完整的解决方案。
Word目录绑定原理
word目录有多种类型,类型是拿什么区别的呢?首先我们插入Word2007中的“自动目录2”,如图5。图5插入自动目录2
目录插入成功之后,我们选择目录,右键—>编辑域,切换到域编辑界面,如图6。
图6编辑域
在域编辑页面在域名项选择TOC,然后单击选项,在选项界面中我们可以看到TOC域支持的开关,不同的开关组合就是不同Word目录,如图7所示。刚才我们选择的“自动目录2”的域代码为TOC\o"1-3"\h\z\u。关于各个开关的含义,您自己看说明就可以了,我就不啰嗦了。
图7编辑域选项
下面我们从WordML的角度继续研究目录。打开word文档,找到Body节点,再找到W:sdt节点,如图8。
图8找到w:sdt节点
w:sdt节点代表SdtBlock,SdtBlock又是什么呢?就是包在目录外面的那个框,SdtBlock并不是word目录必须的元素,插入自动目录的时候word默认会将目录放在SdtBlock中,您也可以选择去除,由于SdtBlock可以帮助我们在程序中迅速找到目录项,所以我要去所有的目标中的目录必须带SdtBlock。SdtBlock节点下有一个w:sdtContent(对应的对象为SdtContentBlock)子节点,该子节点下包含了多个w:p(对应的对象为Paragraph)标签,这些w:p标签组成了Word目录。现在我们展开其中一个w:p,看看里面包含了什么秘密。
代码清单1一个目录项
<w:pw:rsidRPr="00F34D5F"w:rsidR="00F34D5F"w:rsidRDefault="00F34D5F"xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
[code]<w:pPr>
<w:pStylew:val="20"/>
<w:rPr>
<w:rFontsw:asciiTheme="minorHAnsi"w:hAnsiTheme="minorHAnsi"w:eastAsiaTheme="minorEastAsia"/>
<w:colorw:val="auto"/>
</w:rPr>
</w:pPr>
<w:hyperlinkw:history="1"w:anchor="_Toc296003347">
<w:rw:rsidRPr="00F34D5F">
<w:rPr>
<w:rStylew:val="ad"/>
<w:rFontsw:hint="eastAsia"/>
<w:colorw:val="auto"/>
</w:rPr>
<w:t>作答有效性分析</w:t>
</w:r>
<w:rw:rsidRPr="00F34D5F">
<w:rPr>
<w:webHidden/>
<w:colorw:val="auto"/>
</w:rPr>
<w:tab/>
</w:r>
<w:rw:rsidRPr="00F34D5F">
<w:rPr>
<w:webHidden/>
<w:colorw:val="auto"/>
</w:rPr>
<w:fldCharw:fldCharType="begin"/>
</w:r>
<w:rw:rsidRPr="00F34D5F">
<w:rPr>
<w:webHidden/>
<w:colorw:val="auto"/>
</w:rPr>
<w:instrTextxml:space="preserve">PAGEREF_Toc296003347\h</w:instrText>
</w:r>
<w:rw:rsidRPr="00F34D5F">
<w:rPr>
<w:webHidden/>
<w:colorw:val="auto"/>
</w:rPr>
</w:r>
<w:rw:rsidRPr="00F34D5F">
<w:rPr>
<w:webHidden/>
<w:colorw:val="auto"/>
</w:rPr>
<w:fldCharw:fldCharType="separate"/>
</w:r>
<w:rw:rsidRPr="00F34D5F">
<w:rPr>
<w:webHidden/>
<w:colorw:val="auto"/>
</w:rPr>
<w:t>1</w:t>
</w:r>
<w:rw:rsidRPr="00F34D5F">
<w:rPr>
<w:webHidden/>
<w:colorw:val="auto"/>
</w:rPr>
<w:fldCharw:fldCharType="end"/>
</w:r>
</w:hyperlink>
</w:p>
[/code]
代码清单1是w:sdtContent中的一个w:p项内容。现在我们来看里面几个关键项。第9行代码“<w:hyperlinkw:history="1"w:anchor="_Toc296003347">”是w:hyperlink(对应的对象为Hyperlink)标记的起始配置,w:hyperlink代表超链接,点击目录会自动跳转到文档中的正确位置,如果您的TOC域支持的开关没有“\h”选项的话是不会产生w:hyperlink标签的,那么您看到的目录项的代码是另一种样子,这里我就不演示了。这里我们重点关注w:anchor属性,该属性指定了超链接的位置。那么w:anchor的值"_Toc296003347"又是什么呢?先不做解释,我们再看另一个标记,第37行的“<w:instrTextxml:space="preserve">PAGEREF_Toc296003347\h</w:instrText>”,w:instrText(对应的对象为FieldCode)标签的值“PAGEREF_Toc296003347\h”是用来标识超链接的页码的,但是它本身并没有页码值,而是引用了一个位置,最后更新页码的时候会将那个位置所在页的页码赋值给第57行的<w:t>。第50行的<w:fldCharw:fldCharType="separate"/>标签是目录项的标题和页码之间的分隔符样式。第16行的“<w:t>作答有效性分析</w:t>”就是当前目录项的标题,实现显示的是word文档正文中的1级、二级或3级标题。
现在我们基本了解了目录的组成,还有一个关键的定位属性没有解释,我们继续查看word文档,看下面这一段代码:
代码2一个二级标题
<w:pw:rsidRPr="00F34D5F"w:rsidR="000535A9"w:rsidP="00F34D5F"w:rsidRDefault="00E24DF2" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:pPr>
<w:pStylew:val="2"/>
<w:indw:firstLine="372"w:firstLineChars="133"/>
<w:rPr>
<w:rFontsw:ascii="微软雅黑"w:hAnsi="微软雅黑"w:eastAsia="微软雅黑"w:cstheme="minorBidi"/>
<w:bCsw:val="0"/>
<w:colorw:val="93550D"/>
<w:szw:val="28"/>
<w:szCsw:val="24"/>
</w:rPr>
</w:pPr>
<w:bookmarkStartw:name="_Toc295939763"w:id="3"/>
<w:bookmarkStartw:name="_Toc296003347"w:id="4"/>
<w:rw:rsidRPr="00F34D5F">
<w:rPr>
<w:rFontsw:hint="eastAsia"w:ascii="微软雅黑"w:hAnsi="微软雅黑"w:eastAsia="微软雅黑"w:cstheme="minorBidi"/>
<w:bCsw:val="0"/>
<w:colorw:val="93550D"/>
<w:szw:val="28"/>
<w:szCsw:val="24"/>
</w:rPr>
<w:t>作答有效性分析</w:t>
</w:r>
<w:bookmarkEndw:id="3"/>
<w:bookmarkEndw:id="4"/>
</w:p>
看代码2所示的内容,实际上是一个二级标题,该二级标题包含在一个单独的<w:p>标记内,从哪里能看出该内容的大纲级别是二级呢?看第3行代码---<w:pStylew:val="2"/>。 然后我们看第13、1
4、25和26四行代码,是两对w:bookmarkStart和bookmarkEnd标签,第14行的w:name="_Toc296003347"是不是很眼熟呢?没错,就是目录项中的定位标记。
到现在为止,我们已经明白了目录的原理,那么为什么会出错呢?我们看一个出错的Word文档,如图9。
图9页码更新出错的Word文档
看图9中,比较突出是几个w:bookmarkStart标签,它们本应该是如代码2里那样,和bookmarkEnd标签一起成对的出现在P标签内然后上学包裹标题,但是现在它却单独跑到了P标签外 ,如果bookmarkEnd标签单独的跑出来也会造成页码更新失败。代码3是标题的内容,我们可以看到只剩下两个孤零零的bookmarkEnd标签。这就是出错的原因。
<w:pw:rsidRPr="00115C2B"w:rsidR="009E7404"w:rsidP="009A7ED0"w:rsidRDefault="00BD76F7" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> <w:pPr> <w:pStylew:val="1"/> <w:jcw:val="center"/> <w:rPr> <w:rFontsw:ascii="微软雅黑"w:hAnsi="微软雅黑"w:cstheme="majorBidi"/> <w:colorw:val="365F91"w:themeColor="accent1"w:themeShade="BF"/> <w:kernw:val="0"/> <w:langw:val="zh-CN"/> </w:rPr> </w:pPr> <w:rw:rsidRPr="00115C2B"> <w:rPr> <w:rFontsw:hint="eastAsia"w:ascii="微软雅黑"w:hAnsi="微软雅黑"w:cstheme="majorBidi"/> <w:colorw:val="365F91"w:themeColor="accent1"w:themeShade="BF"/> <w:kernw:val="0"/> <w:langw:val="zh-CN"/> </w:rPr> <w:t>整体测评结果</w:t> </w:r> <w:bookmarkEndw:id="2"/> <w:bookmarkEndw:id="1"/> </w:p>
修正策略
问题我们已经分析清楚了,其实这是FlexDoc的bug,当然我们可以通过修改FlexDoc的源代码来解决这个问题,但是我实在是懒得读源码,决定在FlexDoc匹配数据之后将word文档写在磁盘上之前来修正目录。流程如下:代码实现
代码很简单,全部代码如下所示:publicstaticvoidFixtDirectory(WordprocessingDocumentwdDoc)
[code]{
Bodybody=wdDoc.MainDocumentPart.Document.Body;
//获取所有包含一、二级标题的段落
varparHasStyle=body.Descendants<Paragraph>().Where(t=>t.Descendants<ParagraphStyleId>().Count()>0&& t.Descendants<ParagraphStyleId>().All(c=>c.Val=="1"||c.Val=="2"));
stringbookMarkName="_Toc{0}";
intnum=988888888;
Dictionary<string,string>bookMarkAddedDic=newDictionary<string,string>();
if(parHasStyle.Count()>0)
{
foreach(ParagraphpinparHasStyle)
{
varbookmarkEnds=p.Descendants<BookmarkEnd>();//获取段落中所有BookmarkEnd标签
varbookmarkStarts=p.Descendants<BookmarkStart>();//获取段落中所有BookmarkStart标签
intbookmarkEndsCount=bookmarkEnds.Count();
intbookmarkStartsCount=bookmarkStarts.Count();
stringname=string.Format(bookMarkName,++num);
stringid=(num++).ToString();
//创建新书签用于添加到标题上下
BookmarkStartbookmarkStart=newBookmarkStart(){Name=name,Id=id};
BookmarkEndbookmarkEnd=newBookmarkEnd(){Id=id};
if(bookmarkEndsCount==0&&bookmarkStartsCount==0)
{
if(p.Descendants<Text>().Count()>0)
{
AddBookMarkToParagraph(p,bookmarkEnd,bookmarkStart);//添加书签
bookMarkAddedDic.Add(p.Descendants<Text>().First().Text,name);//记录添加的书签
}
}
else
if(bookmarkEndsCount!=bookmarkStartsCount)
{
DeleteBookMarkFromParagraph(body,p,bookmarkStarts,bookmarkEnds);//删除孤单书签
AddBookMarkToParagraph(p,bookmarkEnd,bookmarkStart);//添加新书签
stringdicKey=GetKey(p);//获取被添加书签的标题
bookMarkAddedDic.Add(dicKey,name);//记录添加的书签
}
}
FixtDirectory(bookMarkAddedDic,body);//更新目录
}
}
///<summary>
///将段落中文字拼起来得到标题内容
///</summary>
///<paramname="p"></param>
///<returns></returns>
privatestaticstringGetKey(Paragraphp)
{
returnstring.Join("",p.Descendants<Text>().Select(t=>t.Text));
}
///<summary>
///修正书签
///</summary>
///<paramname="bookMarkAddedDic"></param>
///<paramname="body"></param>
privatestaticvoidFixtDirectory(Dictionary<string,string>bookMarkAddedDic,Bodybody)
{
if(bookMarkAddedDic.Count>0)
{
if(body.Descendants<SdtBlock>().Count()>0)
{
//得到SdtContentBlock
SdtContentBlocksdtContentBlock=body.Descendants<SdtBlock>().First().GetFirstChild<SdtContentBlock>();
//遍历每一个超链接,修改里面的书签值
foreach(HyperlinkhyperlinkinsdtContentBlock.Descendants<Hyperlink>())
{
Texttext=hyperlink.Descendants<Text>().First();//得到目录项绑定的标题内容
if(bookMarkAddedDic.Keys.Contains(text.Text))
{
hyperlink.Anchor=bookMarkAddedDic[text.Text];//超链接绑定到书签的name
FieldCodepageRef=hyperlink.Descendants<FieldCode>().First(t=>t.Text.Contains("PAGEREF"));//
pageRef.Text="PAGEREF"+hyperlink.Anchor+"\\h";//更新PAGEREF以更新页码
}
}
}
}
}
///<summary>
///删除孤单标签
///</summary>
///<paramname="body"></param>
///<paramname="p"></param>
///<paramname="bookmarkStarts"></param>
///<paramname="bookmarkEnds"></param>
privatestaticvoidDeleteBookMarkFromParagraph(Bodybody,Paragraphp,IEnumerable<BookmarkStart>bookmarkStarts,
IEnumerable<BookmarkEnd>bookmarkEnds)
{
IEnumerable<BookmarkStart>singleStartElenmentsIn=null;
IEnumerable<BookmarkEnd>singleEndElenmentsIn=null;
IEnumerable<BookmarkStart>singleStartElenmentsOut=null;
IEnumerable<BookmarkEnd>singleEndElenmentsOut=null;
singleStartElenmentsIn=bookmarkStarts.Where(t=>
!bookmarkEnds.Select(c=>c.Id.Value).Contains(t.Id.Value));//获得段落内的孤单BookmarkStart标签
List<BookmarkStart>bookmarkStartsLst=singleStartElenmentsIn.ToList();
singleEndElenmentsIn=bookmarkEnds.Where(t=>!bookmarkStartsLst.Select(c=>c.Id.Value).
Contains(t.Id.Value));//获得段落内的孤单BookmarkEnd标签
singleStartElenmentsOut=body.Descendants<BookmarkStart>().Where(t=>singleEndElenmentsIn.
Select(c=>c.Id.Value).Contains(t.Id.Value));//获得段落外的孤单BookmarkStart标签
singleEndElenmentsOut=body.Descendants<BookmarkEnd>().Where(t=>singleStartElenmentsIn.
Select(c=>c.Id.Value).Contains(t.Id.Value));//获得段落外的孤单BookmarkEnd标签
//删除所有孤单标签
Remove(singleStartElenmentsOut);
Remove(singleEndElenmentsOut);
Remove(singleStartElenmentsIn);
Remove(singleEndElenmentsIn);
}
privatestaticvoidRemove(IEnumerable<OpenXmlElement>singleElenments)
{
singleElenments.ToList().ForEach(t=>t.Remove());//删除标签
}
///<summary>
///添加新的标签到段落中标题上下
///</summary>
///<paramname="p"></param>
///<paramname="bookmarkEnd"></param>
///<paramname="bookmarkStart"></param>
privatestaticvoidAddBookMarkToParagraph(Paragraphp,BookmarkEndbookmarkEnd,BookmarkStartbookmarkStart)
{
if(p.Descendants<Text>().Count()>0)
{
varwtBegin=p.Descendants<Text>().First();
varwtEnd=p.Descendants<Text>().Last();
RunrBegin=wtBegin.ParentasRun;//得到标题内容开始行
RunrEnd=wtEnd.ParentasRun;//得到标题内容结束行
rBegin.InsertBeforeSelf(bookmarkStart);//在标题上面插入BookmarkStart
rEnd.InsertAfterSelf(bookmarkEnd);//在标题下面插入bookmarkEnd
}
}
[/code]
代码很少,我将说明加在注释上,相信各位都能看的懂。最后还希望大家踊跃留言讨论。谢谢!
相关文章推荐
- 关于word中如何生成自动目录以及页码编排
- Word关于目录和页码的使用记录
- word自动生成目录和调整页码字体的方法
- word去除封面页码目录与正文页码分离
- word论文页码的设置(封面无编号、目录罗马数字和正文阿拉伯数字)
- Word中设置论文的页码从第2页算起,不包括目录
- OpenXml编程--去除自动生成的word文档中由分页符和换行符产生的空白页
- Word编程出现ComException错误:系统配置类型不正确,系统无法开始服务器进程,请检查用户名,密码
- word中自动插入目录,页码超出页边距
- C# Office编程——Word 错误类型:“系统找不到 Microsoft.Office.Interop.Word"
- WORD动态目录打印预览出现:错误未…
- word自动生成目录 页码不能右对齐问题
- word 插入目录及错误!未找到目录项
- Word文档目录自动生成和页码设置
- aspose.words生成word文档(.dox、.docx等)时,生成目录后,目录中的页码和实际页码不对应,代码中更新域都没用
- word里的目录索引页码如何跟正文的不一样?
- word 封面 目录不要页码 从第三页正文开始要页码的 设置 方法
- WORD目录,页眉,页脚,页码设置技巧 为你的毕业论文收藏吧-以Word 2013演示
- Word自动生成目录页码靠右对齐
- Word使用样式技巧:解决创建目录后出现的打印错误---超链接错误