python-docx 中的项目符号列表

新手上路,请多包涵

我试图让它在 python-docx 中工作:

在此处输入图像描述

我可以使用这个得到的项目符号列表:

 from docx import Document
doc = Document()
p = doc.add_paragraph()
p.style = 'List Bullet'

r = p.add_run()
r.add_text("Item 1")
# Something's gotta come here to get the Sub-Item 1
r = p.add_run()
r.add_text("Item 2")
# Something's gotta come here to get the Sub-Item 2

我想,在中间添加另一个段落不会有帮助,因为这基本上意味着我正在制作另一个 List Bullet 具有与其父级相同的格式,而不是我想要的类似子格式的格式。此外,在同一段落中添加另一个 run 也无济于事(我试过了,把整个事情搞砸了……)。有什么办法吗?

原文由 Vizag 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.4k
2 个回答

有一种方法可以做到这一点,但是您需要做一些额外的工作。目前在 python-docx 中没有用于执行此操作的“本机”接口。每个带项目符号的项目必须是一个单独的段落。运行仅适用于文本字符。

这个想法是列表项目符号或编号由具体的项目符号或编号样式控制,这是指抽象样式。抽象样式决定受影响段落的样式,而具体编号决定抽象序列中的编号/项目符号。这意味着您可以让没有项目符号的段落和散布在项目符号段落之间的编号。同时,您可以随时通过创建新的具体样式重新开始编号/项目符号序列。

所有这些信息都在 Issue #25 中散列(详细但未成功)。我现在没有时间或资源来解决这个问题,但我确实写了一个函数,我在讨论线程的 评论 中留下了它。此函数将根据您想要的缩进级别和段落样式查找抽象样式。然后它将基于该抽象样式创建或检索具体样式并将其分配给您的段落对象:

 def list_number(doc, par, prev=None, level=None, num=True):
    """
    Makes a paragraph into a list item with a specific level and
    optional restart.

    An attempt will be made to retreive an abstract numbering style that
    corresponds to the style of the paragraph. If that is not possible,
    the default numbering or bullet style will be used based on the
    ``num`` parameter.

    Parameters
    ----------
    doc : docx.document.Document
        The document to add the list into.
    par : docx.paragraph.Paragraph
        The paragraph to turn into a list item.
    prev : docx.paragraph.Paragraph or None
        The previous paragraph in the list. If specified, the numbering
        and styles will be taken as a continuation of this paragraph.
        If omitted, a new numbering scheme will be started.
    level : int or None
        The level of the paragraph within the outline. If ``prev`` is
        set, defaults to the same level as in ``prev``. Otherwise,
        defaults to zero.
    num : bool
        If ``prev`` is :py:obj:`None` and the style of the paragraph
        does not correspond to an existing numbering style, this will
        determine wether or not the list will be numbered or bulleted.
        The result is not guaranteed, but is fairly safe for most Word
        templates.
    """
    xpath_options = {
        True: {'single': 'count(w:lvl)=1 and ', 'level': 0},
        False: {'single': '', 'level': level},
    }

    def style_xpath(prefer_single=True):
        """
        The style comes from the outer-scope variable ``par.style.name``.
        """
        style = par.style.style_id
        return (
            'w:abstractNum['
                '{single}w:lvl[@w:ilvl="{level}"]/w:pStyle[@w:val="{style}"]'
            ']/@w:abstractNumId'
        ).format(style=style, **xpath_options[prefer_single])

    def type_xpath(prefer_single=True):
        """
        The type is from the outer-scope variable ``num``.
        """
        type = 'decimal' if num else 'bullet'
        return (
            'w:abstractNum['
                '{single}w:lvl[@w:ilvl="{level}"]/w:numFmt[@w:val="{type}"]'
            ']/@w:abstractNumId'
        ).format(type=type, **xpath_options[prefer_single])

    def get_abstract_id():
        """
        Select as follows:

            1. Match single-level by style (get min ID)
            2. Match exact style and level (get min ID)
            3. Match single-level decimal/bullet types (get min ID)
            4. Match decimal/bullet in requested level (get min ID)
            3. 0
        """
        for fn in (style_xpath, type_xpath):
            for prefer_single in (True, False):
                xpath = fn(prefer_single)
                ids = numbering.xpath(xpath)
                if ids:
                    return min(int(x) for x in ids)
        return 0

    if (prev is None or
            prev._p.pPr is None or
            prev._p.pPr.numPr is None or
            prev._p.pPr.numPr.numId is None):
        if level is None:
            level = 0
        numbering = doc.part.numbering_part.numbering_definitions._numbering
        # Compute the abstract ID first by style, then by num
        anum = get_abstract_id()
        # Set the concrete numbering based on the abstract numbering ID
        num = numbering.add_num(anum)
        # Make sure to override the abstract continuation property
        num.add_lvlOverride(ilvl=level).add_startOverride(1)
        # Extract the newly-allocated concrete numbering ID
        num = num.numId
    else:
        if level is None:
            level = prev._p.pPr.numPr.ilvl.val
        # Get the previous concrete numbering ID
        num = prev._p.pPr.numPr.numId.val
    par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_numId().val = num
    par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_ilvl().val = level

