turns out properties are not callable
[librarian.git] / librarian / pyhtml.py
index 5c2e5c6..163d11c 100644 (file)
@@ -4,18 +4,35 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from lxml import etree
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from lxml import etree
-from librarian import OutputFile, RDFNS, DCNS
-from xmlutils import Xmill, tag, tagged, ifoption
+from librarian import IOFile, Format
+from xmlutils import Xmill, tag, tagged, ifoption, tag_open_close
 from librarian import functions
 import re
 import random
 from librarian import functions
 import re
 import random
+from copy import deepcopy
+
+IMAGE_THUMB_WIDTH = 300
 
 
 class EduModule(Xmill):
 
 
 class EduModule(Xmill):
-    def __init__(self, *args):
-        super(EduModule, self).__init__(*args)
+    def __init__(self, options=None):
+        super(EduModule, self).__init__(options)
         self.activity_counter = 0
         self.activity_counter = 0
-        self.register_text_filter(lambda t: functions.substitute_entities(None, t))
+        self.activity_last = None
+        self.exercise_counter = 0
+
+        # text filters
+        def swap_endlines(txt):
+            if self.options['strofa']:
+                txt = txt.replace("/\n", "<br/>\n")
+            return txt
+        self.register_text_filter(functions.substitute_entities)
+        self.register_escaped_text_filter(swap_endlines)
+
+    @tagged('div', 'stanza')
+    def handle_strofa(self, element):
+        self.options = {'strofa': True}
+        return "", ""
 
     def handle_powiesc(self, element):
         return u"""
 
     def handle_powiesc(self, element):
         return u"""
@@ -28,62 +45,123 @@ class EduModule(Xmill):
 """, u"</div>"
 
     handle_autor_utworu = tag("span", "author")
 """, u"</div>"
 
     handle_autor_utworu = tag("span", "author")
-    handle_nazwa_utworu = tag("h1", "title")
     handle_dzielo_nadrzedne = tag("span", "collection")
     handle_podtytul = tag("span", "subtitle")
     handle_naglowek_akt = handle_naglowek_czesc = handle_srodtytul = tag("h2")
     handle_dzielo_nadrzedne = tag("span", "collection")
     handle_podtytul = tag("span", "subtitle")
     handle_naglowek_akt = handle_naglowek_czesc = handle_srodtytul = tag("h2")
-    handle_naglowek_scena = handle_naglowek_rozdzial = tag('h3')
-    handle_naglowek_osoba = handle_naglowek_podrozdzial = tag('h4')
+    handle_naglowek_scena = tag('h2')
+    handle_naglowek_osoba = tag('h3')
     handle_akap = handle_akap_dialog = handle_akap_cd = tag('p', 'paragraph')
     handle_akap = handle_akap_dialog = handle_akap_cd = tag('p', 'paragraph')
-    handle_strofa = tag('div', 'stanza')
+
+    handle_wyroznienie = tag('em')
+    handle_tytul_dziela = tag('em', 'title')
+    handle_slowo_obce = tag('em', 'foreign')
+
+    def naglowek_to_anchor(self, naglowek):
+        return self.options['urlmapper'].naglowek_to_anchor(naglowek)
+
+    def handle_nazwa_utworu(self, element):
+        toc = []
+        for naglowek in element.getparent().findall('.//naglowek_rozdzial'):
+            a = etree.Element("a")
+            a.attrib["href"] = "#" + self.naglowek_to_anchor(naglowek)
+            a.text = naglowek.text
+            atxt = etree.tostring(a, encoding=unicode)
+            toc.append("<li>%s</li>" % atxt)
+        toc = "<ul class='toc'>%s</ul>" % "".join(toc)
+        add_header = "Lekcja: " if self.options['wldoc'].book_info.type in ('course', 'synthetic') else ''
+        return "<h1 class='title' id='top'>%s" % add_header, "</h1>" + toc
+
+    def handle_naglowek_rozdzial(self, element):
+        return_to_top = u"<a href='#top' class='top-link'>wróć do spisu treści</a>"
+        pre, post = tag_open_close("h2", id=self.naglowek_to_anchor(element))
+        url = self.options['urlmapper'].get_help_url(element)
+        if url:
+            post = " <a class='help' href='%s'>?</a>" % (url,) + post
+        return return_to_top + pre, post
+
+    def handle_naglowek_podrozdzial(self, element):
+        self.activity_counter = 0
+        return tag('h3')(self, element)
+
+    def handle_uwaga(self, _e):
+        return None
 
     def handle_aktywnosc(self, element):
         self.activity_counter += 1
         self.options = {
             'activity': True,
 
     def handle_aktywnosc(self, element):
         self.activity_counter += 1
         self.options = {
             'activity': True,
-            'activity_counter': self.activity_counter
+            'activity_counter': self.activity_counter,
             }
             }
