OpenXml|OpenXml SDK学习笔记(1)(Word的基本结构)

能写多少篇我就不确定了,可能就这一篇就太监了,也有可能会写不少。
OpenXml SDK 相信很多人都不陌生,这个就是管Office一家的文档格式,Word, Excel, PowerPoint等都用到这个。并且,这个格式主要是给Word 2007以上使用的。如果是用到其中Excel部分,那建议直接使用NPOI这样的成品类库就行。
但是,NPOI FOR WORD真的是太难受了。当然,也不是说一定要用NPOI,现在成品的WORD操作库也不是没有,比如DocX。这个库基本算是 Xceed Words for .NET 的简化版。GitHub https://github.com/xceedsoftware/DocX 。而且,这个库非常牛逼的地方在于他是直接操作XML的,效率是上去了,可读性就下去了啊。要读懂这个,恐怕得对ISO/IEC 29500 这个标准非常熟悉。那没救了,我是专精信息系统开发的,对这个标准的理解非常一般。而且说实话,这个标准里 95% 以上的内容我根本用不到,我又不用做一个Word,我只需要把我系统里的东西生成为一个Word显示出来就行了。
那于是,我痛定思痛,自己读文档吧:https://docs.microsoft.com/zh-cn/office/open-xml/open-xml-sdk (当然,英文的质量比中文可高多了,不过懒的看英文)。这文档写的可真是太专业了,想读懂它恐怕得要点技术水平。所以呢,我打算把这个文档给拆一下,做一个笔记。能写多少就随缘了,反正我把需求实现完了就不写了。恐怕两三篇就完事了。那第一篇讲的就是Word的基本结构。
一、WordprocessingML的理解 在看文档和使用的时候,就可以发现这样的一个命名空间:Wordprocessing。也可以看到这样的名词WordprocessingML。什么意思呢,Office家的这个产品叫Word,其作用是处理文字。所以,Wordprocessing翻译成 文字处理 就行了。对于OpenXml结构的docx文件那就是一个压缩包。你把后缀名从docx改成zip就可以用解压软件打开了。在其中,可以看到这样的结构:
OpenXml|OpenXml SDK学习笔记(1)(Word的基本结构)
文章图片



这个结构里,第一个文件夹word就是我们要关注的内容,这个文件夹里是这样的:
OpenXml|OpenXml SDK学习笔记(1)(Word的基本结构)
文章图片



有图片的话会更复杂一点,再多一个media文件夹,里面存着图片。不过这个无关紧要,本次我的需求只是简单的输出一个纯文档的证明文件。所以,不要管图片了。
在这里,重点需要注意注意的xml有两个,document.xml和styles.xml。他们分别对应着docx文件的样式部分和正文部分,大致就是这样的:
OpenXml|OpenXml SDK学习笔记(1)(Word的基本结构)
文章图片


也就是说,如果我们需要通过代码编辑一个纯文本的Word,那就是修改这两个xml就可以了。甚至于,如果不需要搞样式的话,只要改docment.xml就行了。这两个Xml适用的标准就是 ISO/IEC 29500,并且这种Xml就称为:WordprocessingML。
但是,手写xml可太刑了。把整个 ISO/IEC 29500:2016 读完怕不是半条命就要去掉了。再等你把代码写完,恐怕你的工作就已经凉凉了。所以呢,微软自己出了个 OpenXml SDK 帮助开发者编辑这种Xml文件。不过呢,这玩意也是真的难用。而且说实话,里面一大片功能是根本用不着。说实话,日常使用的时候,也就是搞个样式,然后向里面添加文字,设置一下字体和段落样式,顶多插入点图片和表格。
对于我这种做普通的信息管理系统的人来说,图片和表格里都有大把的功能是完全用不着的。而且对于MIS的绝大部分用例而言,我都是只需要生成Word,然后由Word程序读取,而不需要由我来读一个Word模板,然后再向其中修改。当然,如果你会写了,读也不是什么太难的问题,只不过Word那个鬼程序里的Run真的是看不懂生成规律,经常会有乱七八糟的东西。所以,如果有碰到读模板再向里写的需求,我另外写一个笔记。

