Office 文件解析入门

Published: by Creative Commons Licence

  • Categories:

概述 - Office 解析相关协议

这里所讲的 Office 文件指的是微软 Office2007 及以后的 PPT/EXCEL/WORD 等文件格式,因为 Office2007 以前的文件是用二进制格式定义的,不在本文的讨论内容中。

Office2007 以后的文件格式属于 OOXML 文件格式,OOXML 是基于 zip+xml 定义的。

OOXML 全称 Office Open XML File Formats,最初是由 ECMA-376 定义的,它目前已经到第六版。后来 ISO/IEC 29500 也开始掺和OOXML格式的定义,不过可以认为从 ECMA-376 的第2版开始,这两个标准是一样的。目前最新的 OOXML 标准是在 2016 年发布的 ECMA-376 5th edition 或者 ISO/IEC 29500:2016

微软的 Office 实现了OOXML格式,但不是全部,微软在 这篇文章 中描述了其对标准的支持情况。

OOXML = OPC + *ML ,下面会分别介绍 OPC*ML

由于 EMCA 文档可以免费在网上下载的,而 ISO 文档需要付费才能获取,因此下文的内容都是基于 ECMA-376 文档。

OPC - Open Package Convention

关于 OPC 的详细内容请参考 ECMA-376,Fourth Edition,Part 2

OPC 中文名开放打包协定,是一种基于 zip+xml 定义的文件存储格式。一个 OPC 文件(不管其文件后缀是什么)本质上就是一个 zip 文件,你可以用任何常见的解压软件进行解压,解压后你看到的那些文件的组织结构,就是以 OPC 定义的方式存储的。下图是一个PPTX文件解压后的目录结构:

ppt解压后的目录结构

OPC 中有3个重要的概念要理解:Part , Relationship , ContentTypes

Part

Part 可以简单理解为 zip 中的文件,每一个文件都是一个 Part ,它可以是任何格式,比如图片,xml文件等。在 Office 文件中,各种 Markup Language 定义的内容就作为 XML 存储在 Part 中。

Relationship

Relationship 是一种特殊的 Part ,它描述了各 Part 之间的依赖关系。根据OPC协议的规定,所有的 Relationship 都必须存储在名为 _rels 的文件夹中,并且所有 Relationship 的文件名都必须以 .rels 为后缀。

每一个 Part 根据业务需求都可以有对应的 Relationship 文件,比如在 PPTX 文件中,有一个 Part 为 /ppt/presentation.xml ,它对应的 Relationship 文件(只能)为/ppt/_rels/presentation.xml.rels ,不能是任何其它名字或者位置的文件。下面是一个 PPTX 文件中的 presentation.xml.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/slide" Target="slides/slide2.xml"/>
    <Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles" Target="tableStyles.xml"/>
    <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
    <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMasters/slideMaster1.xml"/>
    <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
    <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps" Target="viewProps.xml"/>
    <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps" Target="presProps.xml"/>
</Relationships>

Relationship 文件的根节点是 RelationShips, 它是 Relationship 节点的集合,每一个 Relationship 节点都代表一个依赖关系。它的各个属性的意义如下:

属性名 是否必须 说明
Id 唯一标识符,在当前文件中是唯一的,不同文件中可以重复
Type Target 的文件类型
Target 目标 Part 的路径,可以用相对路径也可以用绝对路径
TargetMode 目标的类型,值可以是 Internal 或者 External (默认是Internal),表示 Target 是OPC文件内部的还是外部的

ContentTypes

ContentTypes 在zip压缩包的根目录下,文件名为 [Content_Types].xml ,它记录了该OPC文件中除了他自己以外的所有文件的类型。下面是一个PPTX文件的 ContentTypes 文件:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
    <Default Extension="jpeg" ContentType="image/jpeg"/>
    <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
    <Default Extension="xml" ContentType="application/xml"/>
    <Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>
    <Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>
    <Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
    <Override PartName="/ppt/slides/slide2.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
    <Override PartName="/ppt/presProps.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"/>
    <Override PartName="/ppt/viewProps.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"/>
    <Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>
    <Override PartName="/ppt/tableStyles.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml"/>
    <Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>
    <Override PartName="/ppt/slideLayouts/slideLayout2.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+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"/>