-        submill = EduModule()
+        submill = EduModule(dict(self.options.items() + {'sub_gen': True}.items()))
 
 
-        opis = submill.generate(element.xpath('opis')[0])
+        if element.xpath('opis'):
+            opis = submill.generate(element.xpath('opis')[0])
+        else:
+            opis = ''
 
         n = element.xpath('wskazowki')
 
         n = element.xpath('wskazowki')
-        if n: wskazowki = submill.generate(n[0])
-
-        else: wskazowki = ''
+        if n:
+            wskazowki = submill.generate(n[0])
+        else:
+            wskazowki = ''
         n = element.xpath('pomoce')
 
         n = element.xpath('pomoce')
 
-        if n: pomoce = submill.generate(n[0])
-        else: pomoce = ''
+        if n:
+            pomoce = submill.generate(n[0])
+        else:
+            pomoce = ''
 
         forma = ''.join(element.xpath('forma/text()'))
 
         forma = ''.join(element.xpath('forma/text()'))
+        get_forma_url = self.options['urlmapper'].get_forma_url
+        forms = []
+        for form_name in forma.split(','):
+            name = form_name.strip()
+            url = get_forma_url(name)
+            if url:
+                forms.append("<a href='%s'>%s</a>" % (url, name))
+            else:
+                forms.append(name)
+        forma = ', '.join(forms)
+        if forma:
+            forma = '<section class="infobox kind"><h1>Metoda</h1><p>%s</p></section>' % forma
 
         czas = ''.join(element.xpath('czas/text()'))
 
         czas = ''.join(element.xpath('czas/text()'))
+        if czas:
+            czas = '<section class="infobox time"><h1>Czas</h1><p>%s min</p></section>' % czas
 
         counter = self.activity_counter
 
 
         counter = self.activity_counter
 
-        return u"""
+        if element.getnext().tag == 'aktywnosc' or (self.activity_last and self.activity_last.getnext() == element):
+            counter_html = """<span class="act_counter">%(counter)d.</span>""" % {'counter': counter}
+        else:
+            counter_html = ''
+
+        self.activity_last = element
+
+        return (
+            u"""
 <div class="activity">
 <div class="activity">
- <div class="text">%(counter)d.
-  %(opis)s
-  %(wskazowki)s
- </div>
- <div class="info">
-  <p>Czas: %(czas)s min</p>
-  <p>Forma: %(forma)s</p>
-  %(pomoce)s
- </div>
- <div class="clearboth"></div>
+  <div class="text">
+    %(counter_html)s
+    %(opis)s""" % {'counter_html': counter_html, 'opis': opis},
+            u"""%(wskazowki)s
+  </div>
+  <aside class="info">
+    %(czas)s
+    %(forma)s
+    %(pomoce)s
+  </aside>
+  <div class="clearboth"></div>
 </div>
 </div>
