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 中没有 "native" 接口来执行此操作。每个带项目符号的项目必须是一个单独的段落。运行仅适用于文本字符。
想法是列表项目符号或编号由具体的项目符号或编号样式控制,具体的项目符号或编号样式指的是抽象样式。抽象样式决定了受影响段落的样式,而具体编号决定了抽象序列中的number/bullet。这意味着您可以让没有项目符号的段落和散布在项目符号段落之间的编号。同时,您可以随时通过创建新的具体样式重新启动 numbering/bulleting 序列。
所有这些信息都在讨论线程的 Issue #25. I don't have the time or resources to lay this to rest right now, but I did write a function that I left in a 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)
样式不仅会影响段落的制表位和其他显示特征,而且还有助于查找适当的摘要编号方案。当您在 p0
的调用中隐式设置 prev=None
时,该函数会创建一个新的具体编号方案。所有剩余的段落都将继承相同的方案,因为它们获得了一个 prev
参数。对 list_number
的调用不必像那样与对 add_paragraph
的调用交织在一起,只要在调用之前设置用作 prev
的段落的编号即可。
我正试图让它在 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 中没有 "native" 接口来执行此操作。每个带项目符号的项目必须是一个单独的段落。运行仅适用于文本字符。
想法是列表项目符号或编号由具体的项目符号或编号样式控制,具体的项目符号或编号样式指的是抽象样式。抽象样式决定了受影响段落的样式,而具体编号决定了抽象序列中的number/bullet。这意味着您可以让没有项目符号的段落和散布在项目符号段落之间的编号。同时,您可以随时通过创建新的具体样式重新启动 numbering/bulleting 序列。
所有这些信息都在讨论线程的 Issue #25. I don't have the time or resources to lay this to rest right now, but I did write a function that I left in a 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)
样式不仅会影响段落的制表位和其他显示特征,而且还有助于查找适当的摘要编号方案。当您在 p0
的调用中隐式设置 prev=None
时,该函数会创建一个新的具体编号方案。所有剩余的段落都将继承相同的方案,因为它们获得了一个 prev
参数。对 list_number
的调用不必像那样与对 add_paragraph
的调用交织在一起,只要在调用之前设置用作 prev
的段落的编号即可。