</Types>

其中有两种标签 DefaultOverride , Default 指明了拥有文件后缀名和文件类型的对应关系, Override 指明了某一个文件的文件类型。

比如:<Default Extension="jpeg" ContentType="image/jpeg"/> 的意思是后缀为 jpeg 的文件都是 image/jpeg 类型的文件,因此在OPC中不建议根据文件后缀名来判断一个文件的类型,而应该根据 ContentTypes 文件中记录的信息来判断文件类型。

*ML (* Markup Language)

关于各种 ML 语言的详细信息请参考 ECMA-376,Fifth Edition,Part 1

Markup Language (ML) 译为 标记语言,是一种用 XML 来描述数据的"语言",比如:

*ML 全名 说明
PML Presentation Markup Language PPT 中各种数据的描述
WML Wordprocessing Markup Language Word 中数据的描述
SML Spreadsheet Markup Language Excel 中数据的描述
DML Drawing Markup Language Office 所有格式中都可以使用,用来描述矢量图形,图表等
SharedML Shared Markup Language 描述了文档属性,音视频,图片,文档主题等内容,它被所有Office文件使用

比如之前提到的 presentation.xml 文件,它的文件内容就是在 PML 中定义的。具体信息可以查看 ECMA-376,Fifth Edition,Part 1。但是,ECMA-376 太长了,还没看就头疼,怎么办。

首先了解一下在 EMCA-376 中用来定义各种 ML 语言的 XSD

XSD - XML Schema Definition 或者 XML Schema

XSD 类似 DTD,它们有相似的作用。在 ECMA-376 中,所有的 Markup Language 都是 XML 格式的,而这些 XML 的具体节点内容,都是用 XSD 来定义的,所以我们要对它有一定的了解:

XML Schema(XSD) 的作用:

  • 定义可出现在文档中的元素
  • 定义可出现在文档中的属性
  • 定义哪个元素是子元素
  • 定义子元素的次序
  • 定义子元素的数目
  • 定义元素是否为空,或者是否可包含文本
  • 定义元素和属性的数据类型
  • 定义元素和属性的默认值以及固定值
    (可以在 这里 找到 XSD 的详细内容。)

XSD 中,有两个比较重要的概念:Simple Type(简单类型)Complex Type(复杂类型)

名称 说明
Simple Type Element 简单类型元素,指那些仅包含文本的元素。它不会包含任何其他的元素或属性。 比如:<id>123</id>,这里元素 id 就是简单类型元素。
Complex Type Element 复杂类型元素,指那些可以包含其他任意元素或者属性的元素。

比如在 PML 中定义 页面大小(SlideSize) 这个元素(节点)的 XSD 如下:

<xsd:complexType name="CT_SlideSize"> 
    <xsd:attribute name="cx" type="ST_SlideSizeCoordinate" use="required"/> 
    <xsd:attribute name="cy" type="ST_SlideSizeCoordinate" use="required"/> 
    <xsd:attribute name="type" type="ST_SlideSizeType" use="optional" default="custom"/> 
</xsd:complexType>

可以看出 CT_SlideSize 是个 complexType(复杂类型) 的元素,并且它有三个 attribute(属性) ,分别是 cx , cytype,根据它们 type 的前缀 ST_ ,可以看出这三个属性都是简单类型的,并且是 required 的。如果你明白了这里的描述,再去看 ECMA-376 中各种 ML 语言的定义应该会简单一点。

PS: 在看 ECMA-376 的时候可以先只看前 16 章的内容,只有160页左右,如果只算内容的话更少,后面的将近 5000 页都可以解析哪部分就去看哪部分就行了。言外之意就是,其实也没有想象中的那么唬人。JUST DO IT !