-""" % locals()
+""" % {'wskazowki': wskazowki, 'czas': czas, 'forma': forma, 'pomoce': pomoce})
 
 
-    handle_opis = ifoption(activity=False)(tag('div', 'description'))
-    handle_wskazowki = ifoption(activity=False)(tag('div', ('hints', 'teacher')))
+    handle_opis = ifoption(sub_gen=True)(tag('div', 'description'))
+    handle_wskazowki = ifoption(sub_gen=True)(tag('div', ('hints', 'teacher')))
 
 
-    @ifoption(activity=False)
-    @tagged('div', 'materials')
+    @ifoption(sub_gen=True)
+    @tagged('section', 'infobox materials')
     def handle_pomoce(self, _):
     def handle_pomoce(self, _):
-        return "Pomoce: ", ""
+        return """<h1>Pomoce</h1>""", ""
 
     def handle_czas(self, *_):
         return
 
     def handle_czas(self, *_):
         return
@@ -102,26 +180,47 @@ class EduModule(Xmill):
             }
 
         typ = element.attrib['typ']
             }
 
         typ = element.attrib['typ']
+        self.exercise_counter += 1
+        self.options = {'exercise_counter': self.exercise_counter}
         handler = exercise_handlers[typ](self.options)
         return handler.generate(element)
 
     # Lists
         handler = exercise_handlers[typ](self.options)
         return handler.generate(element)
 
     # Lists
-    def handle_lista(self, element, attrs={}):
+    def handle_lista(self, element, attrs=None):
+        if attrs is None:
+            attrs = {}
         ltype = element.attrib.get('typ', 'punkt')
         ltype = element.attrib.get('typ', 'punkt')
+        if not element.findall("punkt"):
+            if ltype == 'czytelnia':
+                return '<p>W przygotowaniu.</p>'
+            else:
+                return None
         if ltype == 'slowniczek':
         if ltype == 'slowniczek':
-            self.options = {'slowniczek': True}
-            return '<div class="slowniczek">', '</div>'
-### robie teraz punkty wyboru
-        listtag = {'num': 'ol',
-               'punkt': 'ul',
-               'alfa': 'ul',
-               'czytelnia': 'ul'}[ltype]
+            surl = element.attrib.get('src', None)
+            if surl is None:
+                # print '** missing src on <slowniczek>, setting default'
+                surl = 'http://edukacjamedialna.edu.pl/lekcje/slowniczek/'
+            sxml = etree.fromstring(self.options['provider'].by_uri(surl).get_string())
+
+            self.options = {'slowniczek': True, 'slowniczek_xml': sxml}
+            pre, post = '<div class="slowniczek">', '</div>'
+            if not self.options['wldoc'].book_info.url.slug.startswith('slowniczek'):
+                post += u'<p class="see-more"><a href="%s">Zobacz cały słowniczek.</a></p>' % surl
+            return pre, post
+
+        listtag = {
+            'num': 'ol',
+            'punkt': 'ul',
+            'alfa': 'ul',
+            'czytelnia': 'ul'}[ltype]
 
         classes = attrs.get('class', '')
 
         classes = attrs.get('class', '')
-        if classes: del attrs['class']
+        if classes:
+            del attrs['class']
 
         attrs_s = ' '.join(['%s="%s"' % kv for kv in attrs.items()])
 
         attrs_s = ' '.join(['%s="%s"' % kv for kv in attrs.items()])
-        if attrs_s: attrs_s = ' ' + attrs_s
+        if attrs_s:
+            attrs_s = ' ' + attrs_s
 
         return '<%s class="lista %s %s"%s>' % (listtag, ltype, classes, attrs_s), '</%s>' % listtag
 
 
         return '<%s class="lista %s %s"%s>' % (listtag, ltype, classes, attrs_s), '</%s>' % listtag
 
@@ -131,14 +230,40 @@ class EduModule(Xmill):
         else:
             return '<li>', '</li>'
 
         else:
             return '<li>', '</li>'
 
