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

 2024-01-29 03:01:36  阅读 0

本文为参与“金石计划.瓜分6万现金奖”

1.应用场景:有什么用?

我在从事教育信息化工作时,经常用到词分析。

有些人可能会奇怪,我们都用Word来编辑文本,但你却解析它。 今天你得解释一下,...是什么意思?

解析就是剖析和分析。 解析词就是提取词中的结构和内容。

比如,无论是考驾照还是资格考试,我们都有过在设备上做题的经历。

之所以在前端能看到这些问题,是因为后端有人一一输入的。 就像您提交个人信息报告时一样。

以上的进入方式在我们这个行业会被骂的!

即使你认为这没有什么问题。

因为为了试卷,录入员拿到了这份原稿。

一套试卷有几十道题,每道题有十个左右的属性。 这样,一套题就需要填上几百个方框(请用重庆话读:框拳击手)。

事实上,程序员可以利用技术手段自己读取word的内容。

理论上,程序员可以获得word中显示的所有内容。 家庭背景较差的企业拿到数据后,通过代码逻辑查找试卷名称、选项等属性。 财务背景较好的公司可以通过人工智能NLP文本分类更好地实现智能分类(我就是这么做的)。

无论如何,这两种方法都可以实现。

此时,录入员只负责编辑word。 这个过程称为“单词导入”。

能够导入的前提是能够解析单词的内容。 能够解析word的前提是docx格式。

2、Docx格式:为什么会出现?

不知道你有没有印象。 从某个时期开始, 文件名的尾部多了一个“x”。 原来word和excel的后缀分别是doc、ppt、xls。 后来突然出现了docx、pptx、xlsx。

到目前为止,这些x已经占据了主流市场。

为什么? 为什么会这样呢? 我仿佛听到一家人拿着扫帚,悲伤地唱着:你伤了Word,Excel过去了,你的贪婪,我……

原来的doc格式是加密的,只能用微软自己的软件打开。

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

比如金山WPS也是做办公软件的。 发展迅速,积累了众多用户。 很多人已经开始使用wps,而word用户则面临着分裂。 因此,微软基于Open XML标准对文档族进行了兼容。

由于新标准采用xml格式记录信息,因此在.doc后面添加了x。 新标准之后,WPS甚至可以打开微软创建的文档。 这样,用户只关心文档,而不关心使用的是哪个软件,实现了用户共享。

反正就是这个意思,我猜的,别全信……

3、文件结构:如何解析?

那么,docx 格式到底是什么样的呢?

看起来像一个文件,但实际上是一个压缩包。

下面是我亲自编辑的docx文档。

我会自己解压:将.docx更改为.zip,右键单击并选择解压文件,它将显示其原始形状。

上图演示了解压并打开word/media文件夹。 这是文档中出现的图片。 分析发现,同一张图像在docx中被复制了多次,但在media中只保留了一张原始图像。 由此可见他是一个勤劳节俭的好孩子。 承载相同量的内容,docx格式比doc更小。

我们可以看到,解压后有一个目录结构。

别担心,我不会一一向您介绍所有文档。 因为那是没有用的。 即使它有效,你也不会记得它。 就算记住了,我还是不明白……为什么要学这么全面的知识呢? 汉字总共有10万多个,常用字只有2500个,足够了。

我来说说几个关键点。

3.1 主文件.xml

位于word下的.xml文件是docx的主战场。 可以说,你在文档中能看到的一切,都直接或间接地记录在这里。

.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中只有三种类型的标签,分别是and。

docx 文档基本上由这三个部分组成。 w 指单词,p 指段落,tbl 指表格。 全名是primp。 不要记住这一点,因为它很容易扰乱你的思维。

理清思路,docx文档主要有两大类,一类叫段落,一类叫表格。

3.2 段落标签w:p

在 docx 中,段落是文档中最常见也是最重要的单元。

和你理解的那段话一样。 它属于一个没有换行的段落。 即使你打了6个回车“咔咔”,虽然没有内容,但仍然属于6个段落,在xml中是6。

此外,段落中还包含含有图片、流程图、公式等元素的内容。 也就是说,他们都是弟弟。

我们可以用代码来获取该段落的信息吗? 当然! (这是自问自答,有试图凑字数的嫌疑)

无论使用哪种编程语言,我们都可以做到这一点,因为它只是读取 XML 文件。

但对于教学来说,使用它无疑是最好的选择。

# 导入解析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)

