slowniczek link
[librarian.git] / librarian / pyhtml.py
index a46659c..b4d8557 100644 (file)
@@ -4,44 +4,86 @@
 # 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, RDFNS, DCNS, Format
+from xmlutils import Xmill, tag, tagged, ifoption, tag_open_close
+from librarian import functions
 import re
 import random
 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.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_text_filter(swap_endlines)
+
+    @tagged('div', 'stanza')
+    def handle_strofa(self, element):
+        self.options = {'strofa': True}
+        return "", ""
 
     def handle_powiesc(self, element):
         return u"""
 <div class="module" id="book-text">
 
     def handle_powiesc(self, element):
         return u"""
 <div class="module" id="book-text">
- <span class="teacher-toggle">
+<!-- <span class="teacher-toggle">
   <input type="checkbox" name="teacher-toggle" id="teacher-toggle"/>
   <label for="teacher-toggle">Pokaż treść dla nauczyciela</label>
   <input type="checkbox" name="teacher-toggle" id="teacher-toggle"/>
   <label for="teacher-toggle">Pokaż treść dla nauczyciela</label>
- </span>
+ </span>-->
 
 """, u"</div>"
 
 
 """, u"</div>"
 
-
     handle_autor_utworu = tag("span", "author")
     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 = handle_naglowek_podrozdzial = 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 re.sub(r" +", " ", naglowek.text.strip())
+
+    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'><a name='top'></a>%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>"
+        anchor = "".join(tag_open_close("a", name=self.naglowek_to_anchor(element)))
+        return return_to_top + "<h2>", anchor + "</h2>"
+
+    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])
 
 
         opis = submill.generate(element.xpath('opis')[0])
 
@@ -62,26 +104,27 @@ class EduModule(Xmill):
 
         return u"""
 <div class="activity">
 
         return u"""
 <div class="activity">
- <div class="text">%(counter)d.
-  %(opis)s
-  %(wskazowki)s
+ <div class="text">
+  <span class="act_counter">%(counter)d.</span>
+  %(opis)s""" % locals(), \
+u"""%(wskazowki)s
  </div>
  </div>
- <div class="info">
-  <p>Czas: %(czas)s min</p>
-  <p>Forma: %(forma)s</p>
+ <aside class="info">
+  <section class="infobox time"><h1>Czas</h1><p>%(czas)s min</p></section>
+  <section class="infobox kind"><h1>Metoda</h1><p>%(forma)s</p></section>
   %(pomoce)s
   %(pomoce)s
- </div>
+ </aside>
  <div class="clearboth"></div>
 </div>
 """ % locals()
 
  <div class="clearboth"></div>
 </div>
 """ % locals()
 
-    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
@@ -90,7 +133,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,
@@ -100,16 +143,33 @@ 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
     def handle_lista(self, element, attrs={}):
         ltype = element.attrib.get('typ', 'punkt')
         return handler.generate(element)
 
     # Lists
     def handle_lista(self, element, attrs={}):
         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
+            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 = None
+            if surl:
+                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 self.options['wldoc'].book_info.url.slug != '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',
         listtag = {'num': 'ol',
                'punkt': 'ul',
                'alfa': 'ul',
@@ -129,32 +189,149 @@ 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']
+            defloc = sxml.xpath("//definiendum[text()='%s']" % element.text)
+            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:', element.text
+
+        return u"<dt>", 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"))
+        if has_frames: frames_c = "framed"
+        else: frames_c = ""
+        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
 
-
-class Excercise(EduModule):
+    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']
+            make_url = lambda f: self.options['urlmapper'] \
+              .url_for_material(slug, f)
+
+            if 'format' in element.attrib:
+                formats = re.split(r"[, ]+",
+                               element.attrib['format'])
+            else:
+                formats = [None]
+
+            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 = ""
     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
+
+    @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>
@@ -183,22 +360,50 @@ class Excercise(EduModule):
             add_class += ' handles handles-%s' % handles
             self.options = {'handles': handles}
 
             add_class += ' handles handles-%s' % handles
             self.options = {'handles': handles}
 
+        minimum = element.attrib.get('min', None)
+        if minimum: minimum_s = ' data-minimum="%d"' % int(minimum)
+        else: minimum_s = ''
 
         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), \
