verbose curriculum levels in model
[librarian.git] / librarian / parser.py
index e6fdeb1..12fffb3 100644 (file)
 # -*- coding: utf-8 -*-
 #
 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
 # -*- coding: utf-8 -*-
 #
 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.  
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from librarian import ValidationError, NoDublinCore,  ParseError
 #
 from librarian import ValidationError, NoDublinCore,  ParseError
-from librarian import RDFNS
+from librarian import RDFNS, IOFile
 from librarian import dcparser
 
 from xml.parsers.expat import ExpatError
 from lxml import etree
 from lxml.etree import XMLSyntaxError, XSLTApplyError
 
 from librarian import dcparser
 
 from xml.parsers.expat import ExpatError
 from lxml import etree
 from lxml.etree import XMLSyntaxError, XSLTApplyError
 
+import os
 import re
 from StringIO import StringIO
 
 import re
 from StringIO import StringIO
 
+
 class WLDocument(object):
 class WLDocument(object):
-    LINE_SWAP_EXPR = re.compile(r'/\s', re.MULTILINE | re.UNICODE);
+    LINE_SWAP_EXPR = re.compile(r'/\s', re.MULTILINE | re.UNICODE)
+    provider = None
+
+    _edoc = None
+
+    @property
+    def edoc(self):
+        if self._edoc is None:
+            data = self.source.get_string()
+            if not isinstance(data, unicode):
+                data = data.decode('utf-8')
+            data = data.replace(u'\ufeff', '')
+            try:
+                parser = etree.XMLParser()
+                self._edoc = etree.parse(StringIO(data.encode('utf-8')), parser)
+            except (ExpatError, XMLSyntaxError, XSLTApplyError), e:
+                raise ParseError(e)
+        return self._edoc
+
+    _rdf_elem = None
+
+    @property
+    def rdf_elem(self):
+        if self._rdf_elem is None:
+            dc_path = './/' + RDFNS('RDF')
+            self._rdf_elem = self.edoc.getroot().find(dc_path)
+            if self._rdf_elem is None:
+                raise NoDublinCore('Document has no DublinCore - which is required.')
+        return self._rdf_elem
 
 
-    def __init__(self, edoc, parse_dublincore=True):
-        self.edoc = edoc
+    _book_info = None
 
 
-        root_elem = edoc.getroot()
-       
-        dc_path = './/' + RDFNS('RDF')
-        
+    @property
+    def book_info(self):
+        if not self.parse_dublincore:
+            return None
+        if self._book_info is None:
+            self._book_info = dcparser.BookInfo.from_element(
+                    self.rdf_elem, fallbacks=self.meta_fallbacks, strict=self.strict)
+        return self._book_info
+
+    def __init__(self, iofile, provider=None, parse_dublincore=True,  # shouldn't it be in a subclass?
+                 strict=False,  # ?
+                 meta_fallbacks=None):  # ?
+        self.source = iofile
+        self.provider = provider
+        self.parse_dublincore = parse_dublincore
+        self.strict = strict
+        self.meta_fallbacks = meta_fallbacks
+        root_elem = self.edoc.getroot()
         if root_elem.tag != 'utwor':
             raise ValidationError("Invalid root element. Found '%s', should be 'utwor'" % root_elem.tag)
         if root_elem.tag != 'utwor':
             raise ValidationError("Invalid root element. Found '%s', should be 'utwor'" % root_elem.tag)
-
         if parse_dublincore:
         if parse_dublincore:
-            self.rdf_elem = root_elem.find(dc_path)
+            self.book_info
 
 
-            if self.rdf_elem is None:
-                raise NoDublinCore('Document has no DublinCore - which is required.')
-            
-            self.book_info = dcparser.BookInfo.from_element(self.rdf_elem)
-        else:
-            self.book_info = None
-    
     @classmethod
     @classmethod
-    def from_string(cls, xml, swap_endlines=False, parse_dublincore=True):
-        return cls.from_file(StringIO(xml), swap_endlines, parse_dublincore=parse_dublincore)
+    def from_string(cls, xml, *args, **kwargs):
+        return cls(IOFile.from_string(xml), *args, **kwargs)
 
     @classmethod
 
     @classmethod
-    def from_file(cls, xmlfile, swap_endlines=False, parse_dublincore=True):
-
-        # first, prepare for parsing
-        if isinstance(xmlfile, basestring):
-            file = open(xmlfile, 'rb')
-            try:
-                data = file.read()
-            finally:
-                file.close()
-        else:
-            data = xmlfile.read()
-
-        if not isinstance(data, unicode):
-            data = data.decode('utf-8')
-
-        if swap_endlines:
-            data = cls.LINE_SWAP_EXPR.sub(u'<br />\n', data)
-    
-        try:
-            parser = etree.XMLParser(remove_blank_text=False)
-            return cls(etree.parse(StringIO(data), parser), parse_dublincore=parse_dublincore)
-        except (ExpatError, XMLSyntaxError, XSLTApplyError), e:
-            raise ParseError(e)                  
+    def from_file(cls, xmlfile, *args, **kwargs):
+        iofile = IOFile.from_filename(xmlfile)
+        return cls(iofile, *args, **kwargs)
+
+    def swap_endlines(self):
+        """Converts line breaks in stanzas into <br/> tags."""
+        # only swap inside stanzas
+        for elem in self.edoc.iter('strofa'):
+            for child in list(elem):
+                if child.tail:
+                    chunks = self.LINE_SWAP_EXPR.split(child.tail)
+                    ins_index = elem.index(child) + 1
+                    while len(chunks) > 1:
+                        ins = etree.Element('br')
+                        ins.tail = chunks.pop()
+                        elem.insert(ins_index, ins)
+                    child.tail = chunks.pop(0)
+            if elem.text:
+                chunks = self.LINE_SWAP_EXPR.split(elem.text)
+                while len(chunks) > 1:
+                    ins = etree.Element('br')
+                    ins.tail = chunks.pop()
+                    elem.insert(0, ins)
+                elem.text = chunks.pop(0)
 
     def chunk(self, path):
 
     def chunk(self, path):