+    def handle_definiendum(self, element):
+        nxt = element.getnext()
+        definiens_s = ''
+
+        if not element.text:
+            print "!! Empty <definiendum/>"
+            return None
+
+        # let's pull definiens from another document
+        if self.options['slowniczek_xml'] is not None and (nxt is None or nxt.tag != 'definiens'):
+            sxml = self.options['slowniczek_xml']
+            if "'" in (element.text or ''):
+                defloc = sxml.xpath("//definiendum[text()=\"%s\"]" % (element.text or '').strip())
+            else:
+                defloc = sxml.xpath("//definiendum[text()='%s']" % (element.text or '').strip())
+            if defloc:
+                definiens = defloc[0].getnext()
+                if definiens.tag == 'definiens':
+                    subgen = EduModule(self.options)
+                    definiens_s = subgen.generate(definiens)
+            else:
+                print ("!! Missing definiendum in source: '%s'" % element.text).encode('utf-8')
+
+        return u"<dt id='%s'>" % self.naglowek_to_anchor(element), u"</dt>" + definiens_s
+
+    def handle_definiens(self, element):
+        return u"<dd>", u"</dd>"
 
     def handle_podpis(self, element):
         return u"""<div class="caption">""", u"</div>"
 
     def handle_tabela(self, element):
         has_frames = int(element.attrib.get("ramki", "0"))
 
     def handle_podpis(self, element):
         return u"""<div class="caption">""", u"</div>"
 
     def handle_tabela(self, element):
         has_frames = int(element.attrib.get("ramki", "0"))
-        if has_frames: frames_c = "framed"
-        else: frames_c = ""
+        frames_c = "framed" if has_frames else ""
         return u"""<table class="%s">""" % frames_c, u"</table>"
 
     def handle_wiersz(self, element):
         return u"""<table class="%s">""" % frames_c, u"</table>"
 
     def handle_wiersz(self, element):
@@ -151,11 +276,77 @@ class EduModule(Xmill):
         # ustal w opcjach  rzeczy :D
         return
 
         # ustal w opcjach  rzeczy :D
         return
 
+    def handle_link(self, element):
+        if 'url' in element.attrib:
+            return tag('a', href=element.attrib['url'])(self, element)
+        elif 'material' in element.attrib:
+            material_err = u' [BRAKUJĄCY MATERIAŁ]'
+            slug = element.attrib['material']
+
+            def make_url(f):
+                return self.options['urlmapper'].url_for_material(slug, f)
+
+            formats = self.options['urlmapper'].materials(slug)
+
+            try:
+                def_href = make_url(formats[0][0])
+                def_err = u""
+            except (IndexError, self.options['urlmapper'].MaterialNotFound):
+                def_err = material_err
+                def_href = u""
+            fmt_links = []
+            for f in formats[1:]:
+                try:
+                    fmt_links.append(u'<a href="%s">%s</a>' % (make_url(f[0]), f[0].upper()))
+                except self.options['urlmapper'].MaterialNotFound:
+                    fmt_links.append(u'<a>%s%s</a>' % (f[0].upper(), material_err))
+            more_links = u' (%s)' % u', '.join(fmt_links) if fmt_links else u''
+
+            return u"<a href='%s'>" % def_href, u'%s</a>%s' % (def_err, more_links)
+
+    def handle_obraz(self, element):
+        name = element.attrib.get('nazwa', '').strip()
+        if not name:
+            print '!! <obraz> missing "nazwa"'
+            return
+        alt = element.attrib.get('alt', '')
+        if not alt:
+            print '** <obraz> missing "alt"'
+        slug, ext = name.rsplit('.', 1)
+        url = self.options['urlmapper'].url_for_image(slug, ext)
+        thumb_url = self.options['urlmapper'].url_for_image(slug, ext, IMAGE_THUMB_WIDTH)
+        e = etree.Element("a", attrib={"href": url, "class": "image"})
+        e.append(etree.Element("img", attrib={
+            "src": thumb_url,
+            "alt": alt,
+            "width": str(IMAGE_THUMB_WIDTH)}))
+        return etree.tostring(e, encoding=unicode), u""
+
+    def handle_video(self, element):
+        url = element.attrib.get('url')
+        if not url:
+            print '!! <video> missing url'
+            return
+        m = re.match(r'(?:https?://)?(?:www.)?youtube.com/watch\?(?:.*&)?v=([^&]+)(?:$|&)', url)
+        if not m:
+            print '!! unknown <video> url scheme:', url
+            return
+        return """<iframe width="630" height="384" src="http://www.youtube.com/embed/%s"
+            frameborder="0" allowfullscreen></iframe>""" % m.group(1), ""
+
 
 class Exercise(EduModule):
 
 class Exercise(EduModule):
