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 from librarian import ValidationError, NoDublinCore, ParseError, NoProvider
7 from librarian import RDFNS
8 from librarian.cover import DefaultEbookCover
9 from librarian import dcparser
11 from xml.parsers.expat import ExpatError
12 from lxml import etree
13 from lxml.etree import XMLSyntaxError, XSLTApplyError
17 from StringIO import StringIO
20 class WLDocument(object):
21 LINE_SWAP_EXPR = re.compile(r'/\s', re.MULTILINE | re.UNICODE)
24 def __init__(self, edoc, parse_dublincore=True, provider=None,
25 strict=False, meta_fallbacks=None):
27 self.provider = provider
29 root_elem = edoc.getroot()
31 dc_path = './/' + RDFNS('RDF')
33 if root_elem.tag != 'utwor':
34 raise ValidationError("Invalid root element. Found '%s', should be 'utwor'" % root_elem.tag)
37 self.rdf_elem = root_elem.find(dc_path)
39 if self.rdf_elem is None:
40 raise NoDublinCore('Document has no DublinCore - which is required.')
42 self.book_info = dcparser.BookInfo.from_element(
43 self.rdf_elem, fallbacks=meta_fallbacks, strict=strict)
48 def from_string(cls, xml, *args, **kwargs):
49 return cls.from_file(StringIO(xml), *args, **kwargs)
52 def from_file(cls, xmlfile, *args, **kwargs):
54 # first, prepare for parsing
55 if isinstance(xmlfile, basestring):
56 file = open(xmlfile, 'rb')
64 if not isinstance(data, unicode):
65 data = data.decode('utf-8')
67 data = data.replace(u'\ufeff', '')
70 parser = etree.XMLParser(remove_blank_text=False)
71 tree = etree.parse(StringIO(data.encode('utf-8')), parser)
73 return cls(tree, *args, **kwargs)
74 except (ExpatError, XMLSyntaxError, XSLTApplyError), e:
77 def swap_endlines(self):
78 """Converts line breaks in stanzas into <br/> tags."""
79 # only swap inside stanzas
80 for elem in self.edoc.iter('strofa'):
81 for child in list(elem):
83 chunks = self.LINE_SWAP_EXPR.split(child.tail)
84 ins_index = elem.index(child) + 1
85 while len(chunks) > 1:
86 ins = etree.Element('br')
87 ins.tail = chunks.pop()
88 elem.insert(ins_index, ins)
89 child.tail = chunks.pop(0)
91 chunks = self.LINE_SWAP_EXPR.split(elem.text)
92 while len(chunks) > 1:
93 ins = etree.Element('br')
94 ins.tail = chunks.pop()
96 elem.text = chunks.pop(0)
99 if self.provider is None:
100 raise NoProvider('No document provider supplied.')
101 if self.book_info is None:
102 raise NoDublinCore('No Dublin Core in document.')
103 for part_uri in self.book_info.parts:
104 yield self.from_file(self.provider.by_uri(part_uri), provider=self.provider)
106 def chunk(self, path):
107 # convert the path to XPath
108 expr = self.path_to_xpath(path)
109 elems = self.edoc.xpath(expr)
116 def path_to_xpath(self, path):
119 for part in path.split('/'):
120 match = re.match(r'([^\[]+)\[(\d+)\]', part)
124 tag, n = match.groups()
125 parts.append("*[%d][name() = '%s']" % (int(n)+1, tag))
130 return '/'.join(parts)
132 def transform(self, stylesheet, **options):
133 return self.edoc.xslt(stylesheet, **options)
137 parent = self.rdf_elem.getparent()
138 parent.replace(self.rdf_elem, self.book_info.to_etree(parent))
142 return etree.tostring(self.edoc, encoding=unicode, pretty_print=True)
144 def merge_chunks(self, chunk_dict):
147 for key, data in chunk_dict.iteritems():
149 xpath = self.path_to_xpath(key)
150 node = self.edoc.xpath(xpath)[0]
151 repl = etree.fromstring(u"<%s>%s</%s>" % (node.tag, data, node.tag))
152 node.getparent().replace(node, repl)
154 unmerged.append(repr((key, xpath, e)))
158 def clean_ed_note(self, note_tag='nota_red'):
159 """ deletes forbidden tags from nota_red """
161 for node in self.edoc.xpath('|'.join('//%s//%s' % (note_tag, tag) for tag in
162 ('pa', 'pe', 'pr', 'pt', 'begin', 'end', 'motyw'))):
169 """Returns a set of all editors for book and its children.
171 :returns: set of dcparser.Person objects
173 if self.book_info is None:
174 raise NoDublinCore('No Dublin Core in document.')
175 persons = set(self.book_info.editors + self.book_info.technical_editors)
176 for child in self.parts():
177 persons.update(child.editors())
184 def as_html(self, *args, **kwargs):
185 from librarian import html
186 return html.transform(self, *args, **kwargs)
188 def as_text(self, *args, **kwargs):
189 from librarian import text
190 return text.transform(self, *args, **kwargs)
192 def as_epub(self, *args, **kwargs):
193 from librarian import epub
194 return epub.transform(self, *args, **kwargs)
196 def as_pdf(self, *args, **kwargs):
197 from librarian import pdf
198 return pdf.transform(self, *args, **kwargs)
200 def as_mobi(self, *args, **kwargs):
201 from librarian import mobi
202 return mobi.transform(self, *args, **kwargs)
204 def as_fb2(self, *args, **kwargs):
205 from librarian import fb2
206 return fb2.transform(self, *args, **kwargs)
208 def as_cover(self, cover_class=None, *args, **kwargs):
209 if cover_class is None:
210 cover_class = DefaultEbookCover
211 return cover_class(self.book_info, *args, **kwargs).output_file()
214 def latex_dir(self, *args, **kwargs):
215 kwargs['latex_dir'] = True
216 from librarian import pdf
217 return pdf.transform(self, *args, **kwargs)
219 def save_output_file(self, output_file, output_path=None, output_dir_path=None, make_author_dir=False, ext=None):
221 save_path = output_dir_path
223 save_path = os.path.join(save_path, unicode(self.book_info.author).encode('utf-8'))
224 save_path = os.path.join(save_path, self.book_info.uri.slug)
226 save_path += '.%s' % ext
228 save_path = output_path
230 output_file.save_as(save_path)