1043f4fed222cab02674b376db11562a3a3d04dc
[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             if "'" in (element.text or ''):
235                 defloc = sxml.xpath("//definiendum[text()=\"%s\"]" % (element.text or '').strip())
236             else:
237                 defloc = sxml.xpath("//definiendum[text()='%s']" % (element.text or '').strip())
238             if defloc:
239                 definiens = defloc[0].getnext()
240                 if definiens.tag == 'definiens':
241                     subgen = EduModule(self.options)
242                     definiens_s = subgen.generate(definiens)
243             else:
244                 print "!! Missing definiendum in source: '%s'" % element.text
245
246         return u"<dt id='%s'>" % self.naglowek_to_anchor(element), u"</dt>" + definiens_s
247
248     def handle_definiens(self, element):
249         return u"<dd>", u"</dd>"
250
251     def handle_podpis(self, element):
252         return u"""<div class="caption">""", u"</div>"
253
254     def handle_tabela(self, element):
255         has_frames = int(element.attrib.get("ramki", "0"))
256         if has_frames: frames_c = "framed"
257         else: frames_c = ""
258         return u"""<table class="%s">""" % frames_c, u"</table>"
259
260     def handle_wiersz(self, element):
261         return u"<tr>", u"</tr>"
262
263     def handle_kol(self, element):
264         return u"<td>", u"</td>"
265
266     def handle_rdf__RDF(self, _):
267         # ustal w opcjach  rzeczy :D
268         return
269
270     def handle_link(self, element):
271         if 'url' in element.attrib:
272             return tag('a', href=element.attrib['url'])(self, element)
273         elif 'material' in element.attrib:
274             material_err = u' [BRAKUJĄCY MATERIAŁ]'
275             slug = element.attrib['material']
276             make_url = lambda f: self.options['urlmapper'] \
277               .url_for_material(slug, f)
278
279             if 'format' in element.attrib:
280                 formats = re.split(r"[, ]+",
281                                element.attrib['format'])
282             else:
283                 formats = [None]
284
285             formats = self.options['urlmapper'].materials(slug)
286
287             try:
288                 def_href = make_url(formats[0][0])
289                 def_err = u""
290             except (IndexError, self.options['urlmapper'].MaterialNotFound):
291                 def_err = material_err
292                 def_href = u""
293             fmt_links = []
294             for f in formats[1:]:
295                 try:
296                     fmt_links.append(u'<a href="%s">%s</a>' % (make_url(f[0]), f[0].upper()))
297                 except self.options['urlmapper'].MaterialNotFound:
298                     fmt_links.append(u'<a>%s%s</a>' % (f[0].upper(), material_err))
299             more_links = u' (%s)' % u', '.join(fmt_links) if fmt_links else u''
300
301             return u"<a href='%s'>" % def_href, u'%s</a>%s' % (def_err, more_links)
302
303     def handle_obraz(self, element):
304         name = element.attrib.get('nazwa', '').strip()
305         if not name:
306             print '!! <obraz> missing "nazwa"'
307             return
308         alt = element.attrib.get('alt', '')
309         if not alt:
310             print '** <obraz> missing "alt"'
311         slug, ext = name.rsplit('.', 1)
312         url = self.options['urlmapper'].url_for_image(slug, ext)
313         thumb_url = self.options['urlmapper'].url_for_image(slug, ext, IMAGE_THUMB_WIDTH)
314         e = etree.Element("a", attrib={"href": url, "class": "image"})
315         e.append(etree.Element("img", attrib={"src": thumb_url, "alt": alt,
316                     "width": str(IMAGE_THUMB_WIDTH)}))
317         return etree.tostring(e, encoding=unicode), u""
318
319     def handle_video(self, element):
320         url = element.attrib.get('url')
321         if not url:
322             print '!! <video> missing url'
323             return
324         m = re.match(r'(?:https?://)?(?:www.)?youtube.com/watch\?(?:.*&)?v=([^&]+)(?:$|&)', url)
325         if not m:
326             print '!! unknown <video> url scheme:', url
327             return
328         return """<iframe width="630" height="384" src="http://www.youtube.com/embed/%s"
329             frameborder="0" allowfullscreen></iframe>""" % m.group(1), ""
330
331
332 class Exercise(EduModule):
333     INSTRUCTION = ""
334     def __init__(self, *args, **kw):
335         self.question_counter = 0
336         super(Exercise, self).__init__(*args, **kw)
337         self.instruction_printed = False
338
339     @tagged('div', 'description')
340     def handle_opis(self, element):
341         return "", self.get_instruction()
342
343     def handle_rozw_kom(self, element):
344         return u"""<div style="display:none" class="comment">""", u"""</div>"""
345
346     def handle_cwiczenie(self, element):
347         self.options = {'exercise': element.attrib['typ']}
348         self.question_counter = 0
349         self.piece_counter = 0
350
351         pre = u"""
352 <div class="exercise %(typ)s" data-type="%(typ)s">
353 <form action="#" method="POST">
354 <h3>Zadanie %(exercies_counter)d</h3>
355 <div class="buttons">
356 <span class="message"></span>
357 <input type="button" class="check" value="sprawdź"/>
358 <input type="button" class="retry" style="display:none" value="spróbuj ponownie"/>
359 <input type="button" class="solutions" value="pokaż rozwiązanie"/>
360 <input type="button" class="reset" value="reset"/>
361 </div>
362
363 """ % {'exercies_counter': self.options['exercise_counter'], 'typ': element.attrib['typ']}
364         post = u"""
365 <div class="buttons">
366 <span class="message"></span>
367 <input type="button" class="check" value="sprawdź"/>
368 <input type="button" class="retry" style="display:none" value="spróbuj ponownie"/>
369 <input type="button" class="solutions" value="pokaż rozwiązanie"/>
370 <input type="button" class="reset" value="reset"/>
371 </div>
372 </form>
373 </div>
374 """
375         # Add a single <pytanie> tag if it's not there
376         if not element.xpath(".//pytanie"):
377             qpre, qpost = self.handle_pytanie(element)
378             pre = pre + qpre
379             post = qpost + post
380         return pre, post
381
382     def handle_pytanie(self, element):
383         """This will handle <cwiczenie> element, when there is no <pytanie>
384         """
385         add_class = ""
386         self.question_counter += 1
387         self.piece_counter = 0
388         solution = element.attrib.get('rozw', None)
389         if solution: solution_s = ' data-solution="%s"' % solution
390         else: solution_s = ''
391
392         handles = element.attrib.get('uchwyty', None)
393         if handles:
394             add_class += ' handles handles-%s' % handles
395             self.options = {'handles': handles}
396
397         minimum = element.attrib.get('min', None)
398         if minimum: minimum_s = ' data-minimum="%d"' % int(minimum)
399         else: minimum_s = ''
400
401         return '<div class="question%s" data-no="%d" %s>' %\
402             (add_class, self.question_counter, solution_s + minimum_s), \
403             "</div>"
404
405     def get_instruction(self):
406         if not self.instruction_printed:
407             self.instruction_printed = True
408             if self.INSTRUCTION:
409                 return u'<span class="instruction">%s</span>' % self.INSTRUCTION
410             else:
411                 return ""
412         else:
413             return ""
414
415
416
417 class Wybor(Exercise):
418     def handle_cwiczenie(self, element):
419         pre, post = super(Wybor, self).handle_cwiczenie(element)
420         is_single_choice = True
421         pytania = element.xpath(".//pytanie")
422         if not pytania:
423             pytania = [element]
424         for p in pytania:
425             solutions = re.split(r"[, ]+", p.attrib.get('rozw', ''))
426             if len(solutions) != 1:
427                 is_single_choice = False
428                 break
429             choices = p.xpath(".//*[@nazwa]")
430             uniq = set()
431             for n in choices: uniq.add(n.attrib.get('nazwa', ''))
432             if len(choices) != len(uniq):
433                 is_single_choice = False
434                 break
435
436         self.options = {'single': is_single_choice}
437         return pre, post
438
439     def handle_punkt(self, element):
440         if self.options['exercise'] and element.attrib.get('nazwa', None):
441             qc = self.question_counter
442             self.piece_counter += 1
443             no = self.piece_counter
444             eid = "q%(qc)d_%(no)d" % locals()
445             aname = element.attrib.get('nazwa', None)
446             if self.options['single']:
447                 return u"""
448 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
449 <input type="radio" name="q%(qc)d" id="%(eid)s" value="%(aname)s" />
450 <label for="%(eid)s">
451                 """ % locals(), u"</label></li>"
452             else:
453                 return u"""
454 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
455 <input type="checkbox" name="%(eid)s" id="%(eid)s" />
456 <label for="%(eid)s">
457 """ % locals(), u"</label></li>"
458
459         else:
460             return super(Wybor, self).handle_punkt(element)
461
462
463 class Uporzadkuj(Exercise):
464     INSTRUCTION = u"Kliknij wybraną odpowiedź i przeciągnij w nowe miejsce."
465
466     def handle_pytanie(self, element):
467         """
468 Overrides the returned content default handle_pytanie
469         """
470         # we ignore the result, returning our own
471         super(Uporzadkuj, self).handle_pytanie(element)
472         order_items = element.xpath(".//punkt/@rozw")
473
474         return u"""<div class="question" data-original="%s" data-no="%s">""" % \
475             (','.join(order_items), self.question_counter), \
476             u"""</div>"""
477
478     def handle_punkt(self, element):
479         return """<li class="question-piece" data-pos="%(rozw)s">""" \
480             % element.attrib,\
481             "</li>"
482
483
484 class Luki(Exercise):
485     INSTRUCTION = u"Przeciągnij odpowiedzi i upuść w wybranym polu."
486     def find_pieces(self, question):
487         return question.xpath(".//luka")
488
489     def solution_html(self, piece):
490         piece = deepcopy(piece)
491         piece.tail = None
492         sub = EduModule()
493         return sub.generate(piece)
494
495     def handle_pytanie(self, element):
496         qpre, qpost = super(Luki, self).handle_pytanie(element)
497
498         luki = list(enumerate(self.find_pieces(element)))
499         luki_html = ""
500         i = 0
501         random.shuffle(luki)
502         for (i, luka) in luki:
503             i += 1
504             luka_html = self.solution_html(luka)
505             luki_html += u'<span class="draggable question-piece" data-no="%d">%s</span>' % (i, luka_html)
506         self.words_html = '<div class="words">%s</div>' % luki_html
507
508         return qpre, qpost
509
510     def handle_opis(self, element):
511         return '', self.words_html
512
513     def handle_luka(self, element):
514         self.piece_counter += 1
515         return '<span class="placeholder" data-solution="%d"></span>' % self.piece_counter
516
517
518 class Zastap(Luki):
519     INSTRUCTION = u"Przeciągnij odpowiedzi i upuść je na słowie lub wyrażeniu, które chcesz zastąpić."
520
521     def find_pieces(self, question):
522         return question.xpath(".//zastap")
523
524     def solution_html(self, piece):
525         return piece.attrib.get('rozw', '')
526
527     def handle_zastap(self, element):
528         self.piece_counter += 1
529         return '<span class="placeholder zastap question-piece" data-solution="%d">' \
530             % self.piece_counter, '</span>'
531
532
533 class Przyporzadkuj(Exercise):
534     INSTRUCTION = [u"Przeciągnij odpowiedzi i upuść w wybranym polu.",
535                    u"Kliknij numer odpowiedzi, przeciągnij i upuść w wybranym polu."]
536
537     def get_instruction(self):
538         if not self.instruction_printed:
539             self.instruction_printed = True
540             return u'<span class="instruction">%s</span>' % self.INSTRUCTION[self.options['handles'] and 1 or 0]
541         else:
542             return ""
543
544     def handle_cwiczenie(self, element):
545         pre, post = super(Przyporzadkuj, self).handle_cwiczenie(element)
546         lista_with_handles = element.xpath(".//*[@uchwyty]")
547         if lista_with_handles:
548             self.options = {'handles': True}
549         return pre, post
550
551     def handle_pytanie(self, element):
552         pre, post = super(Przyporzadkuj, self).handle_pytanie(element)
553         minimum = element.attrib.get("min", None)
554         if minimum:
555             self.options = {"min": int(minimum)}
556         return pre, post
557
558     def handle_lista(self, lista):
559         if 'nazwa' in lista.attrib:
560             attrs = {
561                 'data-name': lista.attrib['nazwa'],
562                 'class': 'predicate'
563             }
564             self.options = {'predicate': True}
565         elif 'cel' in lista.attrib:
566             attrs = {
567                 'data-target': lista.attrib['cel'],
568                 'class': 'subject'
569             }
570             if lista.attrib.get('krotkie'):
571                 self.options = {'short': True}
572             self.options = {'subject': True}
573         else:
574             attrs = {}
575         pre, post = super(Przyporzadkuj, self).handle_lista(lista, attrs)
576         return pre, post + '<br class="clr"/>'
577
578     def handle_punkt(self, element):
579         if self.options['subject']:
580             self.piece_counter += 1
581             if self.options['handles']:
582                 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>'
583             else:
584                 extra_class = ""
585                 if self.options['short']:
586                     extra_class += ' short'
587                 return '<li data-solution="%s" data-no="%s" class="question-piece draggable%s">' % (element.attrib.get('rozw', ''), self.piece_counter, extra_class), '</li>'
588
589         elif self.options['predicate']:
590             if self.options['min']:
591                 placeholders = u'<li class="placeholder"></li>' * self.options['min']
592             else:
593                 placeholders = u'<li class="placeholder multiple"></li>'
594             return '<li data-predicate="%s">' % element.attrib.get('nazwa', ''), '<ul class="subjects">' + placeholders + '</ul></li>'
595
596         else:
597             return super(Przyporzadkuj, self).handle_punkt(element)
598
599
600 class PrawdaFalsz(Exercise):
601     def handle_punkt(self, element):
602         if 'rozw' in element.attrib:
603             return u'''<li data-solution="%s" class="question-piece">
604             <span class="buttons">
605             <a href="#" data-value="true" class="true">Prawda</a>
606             <a href="#" data-value="false" class="false">Fałsz</a>
607         </span>''' % {'prawda': 'true', 'falsz': 'false'}[element.attrib['rozw']], '</li>'
608         else:
609             return super(PrawdaFalsz, self).handle_punkt(element)
610
611
612 class EduModuleFormat(Format):
613     PRIMARY_MATERIAL_FORMATS = ('pdf', 'odt')
614
615     class MaterialNotFound(BaseException):
616         pass
617
618     def __init__(self, wldoc, **kwargs):
619         super(EduModuleFormat, self).__init__(wldoc, **kwargs)
620
621     def build(self):
622         # Sort materials by slug.
623         self.materials_by_slug = {}
624         for name, att in self.wldoc.source.attachments.items():
625             parts = name.rsplit('.', 1)
626             if len(parts) == 1:
627                 continue
628             slug, ext = parts
629             if slug not in self.materials_by_slug:
630                 self.materials_by_slug[slug] = {}
631             self.materials_by_slug[slug][ext] = att
632
633         edumod = EduModule({'provider': self.wldoc.provider, 'urlmapper': self, 'wldoc': self.wldoc})
634
635         html = edumod.generate(self.wldoc.edoc.getroot())
636
637         return IOFile.from_string(html.encode('utf-8'))
638
639     def materials(self, slug):
640         """Returns a list of pairs: (ext, iofile)."""
641         order = dict(reversed(k) for k in enumerate(self.PRIMARY_MATERIAL_FORMATS))
642         mats = self.materials_by_slug.get(slug, {}).items()
643         if not mats:
644             print "!! Material missing: '%s'" % slug
645         return sorted(mats, key=lambda (x, y): order.get(x, x))
646
647     def url_for_material(self, slug, fmt):
648         return "%s.%s" % (slug, fmt)
649
650     def url_for_image(self, slug, fmt, width=None):
651         return self.url_for_material(self, slug, fmt)
652
653     def text_to_anchor(self, text):
654         return re.sub(r" +", " ", text)
655
656     def naglowek_to_anchor(self, naglowek):
657         return self.text_to_anchor(naglowek.text.strip())
658
659     def get_forma_url(self, forma):
660         return None
661
662     def get_help_url(self, naglowek):
663         return None
664
665
666 def transform(wldoc, stylesheet='edumed', options=None, flags=None, verbose=None):
667     """Transforms the WL document to XHTML.
668
669     If output_filename is None, returns an XML,
670     otherwise returns True if file has been written,False if it hasn't.
671     File won't be written if it has no content.
672     """
673     edumodfor = EduModuleFormat(wldoc)
674     return edumodfor.build()