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