我们来看看打印的结果。 它与源文档完全对应,没有遗漏的部分。

现在是时候详细谈谈 w:p 的人了。

3.3 文本标签w:t

在复杂的xml文件中,您可以拉出一个称为标签的标签。

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

这里存储的内容是word中的文本,t是text的缩写。

你在 docx 中看到的每个单词基本上都是用 and 包裹的。

也就是说,如果我们取出所有标签中的文本,我们就实现了纯文本docx的解析。

代码其实很简单,就是扫描元素中的标签,取出它的内容。

我们已经得到了上面的 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 文本。

是的,提取 docx 文本就是这么简单。 您现在可以编写一个程序将 docx 转换为 txt。

但是...我想告诉大家的一件事是,在代码中运行wt.text时,可能会报错。 为了让大家更容易理解,我特意写了伪代码。 事实上,要从中获取文本内容,您可以执行以下操作:

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

我想如果你这么说的话你就能理解了。 因为在理解正则表达式的名称时无需分散注意力。

3.4 连续块w:r

上面我们讲了如何解析文本。 但这太简单了。

文字有风格。

同一 w:p 段落中的文本可能具有不同的样式。

例如,如果前两个字符是红色,则这两个字符具有相同的样式。 不过最后两个字符是绿色的,与前面的不同。

为了解决这个问题,docx将相同样式的文本包裹在标签中。

r 代表运行。 关于这个run的解释,国内很多文献直接翻译为“跑”。

其实run的英文解释有很多。 我想这里更合适的定义应该是:“一段”、“一个系列”、“连续表演”。 因此,我个人将这个标签命名为:连续块。 该标签内表示的文本是一系列的,其特点是连续且不间断的。

代码还是和处理xml文件一样,要么寻找标签,要么获取属性。

还有其他样式标签您可以自己研究。 首先我给大家举两个例子。

例如,上例中的字体颜色通常位于标签内。 w仍然是word的意思,r是run的意思,Pr是Primp的意思,即修饰、装饰的意思。 该标签的含义是:连续块的样式描述。 同样,它代表段落的风格描述。 ,表示表格单元格的样式描述。

我们来看看连续块修改是如何定义的。

例如,粗体和斜体的说明。

使用代码来判断,主要是读取标签。 如果能找到该标签,则说明有该风格的标签。

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

另一个例子是各种线条的描述。

上面的例子中,如果用代码来判断,除了判断标签是否存在之外,还需要获取属性值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文档后渲染的html页面:

我们可以看到,字体大小、字体、字体颜色、标记都可以恢复。

只剩下两件重要的事情要说。 那是图片和表格。

3.5 图片标签w:

docx中的图片是如何从xml中提取出来的?

您会在连续块中找到一个标签。 这里主要存储的是与图片相关的信息。

图片只是一小类。 除了图片之外,还有图表、形状、流程图等。

今天我们来说说最简单的事情,就是如何提取图片。

图片的标签是

,他在xml中定义如下:

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

其中,隐藏的是图片文件,里面的rId9就是抓拍图片的线索。

你还记得解压时的media文件夹吗? 里面有很多图片。

有图片,但不是rId9?

别担心,有一个专门用于关联的文件。 就是解压后的word/_rels/.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指向哪个文件。 因此,我们解析这个文件,得到对应关系。

然后,当pic中的id等于rId9时,显示图像文件media/.png。

这样就实现了图像分析。

是时候鼓掌了!

3.6 表标签 w:tbl

表格标签具有非常重要的地位,它与段落标签处于同一级别。

分析整个文档的顶层组件,我们不难发现,除了.

起初,我不明白。 为什么像图表和流程图这样的复杂元素配不上表格的崇高地位? 天道法在哪里?

后来解析成表才知道。 我似乎低估了老大的地位。

如果你看到上图没有笑。 这意味着您可能不理解这篇文章。 或者也许是我的幽默感出了问题。

一个简单的表的结构其实并不复杂。 但是,表中的每个单元格都可以容纳另一个 Word 文档。 在表格中,您可以添加图片、图表、文本样式,甚至可以在表格中添加表格。 一切都可以添加,甚至包括老板。 和它放在一起,就感觉很委屈。

让我们看一下该表的基本结构。

<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>

节点元素中有两个主要部分。 一是介绍表列的数量。 另一个包含有关表行的信息。 tr 是表行的缩写。

