style
[librarian.git] / librarian / pyhtml.py
index 6df3647..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
@@ -92,7 +170,7 @@ class EduModule(Xmill):
         return
 
     def handle_cwiczenie(self, element):
         return
 
     def handle_cwiczenie(self, element):
-        excercise_handlers = {
+        exercise_handlers = {
             'wybor': Wybor,
             'uporzadkuj': Uporzadkuj,
             'luki': Luki,
             'wybor': Wybor,
             'uporzadkuj': Uporzadkuj,
             'luki': Luki,
@@ -102,26 +180,47 @@ class EduModule(Xmill):
             }
 
         typ = element.attrib['typ']
             }
 
         typ = element.attrib['typ']
-        handler = excercise_handlers[typ](self.options)
+        self.exercise_counter += 1
+        self.options = {'exercise_counter': self.exercise_counter}
+        handler = exercise_handlers[typ](self.options)
         return handler.generate(element)
 
     # Lists
         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,32 +230,150 @@ 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"))
+        frames_c = "framed" if has_frames else ""
+        return u"""<table class="%s">""" % frames_c, u"</table>"
+
+    def handle_wiersz(self, element):
+        return u"<tr>", u"</tr>"
+
+    def handle_kol(self, element):
+        return u"<td>", u"</td>"
+
     def handle_rdf__RDF(self, _):
         # ustal w opcjach  rzeczy :D
         return
 
     def handle_rdf__RDF(self, _):
         # 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):
+    INSTRUCTION = ""
 
 
-class Excercise(EduModule):
     def __init__(self, *args, **kw):
         self.question_counter = 0
     def __init__(self, *args, **kw):
         self.question_counter = 0
-        super(Excercise, self).__init__(*args, **kw)
+        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):
 
     def handle_rozw_kom(self, element):
-        return None
+        return u"""<div style="display:none" class="comment">""", u"""</div>"""
 
     def handle_cwiczenie(self, element):
 
     def handle_cwiczenie(self, element):
-        self.options = {'excercise': element.attrib['typ']}
+        self.options = {'exercise': element.attrib['typ']}
         self.question_counter = 0
         self.piece_counter = 0
 
         pre = u"""
         self.question_counter = 0
         self.piece_counter = 0
 
         pre = u"""
-<div class="excercise %(typ)s" data-type="%(typ)s">
+<div class="exercise %(typ)s" data-type="%(typ)s">
 <form action="#" method="POST">
 <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>
 <input type="button" class="check" value="sprawdź"/>
         post = u"""
 <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>
 <input type="button" class="solutions" value="pokaż rozwiązanie"/>
 <input type="button" class="reset" value="reset"/>
 </div>
@@ -166,7 +383,7 @@ class Excercise(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
 
@@ -177,33 +394,56 @@ class Excercise(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:
             add_class += ' handles handles-%s' % handles
             self.options = {'handles': handles}
 
 
         handles = element.attrib.get('uchwyty', None)
         if handles:
             add_class += ' handles handles-%s' % handles
             self.options = {'handles': handles}
 
+        minimum = element.attrib.get('min', None)
+        minimum_s = ' data-minimum="%d"' % int(minimum) if minimum else ''
+
         return '<div class="question%s" data-no="%d" %s>' %\
         return '<div class="question%s" data-no="%d" %s>' %\
-            (add_class, self.question_counter, solution_s), \
+            (add_class, self.question_counter, solution_s + minimum_s), \
             "</div>"
 
             "</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(Excercise):
+class Wybor(Exercise):
     def handle_cwiczenie(self, element):
         pre, post = super(Wybor, self).handle_cwiczenie(element)
         is_single_choice = True
     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
 
     def handle_punkt(self, element):
         self.options = {'single': is_single_choice}
         return pre, post
 
     def handle_punkt(self, element):
-        if self.options['excercise'] and element.attrib.get('nazwa', None):
+        if self.options['exercise'] and element.attrib.get('nazwa', None):
             qc = self.question_counter
             self.piece_counter += 1
             no = self.piece_counter
             qc = self.question_counter
             self.piece_counter += 1
             no = self.piece_counter
@@ -226,7 +466,9 @@ class Wybor(Excercise):
             return super(Wybor, self).handle_punkt(element)
 
 
             return super(Wybor, self).handle_punkt(element)
 
 
-class Uporzadkuj(Excercise):
+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
@@ -240,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>"
 
 
             % element.attrib,\
             "</li>"
 
 
-class Luki(Excercise):
+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)
@@ -270,8 +515,7 @@ class Luki(Excercise):
         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
@@ -279,11 +523,13 @@ class Luki(Excercise):
 
 
 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
@@ -291,20 +537,46 @@ class Zastap(Luki):
             % self.piece_counter, '</span>'
 
 
             % self.piece_counter, '</span>'
 
 
-class Przyporzadkuj(Excercise):
+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)
+        if minimum:
+            self.options = {"min": int(minimum)}
+        return pre, post
+
     def handle_lista(self, lista):
         if 'nazwa' in lista.attrib:
             attrs = {
                 'data-name': lista.attrib['nazwa'],
                 'class': 'predicate'
     def handle_lista(self, lista):
         if 'nazwa' in lista.attrib:
             attrs = {
                 'data-name': lista.attrib['nazwa'],
                 'class': 'predicate'
-                }
+            }
             self.options = {'predicate': True}
         elif 'cel' in lista.attrib:
             attrs = {
                 'data-target': lista.attrib['cel'],
                 'class': 'subject'
             self.options = {'predicate': True}
         elif 'cel' in lista.attrib:
             attrs = {
                 '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)
@@ -314,19 +586,35 @@ class Przyporzadkuj(Excercise):
         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']:
 
         elif self.options['predicate']:
-            placeholders = u'<li class="placeholder multiple"/>'
-            return '<li data-predicate="%(nazwa)s">' % element.attrib, '<ul class="subjects">' + placeholders + '</ul></li>'
+            if self.options['min']:
+                placeholders = u'<li class="placeholder"></li>' * self.options['min']
+            else:
+                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)
 
 
-class PrawdaFalsz(Excercise):
+class PrawdaFalsz(Exercise):
     def handle_punkt(self, element):
         if 'rozw' in element.attrib:
             return u'''<li data-solution="%s" class="question-piece">
     def handle_punkt(self, element):
         if 'rozw' in element.attrib:
             return u'''<li data-solution="%s" class="question-piece">
@@ -338,15 +626,68 @@ class PrawdaFalsz(Excercise):
             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()