-        # convert the path to XPath        
+        # convert the path to XPath
         expr = self.path_to_xpath(path)
         elems = self.edoc.xpath(expr)
 
         if len(elems) == 0:
             return None
         expr = self.path_to_xpath(path)
         elems = self.edoc.xpath(expr)
 
         if len(elems) == 0:
             return None
-        else:        
+        else:
             return elems[0]
 
     def path_to_xpath(self, path):
             return elems[0]
 
     def path_to_xpath(self, path):
@@ -85,7 +121,7 @@ class WLDocument(object):
                 parts.append(part)
             else:
                 tag, n = match.groups()
                 parts.append(part)
             else:
                 tag, n = match.groups()
-                parts.append("*[%d][name() = '%s']" % (int(n)+1, tag) )
+                parts.append("*[%d][name() = '%s']" % (int(n)+1, tag))
 
         if parts[0] == '.':
             parts[0] = ''
 
         if parts[0] == '.':
             parts[0] = ''
@@ -98,7 +134,7 @@ class WLDocument(object):
     def update_dc(self):
         if self.book_info:
             parent = self.rdf_elem.getparent()
     def update_dc(self):
         if self.book_info:
             parent = self.rdf_elem.getparent()
-            parent.replace( self.rdf_elem, self.book_info.to_etree(parent) )
+            parent.replace(self.rdf_elem, self.book_info.to_etree(parent))
 
     def serialize(self):
         self.update_dc()
 
     def serialize(self):
         self.update_dc()
@@ -111,9 +147,65 @@ class WLDocument(object):
             try:
                 xpath = self.path_to_xpath(key)
                 node = self.edoc.xpath(xpath)[0]
             try:
                 xpath = self.path_to_xpath(key)
                 node = self.edoc.xpath(xpath)[0]
-                repl = etree.fromstring(u"<%s>%s</%s>" %(node.tag, data, node.tag) )
-                node.getparent().replace(node, repl);
+                repl = etree.fromstring(u"<%s>%s</%s>" % (node.tag, data, node.tag))
+                node.getparent().replace(node, repl)
             except Exception, e:
             except Exception, e:
-                unmerged.append( repr( (key, xpath, e) ) )
+                # WTF xpath may be unused; also: too broad except
+                unmerged.append(repr((key, xpath, e)))
+
+        return unmerged
+
+    def clean_ed_note(self):
+        """ deletes forbidden tags from nota_red """
+
+        forbidden_tags = ('pa', 'pe', 'pr', 'pt', 'begin', 'end', 'motyw')
+        for node in self.edoc.xpath('|'.join('//nota_red//%s' % tag for tag in forbidden_tags)):
+            tail = node.tail
+            node.clear()
+            node.tag = 'span'
+            node.tail = tail
+
+    # Converters
+
+    def as_html(self, *args, **kwargs):
+        from librarian import pyhtml as html
+        return html.transform(self, *args, **kwargs)
+
+    def as_text(self, *args, **kwargs):
+        from librarian import text
+        return text.transform(self, *args, **kwargs)
+
+    def as_epub(self, *args, **kwargs):
+        from librarian import epub
+        return epub.transform(self, *args, **kwargs)
+
+    def as_pdf(self, *args, **kwargs):
+        from librarian import pypdf
+        return pypdf.EduModulePDFFormat(self).build(*args, **kwargs)
+
+    def as_mobi(self, *args, **kwargs):
+        from librarian import mobi
+        return mobi.transform(self, *args, **kwargs)
+
+    def as_fb2(self, *args, **kwargs):
+        from librarian import fb2
+        return fb2.transform(self, *args, **kwargs)
+
+    def as_cover(self, cover_class=None, *args, **kwargs):
+        if cover_class is None:
+            from librarian.styles.wolnelektury.cover import WLCover
+            cover_class = WLCover
+        return cover_class(self.book_info, *args, **kwargs).output_file()
+
+    def save_output_file(self, output_file, output_path=None, output_dir_path=None, make_author_dir=False, ext=None):
+        if output_dir_path:
+            save_path = output_dir_path
+            if make_author_dir:
+                save_path = os.path.join(save_path, unicode(self.book_info.author).encode('utf-8'))
+            save_path = os.path.join(save_path, self.book_info.uri.slug)
+            if ext:
+                save_path += '.%s' % ext
+        else:
+            save_path = output_path
 
 
-        return unmerged
\ No newline at end of file
+        output_file.save_as(save_path)