+    INSTRUCTION = ""
+
     def __init__(self, *args, **kw):
         self.question_counter = 0
         super(Exercise, self).__init__(*args, **kw)
     def __init__(self, *args, **kw):
         self.question_counter = 0
         super(Exercise, self).__init__(*args, **kw)
+        self.instruction_printed = False
+        self.piece_counter = None
+
+    @tagged('div', 'description')
+    def handle_opis(self, element):
+        return "", self.get_instruction()
 
     def handle_rozw_kom(self, element):
         return u"""<div style="display:none" class="comment">""", u"""</div>"""
 
     def handle_rozw_kom(self, element):
         return u"""<div style="display:none" class="comment">""", u"""</div>"""
@@ -168,7 +359,16 @@ class Exercise(EduModule):
         pre = u"""
 <div class="exercise %(typ)s" data-type="%(typ)s">
 <form action="#" method="POST">
         pre = u"""
 <div class="exercise %(typ)s" data-type="%(typ)s">
 <form action="#" method="POST">
-""" % element.attrib
+<h3>Zadanie %(exercies_counter)d</h3>
+<div class="buttons">
+<span class="message"></span>
+<input type="button" class="check" value="sprawdź"/>
+<input type="button" class="retry" style="display:none" value="spróbuj ponownie"/>
+<input type="button" class="solutions" value="pokaż rozwiązanie"/>
+<input type="button" class="reset" value="reset"/>
+</div>
+
+""" % {'exercies_counter': self.options['exercise_counter'], 'typ': element.attrib['typ']}
         post = u"""
 <div class="buttons">
 <span class="message"></span>
         post = u"""
 <div class="buttons">
 <span class="message"></span>
@@ -183,7 +383,7 @@ class Exercise(EduModule):
         # Add a single <pytanie> tag if it's not there
         if not element.xpath(".//pytanie"):
             qpre, qpost = self.handle_pytanie(element)
         # Add a single <pytanie> tag if it's not there
         if not element.xpath(".//pytanie"):
             qpre, qpost = self.handle_pytanie(element)
-            pre = pre + qpre
+            pre += qpre
             post = qpost + post
         return pre, post
 
             post = qpost + post
         return pre, post
 
@@ -194,8 +394,7 @@ class Exercise(EduModule):
         self.question_counter += 1
         self.piece_counter = 0
         solution = element.attrib.get('rozw', None)
         self.question_counter += 1
         self.piece_counter = 0
         solution = element.attrib.get('rozw', None)
-        if solution: solution_s = ' data-solution="%s"' % solution
-        else: solution_s = ''
+        solution_s = ' data-solution="%s"' % solution if solution else ''
 
         handles = element.attrib.get('uchwyty', None)
         if handles:
 
         handles = element.attrib.get('uchwyty', None)
         if handles:
@@ -203,23 +402,43 @@ class Exercise(EduModule):
             self.options = {'handles': handles}
 
         minimum = element.attrib.get('min', None)
             self.options = {'handles': handles}
 
         minimum = element.attrib.get('min', None)
