Fixes #3290: Support for Przyporzadkuj with short items.
[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 IOFile, RDFNS, DCNS, Format
8 from xmlutils import Xmill, tag, tagged, ifoption, tag_open_close
9 from librarian import functions
10 import re
11 import random
12 from copy import deepcopy
13
14 IMAGE_THUMB_WIDTH = 300
15
16 class EduModule(Xmill):
17     def __init__(self, options=None):
18         super(EduModule, self).__init__(options)
19         self.activity_counter = 0
20         self.activity_last = None
21         self.exercise_counter = 0
22
23         # text filters
24         def swap_endlines(txt):
25             if self.options['strofa']:
26                 txt = txt.replace("/\n", "<br/>\n")
27             return txt
28         self.register_text_filter(functions.substitute_entities)
29         self.register_escaped_text_filter(swap_endlines)
30
31     @tagged('div', 'stanza')
32     def handle_strofa(self, element):
33         self.options = {'strofa': True}
34         return "", ""
35
36     def handle_powiesc(self, element):
37         return u"""
38 <div class="module" id="book-text">
39 <!-- <span class="teacher-toggle">
40   <input type="checkbox" name="teacher-toggle" id="teacher-toggle"/>
41   <label for="teacher-toggle">Pokaż treść dla nauczyciela</label>
42  </span>-->
43
44 """, u"</div>"
45
46     handle_autor_utworu = tag("span", "author")
47     handle_dzielo_nadrzedne = tag("span", "collection")
48     handle_podtytul = tag("span", "subtitle")
49     handle_naglowek_akt = handle_naglowek_czesc = handle_srodtytul = tag("h2")
50     handle_naglowek_scena = tag('h2')
51     handle_naglowek_osoba = tag('h3')
52     handle_akap = handle_akap_dialog = handle_akap_cd = tag('p', 'paragraph')
53
54     handle_wyroznienie = tag('em')
55     handle_tytul_dziela = tag('em', 'title')
56     handle_slowo_obce = tag('em', 'foreign')
57
58     def naglowek_to_anchor(self, naglowek):
59         return self.options['urlmapper'].naglowek_to_anchor(naglowek)
60
61     def handle_nazwa_utworu(self, element):
62         toc = []
63         for naglowek in element.getparent().findall('.//naglowek_rozdzial'):
64             a = etree.Element("a")
65             a.attrib["href"] = "#" + self.naglowek_to_anchor(naglowek)
66             a.text = naglowek.text
67             atxt = etree.tostring(a, encoding=unicode)
68             toc.append("<li>%s</li>" % atxt)
69         toc = "<ul class='toc'>%s</ul>" % "".join(toc)
70         add_header = "Lekcja: " if self.options['wldoc'].book_info.type in ('course', 'synthetic') else ''
71         return "<h1 class='title' id='top'>%s" % add_header, "</h1>" + toc
72
73     def handle_naglowek_rozdzial(self, element):
74         return_to_top = u"<a href='#top' class='top-link'>wróć do spisu treści</a>"
75         pre, post = tag_open_close("h2", id=self.naglowek_to_anchor(element))
76         url = self.options['urlmapper'].get_help_url(element)
77         if url:
78             post = " <a class='help' href='%s'>?</a>" % (url,) + post
79         return return_to_top + pre, post
80
81     def handle_naglowek_podrozdzial(self, element):
82         self.activity_counter = 0
83         return tag('h3')(self, element)
84
85     def handle_uwaga(self, _e):
86         return None
87
88     def handle_aktywnosc(self, element):
89         self.activity_counter += 1
90         self.options = {
91             'activity': True,
92             'activity_counter': self.activity_counter,
93             }
94         submill = EduModule(dict(self.options.items() + {'sub_gen': True}.items()))
95
96         if element.xpath('opis'):
97             opis = submill.generate(element.xpath('opis')[0])
98         else:
99             opis = ''
100
101         n = element.xpath('wskazowki')
102         if n: wskazowki = submill.generate(n[0])
103
104         else: wskazowki = ''
105         n = element.xpath('pomoce')
106
107         if n: pomoce = submill.generate(n[0])
108         else: pomoce = ''
109
110         forma = ''.join(element.xpath('forma/text()'))
111         get_forma_url = self.options['urlmapper'].get_forma_url
112         forms = []
113         for form_name in forma.split(','):
114             name = form_name.strip()
115             url = get_forma_url(name)
116             if url:
117                 forms.append("<a href='%s'>%s</a>" % (url, name))
118             else:
119                 forms.append(name)
120         forma = ', '.join(forms)
121         if forma:
122             forma = '<section class="infobox kind"><h1>Metoda</h1><p>%s</p></section>' % forma
123
124         czas = ''.join(element.xpath('czas/text()'))
125         if czas:
126             czas = '<section class="infobox time"><h1>Czas</h1><p>%s min</p></section>' % czas
127
128         counter = self.activity_counter
129
130         if element.getnext().tag == 'aktywnosc' or self.activity_last.getnext() == element:
131             counter_html = """<span class="act_counter">%(counter)d.</span>""" % locals()
132         else:
133             counter_html = ''
134
135         self.activity_last = element
136
137         return u"""
138 <div class="activity">
139  <div class="text">
140   %(counter_html)s
141   %(opis)s""" % locals(), \
142 u"""%(wskazowki)s
143  </div>
144  <aside class="info">
145   %(czas)s
146   %(forma)s
147   %(pomoce)s
148  </aside>
149  <div class="clearboth"></div>
150 </div>
151 """ % locals()
152
153     handle_opis = ifoption(sub_gen=True)(tag('div', 'description'))
154     handle_wskazowki = ifoption(sub_gen=True)(tag('div', ('hints', 'teacher')))
155
156     @ifoption(sub_gen=True)
157     @tagged('section', 'infobox materials')
158     def handle_pomoce(self, _):
159         return """<h1>Pomoce</h1>""", ""
160
161     def handle_czas(self, *_):
162         return
163
164     def handle_forma(self, *_):
165         return
166
167     def handle_cwiczenie(self, element):
168         exercise_handlers = {
169             'wybor': Wybor,
170             'uporzadkuj': Uporzadkuj,
171             'luki': Luki,
172             'zastap': Zastap,
173             'przyporzadkuj': Przyporzadkuj,
174             'prawdafalsz': PrawdaFalsz
175             }
176
177         typ = element.attrib['typ']
178         self.exercise_counter += 1
179         self.options = {'exercise_counter': self.exercise_counter}
180         handler = exercise_handlers[typ](self.options)
181         return handler.generate(element)
182
183     # Lists
184     def handle_lista(self, element, attrs={}):
185         ltype = element.attrib.get('typ', 'punkt')
186         if not element.findall("punkt"):
187             if ltype == 'czytelnia':
188                 return '<p>W przygotowaniu.</p>'
189             else:
190                 return None
191         if ltype == 'slowniczek':
192             surl = element.attrib.get('src', None)
193             if surl is None:
194                 # print '** missing src on <slowniczek>, setting default'
195                 surl = 'http://edukacjamedialna.edu.pl/lekcje/slowniczek/'
196             sxml = etree.fromstring(self.options['provider'].by_uri(surl).get_string())
197
198             self.options = {'slowniczek': True, 'slowniczek_xml': sxml }
199             pre, post = '<div class="slowniczek">', '</div>'
200             if not self.options['wldoc'].book_info.url.slug.startswith('slowniczek'):
201                 post += u'<p class="see-more"><a href="%s">Zobacz cały słowniczek.</a></p>' % surl
202             return pre, post
203
204         listtag = {'num': 'ol',
205                'punkt': 'ul',
206                'alfa': 'ul',
207                'czytelnia': 'ul'}[ltype]
208
209         classes = attrs.get('class', '')
210         if classes: del attrs['class']
211
212         attrs_s = ' '.join(['%s="%s"' % kv for kv in attrs.items()])
213         if attrs_s: attrs_s = ' ' + attrs_s
214
215         return '<%s class="lista %s %s"%s>' % (listtag, ltype, classes, attrs_s), '</%s>' % listtag
216
217     def handle_punkt(self, element):
218         if self.options['slowniczek']:
219             return '<dl>', '</dl>'
220         else:
221             return '<li>', '</li>'
222
223     def handle_definiendum(self, element):
224         nxt = element.getnext()
225         definiens_s = ''
226
227         if not element.text:
228             print "!! Empty <definiendum/>"
229             return None
230
231         # let's pull definiens from another document
232         if self.options['slowniczek_xml'] is not None and (nxt is None or nxt.tag != 'definiens'):
233             sxml = self.options['slowniczek_xml']
234             defloc = sxml.xpath("//definiendum[text()='%s']" % element.text)
235             if defloc:
236                 definiens = defloc[0].getnext()
237                 if definiens.tag == 'definiens':
238                     subgen = EduModule(self.options)
239                     definiens_s = subgen.generate(definiens)
240             else:
241                 print "!! Missing definiendum in source: '%s'" % element.text
242
243         return u"<dt id='%s'>" % self.naglowek_to_anchor(element), u"</dt>" + definiens_s
244
245     def handle_definiens(self, element):
246         return u"<dd>", u"</dd>"
247
248     def handle_podpis(self, element):
249         return u"""<div class="caption">""", u"</div>"
250
251     def handle_tabela(self, element):
252         has_frames = int(element.attrib.get("ramki", "0"))
253         if has_frames: frames_c = "framed"
254         else: frames_c = ""
255         return u"""<table class="%s">""" % frames_c, u"</table>"
256
257     def handle_wiersz(self, element):
258         return u"<tr>", u"</tr>"
259
260     def handle_kol(self, element):
261         return u"<td>", u"</td>"
262
263     def handle_rdf__RDF(self, _):
264         # ustal w opcjach  rzeczy :D
265         return
266
267     def handle_link(self, element):
268         if 'url' in element.attrib:
269             return tag('a', href=element.attrib['url'])(self, element)
270         elif 'material' in element.attrib:
271             material_err = u' [BRAKUJĄCY MATERIAŁ]'
272             slug = element.attrib['material']
273             make_url = lambda f: self.options['urlmapper'] \
274               .url_for_material(slug, f)
275
276             if 'format' in element.attrib:
277                 formats = re.split(r"[, ]+",
278                                element.attrib['format'])
279             else:
280                 formats = [None]
281
282             formats = self.options['urlmapper'].materials(slug)
283
284             try:
285                 def_href = make_url(formats[0][0])
286                 def_err = u""
287             except (IndexError, self.options['urlmapper'].MaterialNotFound):
288                 def_err = material_err
289                 def_href = u""
290             fmt_links = []
291             for f in formats[1:]:
292                 try:
293                     fmt_links.append(u'<a href="%s">%s</a>' % (make_url(f[0]), f[0].upper()))
294                 except self.options['urlmapper'].MaterialNotFound:
295                     fmt_links.append(u'<a>%s%s</a>' % (f[0].upper(), material_err))
296             more_links = u' (%s)' % u', '.join(fmt_links) if fmt_links else u''
297
298             return u"<a href='%s'>" % def_href, u'%s</a>%s' % (def_err, more_links)
299
300     def handle_obraz(self, element):
301         name = element.attrib.get('nazwa', '').strip()
302         if not name:
303             print '!! <obraz> missing "nazwa"'
304             return
305         alt = element.attrib.get('alt', '')
306         if not alt:
307             print '** <obraz> missing "alt"'
308         slug, ext = name.rsplit('.', 1)
309         url = self.options['urlmapper'].url_for_image(slug, ext)
310         thumb_url = self.options['urlmapper'].url_for_image(slug, ext, IMAGE_THUMB_WIDTH)
311         e = etree.Element("a", attrib={"href": url, "class": "image"})
312         e.append(etree.Element("img", attrib={"src": thumb_url, "alt": alt,
313                     "width": str(IMAGE_THUMB_WIDTH)}))
314         return etree.tostring(e, encoding=unicode), u""
315
316     def handle_video(self, element):
317         url = element.attrib.get('url')
318         if not url:
319             print '!! <video> missing url'
320             return
321         m = re.match(r'(?:https?://)?(?:www.)?youtube.com/watch\?(?:.*&)?v=([^&]+)(?:$|&)', url)
322         if not m:
323             print '!! unknown <video> url scheme:', url
324             return
325         return """<iframe width="630" height="384" src="http://www.youtube.com/embed/%s"
326             frameborder="0" allowfullscreen></iframe>""" % m.group(1), ""
327
328
329 class Exercise(EduModule):
330     INSTRUCTION = ""
331     def __init__(self, *args, **kw):
332         self.question_counter = 0
333         super(Exercise, self).__init__(*args, **kw)
334         self.instruction_printed = False
335
336     @tagged('div', 'description')
337     def handle_opis(self, element):
338         return "", self.get_instruction()
339
340     def handle_rozw_kom(self, element):
341         return u"""<div style="display:none" class="comment">""", u"""</div>"""
342
343     def handle_cwiczenie(self, element):
344         self.options = {'exercise': element.attrib['typ']}
345         self.question_counter = 0
346         self.piece_counter = 0
347
348         pre = u"""
349 <div class="exercise %(typ)s" data-type="%(typ)s">
350 <form action="#" method="POST">
351 <h3>Zadanie %(exercies_counter)d</h3>
352 <div class="buttons">
353 <span class="message"></span>
354 <input type="button" class="check" value="sprawdź"/>
355 <input type="button" class="retry" style="display:none" value="spróbuj ponownie"/>
356 <input type="button" class="solutions" value="pokaż rozwiązanie"/>
357 <input type="button" class="reset" value="reset"/>
358 </div>
359
360 """ % {'exercies_counter': self.options['exercise_counter'], 'typ': element.attrib['typ']}
361         post = u"""
362 <div class="buttons">
363 <span class="message"></span>
364 <input type="button" class="check" value="sprawdź"/>
365 <input type="button" class="retry" style="display:none" value="spróbuj ponownie"/>
366 <input type="button" class="solutions" value="pokaż rozwiązanie"/>
367 <input type="button" class="reset" value="reset"/>
368 </div>
369 </form>
370 </div>
371 """
372         # Add a single <pytanie> tag if it's not there
373         if not element.xpath(".//pytanie"):
374             qpre, qpost = self.handle_pytanie(element)
375             pre = pre + qpre
376             post = qpost + post
377         return pre, post
378
379     def handle_pytanie(self, element):
380         """This will handle <cwiczenie> element, when there is no <pytanie>
381         """
382         add_class = ""
383         self.question_counter += 1
384         self.piece_counter = 0
385         solution = element.attrib.get('rozw', None)
386         if solution: solution_s = ' data-solution="%s"' % solution
387         else: solution_s = ''
388
389         handles = element.attrib.get('uchwyty', None)
390         if handles:
391             add_class += ' handles handles-%s' % handles
392             self.options = {'handles': handles}
393
394         minimum = element.attrib.get('min', None)
395         if minimum: minimum_s = ' data-minimum="%d"' % int(minimum)
396         else: minimum_s = ''
397
398         return '<div class="question%s" data-no="%d" %s>' %\
399             (add_class, self.question_counter, solution_s + minimum_s), \
400             "</div>"
401
402     def get_instruction(self):
403         if not self.instruction_printed:
404             self.instruction_printed = True
405             if self.INSTRUCTION:
406                 return u'<span class="instruction">%s</span>' % self.INSTRUCTION
407             else:
408                 return ""
409         else:
410             return ""
411
412
413
414 class Wybor(Exercise):
415     def handle_cwiczenie(self, element):
416         pre, post = super(Wybor, self).handle_cwiczenie(element)
417         is_single_choice = True
418         pytania = element.xpath(".//pytanie")
419         if not pytania:
420             pytania = [element]
421         for p in pytania:
422             solutions = re.split(r"[, ]+", p.attrib.get('rozw', ''))
423             if len(solutions) != 1:
424                 is_single_choice = False
425                 break
426             choices = p.xpath(".//*[@nazwa]")
427             uniq = set()
428             for n in choices: uniq.add(n.attrib.get('nazwa', ''))
429             if len(choices) != len(uniq):
430                 is_single_choice = False
431                 break
432
433         self.options = {'single': is_single_choice}
434         return pre, post
435
436     def handle_punkt(self, element):
437         if self.options['exercise'] and element.attrib.get('nazwa', None):
438             qc = self.question_counter
439             self.piece_counter += 1
440             no = self.piece_counter
441             eid = "q%(qc)d_%(no)d" % locals()
442             aname = element.attrib.get('nazwa', None)
443             if self.options['single']:
444                 return u"""
445 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
446 <input type="radio" name="q%(qc)d" id="%(eid)s" value="%(aname)s" />
447 <label for="%(eid)s">
448                 """ % locals(), u"</label></li>"
449             else:
450                 return u"""
451 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
452 <input type="checkbox" name="%(eid)s" id="%(eid)s" />
453 <label for="%(eid)s">
454 """ % locals(), u"</label></li>"
455
456         else:
457             return super(Wybor, self).handle_punkt(element)
458
459
460 class Uporzadkuj(Exercise):
461     INSTRUCTION = u"Kliknij wybraną odpowiedź i przeciągnij w nowe miejsce."
462
463     def handle_pytanie(self, element):
464         """
465 Overrides the returned content default handle_pytanie
466         """
467         # we ignore the result, returning our own
468         super(Uporzadkuj, self).handle_pytanie(element)
469         order_items = element.xpath(".//punkt/@rozw")
470
471         return u"""<div class="question" data-original="%s" data-no="%s">""" % \
472             (','.join(order_items), self.question_counter), \
473             u"""</div>"""
474
475     def handle_punkt(self, element):
476         return """<li class="question-piece" data-pos="%(rozw)s">""" \
477             % element.attrib,\
478             "</li>"
479
480
481 class Luki(Exercise):
482     INSTRUCTION = u"Przeciągnij odpowiedzi i upuść w wybranym polu."
483     def find_pieces(self, question):
484         return question.xpath(".//luka")
485
486     def solution_html(self, piece):
487         piece = deepcopy(piece)
488         piece.tail = None
489         sub = EduModule()
490         return sub.generate(piece)
491
492     def handle_pytanie(self, element):
493         qpre, qpost = super(Luki, self).handle_pytanie(element)
494
495         luki = list(enumerate(self.find_pieces(element)))
496         luki_html = ""
497         i = 0
498         random.shuffle(luki)
499         for (i, luka) in luki:
500             i += 1
501             luka_html = self.solution_html(luka)
502             luki_html += u'<span class="draggable question-piece" data-no="%d">%s</span>' % (i, luka_html)
503         self.words_html = '<div class="words">%s</div>' % luki_html
504
505         return qpre, qpost
506
507     def handle_opis(self, element):
508         return '', self.words_html
509
510     def handle_luka(self, element):
511         self.piece_counter += 1
512         return '<span class="placeholder" data-solution="%d"></span>' % self.piece_counter
513
514
515 class Zastap(Luki):
516     INSTRUCTION = u"Przeciągnij odpowiedzi i upuść je na słowie lub wyrażeniu, które chcesz zastąpić."
517
518     def find_pieces(self, question):
519         return question.xpath(".//zastap")
520
521     def solution_html(self, piece):
522         return piece.attrib.get('rozw', '')
523
524     def handle_zastap(self, element):
525         self.piece_counter += 1
526         return '<span class="placeholder zastap question-piece" data-solution="%d">' \
527             % self.piece_counter, '</span>'
528
529
530 class Przyporzadkuj(Exercise):
531     INSTRUCTION = [u"Przeciągnij odpowiedzi i upuść w wybranym polu.",
532                    u"Kliknij numer odpowiedzi, przeciągnij i upuść w wybranym polu."]
533
534     def get_instruction(self):
535         if not self.instruction_printed:
536             self.instruction_printed = True
537             return u'<span class="instruction">%s</span>' % self.INSTRUCTION[self.options['handles'] and 1 or 0]
538         else:
539             return ""
540
541     def handle_cwiczenie(self, element):
542         pre, post = super(Przyporzadkuj, self).handle_cwiczenie(element)
543         lista_with_handles = element.xpath(".//*[@uchwyty]")
544         if lista_with_handles:
545             self.options = {'handles': True}
546         return pre, post
547
548     def handle_pytanie(self, element):
549         pre, post = super(Przyporzadkuj, self).handle_pytanie(element)
550         minimum = element.attrib.get("min", None)
551         if minimum:
552             self.options = {"min": int(minimum)}
553         return pre, post
554
555     def handle_lista(self, lista):
556         if 'nazwa' in lista.attrib:
557             attrs = {
558                 'data-name': lista.attrib['nazwa'],
559                 'class': 'predicate'
560             }
561             self.options = {'predicate': True}
562         elif 'cel' in lista.attrib:
563             attrs = {
564                 'data-target': lista.attrib['cel'],
565                 'class': 'subject'
566             }
567             if lista.attrib.get('krotkie'):
568                 self.options = {'short': True}
569             self.options = {'subject': True}
570         else:
571             attrs = {}
572         pre, post = super(Przyporzadkuj, self).handle_lista(lista, attrs)
573         return pre, post + '<br class="clr"/>'
574
575     def handle_punkt(self, element):
576         if self.options['subject']:
577             self.piece_counter += 1
578             if self.options['handles']:
579                 return '<li><span data-solution="%s" data-no="%s" class="question-piece draggable handle add-li">%s</span>' % (element.attrib.get('rozw', ''), self.piece_counter, self.piece_counter), '</li>'
580             else:
581                 extra_class = ""
582                 if self.options['short']:
583                     extra_class += ' short'
584                 return '<li data-solution="%s" data-no="%s" class="question-piece draggable%s">' % (element.attrib.get('rozw', ''), self.piece_counter, extra_class), '</li>'
585
586         elif self.options['predicate']:
587             if self.options['min']:
588                 placeholders = u'<li class="placeholder"></li>' * self.options['min']
589             else:
590                 placeholders = u'<li class="placeholder multiple"></li>'
591             return '<li data-predicate="%s">' % element.attrib.get('nazwa', ''), '<ul class="subjects">' + placeholders + '</ul></li>'
592
593         else:
594             return super(Przyporzadkuj, self).handle_punkt(element)
595
596
597 class PrawdaFalsz(Exercise):
598     def handle_punkt(self, element):
599         if 'rozw' in element.attrib:
600             return u'''<li data-solution="%s" class="question-piece">
601             <span class="buttons">
602             <a href="#" data-value="true" class="true">Prawda</a>
603             <a href="#" data-value="false" class="false">Fałsz</a>
604         </span>''' % {'prawda': 'true', 'falsz': 'false'}[element.attrib['rozw']], '</li>'
605         else:
606             return super(PrawdaFalsz, self).handle_punkt(element)
607
608
609 class EduModuleFormat(Format):
610     PRIMARY_MATERIAL_FORMATS = ('pdf', 'odt')
611
612     class MaterialNotFound(BaseException):
613         pass
614
615     def __init__(self, wldoc, **kwargs):
616         super(EduModuleFormat, self).__init__(wldoc, **kwargs)
617
618     def build(self):
619         # Sort materials by slug.
620         self.materials_by_slug = {}
621         for name, att in self.wldoc.source.attachments.items():
622             parts = name.rsplit('.', 1)
623             if len(parts) == 1:
624                 continue
625             slug, ext = parts
626             if slug not in self.materials_by_slug:
627                 self.materials_by_slug[slug] = {}
628             self.materials_by_slug[slug][ext] = att
629
630         edumod = EduModule({'provider': self.wldoc.provider, 'urlmapper': self, 'wldoc': self.wldoc})
631
632         html = edumod.generate(self.wldoc.edoc.getroot())
633
634         return IOFile.from_string(html.encode('utf-8'))
635
636     def materials(self, slug):
637         """Returns a list of pairs: (ext, iofile)."""
638         order = dict(reversed(k) for k in enumerate(self.PRIMARY_MATERIAL_FORMATS))
639         mats = self.materials_by_slug.get(slug, {}).items()
640         if not mats:
641             print "!! Material missing: '%s'" % slug
642         return sorted(mats, key=lambda (x, y): order.get(x, x))
643
644     def url_for_material(self, slug, fmt):
645         return "%s.%s" % (slug, fmt)
646
647     def url_for_image(self, slug, fmt, width=None):
648         return self.url_for_material(self, slug, fmt)
649
650     def text_to_anchor(self, text):
651         return re.sub(r" +", " ", text)
652
653     def naglowek_to_anchor(self, naglowek):
654         return self.text_to_anchor(naglowek.text.strip())
655
656     def get_forma_url(self, forma):
657         return None
658
659     def get_help_url(self, naglowek):
660         return None
661
662
663 def transform(wldoc, stylesheet='edumed', options=None, flags=None, verbose=None):
664     """Transforms the WL document to XHTML.
665
666     If output_filename is None, returns an XML,
667     otherwise returns True if file has been written,False if it hasn't.
668     File won't be written if it has no content.
669     """
670     edumodfor = EduModuleFormat(wldoc)
671     return edumodfor.build()