本文正在参加「金石计划 . 瓜分6万现金大奖」

一、使用场景:有什么用处?

我在从事教育信息化的工作中,常常会用到word的解析。

有人会疑惑,咱们都是用word修正文字,你却去解析它。你今日必须解释一下,解析……是什么意思?

解析便是解剖并剖析。解析word,便是提取word里边的结构和内容。

举个比方,不管是考驾照,仍是考资格证,咱们都有过在设备上做题的阅历。

docx格式文档详解:xml解析并用html还原

这些标题,你之所以能在前端看到,其实是有人在后端一条条录入的。就像你提交个人信息报备那样。

docx格式文档详解:xml解析并用html还原

上面的这种方式的录入,在咱们职业,是会被骂的!

尽管你觉得,这并没有什么不妥。

由于关于试卷,录入员拿到的是这样的原稿。

docx格式文档详解:xml解析并用html还原

一套试卷有几十道题,一道题又有十来个特点,这么算来,一套题得填上百个框框(请用重庆话读:框框儿)。

其实,程序员完全能够经过技术手段,自己去读出word的内容。

docx格式文档详解:xml解析并用html还原

理论上,word里展现的一切内容,程序员都能拿到。拿到数据之后,家境差点的公司,经过代码逻辑去找试卷名、选项这类特点。家境好点儿的企业,经过人工智能的NLP文本分类(我便是这么干的),能够更好地完成智能分类。

横竖,两种途径都能完成。

而此刻,录入员只负责修正word就能够了。这个过程就叫做“word导入”。

能导入的条件,是能够解析出来word的内容。能解析word的条件,便是docx格局。

二、docx格局:为什么出现?

不知道您各位有没有印象,从某个时期开端,微软工作文件名的小尾巴多了一个“x”。本来word、PowerPoint还有excel,后缀名分别是doc、ppt、xls。后来,就忽然出现出了docx、pptx、xlsx。

到目前为止,这些x们占有了干流商场。

为什么?为什么要这样?我仿佛听到Office宗族在抱着扫帚悲伤地唱:你伤害了Word,还Excel而过,你Access的贪婪,我PowerPoint……

本来的doc格局是加密的,只要微软自己家的软件才干翻开。

后来微软觉得,这样并没有让自己很神圣,反而限制了自己的开展。

比方,金山WPS也是搞工作软件的,它的开展很快,积累了许多用户。不少人开端用wps了,word的用户面对被瓜分。于是,微软就基于Office Open XML规范,把文档宗族做了兼容。

由于新规范是选用的xml格局记录信息的,所以就在.doc后边加了个x。新规范之后,即使是微软创立的文档,WPS也能翻开。这样,用户只关怀文档就行,不必在意用哪个软件,这就完成了用户同享。

横竖,是这个意思,我猜的,别全信……

三、文件结构:怎么来解析?

那么,docx格局究竟是什么姿态的呢?

docx格式文档详解:xml解析并用html还原

它看起来是个文件,其实,它是个紧缩包。

下面便是一个docx文档,是我亲手修正的。

docx格式文档详解:xml解析并用html还原

我再亲自做一下解紧缩:将.docx改为.zip,右键挑选解压文件,它就露出原形了。

docx格式文档详解:xml解析并用html还原

上图演示了解紧缩,并翻开了word/media文件夹,这是文档里出现的图片。剖析发现,在docx中同一张图复制多次,media里边只保留一张原图。这阐明,它是个勤俭节约的好孩子。承载同样多的内容,docx格局比doc的体积要小。

正如咱们看到,解压后它是有目录结构的。

docx格式文档详解:xml解析并用html还原

放心,我不会挨个把文件都给你介绍一遍。由于,那并没有什么用。即使有用,你也记不住。即使你记得住,我也说不明白……为什么要学习那么全面的常识呢?汉字总计10万多个,常用字只要2500个,够用了。

我说几个比较要害的点。

3.1 主文件 document.xml

坐落word下的document.xml文件,是docx的主战场。能够说,文档中你能看到的一切内容,在这儿都有直接或许间接的记录。

