5c2e5c62623e4725e3203bf687fa72fb5b78d89e
[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         exercise_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 = exercise_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
135     def handle_podpis(self, element):
136         return u"""<div class="caption">""", u"</div>"
137
138     def handle_tabela(self, element):
139         has_frames = int(element.attrib.get("ramki", "0"))
140         if has_frames: frames_c = "framed"
141         else: frames_c = ""
142         return u"""<table class="%s">""" % frames_c, u"</table>"
143
144     def handle_wiersz(self, element):
145         return u"<tr>", u"</tr>"
146
147     def handle_kol(self, element):
148         return u"<td>", u"</td>"
149
150     def handle_rdf__RDF(self, _):
151         # ustal w opcjach  rzeczy :D
152         return
153
154
155 class Exercise(EduModule):
156     def __init__(self, *args, **kw):
157         self.question_counter = 0
158         super(Exercise, self).__init__(*args, **kw)
159
160     def handle_rozw_kom(self, element):
161         return u"""<div style="display:none" class="comment">""", u"""</div>"""
162
163     def handle_cwiczenie(self, element):
164         self.options = {'exercise': element.attrib['typ']}
165         self.question_counter = 0
166         self.piece_counter = 0
167
168         pre = u"""
169 <div class="exercise %(typ)s" data-type="%(typ)s">
170 <form action="#" method="POST">
171 """ % element.attrib
172         post = u"""
173 <div class="buttons">
174 <span class="message"></span>
175 <input type="button" class="check" value="sprawdź"/>
176 <input type="button" class="retry" style="display:none" value="spróbuj ponownie"/>
177 <input type="button" class="solutions" value="pokaż rozwiązanie"/>
178 <input type="button" class="reset" value="reset"/>
179 </div>
180 </form>
181 </div>
182 """
183         # Add a single <pytanie> tag if it's not there
184         if not element.xpath(".//pytanie"):
185             qpre, qpost = self.handle_pytanie(element)
186             pre = pre + qpre
187             post = qpost + post
188         return pre, post
189
190     def handle_pytanie(self, element):
191         """This will handle <cwiczenie> element, when there is no <pytanie>
192         """
193         add_class = ""
194         self.question_counter += 1
195         self.piece_counter = 0
196         solution = element.attrib.get('rozw', None)
197         if solution: solution_s = ' data-solution="%s"' % solution
198         else: solution_s = ''
199
200         handles = element.attrib.get('uchwyty', None)
201         if handles:
202             add_class += ' handles handles-%s' % handles
203             self.options = {'handles': handles}
204
205         minimum = element.attrib.get('min', None)
206         if minimum: minimum_s = ' data-minimum="%d"' % int(minimum)
207         else: minimum_s = ''
208
209         return '<div class="question%s" data-no="%d" %s>' %\
210             (add_class, self.question_counter, solution_s + minimum_s), \
211             "</div>"
212
213
214 class Wybor(Exercise):
215     def handle_cwiczenie(self, element):
216         pre, post = super(Wybor, self).handle_cwiczenie(element)
217         is_single_choice = True
218         for p in element.xpath(".//pytanie"):
219             solutions = re.split(r"[, ]+", p.attrib['rozw'])
220             if len(solutions) != 1:
221                 is_single_choice = False
222                 break
223         self.options = {'single': is_single_choice}
224         return pre, post
225
226     def handle_punkt(self, element):
227         if self.options['exercise'] and element.attrib.get('nazwa', None):
228             qc = self.question_counter
229             self.piece_counter += 1
230             no = self.piece_counter
231             eid = "q%(qc)d_%(no)d" % locals()
232             aname = element.attrib.get('nazwa', None)
233             if self.options['single']:
234                 return u"""
235 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
236 <input type="radio" name="q%(qc)d" id="%(eid)s" value="%(aname)s" />
237 <label for="%(eid)s">
238                 """ % locals(), u"</label></li>"
239             else:
240                 return u"""
241 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
242 <input type="checkbox" name="%(eid)s" id="%(eid)s" />
243 <label for="%(eid)s">
244 """ % locals(), u"</label></li>"
245
246         else:
247             return super(Wybor, self).handle_punkt(element)
248
249
250 class Uporzadkuj(Exercise):
251     def handle_pytanie(self, element):
252         """
253 Overrides the returned content default handle_pytanie
254         """
255         # we ignore the result, returning our own
256         super(Uporzadkuj, self).handle_pytanie(element)
257         order_items = element.xpath(".//punkt/@rozw")
258
259         return u"""<div class="question" data-original="%s" data-no="%s">""" % \
260             (','.join(order_items), self.question_counter), \
261             u"""</div>"""
262
263     def handle_punkt(self, element):
264         return """<li class="question-piece" data-pos="%(rozw)s"/>""" \
265             % element.attrib,\
266             "</li>"
267
268
269 class Luki(Exercise):
270     def find_pieces(self, question):
271         return question.xpath("//luka")
272
273     def solution_html(self, piece):
274         return piece.text + ''.join(
275             [etree.tostring(n, encoding=unicode)
276              for n in piece])
277
278     def handle_pytanie(self, element):
279         qpre, qpost = super(Luki, self).handle_pytanie(element)
280
281         luki = list(enumerate(self.find_pieces(element)))
282         luki_html = ""
283         i = 0
284         random.shuffle(luki)
285         for (i, luka) in luki:
286             i += 1
287             luka_html = self.solution_html(luka)
288             luki_html += u'<span class="draggable question-piece" data-no="%d">%s</span>' % (i, luka_html)
289         self.words_html = '<div class="words">%s</div>' % luki_html
290
291         return qpre, qpost
292
293     def handle_opis(self, element):
294         pre, post = super(Luki, self).handle_opis(element)
295         return pre, self.words_html + post
296
297     def handle_luka(self, element):
298         self.piece_counter += 1
299         return '<span class="placeholder" data-solution="%d"></span>' % self.piece_counter
300
301
302 class Zastap(Luki):
303     def find_pieces(self, question):
304         return question.xpath("//zastap")
305
306     def solution_html(self, piece):
307         return piece.attrib['rozw']
308
309     def handle_zastap(self, element):
310         self.piece_counter += 1
311         return '<span class="placeholder zastap question-piece" data-solution="%d">' \
312             % self.piece_counter, '</span>'
313
314
315 class Przyporzadkuj(Exercise):
316     def handle_pytanie(self, element):
317         pre, post = super(Przyporzadkuj, self).handle_pytanie(element)
318         minimum = element.attrib.get("min", None)
319         if minimum:
320             self.options = {"min": int(minimum)}
321         return pre, post
322
323     def handle_lista(self, lista):
324         if 'nazwa' in lista.attrib:
325             attrs = {
326                 'data-name': lista.attrib['nazwa'],
327                 'class': 'predicate'
328             }
329             self.options = {'predicate': True}
330         elif 'cel' in lista.attrib:
331             attrs = {
332                 'data-target': lista.attrib['cel'],
333                 'class': 'subject'
334             }
335             self.options = {'subject': True, 'handles': 'uchwyty' in lista.attrib}
336         else:
337             attrs = {}
338         pre, post = super(Przyporzadkuj, self).handle_lista(lista, attrs)
339         return pre, post + '<br class="clr"/>'
340
341     def handle_punkt(self, element):
342         if self.options['subject']:
343             self.piece_counter += 1
344             if self.options['handles']:
345                 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>'
346             else:
347                 return '<li data-solution="%s" data-no="%s" class="question-piece draggable">' % (element.attrib['rozw'], self.piece_counter), '</li>'
348
349         elif self.options['predicate']:
350             if self.options['min']:
351                 placeholders = u'<li class="placeholder"/>' * self.options['min']
352             else:
353                 placeholders = u'<li class="placeholder multiple"/>'
354             return '<li data-predicate="%(nazwa)s">' % element.attrib, '<ul class="subjects">' + placeholders + '</ul></li>'
355
356         else:
357             return super(Przyporzadkuj, self).handle_punkt(element)
358
359
360 class PrawdaFalsz(Exercise):
361     def handle_punkt(self, element):
362         if 'rozw' in element.attrib:
363             return u'''<li data-solution="%s" class="question-piece">
364             <span class="buttons">
365             <a href="#" data-value="true" class="true">Prawda</a>
366             <a href="#" data-value="false" class="false">Fałsz</a>
367         </span>''' % {'prawda': 'true', 'falsz': 'false'}[element.attrib['rozw']], '</li>'
368         else:
369             return super(PrawdaFalsz, self).handle_punkt(element)
370
371
372 def transform(wldoc, stylesheet='edumed', options=None, flags=None):
373     """Transforms the WL document to XHTML.
374
375     If output_filename is None, returns an XML,
376     otherwise returns True if file has been written,False if it hasn't.
377     File won't be written if it has no content.
378     """
379     edumod = EduModule(options)
380 #    from pdb import set_trace; set_trace()
381     html = edumod.generate(wldoc.edoc.getroot())
382
383     return OutputFile.from_string(html.encode('utf-8'))