-        if minimum: minimum_s = ' data-minimum="%d"' % int(minimum)
-        else: minimum_s = ''
+        minimum_s = ' data-minimum="%d"' % int(minimum) if minimum else ''
 
         return '<div class="question%s" data-no="%d" %s>' %\
             (add_class, self.question_counter, solution_s + minimum_s), \
             "</div>"
 
 
         return '<div class="question%s" data-no="%d" %s>' %\
             (add_class, self.question_counter, solution_s + minimum_s), \
             "</div>"
 
+    def get_instruction(self):
+        if not self.instruction_printed:
+            self.instruction_printed = True
+            if self.INSTRUCTION:
+                return u'<span class="instruction">%s</span>' % self.INSTRUCTION
+            else:
+                return ""
+        else:
+            return ""
+
 
 class Wybor(Exercise):
     def handle_cwiczenie(self, element):
         pre, post = super(Wybor, self).handle_cwiczenie(element)
         is_single_choice = True
 
 class Wybor(Exercise):
     def handle_cwiczenie(self, element):
         pre, post = super(Wybor, self).handle_cwiczenie(element)
         is_single_choice = True
-        for p in element.xpath(".//pytanie"):
-            solutions = re.split(r"[, ]+", p.attrib['rozw'])
+        pytania = element.xpath(".//pytanie")
+        if not pytania:
+            pytania = [element]
+        for p in pytania:
+            solutions = re.split(r"[, ]+", p.attrib.get('rozw', ''))
             if len(solutions) != 1:
                 is_single_choice = False
                 break
             if len(solutions) != 1:
                 is_single_choice = False
                 break
+            choices = p.xpath(".//*[@nazwa]")
+            uniq = set()
+            for n in choices:
+                uniq.add(n.attrib.get('nazwa', ''))
+            if len(choices) != len(uniq):
+                is_single_choice = False
+                break
+
         self.options = {'single': is_single_choice}
         return pre, post
 
         self.options = {'single': is_single_choice}
         return pre, post
 
@@ -248,6 +467,8 @@ class Wybor(Exercise):
 
 
 class Uporzadkuj(Exercise):
 
 
 class Uporzadkuj(Exercise):
+    INSTRUCTION = u"Kliknij wybraną odpowiedź i przeciągnij w nowe miejsce."
+
     def handle_pytanie(self, element):
         """
 Overrides the returned content default handle_pytanie
     def handle_pytanie(self, element):
         """
 Overrides the returned content default handle_pytanie
@@ -261,19 +482,22 @@ Overrides the returned content default handle_pytanie
             u"""</div>"""
 
     def handle_punkt(self, element):
             u"""</div>"""
 
     def handle_punkt(self, element):
-        return """<li class="question-piece" data-pos="%(rozw)s"/>""" \
+        return """<li class="question-piece" data-pos="%(rozw)s">""" \
             % element.attrib,\
             "</li>"
 
 
 class Luki(Exercise):
             % element.attrib,\
             "</li>"
 
 
 class Luki(Exercise):
+    INSTRUCTION = u"Przeciągnij odpowiedzi i upuść w wybranym polu."
+
     def find_pieces(self, question):
     def find_pieces(self, question):
-        return question.xpath("//luka")
+        return question.xpath(".//luka")
 
     def solution_html(self, piece):
 
     def solution_html(self, piece):
-        return piece.text + ''.join(
-            [etree.tostring(n, encoding=unicode)
-             for n in piece])
+        piece = deepcopy(piece)
+        piece.tail = None
+        sub = EduModule()
+        return sub.generate(piece)
 
     def handle_pytanie(self, element):
         qpre, qpost = super(Luki, self).handle_pytanie(element)
 
     def handle_pytanie(self, element):
         qpre, qpost = super(Luki, self).handle_pytanie(element)
@@ -291,8 +515,7 @@ class Luki(Exercise):
         return qpre, qpost
 
     def handle_opis(self, element):
         return qpre, qpost
 
     def handle_opis(self, element):
