2 # -*- coding: utf-8 -*-
4 from slughifi import slughifi
8 def __init__(self, state, lines):
13 return cls(self.state, self.lines)
15 def line(self, position):
16 return self.lines[position]
18 ignore = [re.compile(r"^[\[][PA][\]] - [^ ]+$")]
19 empty_line = re.compile(r"^\s+$")
21 def skip_empty(self, position):
22 while self.line(position) == "" or \
23 self.empty_line.match(self.line(position)) or \
24 filter(lambda r: r.match(self.line(position)),
29 def tag(self, position):
31 Return None -- means that we can't tag it in any way
35 def wrap(self, tagname, content):
36 return u"<%s>%s</%s>" % (tagname, content, tagname)
39 def anymatches(regex):
40 return lambda x: regex.match(x)
43 class Section(Tagger):
44 looks_like = re.compile(r"^[IVX]+[.]\s+(.*)$")
46 def __init__(self, *a):
47 super(Section, self).__init__(*a)
48 self.is_podrozdzial = False
51 pos2 = self.skip_empty(pos)
53 m = self.looks_like.match(self.line(pos))
55 self.title = m.groups()[0]
58 def __unicode__(self):
59 return self.wrap(self.is_podrozdzial and "naglowek_podrozdzial" or "naglowek_rozdzial",
64 looks_like = re.compile(r"([^:]+): (.*)", re.UNICODE)
67 pos = self.skip_empty(pos)
68 m = self.looks_like.match(self.line(pos))
72 m = self.state.get('meta', {})
74 self.state['meta'] = m
78 class Informacje(Tagger):
80 self.title = self.spawn(Section)
82 pos = self.title.tag(pos)
83 if pos is None: return
87 pos = self.skip_empty(pos)
88 meta = self.spawn(Meta)
90 if pos2 is None: break
91 self.meta.append(meta)
98 point = re.compile(r"^[\s]*[-*·]{1,2}(.*)")
99 num = re.compile(r"^[\s]*[a-z][.]\s+(.*)")
101 def __init__(self, *args):
103 super(List, self).__init__(*args)
110 m = self.point.match(l)
112 m = self.num.match(l)
113 if m: self.type = 'num'
115 self.items.append(m.groups()[0].lstrip())
122 def append(self, tagger):
123 self.items.append(tagger)
125 def __unicode__(self):
126 s = '<lista typ="%s">' % self.type
128 if isinstance(i, list):
129 x = "\n".join(map(lambda elem: unicode(elem), i))
132 s += "\n<punkt>%s</punkt>" % x
137 class Paragraph(Tagger):
139 re.compile(r"[\s]*opis zawarto.ci[\s]*", re.I),
140 re.compile(r"^[\s]*$")
143 re.compile(r"[\s]*(przebieg zaj..|opcje dodatkowe)[\s]*", re.I),
147 self.line = self.lines[pos]
149 self.is_podrozdzial = False
151 for x in self.remove_this:
152 if x.match(self.line):
155 for x in self.podrozdzial:
156 if x.match(self.line):
157 self.is_podrozdzial = True
161 def __unicode__(self):
163 if self.is_podrozdzial:
164 tag = 'naglowek_podrozdzial'
167 return u"<%s>%s</%s>" % (tag, self.line, tag)
173 def __init__(self, tag_name, *elems):
174 self.tag_name = tag_name
177 def __unicode__(self):
178 s = u"<%s>" % self.tag_name
181 if isinstance(e, (str, unicode)):
184 s += "\n " + unicode(e)
188 s += u"</%s>" % self.tag_name
192 def eatany(pos, *taggers):
194 for t in list(taggers):
203 def eatseq(pos, *taggers):
205 taggers = list(taggers[:])
208 p = taggers[0].tag(pos)
210 return (tuple(good), pos)
211 good.append(taggers.pop(0))
212 # print "%d -> %d" % (pos, p)
216 print "Got index error for pos=%d" % pos
217 return (tuple(good), pos)
220 def tagger(text, pretty_print=False):
222 tagger(text) function name and signature is a contract.
223 returns auto-tagged text
225 if not isinstance(text, unicode):
226 text = unicode(text.decode('utf-8'))
227 lines = text.split("\n")
231 info = Informacje(state, lines)
233 ((info,), pos) = eatseq(pos, info)
235 # print "[i] %d. %s" % (pos, lines[pos])
240 x, pos = eatany(pos, info.spawn(Section),
241 info.spawn(List), info.spawn(Paragraph))
246 content.append(lines[pos])
248 if pos >= len(lines):
251 return toxml(content, pretty_print=pretty_print)
254 'description': u'Publikacja zrealizowana w ramach projektu Cyfrowa Przyszłość (http://cyfrowaprzyszlosc.pl).',
255 'relation': u'moduły powiązane linki',
256 'description.material': u'linki do załączników',
257 'rights': u'Creative Commons Uznanie autorstwa - Na tych samych warunkach 3.0',
261 class NotFound(Exception):
265 def find_block(content, title_re, begin=-1, end=-1):
266 title_re = re.compile(title_re, re.I | re.UNICODE)
269 if begin < 0: begin = 0
270 if end < 0: end = len(content)
272 for i in range(begin, end):
274 if isinstance(elem, Paragraph):
275 if title_re.match(elem.line):
278 if isinstance(elem, Section):
279 if title_re.match(elem.title):
283 if isinstance(elem, List):
285 if isinstance(elem, Paragraph) and elem.line:
293 def remove_block(content, title_re, removed=None):
294 rb, re = find_block(content, title_re)
295 if removed is not None and isinstance(removed, list):
296 removed += content[rb:re][:]
301 def mark_activities(content):
304 is_przebieg = re.compile(r"[\s]*przebieg zaj..[\s]*", re.I)
306 is_next_section = re.compile(r"^[IVX]+[.]? ")
307 is_activity = re.compile(r"^[0-9]+[.] (.+)")
309 is_activity_tools = re.compile(r"^pomoce:[\s]*(.+)")
310 is_activity_work = re.compile(r"^forma pracy:[\s]*(.+)")
311 is_activity_time = re.compile(r"^czas:[\s]*([\d]+).*")
313 'pomoce': is_activity_tools,
314 'forma': is_activity_work,
315 'czas': is_activity_time
319 in_activities = False
324 if isinstance(e, Section):
325 if in_activities and \
326 is_next_section.match(e.title):
327 in_activities = False
329 if isinstance(e, Paragraph):
330 if not in_activities and \
331 is_przebieg.match(e.line):
335 m = is_activity.match(e.line)
337 e.line = m.groups()[0]
339 if is_activity_time.match(e.line):
341 activities.append((ab, ae))
346 for ab, ae in activities:
351 act_els.append(Container("opis", content[ab]))
352 for i in range(ab, ae):
354 if isinstance(e, Paragraph):
355 for prop, pattern in activity_props.items():
356 m = pattern.match(e.line)
358 act_els.append(Container(prop, m.groups()[0]))
359 if info_start > i: info_start = i
360 act_els.insert(1, Container('wskazowki',
361 *content[ab + 1:info_start]))
362 content[ab:ae] = [Container('aktywnosc', *act_els)]
366 def mark_dictionary(content):
370 is_dictionary = re.compile(r"[\s]*s.owniczek[\s]*", re.I)
371 is_dictentry = re.compile(r"([^-]+) - (.+)")
372 slowniczek = content[0].spawn(List)
373 slowniczek.type = 'slowniczek'
374 while i < len(content):
376 if isinstance(e, Section):
377 if is_dictionary.match(e.title):
381 content[db:de] = [slowniczek]
384 if isinstance(e, Paragraph):
385 m = is_dictentry.match(e.line)
387 slowniczek.append([Container('definiendum', m.groups()[0]),
388 Container('definiens', m.groups()[1])])
397 def move_evaluation(content):
400 content = remove_block(content, r"ewaluacja[+ PA\[\].]*", evaluation)
402 # print "found evaluation %s" % (evaluation,)
403 evaluation[0].is_podrozdzial = True
405 opcje_dodatkowe = find_block(content, r"opcje dodatkowe\s*")
407 # print "putting evaluation just before opcje dodatkowe @ %s" % (opcje_dodatkowe, )
408 content[opcje_dodatkowe[0]:opcje_dodatkowe[0]] = evaluation
410 materialy = find_block(content, r"materia.y[+ AP\[\].]*")
412 # print "putting evaluation just before materialy @ %s" % (materialy, )
413 content[materialy[0]:materialy[0]] = evaluation
415 print "er.. no idea where to place evaluation"
419 def toxml(content, pretty_print=False):
420 # some transformations
421 content = mark_activities(content)
422 content = mark_dictionary(content)
424 content = remove_block(content, r"wykorzyst(yw)?ane metody[+ PA\[\].]*")
428 content = remove_block(content, r"(pomoce|potrzebne materia.y)[+ PA\[\]]*")
431 content = move_evaluation(content)
433 info = content.pop(0)
437 slug = slughifi(meta.get(u'Tytuł modułu', ''))
442 holder['xml'] += u"%s\n" % t
445 p(u'<dc:%s xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">%s</dc:%s>' % (k, v, k))
448 p(u'<%s>%s</%s>' % (tag, ct, tag))
455 p(u'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">')
456 p(u'<rdf:Description rdf:about="http://redakcja.cyfrowaprzyszlosc.pl/documents/">')
457 authors = map(unicode.strip, meta[u'Autorzy'].split(u','))
458 for author in authors:
459 names = author.split(u' ')
460 lastname = names.pop()
461 names.insert(0, lastname + ",")
462 author = u' '.join(names)
463 dc(u'creator', author)
464 dc(u'title', meta.get(u'Tytuł modułu', u''))
465 dc(u'relation.isPartOf', meta.get(u'Dział', u''))
466 dc(u'publisher', u'Fundacja Nowoczesna Polska')
467 dc(u'subject.competence', meta.get(u'Wybrana kompetencja z Katalogu', u''))
468 dc(u'subject.curriculum', meta.get(u'Odniesienie do podstawy programowej', u''))
469 for keyword in meta.get(u'Słowa kluczowe', u'').split(u','):
470 keyword = keyword.strip()
471 dc(u'subject', keyword)
472 dc(u'description', dc_fixed['description'])
473 dc(u'description.material', dc_fixed['description.material'])
474 dc(u'relation', dc_fixed['relation'])
475 dc(u'identifier.url', u'http://cyfrowaprzyszlosc.pl/%s' % slug)
476 dc(u'rights', dc_fixed['rights'])
477 dc(u'rights.license', u'http://creativecommons.org/licenses/by-sa/3.0/')
478 dc(u'format', u'xml')
480 dc(u'date', u'2012-11-09') # TODO
481 dc(u'audience', meta.get(u'Poziom edukacyjny', u''))
482 dc(u'language', u'pol')
483 p(u'</rdf:Description>')
487 t(u'nazwa_utworu', meta.get(u'Tytuł modułu', u''))
489 a(u'<!-- Numer porządkowy: %s -->' % meta.get(u'Numer porządkowy', u''))
492 p(unicode(info.title))
494 if isinstance(elm, unicode) or isinstance(elm, str):
503 from lxml import etree
504 from StringIO import StringIO
505 xml = etree.parse(StringIO(holder['xml']))
506 holder['xml'] = etree.tostring(xml, pretty_print=pretty_print, encoding=unicode)
512 # ogarnąć podrozdziały
515 # usunąć 'opis zawartości'