PPTX 解析的基本流程

这里以 PPTX 的解析为例,其它的 WORD 或者 EXCEL 可以触类旁通。

基本流程如下:

  1. 读取 /[Content_Types].xml 文件,获取到所有文件的类型;
    这部分需要了解 OPC 中关于 ContentTypes 的内容。
  2. 读取 /_rels/.rels 这个所谓的 Package Relationship 文件,获取 presentation.xml 文件的位置,比如 /ppt/presentation.xml
    这部分需要了解 OPC 中关于 Relationship 的内容以及 PML 的基本内容。
  3. 读取 /ppt/presentation.xml 文件以及其关联的 Relationship 文件 /ppt/_rels/presentation.xml.rels,得到该PPT所有页面文件的存储位置,比如 /ppt/slides/slide1.xml,以及 slideMaster(母版)文件的存储位置,比如 /ppt/slideMasters/slideMaster1.xml,以及 slideLayout(板式)等相关文件的存储位置;
    这部分需要了解 PML 中关于 Presentation 的定义,也就是 PML 的 XSD 中定义的 CT_Presentation 类型。这个时候可以去详细的阅读 Presentation 章节的详细信息了。
  4. 读取 /ppt/slides/slide1.xml 文件内容以及其关联的 Relationship /ppt/slides/_rels/slide1.xml.rels,可以得到该页面的所有元素信息;
    这部分要完整解析就需要了解所有 PML、DML、SharedML 中的内容了。

不知道看到这里你还要不要自己解析啊?如果不愿意了,那大名鼎鼎的 POI 也许可以救你于水深火热。

POI/NPOI

POI 是 Apache 的项目,这里 是它的官网(NPOI 是 POI 的 .NET 版本)。背景知识我就不介绍了,这里摘自维基百科对它模块划分的介绍,可以在自己解析的时候参考:

POIFS (Poor Obfuscation Implementation File System) – This component reads and writes Microsoft's OLE 2 Compound document format. Since all Microsoft Office files are OLE 2 files, this component is the basic building block of all the other POI elements. POIFS can therefore be used to read a wider variety of files, beyond those whose explicit decoders are already written in POI.(也就是OPC模块)
HSSF (Horrible SpreadSheet Format) – reads and writes Microsoft Excel (XLS) format files. It can read files written by Excel 97 onwards; this file format is known as the BIFF 8 format. As the Excel file format is complex and contains a number of tricky characteristics, some of the more advanced features cannot be read.()
XSSF (XML SpreadSheet Format) – reads and writes Office Open XML (XLSX) format files. Similar feature set to HSSF, but for Office Open XML files.
HPSF (Horrible Property Set Format) – reads "Document Summary" information from Microsoft Office files. This is essentially the information that one can see by using the File|Properties menu item within an Office application.
HWPF (Horrible Word Processor Format) – aims to read and write Microsoft Word 97 (DOC) format files. This component is in initial stages of development.
XWPF (XML Word Processor Format) – similar feature set to HWPF, but for Office Open XML files.
HSLF (Horrible Slide Layout Format) – a pure Java implementation for Microsoft PowerPoint files. This provides the ability to read, create and edit presentations (though some things are easier to do than others)
HDGF (Horrible DiaGram Format) – an initial pure Java implementation for Microsoft Visio binary files. It provides an ability to read the low level contents of the files.
HPBF (Horrible PuBlisher Format) – a pure Java implementation for Microsoft Publisher files.
HSMF (Horrible Stupid Mail Format[7][better source needed]) – a pure Java implementation for Microsoft Outlook MSG files.[8]
DDF (Dreadful Drawing Format) – a package for decoding the Microsoft Office Drawing format.

需要注意的是 POI 貌似对 PPT 的支持比较弱?

最后

完全解析 Office 格式是一项巨大的工程,开始之前要有充分的成本考虑。