簡體   English   中英

python-docx 中的項目符號列表

[英]Bullet Lists in 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也無濟於事(我試過這個,搞砸了整個事情......)。 有什么辦法嗎?

有一種方法可以做到這一點,但它需要你做一些額外的工作。 python-docx 中目前沒有用於執行此操作的“本機”接口。 每個項目符號必須是一個單獨的段落。 運行僅適用於文本字符。

這個想法是列表項目符號或編號由具體的項目符號或數字樣式控制,它指的是抽象樣式。 抽象樣式決定了受影響段落的樣式,而具體編號決定了抽象序列中的編號/項目符號。 這意味着您可以有沒有項目符號的段落,並且在項目符號段落之間穿插編號。 同時,您可以通過創建新的具體樣式隨時重新開始編號/項目符號序列。

所有這些信息都在問題 #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)

樣式不僅會影響段落的制表位和其他顯示特征,還會幫助查找適當的摘要編號方案。 當您在p0調用中隱式設置prev=None時,該函數會創建一個新的具體編號方案。 所有剩余的段落都將繼承相同的方案,因為它們獲得了一個prev參數。 list_number的調用不必像這樣與對add_paragraph的調用交錯,只要在調用之前設置用作prev的段落的編號即可。

您可以在我維護的名為haggis的庫中找到此函數的實現,該庫可在 GitHub 和 PyPi 上找到: haggis.files.docx.list_number

我發現@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 的建議從具有我想要的項目符號樣式的文檔開始,而不是從空白文檔開始。 在我的模板中,我能夠更正項目符號的一些問題(其中一些被錯誤地設置為數字)。 然后我將結果保存到我想要的文件名,一切都很好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM