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
44 text_substitutions = [
47 #('...', '…'), # Temporary turnoff for epub
52 ("'", "\u2019"), # This was enabled for epub.
56 def meta_object(self):
57 if not hasattr(self, '_meta_object'):
58 elem = self.find(RDFNS('RDF'))
60 self._meta_object = dcparser.BookInfo.from_element(elem)
62 self._meta_object = None
63 return self._meta_object
67 if self.meta_object is not None:
68 return self.meta_object
70 if self.getparent() is not None:
71 return self.getparent().meta
73 return self.document.base_meta
77 return get_translation(self.meta.language).gettext
79 def in_context_of(self, setting):
80 parent = self.getparent()
84 return getattr(parent, setting)
85 except AttributeError:
86 return parent.in_context_of(setting)
88 def signal(self, signal):
89 parent = self.getparent()
90 if parent is not None:
93 def raw_printable_text(self, builder):
94 from librarian.html import raw_printable_text
96 # TODO: podtagi, wyroznienia, etc
98 t += self.normalize_text(self.text, builder)
100 if not isinstance(c, WLElement):
102 if c.tag not in ('pe', 'pa', 'pt', 'pr', 'motyw'):
103 t += c.raw_printable_text(builder)
104 t += self.normalize_text(c.tail, builder)
107 def normalize_text(self, text, builder):
109 for e, s in self.text_substitutions:
110 text = text.replace(e, s)
111 # FIXME: TEmporary turnoff
112 # text = re.sub(r'\s+', ' ', text)
113 ### TODO: Added now for epub
115 if getattr(builder, 'hyphenator', None) is not None:
117 wlist = re.compile(r'\w+|[^\w]', re.UNICODE).findall(text)
119 newt += builder.hyphenator.inserted(w, '\u00AD')
123 text = re.sub(r'(?<=\s\w)\s+', '\u00A0', text)
127 def _build_inner(self, builder, build_method):
128 child_count = len(self)
129 if self.CAN_HAVE_TEXT and self.text:
130 text = self.normalize_text(self.text, builder)
135 builder.push_text(text)
136 for i, child in enumerate(self):
137 if isinstance(child, WLElement):
138 getattr(child, build_method)(builder)
139 elif getattr(builder, 'debug') and child.tag is etree.Comment:
140 builder.process_comment(child)
141 if self.CAN_HAVE_TEXT and child.tail:
142 text = self.normalize_text(child.tail, builder)
143 if self.STRIP and i == child_count - 1:
145 builder.push_text(text)
147 def _txt_build_inner(self, builder):
148 self._build_inner(builder, 'txt_build')
150 def txt_build(self, builder):
151 if hasattr(self, 'TXT_LEGACY_TOP_MARGIN'):
152 builder.push_legacy_margin(self.TXT_LEGACY_TOP_MARGIN)
154 builder.push_margin(self.TXT_TOP_MARGIN)
155 builder.push_text(self.TXT_PREFIX, True)
156 self._txt_build_inner(builder)
157 builder.push_text(self.TXT_SUFFIX, True)
158 if hasattr(self, 'TXT_LEGACY_BOTTOM_MARGIN'):
159 builder.push_legacy_margin(self.TXT_LEGACY_BOTTOM_MARGIN)
161 builder.push_margin(self.TXT_BOTTOM_MARGIN)
163 def _html_build_inner(self, builder):
164 self._build_inner(builder, 'html_build')
166 def get_html_attr(self, builder):
167 attr = self.HTML_ATTR.copy()
169 attr['class'] = self.HTML_CLASS
170 # always copy the id attribute (?)
171 if self.attrib.get('id'):
172 attr['id'] = self.attrib['id']
173 elif getattr(self, 'SHOULD_HAVE_ID', False) and '_compat_section_id' in self.attrib:
174 attr['id'] = self.attrib['_compat_section_id']
177 def html_build(self, builder):
179 builder.start_element(
181 self.get_html_attr(builder),
184 self._html_build_inner(builder)
186 builder.end_element()
188 def _epub_build_inner(self, builder):
189 self._build_inner(builder, 'epub_build')
191 def get_epub_attr(self, builder):
192 attr = self.EPUB_ATTR.copy()
194 attr['class'] = self.EPUB_CLASS
197 def epub_build(self, builder):
198 from librarian.elements.masters import Master
201 self.CAN_HAVE_TEXT = True
204 start_chunk = self.EPUB_START_CHUNK and isinstance(self.getparent(), Master)
207 builder.start_chunk()
210 if self.SECTION_PRECEDENCE and not self.in_context_of('NO_TOC'):
212 fragment = 'sub%d' % builder.assign_section_number()
213 self.attrib['id'] = fragment
215 builder.add_toc_entry(
217 self.raw_printable_text(builder),
218 self.SECTION_PRECEDENCE
222 attr = self.get_epub_attr(builder)
224 attr['id'] = fragment
226 chunkno, sourceline = 0, self.sourceline
228 chunkno, sourceline = len(builder.splits), sourceline - builder.splits[-1]
229 attr['data-debug'] = f'{chunkno}:{sourceline}'
230 builder.start_element(
235 self._epub_build_inner(builder)
237 builder.end_element()
240 from librarian.elements.masters import Master
241 from librarian.elements.blocks import DlugiCytat, PoezjaCyt
242 from librarian.elements.footnotes import Footnote
244 if self.SECTION_PRECEDENCE:
245 assert isinstance(self.getparent(), (Master, DlugiCytat, PoezjaCyt, Footnote)), \
246 'Header {} inside a <{}> instead of a master.'.format(
247 etree.tostring(self, encoding='unicode'), self.getparent().tag)
250 if isinstance(c, WLElement):
255 # TODO: Remove insanity here.
257 if isinstance(e, WLElement):
260 def snip(self, words, before=None, sub=False):
261 if sub and self.ASIDE:
265 if before is not None:
266 i = self.index(before)
274 words, text = last_words(self[i].tail, words)
275 snippet = [('text', text)] + snippet
278 words, subsnip = self[i].snip(words, sub=True)
279 snippet = subsnip + snippet
281 if words and self.text:
282 words, text = last_words(self.text, words)
283 snippet = [('text', text)] + snippet
285 snippet = [('start', self.tag, self.attrib)] + snippet + [('end',)]
287 if not sub and words and not self.ASIDE:
289 parent = self.getparent()
290 if parent is not None and parent.CAN_HAVE_TEXT:
291 words, parsnip = parent.snip(words, before=self)
292 return words, parsnip[:-1] + snippet + parsnip[-1:]
294 return words, snippet
296 def get_snippet(self, words=15):
297 from librarian.parser import parser
299 words, snippet = self.getparent().snip(words=words, before=self)
301 cursor = snipelem = parser.makeelement('snippet')
302 snipelem._meta_object = self.meta
305 elem = parser.makeelement(s[1], **s[2])
309 cursor = cursor.getparent()
312 cursor[-1].tail = (cursor[-1].tail or '') + s[1]
314 cursor.text = (cursor.text or '') + s[1]
319 sec = getattr(self, 'SHOULD_HAVE_ID', False) and self.attrib.get('_compat_section_id')
322 parent_index = self.getparent().index(self)
324 return self.getparent()[parent_index - 1].get_link()
326 return self.getparent().get_link()
329 class Snippet(WLElement):