其中,有,这里是格子的内容。 我们发现它的内容实际上是它的兄弟节点之一。 在此,向表格老板深深鞠一躬。

tc是table cell的缩写,意思是单元格。 有人可能会说tc是table的缩写,意思是表的列。 当我稍后讨论带有合并单元格的表格时,从结构的角度来看,我们会觉得表格单元格更适合上下文。

先说基本表的分析,如下图:

解析后的代码参考如下:

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]]

运行代码,结果如下:

我这里又挖了一个坑,除了前面讲w:t时的wt.text陷阱。 w:tc的内容应该是解析完整的w:p结构,但是这里我只取了w:t文本。 这是最简单的。 因为我们需要了解表的结构。 因此,别人可以假装看不见。

不过也可以看出,做好表格分析的前提是做好段落分析。 因为表格单元格是段落。

相信有了表格(二维数组)的解析结果,就可以轻松将其还原为表格页面进行显示。 只需循环,循环第一层的行和第二层的列。

让我用1分钟写出代码:

rows_text = [['名称', '后缀名'], ['Word', 'docx']]
table_html = [""] 
for row in rows_text:
    table_html.append("")
    for cell in row:
        table_html.append("")
    table_html.append("")
table_html.append("
"+cell+"
"
) table_html = "".join(table_html) print(table_html) # # # #
名称后缀名
Worddocx

据我所知,除了上面的简单的以外,世界上还有一些稍微复杂的形式。 这是一个包含合并单元格的表格。

例如:

此类表的解析稍微复杂一些,是复杂中最简单的。

我们来看看他们的xml数据是如何定义的。

首先看一个具有跨行的表的示例。

对于跨行的情况,我们发现表的xml数据中的行数和列数没有变化。 只需在要合并的单元格上贴上标签即可。

该标签表示存在跨行的单元格。 v表示垂直方向。 代表垂直合并。 该标签中还有一个属性值w:val。 当值为 时,表示这个cell已经开始合并,也就是说这个cell还没有结束,会一直持续下去,直到遇到异常情况。

我们来看一下跨列的情况。

因为跨列发生在行内,所以是行内矛盾,不会影响其他行。 因此,我们看到只有在第1行的第一个单元格中,使用了标签,表明该行中存在跨列的单元格。 val 值为 2,表示跨越 2 个单位。

为什么前面说tc是table cell的缩写。 这是我的基础。 事实上,这个表的结构是2行2列。 如果c引用它,它应该有2,后一个复用前一个。 然而,当我们看上图中的结构时,只有一个。 那么我们称之为cell更合适,因为它只有一个盒子。 你可以反驳我,我马上就说你是对的,但以后我还是称它为细胞。

页面恢复采用合并单元格,逻辑稍复杂。 但底层仍然和普通表格一样,通过循环行和列。 遇到合并的时候只需要做=2或者=3就可以了。

这里我就不再贴代码给大家了。 原理已经讲得很透彻了,算是做作业了。 你可以自己思考一下,你会收获更多。

唉,给你一个小建议,仅供参考。 因为当我合并单元格时,我花了两天时间才完成。 我给大家提供一个关键点:对于复杂的表,使用一维数组作为最终的数据结构是比较合适的。 该结构可以如下所示 [{"":0, "x_end":1, "":0, "y_end":0, "":"Don’t Ask me"}]。

如果读完的话,2个小时还是读不完的。 那么请不要问我。 如果你问我,我会用狗头表情回答。

4.其他奇妙用途:隐藏初恋照片?

首先声明一下,这个技巧我从来没有用过。 我只是讨论一下它的可行性。

我的很多读者都已婚或者即使没有结婚也有女朋友,他们都害怕自己的妻子。

然而,谁没有热情的青春呢?

有些人只是想保存初恋的照片,夜深人静的时候看看。 不过,我害怕被现任老板发现。

该怎么办? 手机相册不安全! 加密文件或更改其后缀是一种无知的行为。

那么,你一定不能把它放在docx中。 这只是一个压缩包。 解压后,添加一些文件,改回普通文档。 看不出任何问题,经得起组织的考验和检查。

我有这样一份文件,一份非常无辜的文件。

解压后,里面有图片,甚至还有pdf。

唉,程序员的思维状态就是这么简单粗俗。 如果销售人员看到了,他们就会发起一场商战。

我是ITF男孩,我是掘金队的TF男孩,但你……让你从IT的角度看世界。

标签: xml解析 xml格式 docx

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码