OpenXmlSDK学习笔记(1):Word的基本结构
能写多少篇我就不确定了,可能就这⼀篇就太监了,也有可能会写不少。
OpenXml SDK 相信很多⼈都不陌⽣,这个就是管Office⼀家的⽂档格式,Word, Excel, PowerPoint等都⽤到这个。并且,这个格式主要是给Word 2007以上使⽤的。如果是⽤到其中Excel部分,那建议直接使⽤NPOI这样的成品类库就⾏。
⼀、WordprocessingML的理解
在看⽂档和使⽤的时候,就可以发现这样的⼀个命名空间:Wordprocessing。也可以看到这样的名词WordprocessingML。什么意思
呢,Office家的这个产品叫Word,其作⽤是处理⽂字。所以,Wordprocessing翻译成⽂字处理就⾏了。对于OpenXml结构的docx⽂件那就是⼀个压缩包。你把后缀名从docx改成zip就可以⽤解压软件打开了。在其中,可以看到这样的结构:
这个结构⾥,第⼀个⽂件夹word就是我们要关注的内容,这个⽂件夹⾥是这样的:
有图⽚的话会更复杂⼀点,再多⼀个media⽂件夹,⾥⾯存着图⽚。不过这个⽆关紧要,本次我的需求只是简单的输出⼀个纯⽂档的证明⽂件。所以,不要管图⽚了。
在这⾥,重点需要注意注意的xml有两个,l和l。他们分别对应着docx⽂件的样式部分和正⽂部分,⼤致就是这样的:也就是说,如果我们需要通过代码编辑⼀个纯⽂本的Word,那就是修改这两个xml就可以了。甚⾄于,如果不需要搞样式的话,只要改
但是,⼿写xml可太刑了。把整个 ISO/IEC 29500:2016 读完怕不是半条命就要去掉了。再等你把代码写完,恐怕你的⼯作就已经凉凉了。所以呢,微软⾃⼰出了个 OpenXml SDK 帮助开发者编辑这种Xml⽂件。不过呢,这玩意也是真的难⽤。⽽且说实话,⾥⾯⼀⼤⽚功能是根本⽤不着。说实话,⽇常使⽤的时候,也就是搞个样式,然后向⾥⾯添加⽂字,设置⼀下字体和段落样式,顶多插⼊点图⽚和表格。
对于我这种做普通的信息管理系统的⼈来说,图⽚和表格⾥都有⼤把的功能是完全⽤不着的。⽽且对于MIS的绝⼤部分⽤例⽽⾔,我都是只需要⽣成Word,然后由Word程序读取,⽽不需要由我来读⼀个Word模板,然后再向其中修改。当然,如果你会写了,读也不是什么太难的问题,只不过Word那个⿁程序⾥的Run真的是看不懂⽣成规律,经常会有乱七⼋糟的东西。所以,如果有碰到读模板再向⾥写的需求,我另外写⼀个笔记。
⼆、创建⼀个Word⽂件
打开VS,然后创建⼀个命令⾏程序,向⾥⾯添加⼀个名为“DocumentFormat.OpenXml”的Nuget包,这样项⽬的引⽤关系就做完了。然后添加以下代码:
1if (File.Exists("newDocx.docx"))
2 {
3 File.Delete("newDocx.docx");
4 }
5
6using (WordprocessingDocument doc = WordprocessingDocument.Create("newDocx.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document)) 7 {
8var main = doc.MainDocumentPart;
9if (doc.MainDocumentPart == null)
10 {
11 doc.AddMainDocumentPart();
12 }
13
14if (doc.MainDocumentPart.Document == null)
15 {
16 doc.MainDocumentPart.Document = new Document();
17 }
18var body = doc.MainDocumentPart.Document;
19
20 Paragraph para = new Paragraph();
21 Run r = new Run();
22 Text t = new Text();
23 t.Text = "Hello World";
24 r.Append(t);
25 para.Append(r);
26 body.Append(para);
27
28 doc.Save();
29 }
运⾏⼀下,就可以发现在运⾏⽬录下,出现了⼀个名为 newDocx.docx 的⽂件。这个⽂件打开,⾥⾯就⼀⾏ Hello World ⽂本。虽然,这个Hello World程序简单,但是要理解这个东西就特!别!⿇!烦!
⾸先,using语句块⾥,“WordprocessingDocument”对象就是⼀个docx⽂档对象,也就是上⽂所述的那个压缩包。声明这个对象通常使⽤两种⽅法:Open和Create。⾮常好区别,⼀个是打开,⼀个是创建。
之后,“MainDocumentPart”这个属性就相当于压缩包⾥的“word”⽂件夹,⾮常的真实。
接着,“MainDocumentPart.Document”这个属性就相当于“word”⽂件夹下的“l”⽂件,更真实了。
再下,“Paragraph”是⼀个段落。⼀份Word是由多个段落或者表格组成的。所以,在Word⽂档⾥,看到回车符,就可以认为是⼀
个“Paragraph”对象的结束。
在“段落”⾥,有多个连续⽂本,也就是“Run”。⼀个Run就相当于Html⾥的span标签。⽐如,上⽂中,Hello World整个⽂本都是⼀样的格式。所以,就应当在⼀个Run⾥。写成Html⼤致是这样的感觉:
1<p>
2<span>Hello World</span>
3</p>
但是,并不是所有情况都是这样的。在很多时候,⼀个段落的⽂本也是有不同的格式的。⽐如说,中⽂和英⽂的字体不⼀样,或者其它情况。⽐如,下⽂这样:
那这时,就需要对段落内的⽂本再进⾏拆分。上图的格式,写成Html⼤致是这样的感觉:
1<p>
2<span>某</span>
3<span>个</span>
4<span>⼤</span>
5<span>学</span>
6<span>⼤</span>
7<span>学</span>
8<span>⽣</span>
9<span>创</span>
10</p>
所以呢,这⾥每⼀个字都是⼀个Run。在Run⾥,则是正式的⽂字,相当于span的#innerText。但是,WordprocessingML要求你将这些⽂字放在名为Text的段落⾥。
⾄此,整个Word的基本结构就看懂了。Document⾥⾯有若⼲个Paragraph。每个Paragraph⾥,前后格式完全⼀样的⽂本放在⼀个Run⾥。前后格式不⼀样的⽂本放在不同的Run⾥。每⼀个Run⾥⾯再有若⼲个Text。那么,练习⼀下,下⾯的Word有⼏个Paragraph,⼏个Run?
这个就是我需求的⼀部分,我也没有截全。但是看的出来,是有两个段落。所以,两个Paragraph安排上。第⼀个Paragraph⾥,“兹证明”的格式是⼀样的,但是后⾯的下划线的⽂本是“空格符”,⽂本格式是“下划线”。他们的格式与前⾯的“兹证明”不⼀样。所以,哪怕再后续的“学院教师”与“兹证明”的格式相同,这⾥也得分成三个Run。再之后,⼜是⼀个下划线,再⼀个Run。再后,根据需求,这个括号也是三号宋体和⽂字的格式⼀样,所以“(教⼯号:”是⼀个Run。下划线再⼀个Run。“)指导项⽬如下:”⼀个Run。所以,第⼀段⾥就有7个Run。
第⼆段,就留给⼤家做练习了。在我截出来的部分,所有数字符号都是三号宋体,和⽂字⼀样。算⼀下多少个Run?具体过程我就略了,答案是5个。
三、封装OpenXml SDK
如果说你对⾃⼰的技术有信⼼。那直接⽤OpenXml当然也是可以的。但是,我嫌他实在太烦了。于是,⾃⼰封装⼀下这个SDK,让他变的更加易⽤⼀些。对于⼀个⽂档⽽⾔,他的操作基本就是打开,保存,创建。需要注意的是,在新建的时候,直
接“WordprocessingDocument.Create”出来的是⼀个空的压缩包。必须要向其中添加“MainDocumentPart”和“Document”。甚⾄还有其它的东西,都是要⾃⼰加的。所以,在这个封装操作⾥,需要⼀个初始化函数。再者,同原来的WordprocessingDocument类⼀样,这个构造函数肯定也是要私有化的。不然容易出问题。
于是,新建⼀个WordDocument类。就可以敲出这样的代码了:
1 using DocumentFormat.OpenXml.Packaging;
2 using DocumentFormat.OpenXml.Wordprocessing;
3 using System;
4 using System.IO;
5
6 namespace Ricebird.Wordprocessing
7 {
8 public class WordDocument : IDisposable
9 {
10 protected WordprocessingDocument InternalDocument
11 {
12 get; set;
sdk13 } = null;
14
15 #region ctor
16 private WordDocument()
17 {
18
19 }
20 #endregion
21
22 #region 创建对象
23 /// <summary>
24 /// 创建⼀个Word对象
25 /// </summary>
26 /// <returns></returns>
27 public static WordDocument CreateDocument()
28 {
29 WordDocument doc = new WordDocument();
30 doc.InternalDocument = WordprocessingDocument.Create(new MemoryStream(), DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
31 doc.InitializeDocument();
32 return doc;
33 }
34
35 /// <summary>
36 /// 读取⼀个Word⽂档
37 /// </summary>
38 /// <param name="path">⽂档路径</param>
39 /// <param name="createNew">如果⽂件已经存在,是否删除原⽂件</param>
40 /// <returns></returns>
41 public static WordDocument LoadDocument(string path, bool createNew)
42 {
43 if (createNew && File.Exists(path))
44 {
45 File.Delete(path);
46 }
47
48 WordDocument doc = new WordDocument();
49 if (File.Exists(path))
50 {
51 doc.InternalDocument = WordprocessingDocument.Open(path, true);
52 }
53 else
54 {
55 doc.InternalDocument = WordprocessingDocument.Create(path, DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
56 }
57 doc.InitializeDocument();
58 return doc;
59 }
60 #endregion
61
62 #region 初始化⽂档
63 protected void InitializeDocument()
64 {
65 var doc = InternalDocument;
66 if (doc.MainDocumentPart == null)
67 {
68 doc.AddMainDocumentPart();
69 }
70
71 if (doc.MainDocumentPart.Document == null)
72 {
73 doc.MainDocumentPart.Document = new Document();
74 }
75
76 }
77 #endregion
78
79 #region 保存函数
80 /// <summary>
81 /// 保存函数
82 /// </summary>
83 public void Save()
84 {
85 InternalDocument.Save();
86 }
87
88 /// <summary>
89 /// 另存为函数
90 /// </summary>
91 /// <param name="path"></param>
92 public void SaveAs(string path)
93 {
94 InternalDocument.SaveAs(path);
95 }
96 #endregion
97
98 public void Dispose()
99 {
100 InternalDocument?.Dispose();
101 }
102 }
103 }
那由于这个项⽬的运⾏环境是C#7.0。所以就不能⽤10.0的新语法啦,不然全局命名空间还是真的⾹。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论