使用默认内置文档存根中的样式,您可以执行以下操作:

 d = docx.Document()
p0 = d.add_paragraph('Item 1', style='List Bullet')
list_number(d, p0, level=0, num=False)
p1 = d.add_paragraph('Item A', style='List Bullet 2')
list_number(d, p1, p0, level=1)
p2 = d.add_paragraph('Item 2', style='List Bullet')
list_number(d, p2, p1, level=0)
p3 = d.add_paragraph('Item B', style='List Bullet 2')
list_number(d, p3, p2, level=1)

样式不仅会影响段落的制表位和其他显示特征,而且还有助于查找适当的摘要编号方案。当您在对 --- prev=None p0 ,该函数会创建一个新的具体编号方案。所有剩余的段落将继承相同的方案,因为它们获得了 prev 参数。对 list_number 的调用不必与对 add_paragraph 的调用交织在一起,只要用作 prev 的段落的编号是set通话前。

您可以在我维护的名为 haggis 的库中找到此函数的实现,可在 GitHub 和 PyPi 上找到: haggis.files.docx.list_number

原文由 Mad Physicist 发布,翻译遵循 CC BY-SA 4.0 许可协议

我发现@Mad Physicist 的答案对缩进的项目符号列表不起作用。我将其修改为仅在布尔值 num 为 True 时放入 numId 的值——但这暴露了 get_abstract_id() 函数使用“num”作为其自己的局部变量。所以我在整个函数中将“num”更改为“numbr”,并在倒数第二行添加了一个布尔值 if:

 if num:
    par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_numId().val = numbr

所以对我来说这是整个功能:

 def get_abstract_id():
    """
    Select as follows:

        1. Match single-level by style (get min ID)
        2. Match exact style and level (get min ID)
        3. Match single-level decimal/bullet types (get min ID)
        4. Match decimal/bullet in requested level (get min ID)
        3. 0
    """
    for fn in (style_xpath, type_xpath):
        for prefer_single in (True, False):
            xpath = fn(prefer_single)
            ids = numbering.xpath(xpath)
            if ids:
                return min(int(x) for x in ids)
    return 0

if (prev is None or
        prev._p.pPr is None or
        prev._p.pPr.numPr is None or
        prev._p.pPr.numPr.numId is None):
    if level is None:
        level = 0
    numbering = doc.part.numbering_part.numbering_definitions._numbering
    # Compute the abstract ID first by style, then by num
    anum = get_abstract_id()
    # Set the concrete numbering based on the abstract numbering ID
    numbr = numbering.add_num(anum)
    # Make sure to override the abstract continuation property
    numbr.add_lvlOverride(ilvl=level).add_startOverride(1)
    # Extract the newly-allocated concrete numbering ID
    numbr = numbr.numId
else:
    if level is None:
        level = prev._p.pPr.numPr.ilvl.val
    # Get the previous concrete numbering ID
    numbr = prev._p.pPr.numPr.numId.val
if num:
    par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_numId().val = numbr
par._p.get_or_add_pPr().get_or_add_numPr().get_or_add_ilvl().val = level

非常感谢 Mad Physicist、scanny 以及所有在 python-docx 上辛勤工作的人;你帮了大忙!!!

编辑:我应该补充一点,我还利用了 scanny 的建议,从一个具有我想要的项目符号样式的文档开始,而不是从一个空白文档开始。在我的模板中,我能够更正项目符号的一些问题(其中一些错误地设置为数字)。然后我将结果保存到我想要的文件名中,一切都很好。

原文由 Lee Thomas 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