web-dev-qa-db-fra.com

Listes de puces dans python-docx

J'essaie de faire fonctionner ceci dans python-docx:

enter image description here

Une liste de puces que je peux obtenir en utilisant ceci:

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

Je pense que l'ajout d'un autre paragraphe au milieu n'aidera pas car cela signifierait essentiellement que je fais un autre List Bullet avec la même mise en forme que son parent et non la mise en forme enfantine que je souhaite. De plus, l'ajout d'un autre run au même paragraphe n'aide pas non plus (j'ai essayé, ça gâche tout ..). Une façon de le faire?

5
Vizag

Il existe un moyen de le faire, mais cela implique un peu de travail supplémentaire de votre part. Il n'y a actuellement aucune interface "native" dans python-docx pour ce faire. Chaque élément à puce doit être un paragraphe individuel. Les exécutions s'appliquent uniquement aux caractères du texte.

L'idée est que la puce ou la numérotation des listes est contrôlée par un style de puce ou de nombre concret, qui fait référence à un style abstrait. Le style abstrait détermine le style du paragraphe affecté, tandis que la numérotation concrète détermine le nombre/puce dans la séquence abstraite. Cela signifie que vous pouvez avoir des paragraphes sans puces et numérotation entrecoupés entre les paragraphes à puces. Dans le même temps, vous pouvez redémarrer la séquence de numérotation/puce à tout moment en créant un nouveau style concret.

Toutes ces informations sont hachées (en détail mais sans succès) dans numéro 25 . Je n'ai pas le temps ni les ressources pour mettre cela au repos en ce moment, mais j'ai écrit une fonction que j'ai laissée dans un commentaire dans le fil de discussion. Cette fonction recherchera un style abstrait basé sur le niveau d'indentation et le style de paragraphe souhaités. Il créera ou récupérera ensuite un style concret basé sur ce style abstrait et l'affectera à votre objet paragraphe:

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

En utilisant les styles du talon de document intégré par défaut, vous pouvez faire quelque chose comme ceci:

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)

Le style affectera non seulement les taquets de tabulation et les autres caractéristiques d'affichage du paragraphe, mais aidera également à rechercher le schéma de numérotation abstrait approprié. Lorsque vous définissez implicitement prev=None dans l'appel à p0, la fonction crée un nouveau schéma de numérotation concret. Tous les paragraphes restants hériteront du même schéma car ils obtiennent un paramètre prev. Les appels à list_number ne doit pas être entrelacé avec les appels à add_paragraph comme ça, tant que la numérotation du paragraphe utilisé comme prev est définie avant l'appel.

7
Mad Physicist