-        pre, post = super(Luki, self).handle_opis(element)
-        return pre, self.words_html + post
+        return '', self.words_html
 
     def handle_luka(self, element):
         self.piece_counter += 1
 
     def handle_luka(self, element):
         self.piece_counter += 1
@@ -300,11 +523,13 @@ class Luki(Exercise):
 
 
 class Zastap(Luki):
 
 
 class Zastap(Luki):
+    INSTRUCTION = u"Przeciągnij odpowiedzi i upuść je na słowie lub wyrażeniu, które chcesz zastąpić."
+
     def find_pieces(self, question):
     def find_pieces(self, question):
-        return question.xpath("//zastap")
+        return question.xpath(".//zastap")
 
     def solution_html(self, piece):
 
     def solution_html(self, piece):
-        return piece.attrib['rozw']
+        return piece.attrib.get('rozw', '')
 
     def handle_zastap(self, element):
         self.piece_counter += 1
 
     def handle_zastap(self, element):
         self.piece_counter += 1
@@ -313,6 +538,23 @@ class Zastap(Luki):
 
 
 class Przyporzadkuj(Exercise):
 
 
 class Przyporzadkuj(Exercise):
+    INSTRUCTION = [u"Przeciągnij odpowiedzi i upuść w wybranym polu.",
+                   u"Kliknij numer odpowiedzi, przeciągnij i upuść w wybranym polu."]
+
+    def get_instruction(self):
+        if not self.instruction_printed:
+            self.instruction_printed = True
+            return u'<span class="instruction">%s</span>' % self.INSTRUCTION[self.options['handles'] and 1 or 0]
+        else:
+            return ""
+
+    def handle_cwiczenie(self, element):
+        pre, post = super(Przyporzadkuj, self).handle_cwiczenie(element)
+        lista_with_handles = element.xpath(".//*[@uchwyty]")
+        if lista_with_handles:
+            self.options = {'handles': True}
+        return pre, post
+
     def handle_pytanie(self, element):
         pre, post = super(Przyporzadkuj, self).handle_pytanie(element)
         minimum = element.attrib.get("min", None)
     def handle_pytanie(self, element):
         pre, post = super(Przyporzadkuj, self).handle_pytanie(element)
         minimum = element.attrib.get("min", None)
@@ -332,7 +574,9 @@ class Przyporzadkuj(Exercise):
                 'data-target': lista.attrib['cel'],
                 'class': 'subject'
             }
                 'data-target': lista.attrib['cel'],
                 'class': 'subject'
             }
-            self.options = {'subject': True, 'handles': 'uchwyty' in lista.attrib}
+            if lista.attrib.get('krotkie'):
+                self.options = {'short': True}
+            self.options = {'subject': True}
         else:
             attrs = {}
         pre, post = super(Przyporzadkuj, self).handle_lista(lista, attrs)
         else:
             attrs = {}
         pre, post = super(Przyporzadkuj, self).handle_lista(lista, attrs)
@@ -342,16 +586,29 @@ class Przyporzadkuj(Exercise):
         if self.options['subject']:
             self.piece_counter += 1
             if self.options['handles']:
         if self.options['subject']:
             self.piece_counter += 1
             if self.options['handles']:
-                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>'
+                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>')
             else:
             else:
-                return '<li data-solution="%s" data-no="%s" class="question-piece draggable">' % (element.attrib['rozw'], self.piece_counter), '</li>'
+                extra_class = ""
+                if self.options['short']:
+                    extra_class += ' short'
+                return '<li data-solution="%s" data-no="%s" class="question-piece draggable%s">' % (
+                    element.attrib.get('rozw', ''),
+                    self.piece_counter, extra_class), '</li>'
 
         elif self.options['predicate']:
             if self.options['min']:
 
         elif self.options['predicate']:
             if self.options['min']:
