59ed8a41d1b3853423f27d54d1e2f58c6b5f037a
[librarian.git] / librarian / renderers.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 from lxml import etree
7 from . import UnicodeException
8 from .utils import extend_element
9
10
11 class UnknownElement(UnicodeException):
12     pass
13
14
15 class Renderer(object):
16     """ Renders an element in a context to some kind of container. """
17     def render(self, element, ctx):
18         """ Renders the element in the context. """
19         raise NotImplemented
20
21     def render_text(self, text, ctx):
22         """ Renders the text in the context. """
23         raise NotImplemented
24
25
26 class TreeRenderer(Renderer):
27     """ Renders insides as XML in a <_/> container. """
28     root_name = "_"
29
30     def __init__(self, tag_name=None, attrib=None):
31         self.tag_name = tag_name
32         self.attrib = attrib or {}
33
34     def container(self):
35         root = etree.Element(self.root_name)
36         if self.tag_name:
37             inner = etree.Element(self.tag_name, **self.attrib)
38             root.append(inner)
39             return root, inner
40         else:
41             return root, root
42
43     def text_container(self):
44         root = etree.Element(self.root_name)
45         return root, root
46
47     def subcontext(self, element, ctx):
48         return ctx
49
50     def get_insides(self, element, ctx):
51         subctx = self.subcontext(element, ctx)
52         if element.text:
53             yield self.render_text(element.text, ctx)
54         for child in element:
55             try:
56                 yield ctx.format.render(child, subctx)
57             except UnknownElement:
58                 pass
59             if child.tail:
60                 yield self.render_text(child.tail, ctx)
61
62     def render(self, element, ctx):
63         root, inner = self.container()
64         for inside in self.get_insides(element, ctx):
65             extend_element(inner, inside)
66         return root
67
68     def render_text(self, text, ctx):
69         root, inner = self.text_container()
70         inner.text = text
71         return root
72
73
74
75 class Register(object):
76     """ Class-renderer register.
77
78     >>> from librarian.core import Div
79     >>> renderer = Renderer()
80     >>> reg = Register()
81     >>> reg.register(Div, 'a.b', renderer)
82     >>> reg.get(Div, 'a.b.c') is renderer
83     True
84
85     """
86     def __init__(self):
87         self.classes = {}
88
89     def register(self, tag, klass, renderer):
90         self.classes[tag, klass] = renderer
91
92     def get(self, tag, klass=None):
93         while klass:
94             try:
95                 return self.classes[tag, klass]
96             except KeyError:
97                 try:
98                     klass = klass.rsplit('.', 1)[-2]
99                 except IndexError:
100                     klass = None
101         try:
102             return self.classes[tag, None]
103         except KeyError:
104             raise UnknownElement(tag)
105
106     def get_for(self, element):
107         return self.get(type(element), element.get('class'))