d17747db0f85489b42f7336defb59fb5f427c947
[librarian.git] / src / librarian / elements / base.py
1 # -*- coding: utf-8
2
3 import re
4 from lxml import etree
5 from librarian import dcparser, RDFNS
6 from librarian.util import get_translation
7
8
9 class WLElement(etree.ElementBase):
10     SECTION_PRECEDENCE = None
11
12     TXT_TOP_MARGIN = 0
13     TXT_BOTTOM_MARGIN = 0
14     TXT_PREFIX = ""
15     TXT_SUFFIX = ""
16
17     HTML_TAG = None
18     HTML_ATTR = {}
19     HTML_CLASS = None
20
21     EPUB_TAG = None
22     EPUB_ATTR = {}
23     EPUB_CLASS = None
24     EPUB_START_CHUNK = False
25    
26     CAN_HAVE_TEXT = True
27     STRIP = False
28
29     text_substitutions = [
30         (u'---', u'—'),
31         (u'--', u'–'),
32         #(u'...', u'…'),  # Temporary turnoff for epub
33         (u',,', u'„'),
34         (u'"', u'”'),
35         ('\ufeff', ''),
36
37         ("'", "\u2019"),    # This was enabled for epub.
38     ]
39
40     @property
41     def meta_object(self):
42         if not hasattr(self, '_meta_object'):
43             elem = self.find(RDFNS('RDF'))
44             if elem is not None:
45                 self._meta_object = dcparser.BookInfo.from_element(elem)
46             else:
47                 self._meta_object = None
48         return self._meta_object
49
50     @property
51     def meta(self):
52         if self.meta_object is not None:
53             return self.meta_object
54         else:
55             if self.getparent() is not None:
56                 return self.getparent().meta
57             else:
58                 return self.document.base_meta
59
60     @property
61     def gettext(self):
62         return get_translation(self.meta.language).gettext
63
64     def in_context_of(self, setting):
65         parent = self.getparent()
66         if parent is None:
67             return False
68         try:
69             return getattr(parent, setting)
70         except AttributeError:
71             return parent.in_context_of(setting)
72
73     def signal(self, signal):
74         parent = self.getparent()
75         if parent is not None:
76             parent.signal(signal)
77     
78     def raw_printable_text(self, builder):
79         from librarian.html import raw_printable_text
80
81         # TODO: podtagi, wyroznienia, etc
82         t = ''
83         t += self.normalize_text(self.text, builder)
84         for c in self:
85             if not isinstance(c, WLElement):
86                 continue
87             if c.tag not in ('pe', 'pa', 'pt', 'pr', 'motyw'):
88                 t += c.raw_printable_text(builder)
89             t += self.normalize_text(c.tail, builder)
90         return t
91     
92     def normalize_text(self, text, builder):
93         text = text or ''
94         for e, s in self.text_substitutions:
95             text = text.replace(e, s)
96             # FIXME: TEmporary turnoff
97 #        text = re.sub(r'\s+', ' ', text)
98 ### TODO: Added now for epub
99
100         if getattr(builder, 'hyphenator', None) is not None:
101             newt = ''
102             wlist = re.compile(r'\w+|[^\w]', re.UNICODE).findall(text)
103             for w in wlist:
104                 newt += builder.hyphenator.inserted(w, u'\u00AD')
105             text = newt
106
107         text = re.sub(r'(?<=\s\w)\s+', u'\u00A0', text)
108
109         return text
110
111     def _build_inner(self, builder, build_method):
112         child_count = len(self)
113         if self.CAN_HAVE_TEXT and self.text:
114             text = self.normalize_text(self.text, builder)
115             if self.STRIP:
116                 text = text.lstrip()
117                 if not child_count:
118                     text = text.rstrip()
119             builder.push_text(text)
120         for i, child in enumerate(self):
121             if isinstance(child, WLElement):
122                 getattr(child, build_method)(builder)
123             if self.CAN_HAVE_TEXT and child.tail:
124                 text = self.normalize_text(child.tail, builder)
125                 if self.STRIP and i == child_count - 1:
126                     text = text.rstrip()
127                 builder.push_text(text)
128
129     def _txt_build_inner(self, builder):
130         self._build_inner(builder, 'txt_build')
131
132     def txt_build(self, builder):
133         if hasattr(self, 'TXT_LEGACY_TOP_MARGIN'):
134             builder.push_legacy_margin(self.TXT_LEGACY_TOP_MARGIN)
135         else:
136             builder.push_margin(self.TXT_TOP_MARGIN)
137         builder.push_text(self.TXT_PREFIX, True)
138         self._txt_build_inner(builder)
139         builder.push_text(self.TXT_SUFFIX, True)
140         if hasattr(self, 'TXT_LEGACY_BOTTOM_MARGIN'):
141             builder.push_legacy_margin(self.TXT_LEGACY_BOTTOM_MARGIN)
142         else:
143             builder.push_margin(self.TXT_BOTTOM_MARGIN)
144
145     def _html_build_inner(self, builder):
146         self._build_inner(builder, 'html_build')
147
148     def get_html_attr(self, builder):
149         attr = self.HTML_ATTR.copy()
150         if self.HTML_CLASS:
151             attr['class'] = self.HTML_CLASS
152         # always copy the id attribute (?)
153         if self.attrib.get('id'):
154             attr['id'] = self.attrib['id']
155         elif '_compat_section_id' in self.attrib:
156             attr['id'] = self.attrib['_compat_section_id']
157         return attr
158
159     def html_build(self, builder):
160         if self.HTML_TAG:
161             builder.start_element(
162                 self.HTML_TAG,
163                 self.get_html_attr(builder),
164             )
165
166         self._html_build_inner(builder)
167         if self.HTML_TAG:
168             builder.end_element()
169
170     def _epub_build_inner(self, builder):
171         self._build_inner(builder, 'epub_build')
172
173     def get_epub_attr(self, builder):
174         attr = self.EPUB_ATTR.copy()
175         if self.EPUB_CLASS:
176             attr['class'] = self.EPUB_CLASS
177         return attr
178
179     def epub_build(self, builder):
180         from librarian.elements.masters import Master
181
182         # TEMPORARY
183         self.CAN_HAVE_TEXT = True
184         self.STRIP = False
185        
186         start_chunk = self.EPUB_START_CHUNK and isinstance(self.getparent(), Master)
187
188         if start_chunk:
189             builder.start_chunk()
190
191         fragment = None
192         if self.SECTION_PRECEDENCE and not self.in_context_of('NO_TOC'):
193             if not start_chunk:
194                 fragment = 'sub%d' % builder.assign_section_number()
195                 self.attrib['id'] = fragment
196
197             builder.add_toc_entry(
198                 fragment,
199                 self.raw_printable_text(builder),
200                 self.SECTION_PRECEDENCE
201             )
202             
203         if self.EPUB_TAG:
204             attr = self.get_epub_attr(builder)
205             if fragment:
206                 attr['id'] = fragment
207             builder.start_element(
208                 self.EPUB_TAG,
209                 attr
210             )
211
212         self._epub_build_inner(builder)
213         if self.EPUB_TAG:
214             builder.end_element()
215
216     def validate(self):
217         from librarian.elements.masters import Master
218         from librarian.elements.blocks import DlugiCytat, PoezjaCyt
219         from librarian.elements.footnotes import Footnote
220
221         if self.SECTION_PRECEDENCE:
222             assert isinstance(self.getparent(), (Master, DlugiCytat, PoezjaCyt, Footnote)), \
223                     'Header {} inside a <{}> instead of a master.'.format(
224                             etree.tostring(self, encoding='unicode'), self.getparent().tag)
225
226         for c in self:
227             if isinstance(c, WLElement):
228                 c.validate()
229
230
231     def sanitize(self):
232         # TODO: Remove insanity here.
233         for e in self:
234             if isinstance(e, WLElement):
235                 e.sanitize()