document.xml是一个XML格局的文档。

咱们来翻开它,咱们先只翻开一级。

为了你能看下去,我对数据做了处理,保证你只能看到要害信息。

<w:document>
    <w:body>
        <w:p>...</w:p>
        <w:p>...</w:p>
        <w:tbl>...</w:tbl>
        <w:p>...</w:p>
        ...
        <w:sectPr>...</w:sectPr>
    </w:body>
</w:document>

咱们看到,body体里,只要3种标签,分别是<w:p><w:tbl><w:sectPr>

docx格式文档详解:xml解析并用html还原

一个docx文档,根本由这三种成分组成。w指的是wordp指的是paragraph阶段,tbl表明table表格。sectPr全称是section primp,这个千万别记,简略打乱思路。

清空你的大脑,docx文档里就两种大类,一种叫<w:p>阶段,另一种叫<w:tbl>表格。

3.2 阶段标签 w:p

在docx中,阶段是最常见的,是文档中最主要的组成单元。

和你了解的阶段相同。不换行就归于一个阶段。即使是“咔咔咔”敲上6个回车,虽然没有内容,那它也归于6个阶段,在xml中是6个<w:p></w:p>

别的,包含图片、流程图、公式等元素的内容,也是包含在阶段中的。换句话说,它们都是小弟。

咱们能用代码取到阶段的信息吗?当然能!(这自问自答,被怀疑是凑字数)

咱们用哪类编程语言都能够做到,由于仅仅便是读取XML文件。

可是关于教学而言,用python无疑是最佳的挑选。

# 导入解析xml的库
import xml.dom.minidom as xdom
# 加载文档
xp = xdom.parse('word/document.xml')
# 获取文档根节点
root=xp.documentElement
# 获取body节点们
bodys = root.getElementsByTagName("w:body") 
# 由于getElements回来多个目标,咱们只要一个
body = bodys[0]
# 循环遍历body下的节点
for i,ele in enumerate(body.childNodes):
    e_name = ele.nodeName
    # 打印{序号} -> {节点称号} is {目标}
    print(i,"->",e_name,"is",ele)

咱们看一下打印成果,它和源文档完全对应,一个都没有少。

docx格式文档详解:xml解析并用html还原

下面该详细说一下w:p的小弟们了。

3.3 文本标签 w:t

在纷杂的xml文件中,能够扒拉出来一个叫<w:t>标签。

<w:p>
    <w:r>
        <w:t>word的doc格局本来是不开源的,后来改成了docx格局后,是开源的。</w:t>
        ……
    </w:r>
    ……
</w:p>

这儿边存储的内容,便是word里边的文字,t便是text的简称。

你在docx里边看到的每一个字,根本上都是被<w:t></w:r>所包裹的。

也便是说,假如咱们拿出一切<w:t>标签内的文本,咱们就做到了纯文本docx的解析。

代码,其实很简略,便是在<w:p>元素中,扫描<w:t>的标签并取出其内容。

上面咱们现已拿到了body标签,所以从那里继续。

# 循环body下的大单元 w:p阶段,w:tbl表格
for i,ele in enumerate(body.childNodes):
    # 找到包含w:t的标签,可能是多个
    wts = ele.getElementsByTagName("w:t") 
    ele_text = "" # 记录大单元内一切文本
    for wt in wts: # 循环
        ele_text = ele_text + wt.text
    print(ele_text) # 打印输出

看看代码对应的作用。

docx格式文档详解:xml解析并用html还原

祝贺你,你现已学会了解析docx的文本了。

是的,提取一个docx的文本,便是这么简略。你现在就能够写一个程序,能够做到把docx转为txt。

可是……有一点我要告诉你,运转代码里的wt.text可能会报错,为了便于你了解,我特意写了伪代码。实际上,要从<w:t>中取出文本内容,能够像下面这样:

import re # 导入正则库
# 构建一个正则,去除<>标签
pattern_del_tag = re.compile(r'<[^>]+>',re.S)
# 把<w:t>元素转为xml格局<w:t>xxx</w:t>,然后去标签
t_text =  pattern_del_tag.sub('', wt.toxml())

