DOCX的非正式介绍

本文概述

  • 一个简单的DOCX文件
  • 段落结构
  • 如果不清楚, 请对XML进行反向工程!
  • DOCX非常复杂, 不是吗?
DOCX格式大约有十亿人使用Microsoft Office, 它是在办公室之间交换文档文件的最流行的事实上的标准。它最接近的竞争对手-ODT格式-仅受Open / LibreOffice和某些开源产品的支持, 使其与标准相差甚远。 PDF格式不是竞争对手, 因为PDF无法编辑, 而且不包含完整的文档结构, 因此只能进行有限的局部更改, 例如水印, 签名等。这就是为什么大多数业务文档都以DOCX格式创建的原因。没有替代它的好选择。
尽管DOCX是一种复杂的格式, 但你可能希望手动解析它以完成更简单的任务, 例如索引, 转换为TXT以及进行其他小的修改。我想为你提供有关DOCX内部结构的足够信息, 因此你不必参考ECMA规范(5, 000页的庞大手册)。
理解格式的最佳方法是使用MSWord创建一个简单的单字文档, 并观察编辑文档如何更改基础XML。在某些情况下, 你可能会遇到DOCX无法在MS Word中正确格式化, 不知道为什么的情况, 或者遇到无法清楚地生成所需格式的实例。准确地了解和理解XML的运行状况将对此有所帮助。
我在协作DOCX编辑器CollabOffice上工作了大约一年, 我想与开发人员社区分享一些知识。在本文中, 我将解释DOCX文件结构, 总结散布在Internet上的信息。本文是介于庞大, 复杂的ECMA规范和当前可用的简单Internet教程之间的中介。你可以在我的github帐户的srcmini-docx项目中找到本文随附的文件。
一个简单的DOCX文件 DOCX文件是XML文件的ZIP存档。如果你创建一个新的空Microsoft Word文档, 请在其中写一个单词” Test” 并将其解压缩, 你将看到以下文件结构:
DOCX的非正式介绍

文章图片
即使我们已经创建了一个简单的文档, Microsoft Word中的保存过程仍以XML格式生成了默认主题, 文档属性, 字体表等。
DOCX内的所有文件都是XML文件, 即使扩展名为” .rels” 的文件也是如此。
鸣叫
首先, 让我们删除未使用的内容并关注包含主要文本元素的document.xml。删除文件时, 请确保已从其他xml文件中删除了对该文件的所有关系引用。这是一个代码差异示例, 说明我如何清除对app.xml和core.xml的依赖关系。如果你有任何未解决/丢失的引用, MSWord将认为文件已损坏。
这是我们简化的, 最小的DOCX文档的结构(这是github上的项目):
DOCX的非正式介绍

文章图片
让我们从顶部开始按文件分类:
_rels / .rels 这定义了告诉MS Word在何处查找文档内容的参考。在这种情况下, 它引用了word / document.xml:
< ?xml version="1.0" encoding="UTF-8" standalone="yes"?> < Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> < Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/> < /Relationships>

_rels / document.xml.rels 该文件定义对嵌入在文档内容中的资源(例如图像)的引用。我们的简单文档没有嵌入式资源, 因此关系标签为空:
< ?xml version="1.0" encoding="UTF-8" standalone="yes"?> < Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> < /Relationships>

[Content_Types] .xml [Content_Types] .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"/> < Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/> < /Types>

document.xml 最后, 这是带有文档文本内容的主要XML。为了清楚起见, 我删除了一些名称空间声明, 但是你可以在github项目中找到该文件的完整版本。在该文件中, 你会发现文档中的某些名称空间引用尚未使用, 但是你不应删除它们, 因为MS Word需要它们。
这是我们的简化示例:
< w:document> < w:body> < w:p w:rsidR="005F670F" w:rsidRDefault="005F79F5"> < w:r> < w:t> Test< /w:t> < /w:r> < /w:p> < w:sectPr w:rsidR="005F670F"> < w:pgSz w:w="12240" w:h="15840"/> < w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/> < w:cols w:space="720"/> < w:docGrid w:linePitch="360"/> < /w:sectPr> < /w:body> < /w:document>

