web-dev-qa-db-ja.com

python-docxの箇条書き

これをpython-docxで動作させるようにしています:

enter image description here

これを使用して取得できる箇条書きリスト:

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を追加しても効果はありません(私はこれを試しましたが、全体を台無しにしています。)。それを行う方法はありますか?

5
Vizag

それを行う方法はありますが、それはあなたの側で少し余分な作業を伴います。現在、python-docxには、これを行うための「ネイティブ」インターフェースはありません。箇条書きの各項目は、個別の段落でなければなりません。ランはテキスト文字にのみ適用されます。

リストの箇条書きまたは番号付けは、抽象的なスタイルを参照する具体的な箇条書きまたは番号スタイルによって制御されるという考え方です。抽象スタイルは、影響を受ける段落のスタイルを決定し、具体的な番号付けは、抽象シーケンス内の番号/箇条書きを決定します。つまり、箇条書きのない段落と、箇条書きの段落の間に番号を付けることができます。同時に、新しいコンクリートスタイルを作成することで、番号付け/箇条書きのシーケンスをいつでも再開できます。

この情報はすべて Issue#25 でハッシュされます(詳細には失敗します)。これを今すぐ休めるための時間やリソースはありませんが、ディスカッションスレッドの comment に残した関数を作成しました。この関数は、インデントのレベルと必要な段落スタイルに基づいて抽象的なスタイルを検索します。次に、その抽象的なスタイルに基づいて具象スタイルを作成または取得し、それを段落オブジェクトに割り当てます。

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パラメーターを取得するため、同じスキームを継承します。 prevとして使用される段落の番号付けが呼び出しの前に設定されている限り、list_numberの呼び出しをadd_paragraphの呼び出しとインターリーブする必要はありません。

7
Mad Physicist