1 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
4 from collections import defaultdict
6 from urllib.request import urlopen
8 from librarian.html import add_table_of_contents, add_table_of_themes, add_image_sizes
9 from librarian import OutputFile
13 file_extension = "html"
20 no_externalities = False
24 root_attrib = {'id': 'book-text'}
26 def __init__(self, gallery_path=None, gallery_url=None, base_url=None):
27 self._base_url = base_url
28 self.gallery_path = gallery_path
29 self.gallery_url = gallery_url
31 self.tree = text = etree.Element(self.root_tag, **self.root_attrib)
32 self.header = etree.Element('h1')
34 self.footnotes = etree.Element('div', id='footnotes')
35 self.counters = defaultdict(lambda: 1)
37 self.nota_red = etree.Element('div', id='nota_red')
41 'header': self.header,
42 'footnotes': self.footnotes,
43 'nota_red': self.nota_red,
45 self.current_cursors = [text]
49 if self._base_url is not None:
52 return 'https://wolnelektury.pl/media/book/pictures/{}/'.format(self.document.meta.url.slug)
56 return self.current_cursors[-1]
58 def enter_fragment(self, fragment):
59 cursor = self.cursors.get(fragment, self.cursor)
60 self.current_cursors.append(cursor)
62 def exit_fragment(self):
63 self.current_cursors.pop()
65 def create_fragment(self, name, element):
66 assert name not in self.cursors
67 self.cursors[name] = element
69 def forget_fragment(self, name):
70 del self.cursors[name]
72 def build(self, document, element=None, **kwargs):
73 self.document = document
75 self.assign_ids(self.document.tree)
79 element = document.tree.getroot()
81 element.html_build(self)
82 self.postprocess(document)
85 def assign_ids(self, tree):
86 # Assign IDs depth-first, to account for any <numeracja> inside.
87 for _e, elem in etree.iterwalk(tree, events=('end',)):
88 if getattr(elem, 'NUMBERING', None):
91 def prepare_images(self):
92 # Temporarily use the legacy method, before transitioning to external generators.
93 if self.gallery_path is None:
96 os.makedirs(self.gallery_path)
99 add_image_sizes(self.document.tree, self.gallery_path, self.gallery_url, self.base_url)
102 if not len(self.tree):
104 return OutputFile.from_bytes(
113 def postprocess(self, document):
114 _ = document.tree.getroot().gettext
116 if document.meta.translators:
117 self.enter_fragment('header')
118 self.start_element('span', {'class': 'translator'})
119 self.push_text(_("translated by") + " ")
122 translator.readable()
123 for translator in document.meta.translators
129 self.tree.insert(0, self.header)
131 if self.with_nota_red and len(self.nota_red):
132 self.tree.append(self.nota_red)
134 add_table_of_themes(self.tree)
136 add_table_of_contents(self.tree)
138 if self.counters['fn'] > 1:
139 fnheader = etree.Element("h3")
140 fnheader.text = _("Footnotes")
141 self.footnotes.insert(0, fnheader)
142 self.tree.append(self.footnotes)
144 def start_element(self, tag, attrib=None):
145 self.current_cursors.append(etree.SubElement(
151 def end_element(self):
152 self.current_cursors.pop()
154 def push_text(self, text):
157 cursor[-1].tail = (cursor[-1].tail or '') + text
159 cursor.text = (cursor.text or '') + text
161 def add_visible_number(self, element):
162 assert '_id' in element.attrib, etree.tostring(element)
163 self.start_element('a', {
164 'href': f'#{element.attrib["_id"]}',
167 self.push_text(element.attrib['_visible_numbering'])
171 class StandaloneHtmlBuilder(HtmlBuilder):
172 css_url = "https://static.wolnelektury.pl/css/compressed/book_text.css"
174 def postprocess(self, document):
175 super(StandaloneHtmlBuilder, self).postprocess(document)
177 tree = etree.Element('html')
178 body = etree.SubElement(tree, 'body')
179 body.append(self.tree)
182 head = etree.Element('head')
185 etree.SubElement(head, 'meta', charset='utf-8')
186 etree.SubElement(head, 'title').text = document.meta.title
192 content="width=device-width, initial-scale=1, maximum-scale=1"
195 if self.no_externalities:
198 ).text = urlopen(self.css_url).read().decode('utf-8')
210 src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"
216 src="http://malsup.github.io/min/jquery.cycle2.min.js"
220 class SnippetHtmlBuilder(HtmlBuilder):
223 with_footnotes = False
224 with_nota_red = False
226 with_numbering = False
229 class AbstraktHtmlBuilder(HtmlBuilder):
232 with_footnotes = False
233 with_nota_red = False
235 with_numbering = False
237 root_tag = 'blockquote'
240 def build(self, document, element=None, **kwargs):
242 element = document.tree.find('//abstrakt')
244 return OutputFile.from_bytes(b'')
245 element.attrib['_force'] = '1'
246 return super().build(document, element, **kwargs)
249 class DaisyHtmlBuilder(StandaloneHtmlBuilder):
250 file_extension = 'xhtml'
253 with_footnotes = False
254 with_nota_red = False
255 with_deep_identifiers = False
256 no_externalities = True
257 with_numbering = False
260 tree = etree.ElementTree(self.tree)
261 tree.docinfo.public_id = '-//W3C//DTD XHTML 1.0 Transitional//EN'
262 tree.docinfo.system_url = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
263 return OutputFile.from_bytes(