我以为这么讲,你反而能了解。由于,不必在了解什么叫<w:t>的时分,还要分散注意力到正则表达式。

3.4 接连块 w:r

上面咱们讲了怎么去解析文字。可是,那太简略了。

文字是有款式的。

docx格式文档详解:xml解析并用html还原

都在同一段w:p内的文字,它们的款式,可能不相同。

比方前两个字是红色,那么这两个字款式相同。可是,后两个字是绿色,和前面又不相同。

为了处理这个问题,docx把具有相同款式的文字,用<w:r>标签包裹。

docx格式文档详解:xml解析并用html还原

r代表run。关于这个run的解释,许多国内文档都直接翻译为“运转”。

其实,run在英文中有许多解释。我觉得在这儿更适合它的释义应该是:“一段”、“一系列”、“接连演出”。因此,我个人给这个标签起名叫:接连块。表明在这个标签之内的文本,是一个系列的,他们的特点是接连不间断的。

代码仍然是处理xml文件的那一套,不是找标签便是拿特点。

还有其他的款式标签,你能够自己研讨。我这儿先抛砖引玉,举两个比方。

比方上面比方中的字体颜色,一般在<w:rPr>标签内。w仍然表明wordr表明runPr表明Primp,是润饰、装修的意思。<w:rPr>这个标签的释义便是:接连块的款式阐明。相似的还有<w:pPr>,表明对阶段paragraph的款式阐明。<w:tcPr>,表明对表格单元格table cell的款式阐明。

咱们来看一下,接连块润饰<w:rPr>是怎么界说的。

比方关于粗体、斜体的阐明。

docx格式文档详解:xml解析并用html还原

用代码进行判别,主要是tag的读取。能找到tag,就阐明有此种款式的符号。

w_i = w_rPr.getElementsByTagName("w:i")
if w_i:
    print("是斜体")
w_b = w_rPr.getElementsByTagName("w:b")
if w_i:
    print("是粗体")

再比方关于各种线条的阐明。

docx格式文档详解:xml解析并用html还原

上面的比方,假如用代码进行判别的话,除了对 tag存在的判别,还需要获取特点值w:val,表明用了哪一类详细的款式。

w_u = w_rPr.getElementsByTagName("w:u")
if w_u:
    # 获取特点值用 getAttribute
    line_value = w_u[0].getAttribute("w:val")
    if line_value == "single":
        print("是下划线")
    if line_value == "double":
        print("是双划线")
    if line_value == "wave":
        print("是波浪形线")
    if line_value == "dotted":
        print("是虚线")

我能够很负责任地说,只要是文档中出现的信息,在xml文件中都能够找到对应的标示。

我能够“吧啦吧啦”全告诉你,可是会影响你看其他的内容。有兴趣你能够去查资料,都是手册类型的资料,很方便。

我倒是觉得,你自己在word中符号一下,然后解紧缩观察xml文件的改变,这姿态学的更牢。横竖我便是这么学来的。

以下是我用html把一个word文档做了恢复。

这是docx原文档:

docx格式文档详解:xml解析并用html还原

这是解析docx文档后出现的html页面:

docx格式文档详解:xml解析并用html还原

咱们能够看到,包含字号、字体、字色、标线都能够恢复。

只剩下两个重要的内容没有说了。那便是图片和表格。

3.5 图像标签 w:drawing

docx中的图片是怎么从xml中提取出来的呢?

你在接连块<w:r>中会发现有一个<w:drawing>标签。这儿边主要存放的,便是图像相关的信息。

图片仅仅是<w:drawing>中的一个小分类。除了图片,还有图表、形状、流程图等。

docx格式文档详解:xml解析并用html还原

今日,咱们说个最简略的,那便是怎么提取图片。

图片的标签是<pic:pic>,他在xml中如下界说:

<w:drawing>
    <pic:pic>
        <pic:blipFill>
            <a:blip r:embed="rId9"/>
        </pic:blipFill>
    </pic:pic>
</w:drawing>

其中,图片文件就藏在<a:blip r:embed="rId9"/>中,里边的rId9便是捕获图片的头绪。

