近期工作中遇到一个增加导出为 Word 格式的需求,因此花了点时间仔细了解了下 docx 格式,发现原来一篇 Word 背后有如此复杂的结构。本文主要介绍 docx 文件的结构,但是 pptx、xlsx 的原理应该是类似的。
docx 格式的奥秘
对于 docx 文件大部分人都停留在这是一种 Office 的文件格式,可以用 Word 软件打开,并没有深入去了解下它内部的组成结构是怎么样的。实际上 docx 是一个压缩文件(Zip 格式),可以用 Zip 软件进行解压。
解压之后可以看到,它是有一系列 XML 文件组成:
这就是 docx 文件的奥秘。
OOXML 规范介绍
这些 XML 的用处、每个 XML 文件的定义格式、Zip 目录结构等,都是在 OOXML 规范定义的非常清楚,下面是 OOXML 官网对此的介绍:
Office Open XML, also known as OpenXML or OOXML, is an XML-based format for office documents, including word processing documents, spreadsheets, presentations, as well as charts, diagrams, shapes, and other graphical material. The specification was developed by Microsoft and adopted by ECMA International as ECMA-376 in 2006. A second version was released in December, 2008, and a third version of the standard released in June, 2011. The specification has been adopted by ISO and IEC as ISO/IEC 29500.
由此可见,word/ppt/excel 等几种格式的组织结构都是在同一个规范下定义的,他们的原理以及解析的方法都一样。OOXML 是微软公司在 2006 年公布的规范。类似的还有 Open Document Format (ODF),它是 OpenOffice.org 开源软件所使用的规范。
从官网介绍可以看出 OOXML 规范主要包括两个方面,第一个显然是每个 XML 文件的语法定义,类似 markdown 语言的语法描述一样,word 文件的格式、样式、内容布局等等都是和 XML 里面的 TAG 一一对应。第二个,就是对文件的组织结构的定义,前面看到 docx 实际上是一个 Zip 文件,解压缩出来的目录结构是有规则的,这个通过 Open Packaging Conventions (OPC) 定义,具体可以查看 Anatomy of a WordProcessingML File。
接下来简单介绍下解压后的内容,如果要更详细的了解,还是建议仔细阅读下上文的链接以及相关的资料。
在解压的根目录,一般会有以下几个文件及目录:
kodango -> ~/Downloads/test $ ls -l total 8 -rwxr-xr-x@ 1 kodango staff 2529 1 1 1980 [Content_Types].xml drwxr-xr-x@ 3 kodango staff 96 11 20 18:07 _rels drwxr-xr-x@ 5 kodango staff 160 11 20 18:07 customXml drwxr-xr-x@ 5 kodango staff 160 11 20 18:07 docProps drwxr-xr-x@ 17 kodango staff 544 1 21 16:20 word
下面逐个介绍下它们的用处。
[Content_Types].xml
这个文件是定义里面每一个 XML 文件的内容类型,打开看其实一目了然:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> <Default Extension="xml" ContentType="application/xml"/> <Default Extension="jpg" ContentType="image/jpeg"/> <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml"/> <Override PartName="/customXml/itemProps1.xml" ContentType="application/vnd.openxmlformats-officedocument.customXmlProperties+xml"/> <Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/> <Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/> <Override PartName="/word/settings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"/> <Override PartName="/word/webSettings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"/> <Override PartName="/word/footnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"/> <Override PartName="/word/endnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"/> <Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/> <Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/> <Override PartName="/word/header2.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/> <Override PartName="/word/footer2.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/> <Override PartName="/word/fontTable.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"/> <Override PartName="/word/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/> <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/> <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/> <Override PartName="/docProps/custom.xml" ContentType="application/vnd.openxmlformats-officedocument.custom-properties+xml"/> </Types>
比如
1、/word/document.xml 的类型是 application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml,说明这是保存 word 实际内容的文件,并且也可以看出这是从一个模版文件(dotx)解压出来的。
2、/word/styles.xml 的类型是application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml ,说明这是一个 word 样式文件,样式的定义都是保存在这个文件里面。
其他还有页眉、页脚、列表、属性等等,一个 docx 文件就是有这样非常多的部分组成,每个部分其实都是一个 Part(PartName属性),Part 之间会相关的关系(Relation)。
_rels
_rels 目录下会有一个 .rels 后缀的文件,它里面保存了这个目录下各个 Part 之间的关系。_rels 目录不止一个,它实际上是有层级的。
其中,根目录下面的 _rels 目录下就一个文件 .rels,它的内容很简单:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/> <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/> <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties" Target="docProps/custom.xml"/> </Relationships>
显而易见,这个文件定义了顶层的关系,即 Package(Zip 包)和它所直接包含的 Part 之间的关系。
而对于一个 Part 来说,如果它依赖其他 Part,那么需要为这个 Part 创建一个目录,并且也有一个 _rels 目录,目录下面会有一个 partname.rels 文件。比如 /word/document.xml 就是很典型的例子:
$ tree word word ├── _rels │ └── document.xml.rels ├── document.xml
document.xml.rels 文件的内容:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.jpg"/> <Relationship Id="rId13" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer2.xml"/> <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/> <Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes" Target="endnotes.xml"/> <Relationship Id="rId12" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header2.xml"/> <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml" Target="../customXml/item1.xml"/> <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml"/> <Relationship Id="rId11" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/> <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/> <Relationship Id="rId15" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/> <Relationship Id="rId10" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml"/> <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/> <Relationship Id="rId9" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image2.jpg"/> <Relationship Id="rId14" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/> </Relationships>
上面的内容也很好理解,/word/document.xml 和样式、页眉、页脚等等 Part 是有关系的。
word
上面介绍关系(Relationship)的时候已经介绍了 word 这个目录,无外乎是我们所看到的 word 内容所对应的各个文件,可以查看这里了解。
docProps
从名字可以看出,这个目录下的 XML 文件是保存了 docx 文件的属性。其实你打开 word 文件的时候,可以从菜单栏 File -> Properties 打开属性的对话框看到对应的属性内容。
背后 OOXML 把属性分成三类,分别是 Core 、Extended 以及 Custom,分别对应 core.xml、app.xml 以及 custom.xml。其中 custom.xml 保存的是自定义的属性,一般这一类属性会在文件中被引用到,比如定义一个 documentName 的属性,然后在封面、页眉等地方引用(百度 DOCPROPERTY 进一步了解)。
理解了这些简单的概念之后,其实就可以开始用代码去解析或者处理 docx 文件了,当然每个文件的语法定义依然是一个很复杂的部分,这块本文不会详细介绍,还是需要结合业务场景,当用到的时候再去了解。
解析 docx 文件
有许多库已经实现了 OOXML 规范,因此可以解析和处理 docx 文件,比如 docx4j 或者 apache poi 。这里以 docx4j 为例,这两个库都是 Java 相关的库,其他语言也有类似的库。
最近我也是在关注了解压word文件之后需要提取一些数据的问题,然后发现在docProps/app.xml中,会有几个字段显示word内容的一些基本信息,比如Words, Characters,CharactersWithSpaces这个三个字段和用office word打开后显示的字数是不一样的,我猜可能编码不一样导致的,但是此时暂时未得到最后答案,不知道博主是否有了解?
@tangs:建议看spring源码中关于XML读取的代码,你会有所发现