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