还记不记得解紧缩时,那个media文件夹,里边有好多图片。

docx格式文档详解:xml解析并用html还原

是有图片,但这也不是rId9呀?

别急,有个文件专门做关联这件事情。它便是解紧缩之后的word/_rels/document.xml.rels文件。

翻开这个文件:

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<Relationships>
    ……
    <Relationship Target="media/image1.png" Id="rId9"/>
    <Relationship Target="media/image3.png" Id="rId11"/>
    <Relationship Target="media/image2.GIF" Id="rId10"/>
</Relationships>

哈哈,这儿有档案,记录了哪个id指向哪个文件。于是,咱们解析这个文件,就能够拿到对应关系。

然后,遇到picid等于rId9的,就把media/image1.png这个图片文件展现出来。

这,就完成了图片的解析。

能够鼓掌了!

3.6 表格标签 w:tbl

表格标签<w:tbl>的位置很重,它和阶段标签<w:p>平级。

剖析整个文档的顶层组成元素,咱们不难发现,除了<w:tbl>便是<w:p>

一开端,我不了解。为什么图表、流程图那么杂乱的元素,却不配得到表格那么高的位置。天理何在?

后来,我解析到一个表格后发现。我,如同低估了<w:tbl>大佬的位置。

docx格式文档详解:xml解析并用html还原

假如,你看到上面的图没有笑喷。那阐明,你可能没有看懂本文。或许是我的笑点出了问题。

单纯表格的结构,其实不杂乱。可是,表格里每一个单元格,却能够容纳另一个word文档。表格里边,图片、图表、文本款式,乃至表格里再来一个表格,什么都能够添加,它乃至是包含了<w:p>大佬。<w:tbl><w:p>并列,它反而是冤枉的。

咱们来看一下表格<w:tbl>的根本结构。

<w:tbl>
    <w:tblGrid>
        <w:gridCol/>
        <w:gridCol/>
    </w:tblGrid>
    <w:tr>
        <w:tc><w:p>...</w:p></w:tc>
        <w:tc>...</w:tc>
    </w:tr>
    <w:tr>
    ...
    </w:tr>
</w:tbl>

节点元素中,主要有两部分内容,一个<w:tblGrid>介绍表格列的个数。另一个是<w:tr>包含表格行的信息。 trtable row的缩写。

其中,<w:tr>里边有<w:tc>,这儿边是格子的内容。咱们发现其内容,居然是一个它的兄弟节点<w:p>。在此,给表格大佬深深地鞠上一躬。

tctable cell的缩写,表明单元格的意思。有人可能会说,tctable column的缩写,表明表格的列。当我后边论说带有兼并单元格的表格时,从结构上看,咱们会感觉table cell更适合语境。

先说根本表格的解析,如下图所示:

docx格式文档详解:xml解析并用html还原

解析的代码参阅如下:

w_table = body.childNodes[0] # 拿到表格节点
# 获取一切的行
w_trs = w_table.getElementsByTagName("w:tr")
rows_text = [] # 存放行的文本
for r_index, tr in enumerate(w_trs):
    # 获取一切的单元格
    cells = tr.getElementsByTagName("w:tc")
    cells_text = [] # 存放单元格的文本
    for c_index, cell in enumerate(cells):
        # 获取一切的文本
        wts = cell.getElementsByTagName("w:t") 
        for wt in wts: # 把文本拼接
            cells_text.append(wt.text)
    rows_text.append(cells_text)
print(rows_text) # 打印成果,二维数组[[r1c1,r1c2],[r2c1,r2c2]]

运转一下代码,成果如下:

docx格式文档详解:xml解析并用html还原

这儿边我又挖坑了,除了前面讲w:t时的wt.text陷阱之外。w:tc的内容应该是去解析完好的w:p结构,而这儿我只取了w:t文本。这样最简略。由于,咱们是要了解表格的结构。因此,其他的能够假装看不见。

可是,也由此可见,做好表格解析的条件,是做好阶段解析。由于表格单元格里是阶段。

