File size: 5,549 Bytes
55d9355
 
 
 
 
 
 
 
ca18aa5
 
 
9c306e9
ca18aa5
 
 
 
 
 
6bc379e
 
 
 
 
 
 
ca18aa5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55d9355
 
 
9c306e9
55d9355
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""Notas de rodapé nativas, inclusive quando o documento já contém notas."""
from lxml import etree
from docx.oxml import OxmlElement, parse_xml
from docx.oxml.ns import qn
from docx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
from docx.opc.packuri import PackURI
from docx.opc.part import Part, XmlPart
from docx.shared import Pt
from docx.text.paragraph import Paragraph
from copy import deepcopy
import re
from .public_text import public_text


def add_first_footnote(doc, pattern, text):
    """Attach a note immediately after the first body/table occurrence of a term."""
    for element in doc.element.body.iter(qn('w:p')):
        paragraph = Paragraph(element, doc._body)
        if element.find('.//'+qn('m:oMath')) is not None:
            display_text = ''.join(node.text or '' for node in element.iter()
                                   if node.tag in (qn('w:t'),qn('m:t')))
            if re.search(pattern,display_text):
                # Word footnote references belong outside the math object.
                # Keep the expression editable and place its marker after it.
                return add_footnote(doc,paragraph,text)
        match = re.search(pattern, paragraph.text)
        if not match:
            continue
        offset = 0
        for run in paragraph.runs:
            end = offset + len(run.text)
            if offset < match.end() <= end:
                cut = match.end() - offset
                tail = run.text[cut:]
                run.text = run.text[:cut]
                identifier = add_footnote(doc, paragraph, text)
                marker = paragraph.runs[-1]._r
                run._r.addnext(marker)
                if tail:
                    remainder = OxmlElement('w:r')
                    if run._r.rPr is not None:
                        remainder.append(deepcopy(run._r.rPr))
                    content = OxmlElement('w:t')
                    content.text = tail
                    content.set(qn('xml:space'), 'preserve')
                    remainder.append(content)
                    marker.addnext(remainder)
                return identifier
            offset = end
        return add_footnote(doc, paragraph, text)
    return None


def order_footnotes(doc):
    """Keep IDs and stored note order aligned with their first body occurrence.

    Notes can be attached retroactively to earlier terms. Align both orders so
    Word and LibreOffice associate every visible number with the right text.
    """
    try:
        part = doc.part.part_related_by(RT.FOOTNOTES)
    except KeyError:
        return
    root = parse_xml(part.blob)
    notes = {n.get(qn('w:id')): n for n in root if int(n.get(qn('w:id'))) > 0}
    refs = list(doc.element.body.iter(qn('w:footnoteReference')))
    ordered_ids = list(dict.fromkeys(r.get(qn('w:id')) for r in refs))
    ordered_ids += [key for key in notes if key not in ordered_ids]
    mapping = {key: str(i+1) for i,key in enumerate(ordered_ids)}
    for reference in refs:
        reference.set(qn('w:id'),mapping[reference.get(qn('w:id'))])
    for old_id in ordered_ids:
        note = notes[old_id]
        note.set(qn('w:id'),mapping[old_id])
        root.append(note)
    if isinstance(part,XmlPart):
        part._element = root
    else:
        part._blob = etree.tostring(root,xml_declaration=True,encoding='UTF-8',standalone=True)


def add_footnote(doc, paragraph, text):
    text = public_text(text)
    try:
        part = doc.part.part_related_by(RT.FOOTNOTES)
        root = parse_xml(part.blob)
    except KeyError:
        root = OxmlElement('w:footnotes')
        for identifier, kind in [(-1, 'separator'), (0, 'continuationSeparator')]:
            note = OxmlElement('w:footnote')
            note.set(qn('w:id'), str(identifier)); note.set(qn('w:type'), kind)
            p = OxmlElement('w:p'); r = OxmlElement('w:r')
            r.append(OxmlElement('w:'+kind)); p.append(r); note.append(p); root.append(note)
        part = Part(PackURI('/word/footnotes.xml'), CT.WML_FOOTNOTES, b'', doc.part.package)
        doc.part.relate_to(part, RT.FOOTNOTES)
    identifier = max([0] + [int(n.get(qn('w:id'))) for n in root]) + 1
    note = OxmlElement('w:footnote'); note.set(qn('w:id'), str(identifier))
    p = OxmlElement('w:p'); props = OxmlElement('w:pPr')
    spacing = OxmlElement('w:spacing'); spacing.set(qn('w:after'), '60')
    props.append(spacing); p.append(props)
    for marker, value in [('footnoteRef', None), ('t', ' '+text)]:
        r = OxmlElement('w:r'); rp = OxmlElement('w:rPr')
        font = OxmlElement('w:rFonts')
        font.set(qn('w:ascii'), 'Arial'); font.set(qn('w:hAnsi'), 'Arial'); rp.append(font)
        size = OxmlElement('w:sz'); size.set(qn('w:val'), '14'); rp.append(size)
        if value is None:
            vertical = OxmlElement('w:vertAlign'); vertical.set(qn('w:val'), 'superscript'); rp.append(vertical)
        r.append(rp); element = OxmlElement('w:'+marker)
        if value is not None:
            element.text = value; element.set(qn('xml:space'), 'preserve')
        r.append(element); p.append(r)
    note.append(p); root.append(note)
    if isinstance(part, XmlPart):
        part._element = root
    else:
        part._blob = etree.tostring(root, xml_declaration=True, encoding='UTF-8', standalone=True)
    run = paragraph.add_run(); run.font.superscript = True
    run.font.size = Pt(7)
    reference = OxmlElement('w:footnoteReference'); reference.set(qn('w:id'), str(identifier))
    run._r.append(reference)
    return identifier