Python-docx将Word文档的目录或文本框作为普通段落读入

📢作者: 小小明-代码实体

📢博客主页:https://blog.csdn.net/as604049322

📢欢迎点赞 👍 收藏 ⭐留言 📝 欢迎讨论!

昨天我们处理Word文档的自动编号,详见《Python解析Word文档的自动编号》
链接:https://xxmdmst.blog.csdn.net/article/details/139638262

今天我们处理一下Word文档的目录和文本框,例如:

在这里插入图片描述

如果我们按照传统方式读取,是无法读取目录和文本框的。

这时,我们只需要想办法目录和文本框中的P节点与普通P节点一起被读取即可。

查看其xml结构后知道,w:sdt是目录节点,文本框节点存在于p里面的v:textbox节点下。

可以写出如下代码:

from docx import Document
from docx.oxml import ns
from docx.text.paragraph import Paragraph

doc = Document('目录测试.docx')

ns.nsmap.update(doc.element.nsmap)
body = doc.element.body
paragraphs = []
for p in body.xpath('w:p | w:sdt/w:sdtContent/w:p | w:p//v:textbox//w:p'):
    paragraphs.append(Paragraph(p, body))

注意上面的代码使用Paragraph(p, body)封装是为了让paragraphs的结果类型与doc.paragraphs保存一致,可以看到其源码为:

在这里插入图片描述

如果不需要Paragraph的特殊功能,仅做基本的数据读取,也可以不封装。

然后就能将普通段落和目录内的段落以及文本框内的段落,都按顺序读取:

for paragraph in paragraphs:
    print(paragraph.text)

如果这时,我们需要将自动编号也读取进来,需要注意文本框内的段落是单独计数的。

最后我们将该功能整合到上次的代码中:

import re

from docx import Document
from docx.oxml.ns import qn, nsmap
from docx.text.paragraph import Paragraph


