使用 Python-Docx 获取 docx 文件中列表项的列表编号
Getting the List Numbers of List Items in docx file using Python-Docx
当我访问段落文本时,它不包括列表中的编号。
当前代码:
document = Document("C:/Foo.docx")
for p in document.paragraphs:
print(p.text)
docx 文件中的列表:
我期待:
(1) 两者入籍...
(2) ...
入籍
(3) ...
入籍
我得到的:
两者的归化...
...
的归化
...
入籍
检查文档的 XML 后,列表编号存储在 w:abstructNum 中,但我不知道如何访问它们或将它们连接到正确的列表项。
如何访问 python-docx 中每个列表项的编号,以便它们可以包含在我的输出中?
还有一种方法可以使用 python-docx 来确定这些列表的正确嵌套吗?
这对我有用,使用模块 docx2python
from docx2python import docx2python
document = docx2python("C:/input/MyDoc.docx")
print(document.body)
根据 [ReadThedocs.Python-DocX]: Style-related objects - _NumberingStyle objects, this functionality is not implemented yet.
The alternative (at least one of them) [PyPI]: docx2python 处理这些元素有点糟糕(主要是因为它 returns 所有内容都转换为字符串)。
因此,一个解决方案是手动解析 XML 文件 - 发现如何凭经验处理这个例子。一个好的文档位置是 Office Open XML(我不知道它是否是所有处理 .docx 文件的工具所遵循的标准(尤其是 MS Word )):
- 从word/document.[=178=获取每个段落(w:p节点) ]
检查它是否是编号项目(它有w:pPr -> w:numPr) 子节点
获取号码样式Id和等级:w:val w:numId 和 w:ilvl 的属性子节点(来自上一个项目符号的节点)
将 2 个值与 (in word/numbering.xml):
匹配
- w:abstractNumId w:abstractNum[= 的属性105=]节点
- w:ilvl w:lvl[=的属性105=] 子节点
并得到对应w:numFmt[=的w:val属性108=] 和 w:lvlText 子节点(请注意,项目符号也包括在内,可以根据 bullet 上述 w:numFmt 属性的值)
然而,这看起来 极其复杂,所以我提出了一个利用 的解决方法 (gainarie) docx2python部分支持。
测试文档(sample.docx - 使用 LibreOffice 创建):
code00.py:
#!/usr/bin/env python
import sys
import docx
from docx2python import docx2python as dx2py
def ns_tag_name(node, name):
if node.nsmap and node.prefix:
return "{{{:s}}}{:s}".format(node.nsmap[node.prefix], name)
return name
def descendants(node, desc_strs):
if node is None:
return []
if not desc_strs:
return [node]
ret = {}
for child_str in desc_strs[0]:
for child in node.iterchildren(ns_tag_name(node, child_str)):
descs = descendants(child, desc_strs[1:])
if not descs:
continue
cd = ret.setdefault(child_str, [])
if isinstance(descs, list):
cd.extend(descs)
else:
cd.append(descs)
return ret
def simplified_descendants(desc_dict):
ret = []
for vs in desc_dict.values():
for v in vs:
if isinstance(v, dict):
ret.extend(simplified_descendants(v))
else:
ret.append(v)
return ret
def process_list_data(attrs, dx2py_elem):
#print(simplified_descendants(attrs))
desc = simplified_descendants(attrs)[0]
level = int(desc.attrib[ns_tag_name(desc, "val")])
elem = [i for i in dx2py_elem[0].split("\t") if i][0]#.rstrip(")")
return " " * level + elem + " "
def main(*argv):
fname = r"./sample.docx"
docd = docx.Document(fname)
docdpy = dx2py(fname)
dr = docdpy.docx_reader
#print(dr.files) # !!! Check word/numbering.xml !!!
docdpy_runs = docdpy.document_runs[0][0][0]
if len(docd.paragraphs) != len(docdpy_runs):
print("Lengths don't match. Abort")
return -1
subnode_tags = (("pPr",), ("numPr",), ("ilvl",)) # (("pPr",), ("numPr",), ("ilvl", "numId")) # numId is for matching elements from word/numbering.xml
for idx, (par, l) in enumerate(zip(docd.paragraphs, docdpy_runs)):
#print(par.text, l)
numbered_attrs = descendants(par._element, subnode_tags)
#print(numbered_attrs)
if numbered_attrs:
print(process_list_data(numbered_attrs, l) + par.text)
else:
print(par.text)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q066374154]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
Doc title
doc subtitle
heading1 text0
Paragr0 line0
Paragr0 line1
Paragr0 line2
space Paragr0 line3
a) aa (numbered)
heading1 text1
Paragrx line0
Paragrx line1
a) w tabs Paragrx line2 (NOT numbered – just to mimic 1ax below)
1) paragrx 1x (numbered)
a) paragrx 1ax (numbered)
I) paragrx 1aIx (numbered)
b) paragrx 1bx (numbered)
2) paragrx 2x (numbered)
3) paragrx 3x (numbered)
-- paragrx bullet 0
-- paragrx bullet 00
paragxx text
Done.
备注:
- 仅处理来自 word/document.xml 的节点(通过段落的 _element (LXML节点)属性)
- 一些列表属性没有被捕获(由于 docx2python 的限制)
- 这远非稳健
- descendants, simplified_descendants 可以简化很多,但我想尽可能保持前者的通用性(如果功能需要扩展)
当我访问段落文本时,它不包括列表中的编号。
当前代码:
document = Document("C:/Foo.docx")
for p in document.paragraphs:
print(p.text)
docx 文件中的列表:
我期待:
(1) 两者入籍...
(2) ...
入籍
(3) ...
我得到的:
两者的归化...
...
的归化
...
检查文档的 XML 后,列表编号存储在 w:abstructNum 中,但我不知道如何访问它们或将它们连接到正确的列表项。 如何访问 python-docx 中每个列表项的编号,以便它们可以包含在我的输出中? 还有一种方法可以使用 python-docx 来确定这些列表的正确嵌套吗?
这对我有用,使用模块 docx2python
from docx2python import docx2python
document = docx2python("C:/input/MyDoc.docx")
print(document.body)
根据 [ReadThedocs.Python-DocX]: Style-related objects - _NumberingStyle objects, this functionality is not implemented yet.
The alternative (at least one of them) [PyPI]: docx2python 处理这些元素有点糟糕(主要是因为它 returns 所有内容都转换为字符串)。
因此,一个解决方案是手动解析 XML 文件 - 发现如何凭经验处理这个例子。一个好的文档位置是 Office Open XML(我不知道它是否是所有处理 .docx 文件的工具所遵循的标准(尤其是 MS Word )):
- 从word/document.[=178=获取每个段落(w:p节点) ]
检查它是否是编号项目(它有w:pPr -> w:numPr) 子节点
获取号码样式Id和等级:w:val w:numId 和 w:ilvl 的属性子节点(来自上一个项目符号的节点)
将 2 个值与 (in word/numbering.xml):
匹配- w:abstractNumId w:abstractNum[= 的属性105=]节点
- w:ilvl w:lvl[=的属性105=] 子节点
并得到对应w:numFmt[=的w:val属性108=] 和 w:lvlText 子节点(请注意,项目符号也包括在内,可以根据 bullet 上述 w:numFmt 属性的值)
然而,这看起来 极其复杂,所以我提出了一个利用 的解决方法 (gainarie) docx2python部分支持。
测试文档(sample.docx - 使用 LibreOffice 创建):
code00.py:
#!/usr/bin/env python
import sys
import docx
from docx2python import docx2python as dx2py
def ns_tag_name(node, name):
if node.nsmap and node.prefix:
return "{{{:s}}}{:s}".format(node.nsmap[node.prefix], name)
return name
def descendants(node, desc_strs):
if node is None:
return []
if not desc_strs:
return [node]
ret = {}
for child_str in desc_strs[0]:
for child in node.iterchildren(ns_tag_name(node, child_str)):
descs = descendants(child, desc_strs[1:])
if not descs:
continue
cd = ret.setdefault(child_str, [])
if isinstance(descs, list):
cd.extend(descs)
else:
cd.append(descs)
return ret
def simplified_descendants(desc_dict):
ret = []
for vs in desc_dict.values():
for v in vs:
if isinstance(v, dict):
ret.extend(simplified_descendants(v))
else:
ret.append(v)
return ret
def process_list_data(attrs, dx2py_elem):
#print(simplified_descendants(attrs))
desc = simplified_descendants(attrs)[0]
level = int(desc.attrib[ns_tag_name(desc, "val")])
elem = [i for i in dx2py_elem[0].split("\t") if i][0]#.rstrip(")")
return " " * level + elem + " "
def main(*argv):
fname = r"./sample.docx"
docd = docx.Document(fname)
docdpy = dx2py(fname)
dr = docdpy.docx_reader
#print(dr.files) # !!! Check word/numbering.xml !!!
docdpy_runs = docdpy.document_runs[0][0][0]
if len(docd.paragraphs) != len(docdpy_runs):
print("Lengths don't match. Abort")
return -1
subnode_tags = (("pPr",), ("numPr",), ("ilvl",)) # (("pPr",), ("numPr",), ("ilvl", "numId")) # numId is for matching elements from word/numbering.xml
for idx, (par, l) in enumerate(zip(docd.paragraphs, docdpy_runs)):
#print(par.text, l)
numbered_attrs = descendants(par._element, subnode_tags)
#print(numbered_attrs)
if numbered_attrs:
print(process_list_data(numbered_attrs, l) + par.text)
else:
print(par.text)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q066374154]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32 Doc title doc subtitle heading1 text0 Paragr0 line0 Paragr0 line1 Paragr0 line2 space Paragr0 line3 a) aa (numbered) heading1 text1 Paragrx line0 Paragrx line1 a) w tabs Paragrx line2 (NOT numbered – just to mimic 1ax below) 1) paragrx 1x (numbered) a) paragrx 1ax (numbered) I) paragrx 1aIx (numbered) b) paragrx 1bx (numbered) 2) paragrx 2x (numbered) 3) paragrx 3x (numbered) -- paragrx bullet 0 -- paragrx bullet 00 paragxx text Done.
备注:
- 仅处理来自 word/document.xml 的节点(通过段落的 _element (LXML节点)属性)
- 一些列表属性没有被捕获(由于 docx2python 的限制)
- 这远非稳健
- descendants, simplified_descendants 可以简化很多,但我想尽可能保持前者的通用性(如果功能需要扩展)