fix video
[librarian.git] / librarian / formats / html / __init__.py
1 # -*- coding: utf-8 -*-
2 #
3 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
5 #
6 import re
7 from lxml import etree
8 from librarian.formats import Format
9 from librarian.output import OutputFile
10 from librarian.renderers import Register, TreeRenderer
11 from librarian.utils import Context, get_resource
12 from librarian import core, get_provider
13
14
15 class HtmlFormat(Format):
16     format_name = 'HTML'
17     format_ext = 'html'
18
19     renderers = Register()
20
21     def __init__(self, doc, standalone=False):
22         super(HtmlFormat, self).__init__(doc)
23         self.standalone = standalone
24
25     def build(self, files_path=None):
26         if self.standalone:
27             tmpl = get_resource("formats/html/res/html_standalone.html")
28         else:
29             tmpl = get_resource("formats/html/res/html.html")
30         t = etree.parse(tmpl)
31
32         ctx = Context(format=self)
33         ctx.files_path = files_path
34         ctx.toc = TOC()
35         ctx.toc_level = 0
36         ctx.footnotes = Footnotes()
37
38         if self.standalone:
39             t.find('head/title').text = u"%s (%s)" % (self.doc.meta.title(), self.doc.meta.author())
40
41         t.find('.//div[@id="content"]').extend(
42             self.render(self.doc.edoc.getroot(), ctx))
43         # t.find('.//div[@id="toc"]').append(ctx.toc.render())
44         t.find('.//div[@id="footnotes"]').extend(ctx.footnotes.output)
45
46         return OutputFile.from_string(etree.tostring(
47             t, encoding='utf-8', method="html"))
48
49     def render(self, element, ctx):
50         return self.renderers.get_for(element).render(element, ctx)
51
52
53 # Helpers
54
55 class NaturalText(TreeRenderer):
56     def render_text(self, text, ctx):
57         root, inner = self.text_container()
58         chunks = re.split('(?<=\s\w) ', text)
59         inner.text = chunks[0]
60         for chunk in chunks[1:]:
61             x = etree.Entity("nbsp")
62             x.tail = chunk
63             inner.append(x)
64         return root
65
66
67 class LiteralText(TreeRenderer):
68     pass
69
70
71 class Silent(TreeRenderer):
72     def render_text(self, text, ctx):
73         root, inner = self.text_container()
74         return root
75
76
77 class Footnotes(object):
78     def __init__(self):
79         self.counter = 0
80         self.output = etree.Element("_")
81
82     def append(self, item):
83         self.counter += 1
84         e = etree.Element(
85             "a",
86             href="#footnote-anchor-%d" % self.counter,
87             id="footnote-%d" % self.counter,
88             style="float:left;margin-right:1em")
89         e.text = "[%d]" % self.counter
90         e.tail = " "
91         self.output.append(e)
92         self.output.extend(item)
93         anchor = etree.Element(
94             "a",
95             id="footnote-anchor-%d" % self.counter,
96             href="#footnote-%d" % self.counter)
97         anchor.text = "[%d]" % self.counter
98         return anchor
99
100
101 class TOC(object):
102     def __init__(self):
103         self.items = []
104         self.counter = 0
105
106     def add(self, title, level=0):
107         self.counter += 1
108         self.items.append((level, title, self.counter))
109         return self.counter
110
111     def render(self):
112         out = etree.Element("ul", id="toc")
113         curr_level = 0
114         cursor = out
115         for level, title, counter in self.items:
116             while level > curr_level:
117                 ins = etree.Element("ul")
118                 cursor.append(ins)
119                 cursor = ins
120                 curr_level += 1
121             while level < curr_level:
122                 cursor = cursor.getparent()
123                 curr_level -= 1
124             ins = etree.Element("li")
125             ins.append(etree.Element("a", href="#sect%d" % counter))
126             ins[0].text = title
127             cursor.append(ins)
128         return out
129
130
131 # Renderers
132
133 HtmlFormat.renderers.register(core.Aside, None, NaturalText('aside'))
134 HtmlFormat.renderers.register(core.Aside, 'comment', Silent())
135
136
137 class AsideFootnote(NaturalText):
138     def render(self, element, ctx):
139         output = super(AsideFootnote, self).render(element, ctx)
140         anchor = ctx.footnotes.append(output)
141         root, inner = self.container()
142         inner.append(anchor)
143         return root
144 HtmlFormat.renderers.register(core.Aside, 'footnote', AsideFootnote())
145
146
147 class Header(NaturalText):
148     def render(self, element, ctx):
149         root = super(Header, self).render(element, ctx)
150         if ctx.toc_level == 1:
151             d = etree.SubElement(root, 'div', {'class': "page-header"})
152             d.insert(0, root[0])
153         else:
154             root[0].tag = 'h2'
155             if root[0].text:
156                 d = etree.SubElement(
157                     root[0], 'a', {'id': root[0].text, 'style': 'pointer: hand; color:#ddd; font-size:.8em'})
158                 # d.text = "per"
159         return root
160
161
162 HtmlFormat.renderers.register(core.Header, None, Header('h1'))
163
164
165 HtmlFormat.renderers.register(core.Div, None, NaturalText('div'))
166
167
168 class DivDefined(NaturalText):
169     def render(self, element, ctx):
170         output = super(DivDefined, self).render(element, ctx)
171         output[0].text = (output[0].text or '') + ':'
172         output[0].attrib['id'] = output[0].text  # not so cool?
173         return output
174
175 HtmlFormat.renderers.register(core.Div, 'defined', DivDefined('dt', {'style': 'display: inline-block'}))
176
177
178 class DivImage(NaturalText):
179     def render(self, element, ctx):
180         output = super(DivImage, self).render(element, ctx)
181         src = element.attrib.get('src', '')
182         if src.startswith('file://'):
183             src = ctx.files_path + src[7:]
184         output[0].attrib['src'] = src
185         output[0].attrib['style'] = 'display: block; width: 60%; margin: 3em auto'
186         return output
187
188 HtmlFormat.renderers.register(core.Div, 'img', DivImage('img'))
189
190
191 class DivVideo(NaturalText):
192     def render(self, element, ctx):
193         output = super(DivVideo, self).render(element, ctx)
194         provider = get_provider(element.attrib.get('provider'))
195         video_id = element.attrib.get('videoid', '')
196         attribs = {
197             'width': '854',
198             'height': '480',
199             'src': provider['embed'] % video_id,
200             'frameborder': '0',
201             'allowfullscreen': '',
202         }
203         for attrib, value in attribs.iteritems():
204             output[0].attrib[attrib] = value
205         return output
206
207 HtmlFormat.renderers.register(core.Div, 'video', DivVideo('iframe'))
208
209 HtmlFormat.renderers.register(core.Div, 'item', NaturalText('li'))
210 HtmlFormat.renderers.register(core.Span, 'item', NaturalText('li'))
211 HtmlFormat.renderers.register(core.Div, 'list', NaturalText('ul'))
212 HtmlFormat.renderers.register(core.Div, 'list.enum', NaturalText('ol'))
213
214
215 class DivListDefinitions(NaturalText):
216     def render(self, element, ctx):
217         output = super(DivListDefinitions, self).render(element, ctx)
218         # if ctx.toc_level > 2:
219         #     output[0].attrib['style'] = 'float: right'
220         return output
221
222 HtmlFormat.renderers.register(core.Div, 'list.definitions', DivListDefinitions('ul'))
223 HtmlFormat.renderers.register(core.Div, 'p', NaturalText('p'))
224
225
226 class Section(NaturalText):
227     def subcontext(self, element, ctx):
228         return Context(ctx, toc_level=ctx.toc_level + 1)
229
230     def render(self, element, ctx):
231         counter = ctx.toc.add(element.meta.title(), ctx.toc_level)
232         root = super(Section, self).render(element, ctx)
233         root[0].set("id", "sect%d" % counter)
234         return root
235 HtmlFormat.renderers.register(core.Section, None, Section('section'))
236
237
238 HtmlFormat.renderers.register(core.Span, None, NaturalText('span'))
239 HtmlFormat.renderers.register(core.Span, 'cite', NaturalText('cite'))
240 HtmlFormat.renderers.register(core.Span, 'cite.code', LiteralText('code'))
241 HtmlFormat.renderers.register(core.Span, 'emph', NaturalText('em'))
242 HtmlFormat.renderers.register(core.Span, 'emp', NaturalText('strong'))
243
244
245 class SpanUri(LiteralText):
246     def render(self, element, ctx):
247         root = super(SpanUri, self).render(element, ctx)
248         root[0].attrib['href'] = element.text
249         return root
250 HtmlFormat.renderers.register(core.Span, 'uri', SpanUri('a'))
251
252
253 class SpanLink(LiteralText):
254     def render(self, element, ctx):
255         root = super(SpanLink, self).render(element, ctx)
256         src = element.attrib.get('href', '')
257         if src.startswith('file://'):
258             src = ctx.files_path + src[7:]
259         root[0].attrib['href'] = src
260         return root
261 HtmlFormat.renderers.register(core.Span, 'link', SpanLink('a'))