主节点< w:document> 表示文档本身, < w:body> 包含段落, 并且嵌套在< w:body> 中的是由< w:sectPr> 定义的页面尺寸。
< w:rsidR> 是可以忽略的属性;由MS Word内部人员使用。
让我们看一个包含三个段落的更复杂的文档。我在Microsoft Word的屏幕截图中用相同的颜色突出显示了XML, 因此你可以看到相关性:
DOCX的非正式介绍

文章图片
< w:p w:rsidR="0081206C" w:rsidRDefault="00E10CAE"> < w:r> < w:t xml:space="preserve"> This is our example first paragraph. It's default is left aligned, and now I'd like to introduce< /w:t> < /w:r> < w:r> < w:rPr> < w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> < w:color w:val="000000"/> < /w:rPr> < w:t> some bold< /w:t> < /w:r> < w:r> < w:rPr> < w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> < w:b/> < w:color w:val="000000"/> < /w:rPr> < w:t xml:space="preserve"> text< /w:t> < /w:r> < w:r> < w:rPr> < w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> < w:color w:val="000000"/> < /w:rPr> < w:t xml:space="preserve"> , < /w:t> < /w:r> < w:proofErr w:type="gramStart"/> < w:r> < w:t xml:space="preserve"> and also change the< /w:t> < /w:r> < w:r w:rsidRPr="00E10CAE"> < w:rPr> < w:rFonts w:ascii="Impact" w:hAnsi="Impact"/> < /w:rPr> < w:t> font style< /w:t> < /w:r> < w:r> < w:rPr> < w:rFonts w:ascii="Impact" w:hAnsi="Impact"/> < /w:rPr> < w:t xml:space="preserve"> < /w:t> < /w:r> < w:r> < w:t> to 'Impact'.< /w:t> < /w:r> < /w:p> < w:p w:rsidR="00E10CAE" w:rsidRDefault="00E10CAE"> < w:r> < w:t> This is new paragraph.< /w:t> < /w:r> < /w:p> < w:p w:rsidR="00E10CAE" w:rsidRPr="00E10CAE" w:rsidRDefault="00E10CAE"> < w:r> < w:t> This is one more paragraph, a bit longer.< /w:t> < /w:r> < /w:p>

段落结构 一个简单的文档由段落组成, 一个段落由运行组成(一系列具有相同字体, 颜色等的文本), 而运行则由字符组成(例如< w:t> )。< w:t> 标记可能具有里面有几个字符, 同一轮中可能有几个。
同样, 我们可以忽略< w:rsidR> 。
文字属性 基本文本属性包括字体, 大小, 颜色, 样式等。大约有40个用于指定文本外观的标签。如你在我们的三段示例中所见, 每个运行在< w:rPr> 内都有自己的属性, 指定< w:color> , < w:rFonts> 和粗体度< w:b> 。
需要注意的重要一点是, 属性区分普通和复杂脚本(例如阿拉伯语)这两组字符, 并且属性会根据所影响的字符类型而具有不同的标记。
大多数普通脚本属性标签都具有匹配的复杂脚本标签, 并带有添加的” C” , 以指定该属性用于复杂脚本。例如:< w:i> (斜体)变为< w:iCs> , 普通脚本的< w:b> 粗体标签对于复杂脚本变为< w:bCs> 。
款式 Microsoft Word中有一个完整的工具栏, 专门用于样式:普通, 无间距, 标题1, 标题2, 标题等等。这些样式存储在/word/styles.xml中(请注意:在我们的简单示例的第一步中, 我们从DOCX中删除了该XML。创建一个新的DOCX来查看)。
将文本定义为样式后, 你将在段落属性标签< w:pPr> 中找到对该样式的引用。这是一个示例, 其中我以标题1的样式定义了文本:
< w:p> < w:pPr> < w:pStyle w:val="Heading1"/> < /w:pPr> < w:r> < w:t> My heading 1< /w:t> < /w:r> < /w:p>

