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