Some experiments with the language: html, epub, covers.
[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
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):
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.toc = TOC()
34         ctx.toc_level = 0
35         ctx.footnotes = Footnotes()
36
37         if self.standalone:
38             t.find('head/title').text = u"%s (%s)" % (self.doc.meta.title(), self.doc.meta.author())
39
40         t.find('.//div[@id="content"]').extend(
41             self.render(self.doc.edoc.getroot(), ctx))
42         t.find('.//div[@id="toc"]').append(ctx.toc.render())
43         t.find('.//div[@id="footnotes"]').extend(ctx.footnotes.output)
44
45         return OutputFile.from_string(etree.tostring(
46             t, encoding='utf-8', method="html"))
47
48     def render(self, element, ctx):
49         return self.renderers.get_for(element).render(element, ctx)
50
51
52 # Helpers
53
54 class NaturalText(TreeRenderer):
55     def render_text(self, text, ctx):
56         root, inner = self.text_container()
57         chunks = re.split('(?<=\s\w) ', text)
58         inner.text = chunks[0]
59         for chunk in chunks[1:]:
60             x = etree.Entity("nbsp")
61             x.tail = chunk
62             inner.append(x)
63         return root
64
65
66 class LiteralText(TreeRenderer):
67     pass
68
69
70 class Footnotes(object):
71     def __init__(self):
72         self.counter = 0
73         self.output = etree.Element("_")
74
75     def append(self, item):
76         self.counter += 1
77         e = etree.Element("a",
78             href="#footnote-anchor-%d" % self.counter,
79             id="footnote-%d" % self.counter,
80             style="float:left;margin-right:1em")
81         e.text = "[%d]" % self.counter
82         e.tail = " "
83         self.output.append(e)
84         self.output.extend(item)
85         anchor = etree.Element("a",
86             id="footnote-anchor-%d" % self.counter,
87             href="#footnote-%d" % self.counter)
88         anchor.text = "[%d]" % self.counter
89         return anchor
90
91
92 class TOC(object):
93     def __init__(self):
94         self.items = []
95         self.counter = 0
96
97     def add(self, title, level=0):
98         self.counter += 1
99         self.items.append((level, title, self.counter))
100         return self.counter
101
102     def render(self):
103         out = etree.Element("ul", id="toc")
104         curr_level = 0
105         cursor = out
106         for level, title, counter in self.items:
107             while level > curr_level:
108                 ins = etree.Element("ul")
109                 cursor.append(ins)
110                 cursor = ins
111                 curr_level += 1
112             while level < curr_level:
113                 cursor = cursor.getparent()
114                 curr_level -= 1
115             ins = etree.Element("li")
116             ins.append(etree.Element("a", href="#sect%d" % counter))
117             ins[0].text = title
118             cursor.append(ins)
119         return out
120
121
122 # Renderers
123
124 HtmlFormat.renderers.register(core.Aside, None, NaturalText('aside'))
125
126 class AsideFootnote(NaturalText):
127     def render(self, element, ctx):
128         output = super(AsideFootnote, self).render(element, ctx)
129         anchor = ctx.footnotes.append(output)
130         root, inner = self.container()
131         inner.append(anchor)
132         return root
133 HtmlFormat.renderers.register(core.Aside, 'footnote', AsideFootnote())
134
135        
136 HtmlFormat.renderers.register(core.Header, None, NaturalText('h1'))
137
138
139 HtmlFormat.renderers.register(core.Div, None, NaturalText('div'))
140 HtmlFormat.renderers.register(core.Div, 'item', NaturalText('li'))
141 HtmlFormat.renderers.register(core.Div, 'list', NaturalText('ul'))
142 HtmlFormat.renderers.register(core.Div, 'p', NaturalText('p'))
143
144
145 class Section(NaturalText):
146     def subcontext(self, element, ctx):
147         return Context(ctx, toc_level=ctx.toc_level + 1)
148
149     def render(self, element, ctx):
150         counter = ctx.toc.add(element.meta.title(), ctx.toc_level)
151         root = super(Section, self).render(element, ctx)
152         root[0].set("id", "sect%d" % counter)
153         return root
154 HtmlFormat.renderers.register(core.Section, None, Section('section'))
155
156
157 HtmlFormat.renderers.register(core.Span, None, NaturalText('span'))
158 HtmlFormat.renderers.register(core.Span, 'cite', NaturalText('cite'))
159 HtmlFormat.renderers.register(core.Span, 'cite.code', LiteralText('code'))
160 HtmlFormat.renderers.register(core.Span, 'emph', NaturalText('em'))
161
162 class SpanUri(LiteralText):
163     def render(self, element, ctx):
164         root = super(SpanUri, self).render(element, ctx)
165         root[0].attrib['href'] = element.text
166         return root
167 HtmlFormat.renderers.register(core.Span, 'uri', SpanUri('a'))