1 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
8 from tempfile import NamedTemporaryFile
10 from lxml import etree
11 from urllib.request import FancyURLopener
12 from .util import makedirs
14 # Compatibility imports.
15 from .meta.types.wluri import WLURI
18 class UnicodeException(Exception):
20 """ Dirty workaround for Python Unicode handling problems. """
21 args = self.args[0] if len(self.args) == 1 else self.args
24 except UnicodeDecodeError:
25 message = str(args, encoding='utf-8', errors='ignore')
29 class ParseError(UnicodeException):
33 class ValidationError(UnicodeException):
37 class NoDublinCore(ValidationError):
38 """There's no DublinCore section, and it's required."""
42 class NoProvider(UnicodeException):
43 """There's no DocProvider specified, and it's needed."""
48 '''A handy structure to repsent names in an XML namespace.'''
50 def __init__(self, uri):
53 def __call__(self, tag):
54 return '{%s}%s' % (self.uri, tag)
56 def __contains__(self, tag):
57 return tag.startswith('{' + str(self) + '}')
60 return 'XMLNamespace(%r)' % self.uri
63 return '%s' % self.uri
66 class EmptyNamespace(XMLNamespace):
68 super(EmptyNamespace, self).__init__('')
70 def __call__(self, tag):
74 # some common namespaces we use
75 XMLNS = XMLNamespace('http://www.w3.org/XML/1998/namespace')
76 RDFNS = XMLNamespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
77 DCNS = XMLNamespace('http://purl.org/dc/elements/1.1/')
78 XHTMLNS = XMLNamespace("http://www.w3.org/1999/xhtml")
79 PLMETNS = XMLNamespace("http://dl.psnc.pl/schemas/plmet/")
80 FB2NS = XMLNamespace("http://www.gribuser.ru/xml/fictionbook/2.0")
81 XLINKNS = XMLNamespace("http://www.w3.org/1999/xlink")
82 WLNS = EmptyNamespace()
86 """Base class for a repository of XML files.
88 Used for generating joined files, like EPUBs.
91 def by_slug(self, slug):
92 """Should return a file-like object with a WL document XML."""
93 raise NotImplementedError
96 class DirDocProvider(DocProvider):
97 """ Serve docs from a directory of files in form <slug>.xml """
99 def __init__(self, dir_):
103 def by_slug(self, slug):
104 fname = slug + '.xml'
105 return open(os.path.join(self.dir, fname), 'rb')
108 def get_resource(path):
109 return os.path.join(os.path.dirname(__file__), path)
113 """Represents a file returned by one of the converters."""
120 os.unlink(self._filename)
122 def __nonzero__(self):
123 return self._bytes is not None or self._filename is not None
126 def from_bytes(cls, bytestring):
127 """Converter returns contents of a file as a string."""
130 instance._bytes = bytestring
134 def from_filename(cls, filename):
135 """Converter returns contents of a file as a named file."""
138 instance._filename = filename
142 """Get file's contents as a bytestring."""
144 if self._filename is not None:
145 with open(self._filename, 'rb') as f:
151 """Get file as a file-like object."""
153 if self._bytes is not None:
154 return io.BytesIO(self._bytes)
155 elif self._filename is not None:
156 return open(self._filename, 'rb')
158 def get_filename(self):
159 """Get file as a fs path."""
161 if self._filename is not None:
162 return self._filename
163 elif self._bytes is not None:
164 temp = NamedTemporaryFile(prefix='librarian-', delete=False)
165 temp.write(self._bytes)
167 self._filename = temp.name
168 return self._filename
172 def save_as(self, path):
173 """Save file to a path. Create directories, if necessary."""
175 dirname = os.path.dirname(os.path.abspath(path))
177 shutil.copy(self.get_filename(), path)
180 class URLOpener(FancyURLopener):
181 version = 'WL Librarian (http://github.com/fnp/librarian)'
184 urllib._urlopener = URLOpener()