opt-in presence record
[redakcja.git] / src / documents / xml_tools.py
1 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from copy import deepcopy
5 import re
6
7 from lxml import etree
8 from .constants import TRIM_BEGIN, TRIM_END, MASTERS
9
10 RE_TRIM_BEGIN = re.compile("^<!--%s-->$" % TRIM_BEGIN, re.M)
11 RE_TRIM_END = re.compile("^<!--%s-->$" % TRIM_END, re.M)
12
13
14 class ParseError(BaseException):
15     pass
16
17
18 def _trim(text, trim_begin=True, trim_end=True):
19     """ 
20         Cut off everything before RE_TRIM_BEGIN and after RE_TRIM_END, so
21         that eg. one big XML file can be compiled from many small XML files.
22     """
23     if trim_begin:
24         text = RE_TRIM_BEGIN.split(text, maxsplit=1)[-1]
25     if trim_end:
26         text = RE_TRIM_END.split(text, maxsplit=1)[0]
27     return text
28
29
30 def compile_text(parts):
31     """ 
32         Compiles full text from an iterable of parts,
33         trimming where applicable.
34     """
35     texts = []
36     trim_begin = False
37     text = ''
38     for next_text in parts:
39         if not next_text:
40             continue
41         if text:
42             # trim the end, because there's more non-empty text
43             # don't trim beginning, if `text' is the first non-empty part
44             texts.append(_trim(text, trim_begin=trim_begin))
45             trim_begin = True
46         text = next_text
47     # don't trim the end, because there's no more text coming after `text'
48     # only trim beginning if it's not still the first non-empty
49     texts.append(_trim(text, trim_begin=trim_begin, trim_end=False))
50     return "".join(texts)
51
52
53 def add_trim_begin(text):
54     trim_tag = etree.Comment(TRIM_BEGIN)
55     e = etree.fromstring(text)
56     for master in e[::-1]:
57         if master.tag in MASTERS:
58             break
59     if master.tag not in MASTERS:
60         raise ParseError('No master tag found!')
61
62     master.insert(0, trim_tag)
63     trim_tag.tail = '\n\n\n' + (master.text or '')
64     master.text = '\n'
65     return str(etree.tostring(e, encoding="utf-8"), 'utf-8')
66
67
68 def add_trim_end(text):
69     trim_tag = etree.Comment(TRIM_END)
70     e = etree.fromstring(text)
71     for master in e[::-1]:
72         if master.tag in MASTERS:
73             break
74     if master.tag not in MASTERS:
75         raise ParseError('No master tag found!')
76
77     master.append(trim_tag)
78     trim_tag.tail = '\n'
79     prev = trim_tag.getprevious()
80     if prev is not None:
81         prev.tail = (prev.tail or '') + '\n\n\n'
82     else:
83         master.text = (master.text or '') + '\n\n\n'
84     return str(etree.tostring(e, encoding="utf-8"), 'utf-8')
85
86
87 def split_xml(text):
88     """Splits text into chapters.
89
90     All this stuff really must go somewhere else.
91
92     """
93     src = etree.fromstring(text)
94     chunks = []
95
96     splitter = u'naglowek_rozdzial'
97     parts = src.findall('.//naglowek_rozdzial')
98     while parts:
99         # copy the document
100         copied = deepcopy(src)
101
102         element = parts[-1]
103
104         # find the chapter's title
105         name_elem = deepcopy(element)
106         for tag in 'extra', 'motyw', 'pa', 'pe', 'pr', 'pt', 'uwaga':
107             for a in name_elem.findall('.//' + tag):
108                 a.text=''
109                 del a[:]
110         name = etree.tostring(name_elem, method='text', encoding='utf-8').strip()
111
112         # in the original, remove everything from the start of the last chapter
113         parent = element.getparent()
114         del parent[parent.index(element):]
115         element, parent = parent, parent.getparent()
116         while parent is not None:
117             del parent[parent.index(element) + 1:]
118             element, parent = parent, parent.getparent()
119
120         # in the copy, remove everything before the last chapter
121         element = copied.findall('.//naglowek_rozdzial')[-1]
122         parent = element.getparent()
123         while parent is not None:
124             parent.text = None
125             while parent[0] is not element:
126                 del parent[0]
127             element, parent = parent, parent.getparent()
128         chunks[:0] = [[name,
129             str(etree.tostring(copied, encoding='utf-8'), 'utf-8')
130             ]]
131
132         parts = src.findall('.//naglowek_rozdzial')
133
134     chunks[:0] = [[u'początek',
135         str(etree.tostring(src, encoding='utf-8'), 'utf-8')
136         ]]
137
138     for ch in chunks[1:]:
139         ch[1] = add_trim_begin(ch[1])
140     for ch in chunks[:-1]:
141         ch[1] = add_trim_end(ch[1])
142
143     return chunks