Remove DateValue, drop Py<3.6, fix tests.
[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.SubElement(text, '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 self.footnote_counter:
113             fnheader = etree.Element("h3")
114             fnheader.text = _("Footnotes")
115             self.footnotes.insert(0, fnheader)
116             self.tree.append(self.footnotes)
117
118     def start_element(self, tag, attrib=None):
119         self.current_cursors.append(etree.SubElement(
120             self.cursor,
121             tag,
122             **(attrib or {})
123         ))
124
125     def end_element(self):
126         self.current_cursors.pop()
127
128     def push_text(self, text):
129         cursor = self.cursor
130         if len(cursor):
131             cursor[-1].tail = (cursor[-1].tail or '') + text
132         else:
133             cursor.text = (cursor.text or '') + text
134
135
136 class StandaloneHtmlBuilder(HtmlBuilder):
137     css_url = "https://static.wolnelektury.pl/css/compressed/book_text.css"
138
139     def postprocess(self, document):
140         super(StandaloneHtmlBuilder, self).postprocess(document)
141
142         tree = etree.Element('html')
143         body = etree.SubElement(tree, 'body')
144         body.append(self.tree)
145         self.tree = tree
146
147         head = etree.Element('head')
148         tree.insert(0, head)
149
150
151         etree.SubElement(head, 'meta', charset='utf-8')
152         etree.SubElement(head, 'title').text = document.meta.title
153
154         etree.SubElement(
155             head,
156             'meta',
157             name="viewport",
158             content="width=device-width, initial-scale=1, maximum-scale=1"
159         )
160
161         if self.no_externalities:
162             etree.SubElement(
163                 head, 'style',
164             ).text = urlopen(self.css_url).read().decode('utf-8')
165         else:
166             etree.SubElement(
167                 head,
168                 'link',
169                 href=self.css_url,
170                 rel="stylesheet",
171                 type="text/css",
172             )
173
174             etree.SubElement(
175                 body, 'script',
176                 src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"
177             )
178
179             etree.SubElement(
180                 body,
181                 "script",
182                 src="http://malsup.github.io/min/jquery.cycle2.min.js"
183             )
184
185
186 class DaisyHtmlBuilder(StandaloneHtmlBuilder):
187     file_extension = 'xhtml'
188     with_anchors = False
189     with_themes = False
190     with_toc = False
191     with_footnotes = False
192     with_nota_red = False
193     with_deep_identifiers = False
194     no_externalities = True
195
196     def output(self):
197         tree = etree.ElementTree(self.tree)
198         tree.docinfo.public_id = '-//W3C//DTD XHTML 1.0 Transitional//EN'
199         tree.docinfo.system_url = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
200         return OutputFile.from_bytes(
201             etree.tostring(
202                 tree,
203                 encoding='utf-8',
204                 pretty_print=True,
205                 xml_declaration=True
206             )
207         )
208