document validation (stub)
[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, 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 HtmlFormat.renderers.register(core.Div, 'item', NaturalText('li'))
191 HtmlFormat.renderers.register(core.Div, 'list', NaturalText('ul'))
192 HtmlFormat.renderers.register(core.Div, 'list.enum', NaturalText('ol'))
193
194
195 class DivListDefinitions(NaturalText):
196     def render(self, element, ctx):
197         output = super(DivListDefinitions, self).render(element, ctx)
198         # if ctx.toc_level > 2:
199         #     output[0].attrib['style'] = 'float: right'
200         return output
201
202 HtmlFormat.renderers.register(core.Div, 'list.definitions', DivListDefinitions('ul'))
203 HtmlFormat.renderers.register(core.Div, 'p', NaturalText('p'))
204
205
206 class Section(NaturalText):
207     def subcontext(self, element, ctx):
208         return Context(ctx, toc_level=ctx.toc_level + 1)
209
210     def render(self, element, ctx):
211         counter = ctx.toc.add(element.meta.title(), ctx.toc_level)
212         root = super(Section, self).render(element, ctx)
213         root[0].set("id", "sect%d" % counter)
214         return root
215 HtmlFormat.renderers.register(core.Section, None, Section('section'))
216
217
218 HtmlFormat.renderers.register(core.Span, None, NaturalText('span'))
219 HtmlFormat.renderers.register(core.Span, 'cite', NaturalText('cite'))
220 HtmlFormat.renderers.register(core.Span, 'cite.code', LiteralText('code'))
221 HtmlFormat.renderers.register(core.Span, 'emph', NaturalText('em'))
222 HtmlFormat.renderers.register(core.Span, 'emp', NaturalText('strong'))
223
224
225 class SpanUri(LiteralText):
226     def render(self, element, ctx):
227         root = super(SpanUri, self).render(element, ctx)
228         root[0].attrib['href'] = element.text
229         return root
230 HtmlFormat.renderers.register(core.Span, 'uri', SpanUri('a'))
231
232
233 class SpanLink(LiteralText):
234     def render(self, element, ctx):
235         root = super(SpanLink, self).render(element, ctx)
236         src = element.attrib.get('href', '')
237         if src.startswith('file://'):
238             src = ctx.files_path + src[7:]
239         root[0].attrib['href'] = src
240         return root
241 HtmlFormat.renderers.register(core.Span, 'link', SpanLink('a'))