这是来自styles.xml的样式本身:
< w:style w:type="paragraph" w:styleId="Heading1"> < w:name w:val="heading 1"/> < w:basedOn w:val="Normal"/> < w:next w:val="Normal"/> < w:link w:val="Heading1Char"/> < w:uiPriority w:val="9"/> < w:qFormat/> < w:rsid w:val="002F7F18"/> < w:pPr> < w:keepNext/> < w:keepLines/> < w:spacing w:before="480" w:after="0"/> < w:outlineLvl w:val="0"/> < /w:pPr> < w:rPr> < w:rFonts w:asciiTheme="majorHAnsi" w:eastAsiaTheme="majorEastAsia" w:hAnsiTheme="majorHAnsi" w:cstheme="majorBidi"/> < w:b/> < w:bCs/> < w:color w:val="365F91" w:themeColor="accent1" w:themeShade="BF"/> < w:sz w:val="28"/> < w:szCs w:val="28"/> < /w:rPr> < /w:style>

< w:style / w:rPr / w:b> xpath指定字体为粗体, 而< w:style / w:rPr / w:color> 表示字体颜色。 < w:basedOn> 指示MSWord对任何缺少的属性使用” 常规” 样式。
财产继承 文本属性是继承的。运行具有自己的属性(w:p / w:r / w:rPr / *), 但它也继承了段落(w:r / w:pPr / *)的属性, 并且两者都可以从/中引用样式属性word / styles.xml。
< w:r> < w:rPr> < w:rStyle w:val="DefaultParagraphFont"/> < w:sz w:val="16"/> < /w:rPr> < w:tab/> < /w:r>

段落和运行从默认属性开始:w:styles / w:docDefaults / w:rPrDefault / *和w:styles / w:docDefaults / w:pPrDefault / *。要获得角色属性的最终结果, 你应该:
  1. 使用默认的运行/段落属性
  2. 附加运行/段落样式属性
  3. 附加本地运行/段落属性
  4. 将结果运行属性附加到段落属性上
当我说” 将B附加到A” 时, 我的意思是遍历B的所有属性并覆盖所有A的属性, 而所有不相交的属性保持不变。
默认属性可能位于的另一个位置是< w:style> 标记中的w:type =” paragraph” 和w:default =” 1″ 。请注意, 运行中的字符本身永远不会具有默认样式, 因此< w:style w:type =” character” w:default =” 1″ > 实际上不会影响任何文本。
鸣叫
DOCX的非正式介绍

文章图片
1554402290400-dbb29eef3ba6035df7ad726dfc99b2af.png)
运行中的字符可以从其段落继承, 并且两者都可以从styles.xml继承。
切换属性 其中一些属性是” 切换” 属性, 例如< w:b> (粗体)或< w:i> (斜体);这些属性的行为类似于XOR运算符。
这意味着, 如果父样式为粗体, 子运行样式为粗体, 则结果将是常规的非粗体文本。
你必须进行大量测试和反向工程才能正确处理切换属性。查看ECMA-376 Open XML规范的17.7.3段, 以获取有关切换属性/的正式, 详细的规则/
切换属性是布局器正确处理的最复杂的属性。
鸣叫
字型 字体遵循与其他文本属性相同的通用规则, 但是字体属性的默认值在单独的主题文件中指定, 该主题文件在word / _rels / document.xml.rels下引用, 如下所示:
< Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>

