Pregunta

I'm automating the process of creating a Word document with the python-docx module. In particular, I'm creating a multiple choice test where the questions are numbered 1., 2., 3., ... and under each question there are 4 answers that should be labeled as A., B., C., and D. I used a style to create the numbered list and the lettered list. However, I don't know how to restart the letters. For example, the answers for the 2nd question would range from E., F., G., H. Does anyone know how to restart the lettering back to A? I could manually specify the lettering in the answer string but I'm wondering how to do it with the style sheet. Thank you.

¿Fue útil?

Solución

The short answer is that this is not supported yet in python-docx but we might be able to provide you with a workaround if you ask on this issue on the Github issue list for the project: https://github.com/python-openxml/python-docx/issues/25

This particular operation turns out to be way more difficult in Word than anyone I know would have imagined. I believe that has to do with a need to maintain certain backward compatibilities on so many versions across the couple three decades of Word now.

Just to give you an idea, the style references a numbering definition which itself references an abstract numbering definition. Each paragraph with the style gets the next number/letter in the sequence. To restart the sequence, you have to create a NEW numbering definition that references the same abstract numbering sequence as the prior one. Then you reference the new numbering definition on the paragraph where the sequence should restart. Paragraphs with the style following that one get the next number/letter in the restarted sequence.

In order to accomplish that, you need to:

  1. locate the numbering definition of the style
  2. locate the abstract numbering definition it points to
  3. create a new numbering definition with the restart bit set
  4. tweak the paragraph element to refer to the new numbering definition

Anyway, now that I've vented about all that I can tell you we've actually gotten it to work before. We haven't added it to the API yet mostly I suppose because it's not entirely clear what the API should look like and it hasn't risen to the top of the backlog yet. But a couple workaround functions could probably get it done for you if you want it badly enough.

In your case I suppose it would be a toss-up. I suppose I would strongly consider placing those bits in directly in the paragraph in this case, but you'll be best able to decide.

Otros consejos

I've created a pull request (#582) that addresses this situation at a low-level. All I have done is define the XML types necessary to implement the numbering subsystem of WML. @scanny has create a submodule called xmlchemy that creates a semi-abstract representation of the XML so that you can handle multilevel lists and other numbering tasks if you are familiar with the standard. So if you build my fork, the following code will work:

 #!/usr/bin/python

from docx import Document
from docx import oxml


d = Document()


"""
1. Create an abstract numbering definition for a multi-level numbering style.
"""
numXML = d.part.numbering_part.numbering_definitions._numbering
nextAbstractId = max([ J.abstractNumId for J in numXML.abstractNum_lst ] ) + 1
l = numXML.add_abstractNum()
l.abstractNumId = nextAbstractId
m = l.add_multiLevelType()
m.val = 'multiLevel'


"""
2. Define numbering formats for each (zero-indexed)
    level. N.B. The formatting text is one-indexed.
    The user agent will accept up to nine levels.
"""
formats = {0: "decimal", 1: "upperLetter" }
textFmts = {0: '%1.', 1: '%2.' }
for i in range(2):
    lvl = l.add_lvl()
    lvl.ilvl = i
    n = lvl.add_numFmt()
    n.val = formats[i]
    lt = lvl.add_lvlText()
    lt.val = textFmts[i]

"""
3. Link the abstract numbering definition to a numbering definition.
"""
n = numXML.add_num(nextAbstractId)

"""
4. Define a function to set the (0-indexed) numbering level of a paragraph.
"""
def set_ilvl(p,ilvl):
    pr = p._element._add_pPr()
    np = pr.get_or_add_numPr()
    il = np.get_or_add_ilvl()
    il.val = ilvl
    ni = np.get_or_add_numId()
    ni.val = n.numId
    return(p)

"""
5. Create some content
"""
for x in [1,2,3]:
    p = d.add_paragraph()
    set_ilvl(p,0)
    p.add_run("Question %i" % x)
    for y in [1,2,3,4]:
        p2 = d.add_paragraph()
        set_ilvl(p2,1)
        p2.add_run("Choice %i" % y)


d.save('test.docx')
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top