-                placeholders = u'<li class="placeholder"/>' * self.options['min']
+                placeholders = u'<li class="placeholder"></li>' * self.options['min']
             else:
             else:
-                placeholders = u'<li class="placeholder multiple"/>'
-            return '<li data-predicate="%(nazwa)s">' % element.attrib, '<ul class="subjects">' + placeholders + '</ul></li>'
+                placeholders = u'<li class="placeholder multiple"></li>'
+            return (
+                '<li data-predicate="%s">' % element.attrib.get('nazwa', ''),
+                '<ul class="subjects">' + placeholders + '</ul></li>')
 
         else:
             return super(Przyporzadkuj, self).handle_punkt(element)
 
         else:
             return super(Przyporzadkuj, self).handle_punkt(element)
@@ -369,15 +626,68 @@ class PrawdaFalsz(Exercise):
             return super(PrawdaFalsz, self).handle_punkt(element)
 
 
             return super(PrawdaFalsz, self).handle_punkt(element)
 
 
-def transform(wldoc, stylesheet='edumed', options=None, flags=None):
+class EduModuleFormat(Format):
+    PRIMARY_MATERIAL_FORMATS = ('pdf', 'odt')
+
+    class MaterialNotFound(BaseException):
+        pass
+
+    def __init__(self, wldoc, **kwargs):
+        super(EduModuleFormat, self).__init__(wldoc, **kwargs)
+        self.materials_by_slug = None
+
+    def build(self):
+        # Sort materials by slug.
+        self.materials_by_slug = {}
+        for name, att in self.wldoc.source.attachments.items():
+            parts = name.rsplit('.', 1)
+            if len(parts) == 1:
+                continue
+            slug, ext = parts
+            if slug not in self.materials_by_slug:
+                self.materials_by_slug[slug] = {}
+            self.materials_by_slug[slug][ext] = att
+
+        edumod = EduModule({'provider': self.wldoc.provider, 'urlmapper': self, 'wldoc': self.wldoc})
+
+        html = edumod.generate(self.wldoc.edoc.getroot())
+
+        return IOFile.from_string(html.encode('utf-8'))
+
+    def materials(self, slug):
+        """Returns a list of pairs: (ext, iofile)."""
+        order = {pmf: i for (i, pmf) in enumerate(self.PRIMARY_MATERIAL_FORMATS)}
+        mats = self.materials_by_slug.get(slug, {}).items()
+        if not mats:
+            print ("!! Material missing: '%s'" % slug).encode('utf-8')
+        return sorted(mats, key=lambda (x, y): order.get(x, x))
+
+    def url_for_material(self, slug, fmt):
+        return "%s.%s" % (slug, fmt)
+
+    # WTF: tutaj był błąd, ale nikomu to nie przeszkadzało?
+    def url_for_image(self, slug, fmt, width=None):
+        return self.url_for_material(slug, fmt)
+
+    def text_to_anchor(self, text):
+        return re.sub(r" +", " ", text)
+
+    def naglowek_to_anchor(self, naglowek):
+        return self.text_to_anchor(naglowek.text.strip())
+
+    def get_forma_url(self, forma):
+        return None
+
+    def get_help_url(self, naglowek):
+        return None
+
+
+def transform(wldoc, stylesheet='edumed', options=None, flags=None, verbose=None):
     """Transforms the WL document to XHTML.
 
     If output_filename is None, returns an XML,
     otherwise returns True if file has been written,False if it hasn't.
     File won't be written if it has no content.
     """
     """Transforms the WL document to XHTML.
 
     If output_filename is None, returns an XML,
     otherwise returns True if file has been written,False if it hasn't.
     File won't be written if it has no content.
     """
-    edumod = EduModule(options)
-#    from pdb import set_trace; set_trace()
-    html = edumod.generate(wldoc.edoc.getroot())
-
-    return OutputFile.from_string(html.encode('utf-8'))
+    edumodfor = EduModuleFormat(wldoc)
+    return edumodfor.build()