1 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
7 from librarian import dcparser, RDFNS
8 from librarian.util import get_translation
10 def last_words(text, n):
12 for w in reversed(text.split()):
20 return n, ' '.join(reversed(words))
23 class WLElement(etree.ElementBase):
24 SECTION_PRECEDENCE = None
39 EPUB_START_CHUNK = False
45 text_substitutions = [
48 #('...', '…'), # Temporary turnoff for epub
53 ("'", "\u2019"), # This was enabled for epub.
57 def meta_object(self):
58 if not hasattr(self, '_meta_object'):
59 elem = self.find(RDFNS('RDF'))
61 self._meta_object = dcparser.BookInfo.from_element(elem)
63 self._meta_object = None
64 return self._meta_object
68 if self.meta_object is not None:
69 return self.meta_object
71 if self.getparent() is not None:
72 return self.getparent().meta
74 return self.document.base_meta
78 return get_translation(self.meta.language).gettext
80 def in_context_of(self, setting):
81 parent = self.getparent()
85 return getattr(parent, setting)
86 except AttributeError:
87 return parent.in_context_of(setting)
89 def get_context_map(self, setting, key, default=None):
90 parent = self.getparent()
94 return getattr(parent, setting)[key]
95 except AttributeError:
96 return parent.get_context_map(setting, key, default)
98 def signal(self, signal):
99 parent = self.getparent()
100 if parent is not None:
101 parent.signal(signal)
103 def raw_printable_text(self, builder):
104 from librarian.html import raw_printable_text
106 # TODO: podtagi, wyroznienia, etc
108 t += self.normalize_text(self.text, builder)
110 if not isinstance(c, WLElement):
112 if c.tag not in ('pe', 'pa', 'pt', 'pr', 'motyw'):
113 t += c.raw_printable_text(builder)
114 t += self.normalize_text(c.tail, builder)
117 def normalize_text(self, text, builder):
119 for e, s in self.text_substitutions:
120 text = text.replace(e, s)
121 # FIXME: TEmporary turnoff
122 # text = re.sub(r'\s+', ' ', text)
123 ### TODO: Added now for epub
125 if getattr(builder, 'hyphenator', None) is not None:
127 wlist = re.compile(r'\w+|[^\w]', re.UNICODE).findall(text)
129 newt += builder.hyphenator.inserted(w, '\u00AD')
133 text = re.sub(r'(?<=\s\w)\s+', '\u00A0', text)
137 def _build_inner(self, builder, build_method):
138 child_count = len(self)
139 if self.CAN_HAVE_TEXT and self.text:
140 text = self.normalize_text(self.text, builder)
145 builder.push_text(text)
146 for i, child in enumerate(self):
147 if isinstance(child, WLElement):
148 getattr(child, build_method)(builder)
149 # FIXME base builder api
150 elif getattr(builder, 'debug', False) and child.tag is etree.Comment:
151 builder.process_comment(child)
152 if self.CAN_HAVE_TEXT and child.tail:
153 text = self.normalize_text(child.tail, builder)
154 if self.STRIP and i == child_count - 1:
156 builder.push_text(text)
158 def _txt_build_inner(self, builder):
159 self._build_inner(builder, 'txt_build')
161 def txt_build(self, builder):
162 if hasattr(self, 'TXT_LEGACY_TOP_MARGIN'):
163 builder.push_legacy_margin(self.TXT_LEGACY_TOP_MARGIN)
165 builder.push_margin(self.TXT_TOP_MARGIN)
166 builder.push_text(self.TXT_PREFIX, True)
167 self._txt_build_inner(builder)
168 builder.push_text(self.TXT_SUFFIX, True)
169 if hasattr(self, 'TXT_LEGACY_BOTTOM_MARGIN'):
170 builder.push_legacy_margin(self.TXT_LEGACY_BOTTOM_MARGIN)
172 builder.push_margin(self.TXT_BOTTOM_MARGIN)
174 def _html_build_inner(self, builder):
175 self._build_inner(builder, 'html_build')
177 def get_html_attr(self, builder):
178 attr = self.HTML_ATTR.copy()
180 attr['class'] = self.HTML_CLASS
182 # always copy the id attribute (?)
183 if self.attrib.get('id'):
184 attr['id'] = self.attrib['id']
185 if self.attrib.get('_id'):
186 attr['id'] = self.attrib['_id']
189 def html_build(self, builder):
190 # Do we need a number?
191 numbering = self.numbering
192 if numbering == 'main':
193 if builder.with_numbering and self.has_visible_numbering:
194 builder.add_visible_number(self)
197 builder.start_element(
199 self.get_html_attr(builder),
202 self._html_build_inner(builder)
204 builder.end_element()
206 def _epub_build_inner(self, builder):
207 self._build_inner(builder, 'epub_build')
209 def get_epub_attr(self, builder):
210 attr = self.EPUB_ATTR.copy()
212 attr['class'] = self.EPUB_CLASS
215 def epub_build(self, builder):
216 from librarian.elements.masters import Master
219 self.CAN_HAVE_TEXT = True
222 start_chunk = self.EPUB_START_CHUNK and isinstance(self.getparent(), Master)
225 builder.start_chunk()
228 if self.SECTION_PRECEDENCE and not self.in_context_of('NO_TOC'):
230 fragment = 'sub%d' % builder.assign_section_number()
231 self.attrib['id'] = fragment
233 builder.add_toc_entry(
235 self.raw_printable_text(builder),
236 self.SECTION_PRECEDENCE
240 attr = self.get_epub_attr(builder)
242 attr['id'] = fragment
244 chunkno, sourceline = 0, self.sourceline
246 chunkno, sourceline = len(builder.splits), sourceline - builder.splits[-1]
247 attr['data-debug'] = f'{chunkno}:{sourceline}'
248 builder.start_element(
253 self._epub_build_inner(builder)
255 builder.end_element()
258 from librarian.elements.masters import Master
259 from librarian.elements.blocks import DlugiCytat, PoezjaCyt
260 from librarian.elements.footnotes import Footnote
262 if self.SECTION_PRECEDENCE:
263 assert isinstance(self.getparent(), (Master, DlugiCytat, PoezjaCyt, Footnote)), \
264 'Header {} inside a <{}> instead of a master.'.format(
265 etree.tostring(self, encoding='unicode'), self.getparent().tag)
268 if isinstance(c, WLElement):
273 # TODO: Remove insanity here.
275 if isinstance(e, WLElement):
278 def snip(self, words, before=None, sub=False):
279 if sub and self.ASIDE:
283 if before is not None:
284 i = self.index(before)
292 words, text = last_words(self[i].tail, words)
293 snippet = [('text', text)] + snippet
296 words, subsnip = self[i].snip(words, sub=True)
297 snippet = subsnip + snippet
299 if words and self.text:
300 words, text = last_words(self.text, words)
301 snippet = [('text', text)] + snippet
303 snippet = [('start', self.tag, self.attrib)] + snippet + [('end',)]
305 if not sub and words and not self.ASIDE:
307 parent = self.getparent()
308 if parent is not None and parent.CAN_HAVE_TEXT:
309 words, parsnip = parent.snip(words, before=self)
310 return words, parsnip[:-1] + snippet + parsnip[-1:]
312 return words, snippet
314 def get_snippet(self, words=15):
315 from librarian.parser import parser
317 words, snippet = self.getparent().snip(words=words, before=self)
319 cursor = snipelem = parser.makeelement('snippet')
320 snipelem._meta_object = self.meta
323 elem = parser.makeelement(s[1], **s[2])
327 cursor = cursor.getparent()
330 cursor[-1].tail = (cursor[-1].tail or '') + s[1]
332 cursor.text = (cursor.text or '') + s[1]
338 numbering = self.NUMBERING
339 if numbering is None or self.in_context_of('DISABLE_NUMBERING'):
341 numbering = self.get_context_map('SUPPRESS_NUMBERING', numbering, numbering)
346 prefix = self.numbering
348 # TODO: self.context.main_numbering_prefix
349 prefix = 'f' # default numbering prefix
352 def assign_id(self, builder):
353 numbering = self.numbering
355 number = str(builder.counters[numbering])
356 self.attrib['_id'] = self.id_prefix + number
357 builder.counters[numbering] += 1
359 if numbering == 'main':
360 self.attrib['_visible_numbering'] = str(builder.counters['_visible'])
361 builder.counters['_visible'] += 1
363 if numbering == 'fn':
364 self.attrib['_visible_numbering'] = number
367 return self.attrib.get('_id') or self.getparent().get_link()
370 class Snippet(WLElement):