- functions - publicize text transforming functions
[librarian.git] / librarian / pyhtml.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 librarian import OutputFile, RDFNS, DCNS
8 from xmlutils import Xmill, tag, tagged, ifoption
9 from librarian import functions
10 import re
11 import random
12
13
14 class EduModule(Xmill):
15     def __init__(self, *args):
16         super(EduModule, self).__init__(*args)
17         self.activity_counter = 0
18         self.register_text_filter(lambda t: functions.substitute_entities(None, t))
19
20     def handle_powiesc(self, element):
21         return u"""
22 <div class="module" id="book-text">
23 <!-- <span class="teacher-toggle">
24   <input type="checkbox" name="teacher-toggle" id="teacher-toggle"/>
25   <label for="teacher-toggle">Pokaż treść dla nauczyciela</label>
26  </span>-->
27
28 """, u"</div>"
29
30     handle_autor_utworu = tag("span", "author")
31     handle_nazwa_utworu = tag("h1", "title")
32     handle_dzielo_nadrzedne = tag("span", "collection")
33     handle_podtytul = tag("span", "subtitle")
34     handle_naglowek_akt = handle_naglowek_czesc = handle_srodtytul = tag("h2")
35     handle_naglowek_scena = handle_naglowek_rozdzial = tag('h3')
36     handle_naglowek_osoba = handle_naglowek_podrozdzial = tag('h4')
37     handle_akap = handle_akap_dialog = handle_akap_cd = tag('p', 'paragraph')
38     handle_strofa = tag('div', 'stanza')
39
40     def handle_aktywnosc(self, element):
41         self.activity_counter += 1
42         self.options = {
43             'activity': True,
44             'activity_counter': self.activity_counter
45             }
46         submill = EduModule()
47
48         opis = submill.generate(element.xpath('opis')[0])
49
50         n = element.xpath('wskazowki')
51         if n: wskazowki = submill.generate(n[0])
52
53         else: wskazowki = ''
54         n = element.xpath('pomoce')
55
56         if n: pomoce = submill.generate(n[0])
57         else: pomoce = ''
58
59         forma = ''.join(element.xpath('forma/text()'))
60
61         czas = ''.join(element.xpath('czas/text()'))
62
63         counter = self.activity_counter
64
65         return u"""
66 <div class="activity">
67  <div class="text">%(counter)d.
68   %(opis)s
69   %(wskazowki)s
70  </div>
71  <div class="info">
72   <p>Czas: %(czas)s min</p>
73   <p>Forma: %(forma)s</p>
74   %(pomoce)s
75  </div>
76  <div class="clearboth"></div>
77 </div>
78 """ % locals()
79
80     handle_opis = ifoption(activity=False)(tag('div', 'description'))
81     handle_wskazowki = ifoption(activity=False)(tag('div', ('hints', 'teacher')))
82
83     @ifoption(activity=False)
84     @tagged('div', 'materials')
85     def handle_pomoce(self, _):
86         return "Pomoce: ", ""
87
88     def handle_czas(self, *_):
89         return
90
91     def handle_forma(self, *_):
92         return
93
94     def handle_cwiczenie(self, element):
95         excercise_handlers = {
96             'wybor': Wybor,
97             'uporzadkuj': Uporzadkuj,
98             'luki': Luki,
99             'zastap': Zastap,
100             'przyporzadkuj': Przyporzadkuj,
101             'prawdafalsz': PrawdaFalsz
102             }
103
104         typ = element.attrib['typ']
105         handler = excercise_handlers[typ](self.options)
106         return handler.generate(element)
107
108     # Lists
109     def handle_lista(self, element, attrs={}):
110         ltype = element.attrib.get('typ', 'punkt')
111         if ltype == 'slowniczek':
112             self.options = {'slowniczek': True}
113             return '<div class="slowniczek">', '</div>'
114 ### robie teraz punkty wyboru
115         listtag = {'num': 'ol',
116                'punkt': 'ul',
117                'alfa': 'ul',
118                'czytelnia': 'ul'}[ltype]
119
120         classes = attrs.get('class', '')
121         if classes: del attrs['class']
122
123         attrs_s = ' '.join(['%s="%s"' % kv for kv in attrs.items()])
124         if attrs_s: attrs_s = ' ' + attrs_s
125
126         return '<%s class="lista %s %s"%s>' % (listtag, ltype, classes, attrs_s), '</%s>' % listtag
127
128     def handle_punkt(self, element):
129         if self.options['slowniczek']:
130             return '<dl>', '</dl>'
131         else:
132             return '<li>', '</li>'
133
134     def handle_rdf__RDF(self, _):
135         # ustal w opcjach  rzeczy :D
136         return
137
138
139 class Excercise(EduModule):
140     def __init__(self, *args, **kw):
141         self.question_counter = 0
142         super(Excercise, self).__init__(*args, **kw)
143
144     def handle_rozw_kom(self, element):
145         return None
146
147     def handle_cwiczenie(self, element):
148         self.options = {'excercise': element.attrib['typ']}
149         self.question_counter = 0
150         self.piece_counter = 0
151
152         pre = u"""
153 <div class="excercise %(typ)s" data-type="%(typ)s">
154 <form action="#" method="POST">
155 """ % element.attrib
156         post = u"""
157 <div class="buttons">
158 <span class="message"></span>
159 <input type="button" class="check" value="sprawdź"/>
160 <input type="button" class="solutions" value="pokaż rozwiązanie"/>
161 <input type="button" class="reset" value="reset"/>
162 </div>
163 </form>
164 </div>
165 """
166         # Add a single <pytanie> tag if it's not there
167         if not element.xpath(".//pytanie"):
168             qpre, qpost = self.handle_pytanie(element)
169             pre = pre + qpre
170             post = qpost + post
171         return pre, post
172
173     def handle_pytanie(self, element):
174         """This will handle <cwiczenie> element, when there is no <pytanie>
175         """
176         add_class = ""
177         self.question_counter += 1
178         self.piece_counter = 0
179         solution = element.attrib.get('rozw', None)
180         if solution: solution_s = ' data-solution="%s"' % solution
181         else: solution_s = ''
182
183         handles = element.attrib.get('uchwyty', None)
184         if handles:
185             add_class += ' handles handles-%s' % handles
186             self.options = {'handles': handles}
187
188         return '<div class="question%s" data-no="%d" %s>' %\
189             (add_class, self.question_counter, solution_s), \
190             "</div>"
191
192
193 class Wybor(Excercise):
194     def handle_cwiczenie(self, element):
195         pre, post = super(Wybor, self).handle_cwiczenie(element)
196         is_single_choice = True
197         for p in element.xpath(".//pytanie"):
198             solutions = re.split(r"[, ]+", p.attrib['rozw'])
199             if len(solutions) != 1:
200                 is_single_choice = False
201                 break
202         self.options = {'single': is_single_choice}
203         return pre, post
204
205     def handle_punkt(self, element):
206         if self.options['excercise'] and element.attrib.get('nazwa', None):
207             qc = self.question_counter
208             self.piece_counter += 1
209             no = self.piece_counter
210             eid = "q%(qc)d_%(no)d" % locals()
211             aname = element.attrib.get('nazwa', None)
212             if self.options['single']:
213                 return u"""
214 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
215 <input type="radio" name="q%(qc)d" id="%(eid)s" value="%(aname)s" />
216 <label for="%(eid)s">
217                 """ % locals(), u"</label></li>"
218             else:
219                 return u"""
220 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
221 <input type="checkbox" name="%(eid)s" id="%(eid)s" />
222 <label for="%(eid)s">
223 """ % locals(), u"</label></li>"
224
225         else:
226             return super(Wybor, self).handle_punkt(element)
227
228
229 class Uporzadkuj(Excercise):
230     def handle_pytanie(self, element):
231         """
232 Overrides the returned content default handle_pytanie
233         """
234         # we ignore the result, returning our own
235         super(Uporzadkuj, self).handle_pytanie(element)
236         order_items = element.xpath(".//punkt/@rozw")
237
238         return u"""<div class="question" data-original="%s" data-no="%s">""" % \
239             (','.join(order_items), self.question_counter), \
240             u"""</div>"""
241
242     def handle_punkt(self, element):
243         return """<li class="question-piece" data-pos="%(rozw)s"/>""" \
244             % element.attrib,\
245             "</li>"
246
247
248 class Luki(Excercise):
249     def find_pieces(self, question):
250         return question.xpath("//luka")
251
252     def solution_html(self, piece):
253         return piece.text + ''.join(
254             [etree.tostring(n, encoding=unicode)
255              for n in piece])
256
257     def handle_pytanie(self, element):
258         qpre, qpost = super(Luki, self).handle_pytanie(element)
259
260         luki = list(enumerate(self.find_pieces(element)))
261         luki_html = ""
262         i = 0
263         random.shuffle(luki)
264         for (i, luka) in luki:
265             i += 1
266             luka_html = self.solution_html(luka)
267             luki_html += u'<span class="draggable question-piece" data-no="%d">%s</span>' % (i, luka_html)
268         self.words_html = '<div class="words">%s</div>' % luki_html
269
270         return qpre, qpost
271
272     def handle_opis(self, element):
273         pre, post = super(Luki, self).handle_opis(element)
274         return pre, self.words_html + post
275
276     def handle_luka(self, element):
277         self.piece_counter += 1
278         return '<span class="placeholder" data-solution="%d"></span>' % self.piece_counter
279
280
281 class Zastap(Luki):
282     def find_pieces(self, question):
283         return question.xpath("//zastap")
284
285     def solution_html(self, piece):
286         return piece.attrib['rozw']
287
288     def handle_zastap(self, element):
289         self.piece_counter += 1
290         return '<span class="placeholder zastap question-piece" data-solution="%d">' \
291             % self.piece_counter, '</span>'
292
293
294 class Przyporzadkuj(Excercise):
295     def handle_lista(self, lista):
296         if 'nazwa' in lista.attrib:
297             attrs = {
298                 'data-name': lista.attrib['nazwa'],
299                 'class': 'predicate'
300                 }
301             self.options = {'predicate': True}
302         elif 'cel' in lista.attrib:
303             attrs = {
304                 'data-target': lista.attrib['cel'],
305                 'class': 'subject'
306                 }
307             self.options = {'subject': True, 'handles': 'uchwyty' in lista.attrib}
308         else:
309             attrs = {}
310         pre, post = super(Przyporzadkuj, self).handle_lista(lista, attrs)
311         return pre, post + '<br class="clr"/>'
312
313     def handle_punkt(self, element):
314         if self.options['subject']:
315             self.piece_counter += 1
316             if self.options['handles']:
317                 return '<li><span data-solution="%s" data-no="%s" class="question-piece draggable handle">%s</span>' % (element.attrib['rozw'], self.piece_counter, self.piece_counter), '</li>'
318             else:
319                 return '<li data-solution="%s" data-no="%s" class="question-piece draggable">' % (element.attrib['rozw'], self.piece_counter), '</li>'
320
321         elif self.options['predicate']:
322             placeholders = u'<li class="placeholder multiple"/>'
323             return '<li data-predicate="%(nazwa)s">' % element.attrib, '<ul class="subjects">' + placeholders + '</ul></li>'
324
325         else:
326             return super(Przyporzadkuj, self).handle_punkt(element)
327
328
329 class PrawdaFalsz(Excercise):
330     def handle_punkt(self, element):
331         if 'rozw' in element.attrib:
332             return u'''<li data-solution="%s" class="question-piece">
333             <span class="buttons">
334             <a href="#" data-value="true" class="true">Prawda</a>
335             <a href="#" data-value="false" class="false">Fałsz</a>
336         </span>''' % {'prawda': 'true', 'falsz': 'false'}[element.attrib['rozw']], '</li>'
337         else:
338             return super(PrawdaFalsz, self).handle_punkt(element)
339
340
341 def transform(wldoc, stylesheet='edumed', options=None, flags=None):
342     """Transforms the WL document to XHTML.
343
344     If output_filename is None, returns an XML,
345     otherwise returns True if file has been written,False if it hasn't.
346     File won't be written if it has no content.
347     """
348     edumod = EduModule(options)
349 #    from pdb import set_trace; set_trace()
350     html = edumod.generate(wldoc.edoc.getroot())
351
352     return OutputFile.from_string(html.encode('utf-8'))