fixes for edge cases
[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         cursor = self.cursors.get(fragment, self.cursor)
55         self.current_cursors.append(cursor)
56
57     def exit_fragment(self):
58         self.current_cursors.pop()
59
60     def create_fragment(self, name, element):
61         assert name not in self.cursors
62         self.cursors[name] = element
63
64     def forget_fragment(self, name):
65         del self.cursors[name]
66
67     def preprocess(self, document):
68         document._compat_assign_ordered_ids()
69         document._compat_assign_section_ids()
70
71     def build(self, document, **kwargs):
72         self.document = document
73
74         self.preprocess(document)
75         document.tree.getroot().html_build(self)
76         self.postprocess(document)
77         return self.output()
78
79     def output(self):
80         return OutputFile.from_bytes(
81             etree.tostring(
82                 self.tree,
83                 method='html',
84                 encoding='utf-8',
85                 pretty_print=True
86             )
87         )
88
89     def postprocess(self, document):
90         _ = document.tree.getroot().master.gettext
91
92         if document.meta.translators:
93             self.enter_fragment('header')
94             self.start_element('span', {'class': 'translator'})
95             self.push_text(_("translated by") + " ")
96             self.push_text(
97                 ", ".join(
98                     translator.readable()
99                     for translator in document.meta.translators
100                 )
101             )
102             self.exit_fragment()
103
104         if self.with_anchors:
105             add_anchors(self.tree)
106         if self.with_nota_red and len(self.nota_red):
107             self.tree.append(self.nota_red)
108         if self.with_themes:
109             add_table_of_themes(self.tree)
110         if self.with_toc:
111             add_table_of_contents(self.tree)
112
113         if len(self.header):
114             self.tree.insert(0, self.header)
115             
116         if self.footnote_counter:
117             fnheader = etree.Element("h3")
118             fnheader.text = _("Footnotes")
119             self.footnotes.insert(0, fnheader)
120             self.tree.append(self.footnotes)
121
122     def start_element(self, tag, attrib=None):
123         self.current_cursors.append(etree.SubElement(
124             self.cursor,
125             tag,
126             **(attrib or {})
127         ))
128
129     def end_element(self):
130         self.current_cursors.pop()
131
132     def push_text(self, text):
133         cursor = self.cursor
134         if len(cursor):
135             cursor[-1].tail = (cursor[-1].tail or '') + text
136         else:
137             cursor.text = (cursor.text or '') + text
138
139
140 class StandaloneHtmlBuilder(HtmlBuilder):
141     css_url = "https://static.wolnelektury.pl/css/compressed/book_text.css"
142
143     def postprocess(self, document):
144         super(StandaloneHtmlBuilder, self).postprocess(document)
145
146         tree = etree.Element('html')
147         body = etree.SubElement(tree, 'body')
148         body.append(self.tree)
149         self.tree = tree
150
151         head = etree.Element('head')
152         tree.insert(0, head)
153
154
155         etree.SubElement(head, 'meta', charset='utf-8')
156         etree.SubElement(head, 'title').text = document.meta.title
157
158         etree.SubElement(
159             head,
160             'meta',
161             name="viewport",
162             content="width=device-width, initial-scale=1, maximum-scale=1"
163         )
164
165         if self.no_externalities:
166             etree.SubElement(
167                 head, 'style',
168             ).text = urlopen(self.css_url).read().decode('utf-8')
169         else:
170             etree.SubElement(
171                 head,
172                 'link',
173                 href=self.css_url,
174                 rel="stylesheet",
175                 type="text/css",
176             )
177
178             etree.SubElement(
179                 body, 'script',
180                 src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"
181             )
182
183             etree.SubElement(
184                 body,
185                 "script",
186                 src="http://malsup.github.io/min/jquery.cycle2.min.js"
187             )
188
189
190 class SnippetHtmlBuilder(HtmlBuilder):
191     with_anchors = False
192     with_themes = False
193     with_toc = False
194     with_footnotes = False
195     with_nota_red = False
196     with_refs = False
197
198             
199 class DaisyHtmlBuilder(StandaloneHtmlBuilder):
200     file_extension = 'xhtml'
201     with_anchors = False
202     with_themes = False
203     with_toc = False
204     with_footnotes = False
205     with_nota_red = False
206     with_deep_identifiers = False
207     no_externalities = True
208
209     def output(self):
210         tree = etree.ElementTree(self.tree)
211         tree.docinfo.public_id = '-//W3C//DTD XHTML 1.0 Transitional//EN'
212         tree.docinfo.system_url = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
213         return OutputFile.from_bytes(
214             etree.tostring(
215                 tree,
216                 encoding='utf-8',
217                 pretty_print=True,
218                 xml_declaration=True
219             )
220         )
221