18a5b366a3fafdb9a1d5a6459a0849af9f8aa8dc
[librarian.git] / src / librarian / builders / html.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 try:
5     from urllib.request import urlopen
6 except ImportError:
7     from urllib2 import urlopen
8 from lxml import etree
9 from librarian.html import add_anchors, add_table_of_contents, add_table_of_themes
10 from librarian import OutputFile
11
12
13 class HtmlBuilder:
14     file_extension = "html"
15     with_anchors = True
16     with_themes = True
17     with_toc = True
18     with_footnotes = True
19     with_nota_red = True
20     no_externalities = False
21     orphans = True
22
23     def __init__(self, base_url=None):
24         self._base_url = base_url
25
26         self.tree = text = etree.Element('div', **{'id': 'book-text'})
27         self.header = etree.Element('h1')
28
29         self.footnotes = etree.Element('div', id='footnotes')
30         self.footnote_counter = 0
31
32         self.nota_red = etree.Element('div', id='nota_red')
33
34         self.cursors = {
35             None: text,
36             'header': self.header,
37             'footnotes': self.footnotes,
38             'nota_red': self.nota_red,
39         }
40         self.current_cursors = [text]
41
42     @property
43     def base_url(self):
44         if self._base_url is not None:
45             return self._base_url
46         else:
47             return 'https://wolnelektury.pl/media/book/pictures/{}/'.format(self.document.meta.url.slug)
48
49     @property
50     def cursor(self):
51         return self.current_cursors[-1]
52
53     def enter_fragment(self, fragment):
54         self.current_cursors.append(self.cursors[fragment])
55
56     def exit_fragment(self):
57         self.current_cursors.pop()
58
59     def create_fragment(self, name, element):
60         assert name not in self.cursors
61         self.cursors[name] = element
62
63     def forget_fragment(self, name):
64         del self.cursors[name]
65
66     def preprocess(self, document):
67         document._compat_assign_ordered_ids()
68         document._compat_assign_section_ids()
69
70     def build(self, document, **kwargs):
71         self.document = document
72
73         self.preprocess(document)
74         document.tree.getroot().html_build(self)
75         self.postprocess(document)
76         return self.output()
77
78     def output(self):
79         return OutputFile.from_bytes(
80             etree.tostring(
81                 self.tree,
82                 method='html',
83                 encoding='utf-8',
84                 pretty_print=True
85             )
86         )
87
88     def postprocess(self, document):
89         _ = document.tree.getroot().master.gettext
90
91         if document.meta.translators:
92             self.enter_fragment('header')
93             self.start_element('span', {'class': 'translator'})
94             self.push_text(_("translated by") + " ")
95             self.push_text(
96                 ", ".join(
97                     translator.readable()
98                     for translator in document.meta.translators
99                 )
100             )
101             self.exit_fragment()
102
103         if self.with_anchors:
104             add_anchors(self.tree)
105         if self.with_nota_red and len(self.nota_red):
106             self.tree.append(self.nota_red)
107         if self.with_themes:
108             add_table_of_themes(self.tree)
109         if self.with_toc:
110             add_table_of_contents(self.tree)
111
112         if len(self.header):
113             self.tree.insert(0, self.header)
114             
115         if self.footnote_counter:
116             fnheader = etree.Element("h3")
117             fnheader.text = _("Footnotes")
118             self.footnotes.insert(0, fnheader)
119             self.tree.append(self.footnotes)
120
121     def start_element(self, tag, attrib=None):
122         self.current_cursors.append(etree.SubElement(
123             self.cursor,
124             tag,
125             **(attrib or {})
126         ))
127
128     def end_element(self):
129         self.current_cursors.pop()
130
131     def push_text(self, text):
132         cursor = self.cursor
133         if len(cursor):
134             cursor[-1].tail = (cursor[-1].tail or '') + text
135         else:
136             cursor.text = (cursor.text or '') + text
137
138
139 class StandaloneHtmlBuilder(HtmlBuilder):
140     css_url = "https://static.wolnelektury.pl/css/compressed/book_text.css"
141
142     def postprocess(self, document):
143         super(StandaloneHtmlBuilder, self).postprocess(document)
144
145         tree = etree.Element('html')
146         body = etree.SubElement(tree, 'body')
147         body.append(self.tree)
148         self.tree = tree
149
150         head = etree.Element('head')
151         tree.insert(0, head)
152
153
154         etree.SubElement(head, 'meta', charset='utf-8')
155         etree.SubElement(head, 'title').text = document.meta.title
156
157         etree.SubElement(
158             head,
159             'meta',
160             name="viewport",
161             content="width=device-width, initial-scale=1, maximum-scale=1"
162         )
163
164         if self.no_externalities:
165             etree.SubElement(
166                 head, 'style',
167             ).text = urlopen(self.css_url).read().decode('utf-8')
168         else:
169             etree.SubElement(
170                 head,
171                 'link',
172                 href=self.css_url,
173                 rel="stylesheet",
174                 type="text/css",
175             )
176
177             etree.SubElement(
178                 body, 'script',
179                 src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"
180             )
181
182             etree.SubElement(
183                 body,
184                 "script",
185                 src="http://malsup.github.io/min/jquery.cycle2.min.js"
186             )
187
188
189 class SnippetHtmlBuilder(HtmlBuilder):
190     with_anchors = False
191     with_themes = False
192     with_toc = False
193     with_footnotes = False
194     with_nota_red = False
195     with_refs = False
196
197             
198 class DaisyHtmlBuilder(StandaloneHtmlBuilder):
199     file_extension = 'xhtml'
200     with_anchors = False
201     with_themes = False
202     with_toc = False
203     with_footnotes = False
204     with_nota_red = False
205     with_deep_identifiers = False
206     no_externalities = True
207
208     def output(self):
209         tree = etree.ElementTree(self.tree)
210         tree.docinfo.public_id = '-//W3C//DTD XHTML 1.0 Transitional//EN'
211         tree.docinfo.system_url = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
212         return OutputFile.from_bytes(
213             etree.tostring(
214                 tree,
215                 encoding='utf-8',
216                 pretty_print=True,
217                 xml_declaration=True
218             )
219         )
220