-    "</div>"
+            (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(Excercise):
-    def handle_pytanie(self, element):
-        pre, post = super(Wybor, self).handle_pytanie(element)
-        solutions = re.split(r"[, ]+", element.attrib['rozw'])
-        if len(solutions) == 1:
-            self.options = { 'single': True }
+
+class Wybor(Exercise):
+    def handle_cwiczenie(self, element):
+        pre, post = super(Wybor, self).handle_cwiczenie(element)
+        is_single_choice = True
+        pytania = element.xpath(".//pytanie")
+        if not pytania:
+            pytania = [element]
+        for p in pytania:
+            solutions = re.split(r"[, ]+", p.attrib['rozw'])
+            if len(solutions) != 1:
+                is_single_choice = False
+                break
+            choices = p.xpath(".//*[@nazwa]")
+            uniq = set()
+            for n in choices: uniq.add(n.attrib['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):
         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
@@ -221,8 +426,9 @@ class Wybor(Excercise):
             return super(Wybor, self).handle_punkt(element)
 
 
             return super(Wybor, self).handle_punkt(element)
 
 
+class Uporzadkuj(Exercise):
+    INSTRUCTION = u"Kliknij wybraną odpowiedź i przeciągnij w nowe miejsce."
 
 
-class Uporzadkuj(Excercise):
     def handle_pytanie(self, element):
         """
 Overrides the returned content default handle_pytanie
     def handle_pytanie(self, element):
         """
 Overrides the returned content default handle_pytanie
@@ -241,14 +447,16 @@ Overrides the returned content default handle_pytanie
             "</li>"
 
 
             "</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)
@@ -266,8 +474,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
@@ -275,8 +482,10 @@ 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):
         return piece.attrib['rozw']
 
     def solution_html(self, piece):
         return piece.attrib['rozw']
@@ -287,20 +496,44 @@ 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}
+            }
+            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)
@@ -310,19 +543,22 @@ 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['rozw'], self.piece_counter, self.piece_counter), '</li>'
             else:
                 return '<li data-solution="%s" data-no="%s" class="question-piece draggable">' % (element.attrib['rozw'], self.piece_counter), '</li>'
 
         elif self.options['predicate']:
             else:
                 return '<li data-solution="%s" data-no="%s" class="question-piece draggable">' % (element.attrib['rozw'], self.piece_counter), '</li>'
 
         elif self.options['predicate']:
-            placeholders = u'<li class="placeholder multiple"/>'
+            if self.options['min']:
+                placeholders = u'<li class="placeholder"/>' * self.options['min']
+            else:
+                placeholders = u'<li class="placeholder multiple"/>'
             return '<li data-predicate="%(nazwa)s">' % element.attrib, '<ul class="subjects">' + placeholders + '</ul></li>'
 
         else:
             return super(Przyporzadkuj, self).handle_punkt(element)
 
 
             return '<li data-predicate="%(nazwa)s">' % element.attrib, '<ul class="subjects">' + placeholders + '</ul></li>'
 
         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">
@@ -334,6 +570,48 @@ class PrawdaFalsz(Excercise):
             return super(PrawdaFalsz, self).handle_punkt(element)
 
 
             return super(PrawdaFalsz, self).handle_punkt(element)
 
 
+class EduModuleFormat(Format):
+    PRIMARY_MATERIAL_FORMATS = ('pdf', 'odt')
+
+    class MaterialNotFound(BaseException):
+        pass
+
+    def __init__(self, wldoc, **kwargs):
+        super(EduModuleFormat, self).__init__(wldoc, **kwargs)
+
+    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 = dict(reversed(k) for k in enumerate(self.PRIMARY_MATERIAL_FORMATS))
+        mats = self.materials_by_slug.get(slug, {}).items()
+        if not mats:
+            print "!! Material missing: '%s'" % slug
+        return sorted(mats, key=lambda (x, y): order.get(x, x))
+
+    def url_for_material(self, slug, fmt):
+        return "%s.%s" % (slug, fmt)
+
+    def url_for_image(self, slug, fmt, width=None):
+        return self.url_for_material(self, slug, fmt)
+
+
 def transform(wldoc, stylesheet='edumed', options=None, flags=None):
     """Transforms the WL document to XHTML.
 
 def transform(wldoc, stylesheet='edumed', options=None, flags=None):
     """Transforms the WL document to XHTML.
 
@@ -341,8 +619,5 @@ def transform(wldoc, stylesheet='edumed', options=None, flags=None):
     otherwise returns True if file has been written,False if it hasn't.
     File won't be written if it has no content.
     """
     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()