我信任,有了表格的解析成果(二维数组),你很简略就能够把它还原成表格页面用于展现。只需要循环就好,第一层循环行,第二层循环列。

我仍是用1分钟写一下代码吧:

rows_text = [['称号', '后缀名'], ['Word', 'docx']]
table_html = ["<table>"] 
for row in rows_text:
    table_html.append("<tr>")
    for cell in row:
        table_html.append("<td>"+cell+"</td>")
    table_html.append("</tr>")
table_html.append("</table>")
table_html = "".join(table_html)
print(table_html)
# <table>
# <tr><td>称号</td><td>后缀名</td></tr>
# <tr><td>Word</td><td>docx</td></tr>
# </table>

据我所知,国际上的表格除了上面那种简略的,还有一些略微杂乱的。那便是带有兼并单元格的表格。

比方下面这种:

docx格式文档详解:xml解析并用html还原

这类表格的解析略微杂乱一些,它们归于杂乱里边最简略的。

咱们来看看他们的xml数据是怎么界说的。

首要看带有跨行的表格的比方。

docx格式文档详解:xml解析并用html还原

关于跨行的情况,咱们发现表格的xml数据,该有的行和列,数量都没有变。仅仅在要兼并的单元格上符号了一个<w:vMerge>标签。

这个标签表明有跨行的单元格。v表明vertical,是竖直方向的意思。vMerge表明竖直兼并。这个标签里边还有特点值w:val,当值为restart时表明此单元格开端出现兼并,continue表明此单元格没有结束,继续保持,直到遇到非continue情况。

再来看看跨列的情况。

docx格式文档详解:xml解析并用html还原

跨列由于产生在行内,是行内矛盾,不影响其他行。所以,咱们看到只要在第1行的第1格中,选用<w:gridSpan>标签,阐明本行有跨列的单元格。val值是2,表明跨2个单位。

为什么我前面说,tctable cell的缩写。我的根据就在这儿。其实这个表格的结构是2行2列。假如c指的是column的话,它应该有2个<w:tc>,后一个复用前一个。可是,咱们看上图里的结构,它只要一个<w:tc>。那咱们称号它叫单元格更贴切,由于它只要一个框。你能够辩驳我,我会立马说,你说的对,但今后仍然称号它单元格。

带有兼并单元格的页面还原,逻辑略微杂乱。可是底层仍是和普通表格相同,经过循环行和列。你只需要遇到兼并的时分,搞一个colspan=2或许rowspan=3就行。

此处,我不再给我们贴代码了。原理现已讲得很透彻了,算是留一个作业。你能够自己思考一下,会有更多的收成。

唉呀,仍是给我们一个小提示吧,仅供参阅。由于我搞兼并单元格时,花费了2天时间才完成。我把一个要害点提供给我们:关于杂乱表格,最终的数据结构用一维数组更恰当。这个结构能够像这样[{"x_start":0, "x_end":1, "y_start":0, "y_end":0, "content":"不要问我"}]

假如你看完了,2个小时还搞不定。那请您不要来问我,问我也是回复一个狗头表情。

四、其他妙用:藏初恋相片?

首要声明,这招我没用过啊。我仅仅论说它的可行性。

我的许多读者都成婚了,没成婚也有女朋友了,他们还都怕老婆。

可是,谁没有热血芳华呢?

有人就想存着初恋相片,夜深人静的时分看一看。可是,又怕被现任发现。

怎么办?手机相册不安全!文件加暗码或许修正后缀名归于屈打成招。

那么,你千万不要放到docx中。它只不过是个紧缩包。解紧缩之后,加点文件进去,改回来仍是正常的文档。看不出来有任何问题,经得起组织检测和查看。

我有这么个文档,很洁白的一个文档。

docx格式文档详解:xml解析并用html还原

解紧缩之后,里边包含有图片,乃至pdf都有。

docx格式文档详解:xml解析并用html还原

唉,程序员的思想境地便是这么的朴实无华又低级趣味。要是被销售员看到,他们该引发商业战了。

我是ITF男孩,在是TF男孩,但你……带你从IT视角看国际。