根据上述参考, 默认字体名称将在word / theme / themes1.xml中的< a:theme> 标记, a:themeElements / a:fontScheme / a:majorFont或a:minorFont标记内找到。
除非缺少w:docDefaults / w:rPrDefault标记, 否则默认字体大小为10, 然后为11。
文字对齐 文本对齐由< w:jc> 标记指定, 该标记具有四个可用的w:val模式:” 左” , “ 中心” , “ 右” 和” 两者” 。
” left” 是默认模式;文本从段落矩形的左侧开始(通常为页面宽度)。 (本段在左侧对齐, 这是标准的。)
可以预计, “ 居中” 模式会将所有字符居中在页面宽度内。 (同样, 本段示例居中对齐。)
在” 右” 模式下, 段落文本与右页边距对齐。 (注意此文本如何与右侧对齐。)
“ 双向” 模式在单词之间留出额外的间距, 以便使行变宽并占据整个段落宽度, 但最后一行保持对齐。 (此段说明了这一点。)
图片
DOCX支持两种图像:内联图像和浮动图像。
内联图像与其他字符一起出现在段落内, 使用< w:drawing> 而不是使用< w:t> (文本)。你可以使用以下xpath语法找到图像ID:
w:绘图/ wp:内联/ a:图形/ a:graphicData / pic:pic / pic:blipFill / a:blip / @ r:embed
图像ID用于在word / _rels / document.xml.rels文件中查找文件名, 并且应指向word / media子文件夹中的gif / jpeg文件。 (请参阅github项目的word / _rels / document.xml.rels文件, 你可以在其中查看图像ID。)
浮动图像是相对于段落放置的, 文字在它们周围流动。 (这是带有浮动图像的github项目示例文档。)
浮动图片使用< wp:anchor> 而不是< w:drawing> , 因此, 如果你删除< w:p> 中的任何文本, 请不要使用锚点, 如果你不想删除图片。
DOCX的非正式介绍

文章图片
MS Word的图像选项将图像对齐称为” 文本换行模式” 。
桌子
表格的XML标签类似于HTML表格标记–与< table> 相同, 与< tr> 匹配, 依此类推。
表本身的< w:tbl> 具有表属性< w:tblPr> , 每个列属性由< w:tblGrid> 中的< w:gridCol> 表示。行作为< w:tr> 标记一个接一个地跟随, 并且每一行应具有与< w:tblGrid> 中指定的相同的列数:
< w:tbl> < w:tblPr> < w:tblW w:w="5000" w:type="pct" /> < /w:tblPr> < w:tblGrid> < w:gridCol/> < w:gridCol/> < /w:tblGrid> < w:tr> < w:tc> < w:p> < w:r> < w:t> left< /w:t> < /w:r> < /w:p> < /w:tc> < w:tc> < w:p> < w:r> < w:t> right< /w:t> < /w:r> < /w:p> < /w:tc> < /w:tr> < /w:tbl>

