如何在 python-docx 中声明新的 oxml/xmlchemy 标签?

How do I declare new oxml/xmlchemy tags in python-docx?

我正在尝试将基本方程功能构建到 python-docx 中以将公式输出到 docx 文件。有人可以查看在 oxml 中注册新 class 的标准操作程序吗?查看源代码,标签似乎是通过创建复杂类型 class

来声明的
class CT_P(BaseOxmlElement):
    """
    ''<w:p>'' element, containing the properties and text for a paragraph.
    """
    pPr = ZeroOrOne('w:pPr')
    r = ZeroOrMore('w:r')

然后使用 register_element_cls() 函数注册它

from .text.paragraph import CT_P
register_element_cls('w:p', CT_P)

一些 classes 包括其他方法,但很多没有,所以看起来最小的工作示例是这样的:

from docx import Document
from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrOne, ZeroOrMore, OxmlElement
import docx.oxml
docx.oxml.ns.nsmap['m'] = ('http://schemas.openxmlformats.org/officeDocument/2006/math')

class CT_OMathPara(BaseOxmlElement):
    r = ZeroOrMore('w:r')

docx.oxml.register_element_cls('m:oMathPara',CT_OMathPara)  
p = CT_OMathPara()

(注意,我必须声明 m 命名空间,因为它没有在包中使用)。不幸的是,这对我根本不起作用。如果我在上面的例子中声明一个新的 class 派生,然后检查,例如,这个新的 class 的 __repr__,它会导致异常

>> p

File "C:\ProgramData\Anaconda3\lib\site-packages\docx\oxml\ns.py", line 50, in from_clark_name
    nsuri, local_name = clark_name[1:].split('}')

ValueError: not enough values to unpack (expected 2, got 1)

发生这种情况是因为我的 class 中的标签与从 python-docx 包

创建的 w:p 标签非常不同
>> paragraph._element.tag
 '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p'

>> p.tag
 'CT_OMathPara'

但我不知道这是为什么。通过源代码的文件搜索没有显示 CT_P class 的任何其他提及,所以我有点难过。

我认为错误来自 'm' 名称空间前缀 (nspfx) 不存在于 docx.oxml.ns.pfxmap 字典中。命名空间需要双向查找(从 nspfx 到命名空间 url 从 url 到 nspfx)。

所以要从 "outside" 添加新的命名空间,这意味着在加载 ns 模块之后,您需要同时执行这两项操作(如果您要修补 ns 模块直接编码,第二步将在加载时自动处理):

nsmap, pfxmap = docx.oxml.ns.nsmap, docx.oxml.ns.pfxmap
nsmap['m'] = 'http://schemas.openxmlformats.org/officeDocument/2006/math'
pfxmap['http://schemas.openxmlformats.org/officeDocument/2006/math'] = 'm'

这应该可以帮助您解决所遇到的错误,但还有更多内容需要了解。

CT_OMathPara class 是所谓的 自定义元素 class 的示例。这意味着 lxml 为每个具有注册标签 (m:oMathPara) 的元素实例化此 class 的对象,而不是通用的 lxml _Element class.

关键是,你需要让 lxml 进行构造 ,这在它解析 XML 时发生。您无法通过自己构造 class 来获得有意义的对象。

创建新 "loose" 元素(不在 XML 文档树中)的最简单方法是使用 docx.oxml.OxmlElement():

oMathPara = OxmlElement('m:oMathPara')

但更常见的是,docx.oxml.parse_xml() 函数用于解析整个 XML 片段。需要将解析器配置为使用自定义元素 classes,并且这些元素必须向解析器注册,因此当 oxml 模块中的元素时,您可能不想自己这样做会照顾所有需要的人。

所以通常,要获得 CT_OMathPara 的实例,您只需打开一个包含 m:oMathPara 元素的 docx(在注册新的命名空间和自定义元素 class 之后),但您也可以只解析 XML 片段。如果您在 oxml 模块中搜索 parse_xml,您会找到大量示例。您需要在您提供的 XML 顶部获取名称空间声明,这可能有点棘手,但如果您愿意,您当然可以在文本中拼出整个 XML 片段,它只是有点冗长。