二、创建一个Word文件 打开VS,然后创建一个命令行程序,向里面添加一个名为“DocumentFormat.OpenXml”的Nuget包,这样项目的引用关系就做完了。然后添加以下代码:

1 if (File.Exists("newDocx.docx")) 2 { 3File.Delete("newDocx.docx"); 4 } 5 6 using (WordprocessingDocument doc = WordprocessingDocument.Create("newDocx.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document)) 7 { 8var main = doc.MainDocumentPart; 9if (doc.MainDocumentPart == null) 10{ 11doc.AddMainDocumentPart(); 12} 13 14if (doc.MainDocumentPart.Document == null) 15{ 16doc.MainDocumentPart.Document = new Document(); 17} 18var body = doc.MainDocumentPart.Document; 19 20Paragraph para = new Paragraph(); 21Run r = new Run(); 22Text t = new Text(); 23t.Text = "Hello World"; 24r.Append(t); 25para.Append(r); 26body.Append(para); 27 28doc.Save(); 29 }

运行一下,就可以发现在运行目录下,出现了一个名为 newDocx.docx 的文件。这个文件打开,里面就一行 Hello World 文本。虽然,这个Hello World程序简单,但是要理解这个东西就 特!别!麻!烦!
首先,using语句块里,“WordprocessingDocument”对象 就是一个docx文档对象,也就是上文所述的那个压缩包。声明这个对象通常使用两种方法:Open和Create。非常好区别,一个是打开,一个是创建。
之后,“MainDocumentPart”这个属性就相当于压缩包里的“word”文件夹,非常的真实。
接着,“MainDocumentPart.Document”这个属性就相当于“word”文件夹下的“document.xml”文件,更真实了。
再下,“Paragraph”是一个段落。一份Word是由多个段落或者表格组成的。所以,在Word文档里,看到回车符,就可以认为是一个“Paragraph”对象的结束。
OpenXml|OpenXml SDK学习笔记(1)(Word的基本结构)
文章图片



在“段落”里,有多个连续文本,也就是“Run”。一个Run就相当于Html里的span标签。比如,上文中,Hello World整个文本都是一样的格式。所以,就应当在一个Run里。写成Html大致是这样的感觉:
1 2Hello World 3

但是,并不是所有情况都是这样的。在很多时候,一个段落的文本也是有不同的格式的。比如说,中文和英文的字体不一样,或者其它情况。比如,下文这样:
OpenXml|OpenXml SDK学习笔记(1)(Word的基本结构)
文章图片




那这时,就需要对段落内的文本再进行拆分。上图的格式,写成Html大致是这样的感觉:
1 2 3 4 5 6 7 8 9 10

所以呢,这里每一个字都是一个Run。在Run里,则是正式的文字,相当于span的#innerText。但是,WordprocessingML要求你将这些文字放在名为Text的段落里。
至此,整个Word的基本结构就看懂了。Document里面有若干个Paragraph。每个Paragraph里,前后格式完全一样的文本放在一个Run里。前后格式不一样的文本放在不同的Run里。每一个Run里面再有若干个Text。那么,练习一下,下面的Word有几个Paragraph,几个Run?
OpenXml|OpenXml SDK学习笔记(1)(Word的基本结构)
文章图片


这个就是我需求的一部分,我也没有截全。但是看的出来,是有两个段落。所以,两个Paragraph安排上。第一个Paragraph里,“兹证明”的格式是一样的,但是后面的下划线的文本是“空格符”,文本格式是“下划线”。他们的格式与前面的“兹证明”不一样。所以,哪怕再后续的“学院教师”与“兹证明”的格式相同,这里也得分成三个Run。再之后,又是一个下划线,再一个Run。再后,根据需求,这个括号也是三号宋体和文字的格式一样,所以“(教工号:”是一个Run。下划线再一个Run。“)指导项目如下:”一个Run。所以,第一段里就有7个Run。
【OpenXml|OpenXml SDK学习笔记(1)(Word的基本结构)】第二段,就留给大家做练习了。在我截出来的部分,所有数字符号都是三号宋体,和文字一样。算一下多少个Run?具体过程我就略了,答案是5个。

    推荐阅读