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