Add lesson TOC. Also: don't just arbitrary paste data into XML.
[librarian.git] / librarian / pyhtml.py
index d3314cc..302d790 100644 (file)
@@ -4,43 +4,69 @@
 # 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
+
+
+DEFAULT_MATERIAL_FORMAT = 'odt'
+
 
 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(functions.substitute_entities)
 
     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_strofa = tag('div', 'stanza')
     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 handle_nazwa_utworu(self, element):
+        toc = []
+        for naglowek in element.getparent().findall('.//naglowek_rozdzial'):
+            a = etree.Element("a")
+            a.attrib["href"] = "#" + naglowek.text
+            a.text = naglowek.text
+            atxt = etree.tostring(a, encoding=unicode)
+            toc.append("<li>%s</li>" % atxt)
+        toc = "<ul class='toc'>%s</ul>" % "".join(toc)
+        return "<h1 class='title'>Lekcja: ", "</h1>" + toc
+
+    @tagged("h2")
+    def handle_naglowek_rozdzial(self, element):
+        return "", "".join(tag_open_close("a", name=element.text))
+
+    def handle_uwaga(self, _e):
+        return None
 
     def handle_aktywnosc(self, element):
         self.activity_counter += 1
         self.options = {
 
     def handle_aktywnosc(self, element):
         self.activity_counter += 1
         self.options = {
-            'activity': True, 
-            'activity_counter': self.activity_counter
+            'activity': True,
+            '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])
 
@@ -61,35 +87,36 @@ class EduModule(Xmill):
 
         return u"""
 <div class="activity">
 
         return u"""
 <div class="activity">
- <div class="text">%(counter)d. 
+ <div class="text">
+  <span class="act_counter">%(counter)d.</span>
   %(opis)s
   %(wskazowki)s
  </div>
   %(opis)s
   %(wskazowki)s
  </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')))
-    
-    @ifoption(activity=False)
-    @tagged('div', 'materials')
+    handle_opis = ifoption(sub_gen=True)(tag('div', 'description'))
+    handle_wskazowki = ifoption(sub_gen=True)(tag('div', ('hints', 'teacher')))
+
+    @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_forma(self, *_):
         return
     def handle_czas(self, *_):
         return
 
     def handle_forma(self, *_):
         return
-            
+
     def handle_cwiczenie(self, element):
     def handle_cwiczenie(self, element):
-        excercise_handlers = {
+        exercise_handlers = {
             'wybor': Wybor,
             'uporzadkuj': Uporzadkuj,
             'luki': Luki,
             'wybor': Wybor,
             'uporzadkuj': Uporzadkuj,
             'luki': Luki,
@@ -97,29 +124,33 @@ class EduModule(Xmill):
             'przyporzadkuj': Przyporzadkuj,
             'prawdafalsz': PrawdaFalsz
             }
             'przyporzadkuj': Przyporzadkuj,
             'prawdafalsz': PrawdaFalsz
             }
-        
+
         typ = element.attrib['typ']
         typ = element.attrib['typ']
-        handler = excercise_handlers[typ](self.options)
+        handler = exercise_handlers[typ](self.options)
         return handler.generate(element)
 
     # Lists
     def handle_lista(self, element, attrs={}):
         ltype = element.attrib.get('typ', 'punkt')
         if ltype == 'slowniczek':
         return handler.generate(element)
 
     # Lists
     def handle_lista(self, element, attrs={}):
         ltype = element.attrib.get('typ', 'punkt')
         if ltype == 'slowniczek':
-            self.options = {'slowniczek': True}
+            surl = element.attrib.get('href', None)
+            sxml = None
+            if surl:
+                sxml = etree.fromstring(self.options['provider'].by_uri(surl).get_string())
+            self.options = {'slowniczek': True, 'slowniczek_xml': sxml }
             return '<div class="slowniczek">', '</div>'
             return '<div class="slowniczek">', '</div>'
-### robie teraz punkty wyboru
-        listtag = {'num': 'ol', 
-               'punkt': 'ul', 
-               'alfa': 'ul', 
+
+        listtag = {'num': 'ol',
+               'punkt': 'ul',
+               'alfa': 'ul',
                'czytelnia': 'ul'}[ltype]
 
         classes = attrs.get('class', '')
         if classes: del attrs['class']
                'czytelnia': 'ul'}[ltype]
 
         classes = attrs.get('class', '')
         if classes: del attrs['class']
-            
+
         attrs_s = ' '.join(['%s="%s"' % kv for kv in attrs.items()])
         if attrs_s: attrs_s = ' ' + attrs_s
         attrs_s = ' '.join(['%s="%s"' % kv for kv in attrs.items()])
         if attrs_s: attrs_s = ' ' + attrs_s
-            
+
         return '<%s class="lista %s %s"%s>' % (listtag, ltype, classes, attrs_s), '</%s>' % listtag
 
     def handle_punkt(self, element):
         return '<%s class="lista %s %s"%s>' % (listtag, ltype, classes, attrs_s), '</%s>' % listtag
 
     def handle_punkt(self, element):
@@ -128,33 +159,88 @@ class EduModule(Xmill):
         else:
             return '<li>', '</li>'
 
         else:
             return '<li>', '</li>'
 
+    def handle_definiendum(self, element):
+        nxt = element.getnext()
+        definiens_s = ''
+
+        # let's pull definiens from another document
+        if self.options['slowniczek_xml'] and (not nxt or nxt.tag != 'definiens'):
+            sxml = self.options['slowniczek_xml']
+            assert element.text != ''
+            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)
+
+        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
     def handle_rdf__RDF(self, _):
         # ustal w opcjach  rzeczy :D
-        return 
+        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:
+            formats = re.split(r"[, ]+",
+                               element.attrib.get('format', DEFAULT_MATERIAL_FORMAT))
+            make_url = lambda f: self.options['urlmapper'] \
+              .url_for_material(element.attrib['material'], f)
+            def_href = make_url(formats[0])
+            fmt_links = []
+            for f in formats[1:]:
+                fmt_links.append(u'<a href="%s">%s</a>' % (make_url(f), f.upper()))
+            more_links = u' (%s)' % u', '.join(fmt_links) if fmt_links else u''
 
 
+            return u"<a href='%s'>" % def_href, u'</a>%s' % more_links
 
 
-class Excercise(EduModule):
+
+class Exercise(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)
+
+    handle_opis = tag('div', 'description')
 
     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">
 """ % element.attrib
         post = u"""
 <div class="buttons">
 <span class="message"></span>
 <input type="button" class="check" value="sprawdź"/>
 <form action="#" method="POST">
 """ % element.attrib
         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="solutions" value="pokaż rozwiązanie"/>
+<input type="button" class="reset" value="reset"/>
 </div>
 </form>
 </div>
 </div>
 </form>
 </div>
@@ -165,32 +251,70 @@ class Excercise(EduModule):
             pre = pre + qpre
             post = qpost + post
         return pre, post
             pre = pre + qpre
             post = qpost + post
         return pre, post
+
     def handle_pytanie(self, element):
         """This will handle <cwiczenie> element, when there is no <pytanie>
         """
     def handle_pytanie(self, element):
         """This will handle <cwiczenie> element, when there is no <pytanie>
         """
+        add_class = ""
         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 = ''
 
         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 = ''
 
-        return '<div class="question" data-no="%d" %s>' %\
-            (self.question_counter, solution_s), \
-    "</div>"
+        handles = element.attrib.get('uchwyty', None)
+        if 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>' %\
+            (add_class, self.question_counter, solution_s + minimum_s), \
+            "</div>"
+
+
+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 = element.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
 
 
-class Wybor(Excercise):
     def handle_punkt(self, element):
     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
             eid = "q%(qc)d_%(no)d" % locals()
             aname = element.attrib.get('nazwa', None)
             qc = self.question_counter
             self.piece_counter += 1
             no = self.piece_counter
             eid = "q%(qc)d_%(no)d" % locals()
             aname = element.attrib.get('nazwa', None)
-            return u"""
+            if self.options['single']:
+                return u"""
+<li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
+<input type="radio" name="q%(qc)d" id="%(eid)s" value="%(aname)s" />
+<label for="%(eid)s">
+                """ % locals(), u"</label></li>"
+            else:
+                return u"""
 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
 <li class="question-piece" data-qc="%(qc)d" data-no="%(no)d" data-name="%(aname)s">
-<input type="checkbox" name="" id="%(eid)s" />
+<input type="checkbox" name="%(eid)s" id="%(eid)s" />
 <label for="%(eid)s">
 """ % locals(), u"</label></li>"
 
 <label for="%(eid)s">
 """ % locals(), u"</label></li>"
 
@@ -198,7 +322,7 @@ class Wybor(Excercise):
             return super(Wybor, self).handle_punkt(element)
 
 
             return super(Wybor, self).handle_punkt(element)
 
 
-class Uporzadkuj(Excercise):
+class Uporzadkuj(Exercise):
     def handle_pytanie(self, element):
         """
 Overrides the returned content default handle_pytanie
     def handle_pytanie(self, element):
         """
 Overrides the returned content default handle_pytanie
@@ -217,49 +341,97 @@ Overrides the returned content default handle_pytanie
             "</li>"
 
 
             "</li>"
 
 
-class Luki(Excercise):
+class Luki(Exercise):
+    def find_pieces(self, question):
+        return question.xpath(".//luka")
+
+    def solution_html(self, piece):
+        return piece.text + ''.join(
+            [etree.tostring(n, encoding=unicode)
+             for n in piece])
+
+    def handle_pytanie(self, element):
+        qpre, qpost = super(Luki, self).handle_pytanie(element)
+
+        luki = list(enumerate(self.find_pieces(element)))
+        luki_html = ""
+        i = 0
+        random.shuffle(luki)
+        for (i, luka) in luki:
+            i += 1
+            luka_html = self.solution_html(luka)
+            luki_html += u'<span class="draggable question-piece" data-no="%d">%s</span>' % (i, luka_html)
+        self.words_html = '<div class="words">%s</div>' % luki_html
+
+        return qpre, qpost
+
+    def handle_opis(self, element):
+        return '', self.words_html
+
     def handle_luka(self, element):
     def handle_luka(self, element):
-        return '<input type="text" class="luka question-piece" data-solution="%s"></input>' % element.text
+        self.piece_counter += 1
+        return '<span class="placeholder" data-solution="%d"></span>' % self.piece_counter
+
 
 
+class Zastap(Luki):
+    def find_pieces(self, question):
+        return question.xpath(".//zastap")
+
+    def solution_html(self, piece):
+        return piece.attrib['rozw']
 
 
-class Zastap(Excercise):
     def handle_zastap(self, element):
     def handle_zastap(self, element):
-        return '<span class="zastap question-piece" data-solution="%(rozw)s">' % element.attrib, '</span>'
+        self.piece_counter += 1
+        return '<span class="placeholder zastap question-piece" data-solution="%d">' \
+            % self.piece_counter, '</span>'
 
 
 
 
-class Przyporzadkuj(Excercise):
+class Przyporzadkuj(Exercise):
+    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):
     def handle_lista(self, lista):
-        print "in lista %s %s" % (lista.attrib, self.options)
         if 'nazwa' in lista.attrib:
             attrs = {
                 'data-name': lista.attrib['nazwa'],
                 'class': 'predicate'
         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}
+            }
+            self.options = {'subject': True, 'handles': 'uchwyty' in lista.attrib}
         else:
             attrs = {}
         pre, post = super(Przyporzadkuj, self).handle_lista(lista, attrs)
         return pre, post + '<br class="clr"/>'
 
     def handle_punkt(self, element):
         else:
             attrs = {}
         pre, post = super(Przyporzadkuj, self).handle_lista(lista, attrs)
         return pre, post + '<br class="clr"/>'
 
     def handle_punkt(self, element):
-        print "in punkt %s %s" % (element.attrib, self.options)
-
         if self.options['subject']:
         if self.options['subject']:
-            return '<li data-solution="%(rozw)s" class="question-piece draggable">' % element.attrib, '</li>'
+            self.piece_counter += 1
+            if self.options['handles']:
+                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']:
         elif self.options['predicate']:
-            print etree.tostring(element, encoding=unicode)
-            return '<li data-predicate="%(nazwa)s">' % element.attrib, '<ul class="subjects droppable"></ul></li>'
+            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)
 
 
         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">
@@ -271,6 +443,24 @@ class PrawdaFalsz(Excercise):
             return super(PrawdaFalsz, self).handle_punkt(element)
 
 
             return super(PrawdaFalsz, self).handle_punkt(element)
 
 
+class EduModuleFormat(Format):
+    def __init__(self, wldoc, **kwargs):
+        super(EduModuleFormat, self).__init__(wldoc, **kwargs)
+
+    def build(self):
+        edumod = EduModule({'provider': self.wldoc.provider, 'urlmapper': self})
+
+        html = edumod.generate(self.wldoc.edoc.getroot())
+
+        return IOFile.from_string(html.encode('utf-8'))
+
+    def url_for_material(self, slug, fmt=None):
+        # No briliant idea for an API here.
+        if fmt:
+            return "%s.%s" % (slug, fmt)
+        return slug
+
+
 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.
 
@@ -278,8 +468,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()