表格列的宽度可以在< w:tblW> 标记中指定, 但如果你未定义, 则Word会使用其内部算法来找到最小有效表格尺寸的最佳列宽度。
单位
DOCX内的许多XML属性都指定大小或距离。它们是XML中的整数, 但是它们都有不同的单位, 因此需要进行一些转换。这个主题很复杂, 因此, 我建议Lars Corneliussen撰写这篇有关DOCX文件中单位的文章。他提供的表格很有用, 尽管打印错误较小:英寸应为pt / 72, 而不是pt * 72。
这是备忘单:
常见的DOCX XML单位转换
20分 点数DXA / 20 英寸pt / 72 厘米in * 2, 54 字体一半大小pt / 144 动车组* 914400
例子 11906 595.3 8, 27… 21.00086… 4, 135 7562088
使用此标签 pgSz / pgMar / w:spacing 以s wp:extent, a:ext
实施版面设计的技巧
如果要转换DOCX文件(例如, 转换为PDF), 在画布上绘制该文件或计算页数, 则必须实现一个布局器。布局器是一种用于从DOCX文件计算字符位置的算法。
如果需要100%保真度渲染, 这是一项复杂的任务。实施一个好的版面设计器所需的时间以人年为单位, 但是如果你只需要一个简单的有限的版面, 则可以相对快速地完成。
布局器会填充父矩形, 该矩形通常是页面的矩形。它逐个添加单词。当前行溢出时, 它将开始新的一条。如果段落对于父矩形而言过高, 则将其包裹到下一页。
如果你决定实施布局器, 请牢记以下重要事项:
  • 布局器应注意文本对齐和图像上的文本浮动
  • 它应该能够处理嵌套对象, 例如嵌套表
  • 如果要为此类图像提供全面支持, 则必须实施至少两次通过的布局程序, 第一步收集浮动图像的位置, 第二步用文本字符填充空白区域。
  • 注意缩进和间距。每个段落之前和之后都有空格, 这些数字由w:spacing标签指定。垂直间距由w:after和w:before标签指定。请注意, 行间距由w:line指定, 但这不是人们期望的行尺寸。要获得行的大小, 请采用当前字体的高度, 乘以w:line并除以12。
  • DOCX文件不包含有关分页的信息。除非你计算出每一行需要多少空间以确定页数, 否则你将找不到文档中的页数。如果你需要在页面上找到每个字符的确切坐标, 请确保考虑所有间距, 缩进和大小。
  • 如果实现用于处理表的功能齐全的DOCX布局器, 请注意表跨越多个页面时的特殊情况。导致页面溢出的单元也影响其他单元。
  • 创建用于计算表列宽度的最佳算法是一个具有挑战性的数学问题, 并且文字处理器和布??局器通常使用一些次优的实现。我建议使用W3C HTML表文档中的算法作为第一个近似值。我还没有找到MS Word使用的算法的描述, 并且Microsoft随时间对算法进行了微调, 因此不同版本的Word可能会略有不同地布置表格。
如果不清楚, 请对XML进行反向工程! 当不清楚XML标记在MS Word中的工作方式时, 有两种主要的解决方法:
  • 逐步创建所需的内容。从一个简单的docx文件开始。将每个步骤保存到自己的文件中, 例如1.docx, 2.docx。解压缩它们中的每一个, 并使用可视化差异工具进行文件夹比较, 以查看更改后出现的标签。 (对于商业选项, 请尝试Araxis Merge;对于免费选项, 请尝试WinMerge。)
  • 如果你生成MS Word不喜欢的DOCX文件, 请向后处理。逐步简化你的XML。在某些时候, 你将了解MS Word发现哪些更改不正确。
DOCX非常复杂, 不是吗? 这很复杂, 而且微软的许可证禁止在服务器端使用MS Word处理DOCX –这是商业产品的相当标准。但是, Microsoft提供了XSLT文件来处理大多数DOCX标签, 但它不能为你提供100%甚至99%的保真度。不支持诸如在图像上换行文本的过程, 但是你将能够支持大多数文档。 (如果你不需要复杂性, 请考虑使用Markdown作为替代。)
如果你有足够的预算(没有免费的DOCX渲染引擎), 则可能要使用商业产品, 例如Aspose或docx4j。最受欢迎的免费解决方案是LibreOffice, 用于在DOCX和其他格式(包括PDF)之间进行转换。不幸的是, LibreOffice在转换期间包含许多小错误, 并且由于它是一种复杂的开源C ++产品, 因此修复保真度问题的速度缓慢且困难。
【DOCX的非正式介绍】另外, 如果你发现DOCX布局过于复杂而无法实现自己, 则还可以将其转换为HTML并使用浏览器进行呈现。你还可以考虑使用srcmini的自由XML开发人员之一。
DOCX资源以供进一步阅读
  • ECMA DOCX规范
  • 用于C#进行DOCX操作的OpenXML库。它不包含有关布局或渲染代码的信息, 但提供了与DOCX中每个可能的XML节点匹配的类层次结构。
  • 你始终可以使用诸如docx4j, OpenXML和docx之类的关键字在stackoverflow上进行搜索或询问;社区中有一些知识渊博的人。

    推荐阅读