1 # -*- coding: utf-8 -*-
3 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
6 """PDF creation library.
8 Creates one big XML from the book and its children, converts it to LaTeX
9 with TeXML, then runs it by XeLaTeX.
12 from __future__ import with_statement
13 from copy import deepcopy
17 from StringIO import StringIO
18 from tempfile import mkdtemp, NamedTemporaryFile
21 from copy import deepcopy
22 from subprocess import call, PIPE
24 from Texml.processor import process
25 from lxml import etree
26 from lxml.etree import XMLSyntaxError, XSLTApplyError
28 from xmlutils import Xmill, tag, tagged, ifoption, tag_open_close
29 from librarian.dcparser import Person
30 from librarian.parser import WLDocument
31 from librarian import ParseError, DCNS, get_resource, IOFile, Format
32 from librarian import functions
33 from pdf import PDFFormat
39 def _wrap(*args, **kw):
40 value = f(*args, **kw)
42 prefix = (u'<TeXML escape="%d">' % (really and 1 or 0))
44 if isinstance(value, list):
45 import pdb; pdb.set_trace()
46 if isinstance(value, tuple):
47 return prefix + value[0], value[1] + postfix
49 return prefix + value + postfix
54 def cmd(name, parms=None):
55 def wrap(self, element):
56 pre, post = tag_open_close('cmd', name=name)
60 e = etree.Element("parm")
62 pre += etree.tostring(e)
64 post = "</parm>" + post
69 def mark_alien_characters(text):
70 text = re.sub(ur"([\u0400-\u04ff]+)", ur"<alien>\1</alien>", text)
74 class EduModule(Xmill):
75 def __init__(self, options=None):
76 super(EduModule, self).__init__(options)
77 self.activity_counter = 0
78 self.exercise_counter = 0
80 def swap_endlines(txt):
81 if self.options['strofa']:
82 txt = txt.replace("/\n", '<ctrl ch="\\"/>')
84 self.register_text_filter(functions.substitute_entities)
85 self.register_text_filter(mark_alien_characters)
86 self.register_text_filter(swap_endlines)
88 def get_dc(self, element, dc_field, single=False):
89 values = map(lambda t: t.text, element.xpath("//dc:%s" % dc_field, namespaces={'dc': DCNS.uri}))
94 def handle_rdf__RDF(self, _):
95 "skip metadata in generation"
99 def get_rightsinfo(self, element):
100 rights_lic = self.get_dc(element, 'rights.license', True)
101 return u'<cmd name="rightsinfostr">' + \
102 (rights_lic and u'<opt>%s</opt>' % rights_lic or '') +\
103 u'<parm>%s</parm>' % self.get_dc(element, 'rights', True) +\
107 def get_authors(self, element):
108 authors = self.get_dc(element, 'creator.expert') + \
109 self.get_dc(element, 'creator.scenario') + \
110 self.get_dc(element, 'creator.textbook')
111 return u', '.join(authors)
114 def get_title(self, element):
115 return self.get_dc(element, 'title', True)
117 def handle_utwor(self, element):
120 <TeXML xmlns="http://getfo.sourceforge.net/texml/ns1">
122 \\documentclass[%s]{wl}
123 \\usepackage{style}''' % self.options['customization_str'],
124 self.options['has_cover'] and '\usepackage{makecover}',
125 (self.options['morefloats'] == 'new' and '\usepackage[maxfloats=64]{morefloats}') or
126 (self.options['morefloats'] == 'old' and '\usepackage{morefloats}') or
127 (self.options['morefloats'] == 'none' and
128 u'''\\IfFileExists{morefloats.sty}{
129 \\usepackage{morefloats}
131 u'''\\def\\authors{%s}''' % self.get_authors(element),
132 u'''\\author{\\authors}''',
133 u'''\\title{%s}''' % self.get_title(element),
134 u'''\\def\\bookurl{%s}''' % self.get_dc(element, 'identifier.url', True),
135 u'''\\def\\rightsinfo{%s}''' % self.get_rightsinfo(element),
138 return u"".join(filter(None, lines)), u'</TeXML>'
142 def handle_powiesc(self, element):
144 <env name="document">
145 <cmd name="maketitle"/>
149 def handle_texcommand(self, element):
150 cmd = functions.texcommand(element.tag)
151 return u'<TeXML escape="1"><cmd name="%s"><parm>' % cmd, u'</parm></cmd></TeXML>'
157 handle_akap_dialog = \
158 handle_akap_dialog = \
159 handle_autor_utworu = \
161 handle_didaskalia = \
162 handle_didask_tekst = \
163 handle_dlugi_cytat = \
164 handle_dzielo_nadrzedne = \
165 handle_lista_osoba = \
167 handle_miejsce_czas = \
169 handle_motto_podpis = \
170 handle_naglowek_akt = \
171 handle_naglowek_czesc = \
172 handle_naglowek_listy = \
173 handle_naglowek_osoba = \
174 handle_naglowek_podrozdzial = \
175 handle_naglowek_podrozdzial = \
176 handle_naglowek_rozdzial = \
177 handle_naglowek_rozdzial = \
178 handle_naglowek_scena = \
179 handle_nazwa_utworu = \
185 handle_poezja_cyt = \
188 handle_sekcja_asterysk = \
189 handle_sekcja_swiatlo = \
190 handle_separator_linia = \
191 handle_slowo_obce = \
193 handle_tytul_dziela = \
194 handle_wyroznienie = \
197 _handle_strofa = cmd("strofa")
199 def handle_strofa(self, element):
200 self.options = {'strofa': True}
201 return self._handle_strofa(element)
203 def handle_aktywnosc(self, element):
204 self.activity_counter += 1
207 'activity_counter': self.activity_counter,
210 submill = EduModule(self.options)
212 opis = submill.generate(element.xpath('opis')[0])
214 n = element.xpath('wskazowki')
215 if n: wskazowki = submill.generate(n[0])
218 n = element.xpath('pomoce')
220 if n: pomoce = submill.generate(n[0])
223 forma = ''.join(element.xpath('forma/text()'))
225 czas = ''.join(element.xpath('czas/text()'))
227 counter = self.activity_counter
231 <cmd name="activitycounter"><parm>%(counter)d.</parm></cmd>
232 <cmd name="activityinfo"><parm>
233 <cmd name="activitytime"><parm>%(czas)s</parm></cmd>
234 <cmd name="activityform"><parm>%(forma)s</parm></cmd>
235 <cmd name="activitytools"><parm>%(pomoce)s</parm></cmd>
244 handle_opis = ifoption(sub_gen=True)(lambda s, e: ('', ''))
245 handle_wskazowki = ifoption(sub_gen=True)(lambda s, e: ('', ''))
247 @ifoption(sub_gen=True)
248 def handle_pomoce(self, _):
249 return "Pomoce: ", ""
251 def handle_czas(self, *_):
254 def handle_forma(self, *_):
257 def handle_lista(self, element, attrs={}):
258 if not element.findall("punkt"):
260 ltype = element.attrib.get('typ', 'punkt')
261 if ltype == 'slowniczek':
262 surl = element.attrib.get('href', None)
265 sxml = etree.fromstring(self.options['provider'].by_uri(surl).get_string())
266 self.options = {'slowniczek': True, 'slowniczek_xml': sxml }
268 listcmd = {'num': 'enumerate',
271 'slowniczek': 'itemize',
272 'czytelnia': 'itemize'}[ltype]
274 return u'<env name="%s">' % listcmd, u'</env>'
276 def handle_punkt(self, element):
277 return '<cmd name="item"/>', ''
279 def handle_cwiczenie(self, element):
280 exercise_handlers = {
282 'uporzadkuj': Uporzadkuj,
285 'przyporzadkuj': Przyporzadkuj,
286 'prawdafalsz': PrawdaFalsz
289 typ = element.attrib['typ']
290 self.exercise_counter += 1
291 if not typ in exercise_handlers:
292 return '(no handler)'
293 self.options = {'exercise_counter': self.exercise_counter}
294 handler = exercise_handlers[typ](self.options)
295 return handler.generate(element)
297 # XXX this is copied from pyhtml.py, except for return and
298 # should be refactored for no code duplication
299 def handle_definiendum(self, element):
300 nxt = element.getnext()
303 # let's pull definiens from another document
304 if self.options['slowniczek_xml'] and (not nxt or nxt.tag != 'definiens'):
305 sxml = self.options['slowniczek_xml']
306 assert element.text != ''
307 defloc = sxml.xpath("//definiendum[text()='%s']" % element.text)
309 definiens = defloc[0].getnext()
310 if definiens.tag == 'definiens':
311 subgen = EduModule(self.options)
312 definiens_s = subgen.generate(definiens)
314 return u'<cmd name="textbf"><parm>', u"</parm></cmd>: " + definiens_s
316 def handle_definiens(self, element):
319 def handle_podpis(self, element):
320 return u"""<env name="figure">""", u"</env>"
322 def handle_tabela(self, element):
324 for w in element.xpath("wiersz"):
326 if max_col < len(ks):
328 self.options = {'columnts': max_col}
330 # has_frames = int(element.attrib.get("ramki", "0"))
331 # if has_frames: frames_c = "framed"
332 # else: frames_c = ""
333 # return u"""<table class="%s">""" % frames_c, u"</table>"
335 <cmd name="begin"><parm>tabular</parm><parm>%s</parm></cmd>
336 ''' % ('l' * max_col), \
337 u'''<cmd name="end"><parm>tabular</parm></cmd>'''
340 def handle_wiersz(self, element):
341 return u"", u'<ctrl ch="\\"/>'
344 def handle_kol(self, element):
345 if element.getnext() is not None:
346 return u"", u'<spec cat="align" />'
349 def handle_link(self, element):
350 if element.attrib.get('url'):
351 return cmd('href', parms=[element.attrib['url']])(self, element)
353 return cmd('em')(self, element)
356 class Exercise(EduModule):
357 def __init__(self, *args, **kw):
358 self.question_counter = 0
359 super(Exercise, self).__init__(*args, **kw)
361 handle_rozw_kom = ifoption(teacher=True)(cmd('akap'))
363 def handle_cwiczenie(self, element):
365 'exercise': element.attrib['typ'],
368 self.question_counter = 0
369 self.piece_counter = 0
371 header = etree.Element("parm")
372 header_cmd = etree.Element("cmd", name="naglowekpodrozdzial")
373 header_cmd.append(header)
374 header.text = u"Zadanie %d." % self.options['exercise_counter']
376 pre = etree.tostring(header_cmd, encoding=unicode)
378 # Add a single <pytanie> tag if it's not there
379 if not element.xpath(".//pytanie"):
380 qpre, qpost = self.handle_pytanie(element)
385 def handle_pytanie(self, element):
386 """This will handle <cwiczenie> element, when there is no <pytanie>
388 self.question_counter += 1
389 self.piece_counter = 0
391 if self.options['teacher'] and element.attrib.get('rozw'):
392 post += u" [rozwiązanie: %s]" % element.attrib.get('rozw')
395 def handle_punkt(self, element):
396 pre, post = super(Exercise, self).handle_punkt(element)
397 if self.options['teacher'] and element.attrib.get('rozw'):
398 post += u" [rozwiązanie: %s]" % element.attrib.get('rozw')
401 def solution_header(self):
402 par = etree.Element("cmd", name="par")
403 parm = etree.Element("parm")
404 parm.text = u"Rozwiązanie:"
406 return etree.tostring(par)
408 def explicit_solution(self):
409 if self.options['solution']:
410 par = etree.Element("cmd", name="par")
411 parm = etree.Element("parm")
412 parm.text = self.options['solution']
414 return self.solution_header() + etree.tostring(par)
418 class Wybor(Exercise):
419 def handle_cwiczenie(self, element):
420 pre, post = super(Wybor, self).handle_cwiczenie(element)
421 is_single_choice = True
422 pytania = element.xpath(".//pytanie")
426 solutions = re.split(r"[, ]+", p.attrib['rozw'])
427 if len(solutions) != 1:
428 is_single_choice = False
430 choices = p.xpath(".//*[@nazwa]")
432 for n in choices: uniq.add(n.attrib['nazwa'])
433 if len(choices) != len(uniq):
434 is_single_choice = False
437 self.options = {'single': is_single_choice}
440 def handle_punkt(self, element):
441 if self.options['exercise'] and element.attrib.get('nazwa', None):
442 cmd = 'radio' if self.options['single'] else 'checkbox'
443 return u'<cmd name="%s"/>' % cmd, ''
445 return super(Wybor, self).handle_punkt(element)
448 class Uporzadkuj(Exercise):
449 def handle_pytanie(self, element):
450 order_items = element.xpath(".//punkt/@rozw")
451 return super(Uporzadkuj, self).handle_pytanie(element)
454 class Przyporzadkuj(Exercise):
455 def handle_lista(self, lista):
456 header = etree.Element("parm")
457 header_cmd = etree.Element("cmd", name="par")
458 header_cmd.append(header)
459 if 'nazwa' in lista.attrib:
460 header.text = u"Kategorie:"
461 elif 'cel' in lista.attrib:
462 header.text = u"Elementy do przyporządkowania:"
464 header.text = u"Lista:"
465 pre, post = super(Przyporzadkuj, self).handle_lista(lista)
466 pre = etree.tostring(header_cmd, encoding=unicode) + pre
470 class Luki(Exercise):
471 def find_pieces(self, question):
472 return question.xpath(".//luka")
474 def solution(self, piece):
475 piece = deepcopy(piece)
478 return sub.generate(piece)
480 def handle_pytanie(self, element):
481 qpre, qpost = super(Luki, self).handle_pytanie(element)
483 luki = self.find_pieces(element)
485 self.words = u"<env name='itemize'>%s</env>" % (
486 "".join("<cmd name='item'/>%s" % self.solution(luka) for luka in luki)
490 def handle_opis(self, element):
491 return '', self.words
493 def handle_luka(self, element):
495 if self.options['teacher']:
496 piece = deepcopy(element)
499 text = sub.generate(piece)
500 luka += u" [rozwiązanie: %s]" % text
505 def find_pieces(self, question):
506 return question.xpath(".//zastap")
508 def solution(self, piece):
509 return piece.attrib['rozw']
511 def list_header(self):
512 return u"Elementy do wstawienia"
514 def handle_zastap(self, element):
515 piece = deepcopy(element)
518 text = sub.generate(piece)
519 if self.options['teacher'] and element.attrib.get('rozw'):
520 text += u" [rozwiązanie: %s]" % element.attrib.get('rozw')
524 class PrawdaFalsz(Exercise):
525 def handle_punkt(self, element):
526 pre, post = super(PrawdaFalsz, self).handle_punkt(element)
527 if 'rozw' in element.attrib:
528 post += u" [Prawda/Fałsz]"
534 lists = tree.xpath(".//lista")
539 if p.tail is None: p.tail = ''
543 if p.text is None: p.text = ''
549 class EduModulePDFFormat(PDFFormat):
551 edumod = EduModule({"teacher": True})
552 texml = edumod.generate(fix_lists(self.wldoc.edoc.getroot())).encode('utf-8')
554 open("/tmp/texml.xml", "w").write(texml)