class WithNumberDocxReader:
    ideographTraditional = "甲乙丙丁戊己庚辛壬癸"
    ideographZodiac = "子丑寅卯辰巳午未申酉戌亥"

    def __init__(self, docx, gap_text="\t"):
        self.docx = Document(docx)
        nsmap.update(self.docx.element.nsmap)
        self.numId2style = self.get_style_data()
        self.gap_text = gap_text
        self.cnt = {}
        self.cache = {}
        self.result = []

    @property
    def texts(self):
        if self.result:
            return self.result.copy()
        self.clear()
        for paragraph in self.paragraphs:
            number_text = self.get_number_text(paragraph)
            self.result.append(number_text + paragraph.text)
        return self.result.copy()

    def clear(self):
        self.result.clear()
        self.cnt.clear()
        self.cache.clear()

    @property
    def paragraphs(self):
        body = self.docx.element.body
        result = []
        for p in body.xpath('w:p | w:sdt/w:sdtContent/w:p | w:p//v:textbox//w:p'):
            result.append(Paragraph(p, body))
        return result

    def get_style_data(self):
        numbering_part = self.docx.part.numbering_part._element
        abstractId2numId = {num.abstractNumId.val: num.numId for num in numbering_part.num_lst}
        numId2style = {}
        for abstractNumIdTag in numbering_part.findall(qn("w:abstractNum")):
            abstractNumId = abstractNumIdTag.get(qn("w:abstractNumId"))
            numId = abstractId2numId[int(abstractNumId)]
            for lvlTag in abstractNumIdTag.findall(qn("w:lvl")):
                ilvl = lvlTag.get(qn("w:ilvl"))
                style = {tag.tag[tag.tag.rfind("}") + 1:]: tag.get(qn("w:val"))
                         for tag in lvlTag.xpath("./*[@w:val]", namespaces=nsmap)}
                if "numFmt" not in style:
                    numFmtVal = lvlTag.xpath("./mc:AlternateContent/mc:Fallback/w:numFmt/@w:val",
                                             namespaces=nsmap)
                    if numFmtVal and numFmtVal[0] == "decimal":
                        numFmt_format = lvlTag.xpath("./mc:AlternateContent/mc:Choice/w:numFmt/@w:format",
                                                     namespaces=nsmap)
                        if numFmt_format:
                            style["numFmt"] = "decimal" + numFmt_format[0].split(",")[0]
                if style.get("numFmt") == "decimalZero":
                    style["numFmt"] = "decimal01"
                numId2style[(numId, int(ilvl))] = style
        return numId2style

    @staticmethod
    def int2upperLetter(num):
        result = []
        while num > 0:
            num -= 1
            remainder = num % 26
            result.append(chr(remainder + ord('A')))
            num //= 26
        return "".join(reversed(result))

    @staticmethod
    def int2upperRoman(num):
        t = [
            (1000, 'M'), (900, 'CM'), (500, 'D'),
            (400, 'CD'), (100, 'C'), (90, 'XC'),
            (50, 'L'), (40, 'XL'), (10, 'X'),
            (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I')
        ]
        roman_num = ''
        i = 0
        while num > 0:
            val, syb = t[i]
            for _ in range(num // val):
                roman_num += syb
                num -= val
            i += 1
        return roman_num

    @staticmethod
    def int2cardinalText(num):
        if not isinstance(num, int) or num < 0 or num > 999999999:
            raise ValueError(
                "Invalid number: must be a positive integer within four digits")
        base = ["Zero", "One", "Two", "Three", "Four", "Five", "Six",
                "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen",
                "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"]
        tens = ["", "", "Twenty", "Thirty", "Fourty",
                "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"]
        thousands = ["", "Thousand", "Million", "Billion"]

        def two_digits(n):
            if n < 20:
                return base[n]
            ten, unit = divmod(n, 10)
            if unit == 0:
                return f"{tens[ten]}"
            else:
                return f"{tens[ten]}-{base[unit]}"

        def three_digits(n):
            hundred, rest = divmod(n, 100)
            if hundred == 0:
                return two_digits(rest)
            result = f"{base[hundred]} hundred "
            if rest > 0:
                result += two_digits(rest)
            return result.strip()

        if num < 99:
            return two_digits(num)
        chunks = []
        while num > 0:
            num, remainder = divmod(num, 1000)
            chunks.append(remainder)
        words = []
        for i in range(len(chunks) - 1, -1, -1):
            if chunks[i] == 0:
                continue
            chunk_word = three_digits(chunks[i])
            if thousands[i]:
                chunk_word += f" {thousands[i]}"
            words.append(chunk_word)
        words = " ".join(words).lower()
        return words[0].upper() + words[1:]

    @staticmethod
    def int2ordinalText(num):
        if not isinstance(num, int) or num < 0 or num > 999999:
            raise ValueError(
                "Invalid number: must be a positive integer within four digits")
        base = ["Zero", "One", "Two", "Three", "Four", "Five", "Six",
                "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen",
                "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"]
        baseth = ['Zeroth', 'First', 'Second', 'Third', 'Fourth', 'Fifth', 'Sixth', 'Seventh',
                  'Eighth', 'Ninth', 'Tenth', 'Eleventh', 'Twelfth', 'Thirteenth', 'Fourteenth',
                  'Fifteenth', 'Sixteenth', 'Seventeenth', 'Eighteenth', 'Nineteenth', 'Twentieth']
        tens = ["", "", "Twenty", "Thirty", "Fourty",
                "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"]
        tensth = ["", "", "Twentieth", "Thirtieth", "Fortieth",
                  "Fiftieth", "Sixtieth", "Seventieth", "Eightieth", "Ninetieth"]

        def two_digits(n):
            if n <= 20:
                return baseth[n]
            ten, unit = divmod(n, 10)
            result = tensth[ten]
            if unit != 0:
                result = f"{tens[ten]}-{baseth[unit]}"
            return result

        thousand, num = divmod(num, 1000)
        result = []
        if thousand > 0:
            if num == 0:
                return f"{WithNumberDocxReader.int2cardinalText(thousand)} thousandth"
            result.append(f"{WithNumberDocxReader.int2cardinalText(thousand)} thousand")
        hundred, num = divmod(num, 100)
        if hundred > 0:
            if num == 0:
                result.append(f"{base[hundred]} hundredth")
                return " ".join(result)
            result.append(f"{base[hundred]} hundred")
        result.append(two_digits(num))
        result = " ".join(result).lower()
        return result[0].upper() + result[1:]

    @staticmethod
    def int2Chinese(num, ch_num, units):
        if not (0 <= num <= 99999999):
            raise ValueError("仅支持小于一亿以内的正整数")

        def int2Chinese_in(num, ch_num, units):
            if not (0 <= num <= 9999):
                raise ValueError("仅支持小于一万以内的正整数")
            result = [ch_num[int(i)] + unit for i, unit in zip(reversed(str(num).zfill(4)), units)]
            result = "".join(reversed(result))
            zero_char = ch_num[0]
            result = re.sub(f"(?:{zero_char}[{units}])+", zero_char, result)
            result = result.rstrip(units[0])
            if result != zero_char:
                result = result.rstrip(zero_char)
            if result.lstrip(zero_char).startswith("一十"):
                result = result.replace("一", "")
            return result

        if num < 10000:
            result = int2Chinese_in(num, ch_num, units)
        else:
            left = num // 10000
            right = num % 10000
            result = int2Chinese_in(left, ch_num, units) + "万" + int2Chinese_in(right, ch_num, units)
        if result != ch_num[0]:
            result = result.strip(ch_num[0])
        return result

    @staticmethod
    def int2ChineseCounting(num):
        return WithNumberDocxReader.int2Chinese(num, ch_num='〇一二三四五六七八九', units='个十百千')

    @staticmethod
    def int2ChineseLegalSimplified(num):
        return WithNumberDocxReader.int2Chinese(num, ch_num='零壹贰叁肆伍陆柒捌玖', units='个拾佰仟')

    def get_number_text(self, paragraph):
        numpr = paragraph._element.pPr.numPr
        # paragraph.getparent().tag
        if numpr is None or numpr.numId.val == 0:
            return ""
        numId = numpr.numId.val
        ilvl = numpr.ilvl.val
        style = self.numId2style[(numId, ilvl)]
        numFmt: str = style.get("numFmt")
        lvlText = style.get("lvlText")
        isTxbxContent = paragraph._element.getparent().tag.endswith("txbxContent")
        pos_key = (numId, ilvl, isTxbxContent)
        if pos_key in self.cnt:
            self.cnt[pos_key] += 1
        else:
            self.cnt[pos_key] = int(style["start"])
        pos = self.cnt[pos_key]
        num_text = str(pos)
        if numFmt.startswith('decimal'):
            num_text = num_text.zfill(numFmt.count("0") + 1)
        elif numFmt == 'upperRoman':
            num_text = self.int2upperRoman(pos)
        elif numFmt == 'lowerRoman':
            num_text = self.int2upperRoman(pos).lower()
        elif numFmt == 'upperLetter':
            num_text = self.int2upperLetter(pos)
        elif numFmt == 'lowerLetter':
            num_text = self.int2upperLetter(pos).lower()
        elif numFmt == 'ordinal':
            num_text = f"{pos}{'th' if 11 <= pos <= 13 else {1: 'st', 2: 'nd', 3: 'rd'}.get(pos % 10, 'th')}"
        elif numFmt == 'cardinalText':
            num_text = self.int2cardinalText(pos)
        elif numFmt == 'ordinalText':
            num_text = self.int2ordinalText(pos)
        elif numFmt == 'ideographTraditional':
            if 1 <= pos <= 10:
                num_text = self.ideographTraditional[pos - 1]
        elif numFmt == 'ideographZodiac':
            if 1 <= pos <= 12:
                num_text = self.ideographZodiac[pos - 1]
        elif numFmt == 'chineseCounting':
            num_text = self.int2ChineseCounting(pos)
        elif numFmt == 'chineseLegalSimplified':
            num_text = self.int2ChineseLegalSimplified(pos)
        elif numFmt == 'decimalEnclosedCircleChinese':
            pass
        self.cache[pos_key] = num_text
        for i in range(0, ilvl + 1):
            lvlText = lvlText.replace(f'%{i + 1}', self.cache.get((numId, i, isTxbxContent), ""))
        suff_text = {"space": " ", "nothing": ""}.get(style.get("suff"), self.gap_text)
        lvlText += suff_text
        return lvlText

测试一下:

if __name__ == '__main__':
    doc = WithNumberDocxReader(r"目录测试.docx", "")
    for text in doc.texts:
        print(text)

结果顺利正确打印:

35.甲公司为增值税一般纳税人,委托外单位加工一批应交消费税的商品,以银行存款支付 加工费200万元、增值税税额26万元、消费税税额30万元(由受托方代收代缴),该批 商品收回后将直接用于销售。甲公司支付上述相关款项时,应编制的会计分录是(    )。
A. 借:委托加工物资                                                       256
贷:银行存款                                                        256
B.借:委托加工物资                                                     230
应交税费——应交增值税(进项税额)                                26
贷:银行存款                                                        256
C. 借:委托加工物资                                                      200
应交税费   应交增值税(进项税额)                                26
——应交消费税                                           30
贷:银行存款                                                        256
D. 借:委托加工物资                                                       256
贷:银行存款                                                        200
应交税费——应交增值税(销项税额)                                26
——应交消费税                                              30
A.已确认销售收入但尚未发出商品
B.结转完工入库产品成本
C.已收到材料但尚未收到发票账单
D.已收到发票账单并付款但尚未收到材料

36.下列各项中,不会引起企业期末存货账面价值发生变动的是(    )。

A.已确认销售收入但尚未发出商品
B.结转完工入库产品成本
C.已收到材料但尚未收到发票账单
D.已收到发票账单并付款但尚未收到材料

以上是文本框的内容。

再次编号:
E.已确认销售收入但尚未发出商品
F.结转完工入库产品成本
G.已收到材料但尚未收到发票账单
H.已收到发票账单并付款但尚未收到材料

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/705317.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

maven 显式依赖包包含隐式依赖包,引起依赖包冲突

问题&#xff1a;FlinkCDC 3.0.1 代码 maven依赖包冲突 什么是依赖冲突 依赖冲突是指项目依赖的某一个jar包&#xff0c;有多个不同的版本&#xff0c;因而造成类包版本冲突 依赖冲突的原因 依赖冲突很经常是类包之间的间接依赖引起的。每个显式声明的类包都会依赖于一些其它…

springbot 界面美观的超市收银管理系统。

springbot 界面美观的超市收银管理系统。 功能&#xff1a;登录&#xff0c;用户管理&#xff0c;权限菜单管理&#xff0c;首页订单&#xff0c;收入&#xff0c;用户统计&#xff0c; 收银台&#xff0c;销售账单&#xff0c;库存管理&#xff0c;商品分类&#xff0c;供应…

如何在浏览器书签栏设置2个书签实现一键到达网页顶部和底部

本次设置浏览器为&#xff1a;Chrome浏览器&#xff08;其他浏览器可自行测试&#xff09; 1&#xff0c;随便收藏一个网页到浏览器书签栏 2&#xff0c;右键这个书签 3&#xff0c;修改 4&#xff0c;修改名称 5&#xff0c;修改网址&#xff1a; javascript:(function(…

Vue3中使用深度选择器不起作用

问题&#xff1a; 想要给这个菜单设置高度100%&#xff0c;使用深度样式选择器无效 这样写无效 但是如下在控制台写是有效果的 解决&#xff1a; 参考 解决方法是给这个组件增加一个根元素&#xff0c;然后再使用深度选择器

【Linux】线程(一)

谈论之前需要先谈论一些线程的背景知识 其中就有进程地址空间&#xff0c;又是这个让我们又爱又恨的东西 目录 背景知识&#xff1a;地址空间&#xff1a; 背景知识&#xff1a; 地址空间&#xff1a; 说在前边&#xff0c;OS通常分为4个核心模块&#xff1a;执行流管理&…

IDEA项目上传Github流程+常见问题解决

一、Github上创建仓库 项目创建好后如图所示 二、IDEA连接Github远程仓库 管理远程 复制远程地址 定义远程 登录Github 点击进入File->Settings->Version Control->Github登录自己的账号并勾上“√” 三、推送项目 点击推送 修改为main 点击确定&#xff0c;打开远程…

Python基础教程(十六):正则表达式

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

Linux服务器快速下载GoogleDriver小技巧——利用gdown工具

Linux服务器快速下载GoogleDriver小技巧——利用gdown工具 1. 安装gdown pip install gdown安装好后如果在终端输入gdown显示如下错误&#xff1a;gdown: command not found&#xff0c;则说明gdown默认安装的位置需要软链接一下&#xff0c;执行以下命令&#xff1a; sudo …

Qt全局快捷键QGlobalHotKey的自研之路

这两天对Qt的快捷键格外感兴趣。 前两天在使用QHotKey的过程中&#xff0c;发现不能定义小键盘键盘码&#xff0c;自己二次修改了该库之后已经可以设置小键盘快捷键了。文章在这里&#xff1a;Qt第三方库QHotKey设置小键盘数字快捷键。 昨天突发奇想&#xff1a;目前所有的快…

前端项目打包部署

打包 vue-cli脚手架的前端项目&#xff0c;点击npm脚本中的第二条编译命令&#xff0c;即可将项目编译&#xff0c;生成一个dist的文件夹&#xff0c;里面存放的就是编译好的前端项目文件&#xff0c;没有脚手架就在终端敲击npm run build命令编译前端项目 部署 Nginx 介绍:…

D咖饮品机入驻奇轩商贸,为DF101大规模入驻荆州拉开序幕

荆州&#xff0c;一座历史悠久的城市&#xff0c;如今正焕发着新的活力与魅力。而这股活力的源泉之一&#xff0c;正是奇轩商贸的一次创新尝试——D咖智能饮品机的入驻。这不仅仅是一次机器设备的更新&#xff0c;更是一场技术与美味的碰撞&#xff0c;为DF101大规模入驻荆州市…

汽车EDI:BRP EDI项目案例

项目背景 BRP Inc.使用EDI&#xff08;电子数据交换&#xff09;来处理其与供应商、客户和合作伙伴之间的业务交流。通过EDI&#xff0c;BRP可以在各种业务流程中自动化数据交换&#xff0c;例如采购订单、发货通知、发票、付款和库存信息等&#xff0c;从而提高操作效率、降低…

【小白学Python】自定义图片的生成(二)

Python学习 【小白学Python】自定义图片的生成&#xff08;一&#xff09; 目录 1. 文件内容2.生成图片规则3. 修改代码2.1 尝试一行汉字展示3.1 读取txt文件3.2 解决文字过长问题3.3 删减指定文字 4. 总结 1. 文件内容 正如上篇文章所说&#xff0c;我需要读取txt文件的文字内…

KUKA机器人KRC5控制柜面板LED显示

对于KUKA机器人新系列控制柜KRC5控制柜来说&#xff0c;其控制柜面板LED布局如下图&#xff1a; 其中①②③④分别为&#xff1a; 1、机器人控制柜处于不同状态时&#xff0c;LED显示如下&#xff1a; 2、机器人控制柜正在运行时&#xff1a; 3、机器人控制柜运行时出现的故障…

数据结构重要知识总结

数组 数组&#xff08;Array&#xff09; 是一种很常见的数据结构。它由相同类型的元素&#xff08;element&#xff09;组成&#xff0c;并且是使用一块连续的内存来存储。 我们直接可以利用元素的索引&#xff08;index&#xff09;可以计算出该元素对应的存储地址。 数组…

【C++】stack、queue模拟实现

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 导读 1. stack和queue的底层 1.1 stack 1.2 queue 2. 什么是适配器 3. 常见适配器 4. stack具体实现 4.1 成员变量 4.2 …

wms海外仓系统有哪些?选择的时候怎么避坑

虽然说wms海外仓系统能够在很大程度上提升海外仓的经营效率&#xff0c;但是如果在选择wms海外仓系统的时候没有慎重考虑&#xff0c;也是非常容易踩坑的。 这样不只是不能提升自己海外仓的效率&#xff0c;反倒是浪费了大量的预算和精力&#xff0c;这就得不偿失了。今天我们…

【Three.js】知识梳理二十三:Three.js与其他WebGL库与框架对比

在WebGL开发中&#xff0c;Three.js是一个非常流行的库&#xff0c;它简化了3D图形的创建和渲染过程。然而&#xff0c;市场上还有许多其他的WebGL库&#xff0c;如 Babylon.js、PlayCanvas、PIXI.js 和 Cesium&#xff0c;它们也有各自的特点和优势。本文将对Three.js 与这些常…

早知 121私人导航升级新版本, 第一次使用原生dialog标签。

早知121项目介绍说明 早知121 - 一个快速创建私人导航网站。 用途&#xff1a; 创建个人的工作导航&#xff0c;收集常用网址&#xff0c;可贡献给同事。创建个人垂直领域导航 优点&#xff1a; - 不需懂技术&#xff0c;不用维护服务器&#xff0c;维护私人导航收藏站。 网…

贝壳找房: 为 AI 平台打造混合多云的存储加速底座

贝壳机器学习平台的计算资源&#xff0c;尤其是 GPU&#xff0c;主要依赖公有云服务&#xff0c;并分布在不同的地理区域。为了让存储可以灵活地跟随计算资源&#xff0c;存储系统需具备高度的灵活性&#xff0c;支持跨区域的数据访问和迁移&#xff0c;同时确保计算任务的连续…