a4a8593b15a6443d55a0b8cdb277f5cc53b7d9aa
[librarian.git] / librarian / document.py
1 # -*- coding: utf-8 -*-
2 #
3 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
5 #
6 from StringIO import StringIO
7 from lxml import etree
8 from . import SSTNS
9 from .core import Section
10 from .parser import SSTParser
11
12
13 class Document(object):
14     def __init__(self, edoc, meta_context=None):
15         self.edoc = edoc
16         root_elem = edoc.getroot()
17         # Do I use meta_context?
18         if meta_context is not None:
19             root_elem.meta_context = meta_context
20         self.validate()
21
22     def validate(self):
23         root_elem = self.edoc.getroot()
24         if not isinstance(root_elem, Section):
25             if root_elem.tag != SSTNS('section'):
26                 if root_elem.tag == 'section':
27                     for element in root_elem.iter():
28                         if element.tag in ('section', 'header', 'div', 'span', 'aside', 'metadata'):
29                             element.tag = str(SSTNS(element.tag))
30
31                     parser = SSTParser()
32                     tree = etree.parse(StringIO(etree.tostring(root_elem)), parser)
33                     tree.xinclude()
34                     self.edoc = tree
35                     root_elem = self.edoc.getroot()
36                 else:
37                     raise ValueError("Invalid root element. Found '%s', should be '%s'" % (
38                         root_elem.tag, SSTNS('section')))
39             else:
40                 raise ValueError("Invalid class of root element. Use librarian.parser.SSTParser.")
41         if len(root_elem) < 1 or root_elem[0].tag != SSTNS('metadata'):
42             raise ValueError("The first tag in section should be metadata")
43         if len(root_elem) < 2 or root_elem[1].tag != SSTNS('header'):
44             raise ValueError("The first tag after metadata should be header")
45         header = root_elem[1]
46         if not getattr(header, 'text', None) or not header.text.strip():
47             raise ValueError(
48                 "The first header should contain the title in plain text (no links, emphasis etc.) and cannot be empty")
49
50     @classmethod
51     def from_string(cls, xml, *args, **kwargs):
52         return cls.from_file(StringIO(xml), *args, **kwargs)
53
54     @classmethod
55     def from_file(cls, xmlfile, *args, **kwargs):
56         # first, prepare for parsing
57         if isinstance(xmlfile, basestring):
58             file = open(xmlfile, 'rb')
59             try:
60                 data = file.read()
61             finally:
62                 file.close()
63         else:
64             data = xmlfile.read()
65
66         if not isinstance(data, unicode):
67             data = data.decode('utf-8')
68
69         data = data.replace(u'\ufeff', '')
70         # This is bad. The editor shouldn't spew unknown HTML entities.
71         data = data.replace(u'&nbsp;', u'\u00a0')
72
73         parser = SSTParser()
74         tree = etree.parse(StringIO(data.encode('utf-8')), parser)
75         tree.xinclude()
76         return cls(tree, *args, **kwargs)
77
78     @property
79     def meta(self):
80         """ Document's metadata is root's metadata. """
81         return self.edoc.getroot().meta