Added converter string_to_unicode_list to dcparser. Added handling of 'identifier...
[wolnelektury.git] / lib / dcparser / dcparser.py
1 # -*- coding: utf-8 -*-
2 from xml.parsers.expat import ExpatError
3
4 # Import ElementTree from anywhere
5 try:
6     import xml.etree.ElementTree as ET # Python >= 2.5
7 except ImportError:
8     try:
9         import elementtree.ElementTree as ET # effbot's pure Python module
10     except ImportError:
11         import lxml.etree as ET # ElementTree API using libxml2
12
13 import converters
14
15
16 __all__ = ('parse', 'ParseError')
17
18
19 class ParseError(Exception):
20     def __init__(self, message):
21         super(self, Exception).__init__(message)
22
23
24 class XMLNamespace(object):
25     '''Represents XML namespace.'''
26     
27     def __init__(self, uri):
28         self.uri = uri
29
30     def __call__(self, tag):
31         return '{%s}%s' % (self.uri, tag)
32
33     def __contains__(self, tag):
34         return tag.startswith(str(self))
35
36     def __repr__(self):
37         return 'XMLNamespace(%r)' % self.uri
38     
39     def __str__(self):
40         return '%s' % self.uri
41
42
43 class BookInfo(object):
44     RDF = XMLNamespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
45     DC = XMLNamespace('http://purl.org/dc/elements/1.1/')
46     
47     mapping = {
48         DC('creator')        : ('author', converters.str_to_person),
49         DC('title')          : ('title', converters.str_to_unicode),
50         DC('subject.period') : ('epoch', converters.str_to_unicode),
51         DC('subject.type')   : ('kind', converters.str_to_unicode),
52         DC('subject.genre')  : ('genre', converters.str_to_unicode),
53         DC('date')           : ('created_at', converters.str_to_date),
54         DC('date.pd')        : ('released_to_public_domain_at', converters.str_to_date),
55         DC('contributor.translator') : ('translator', converters.str_to_person),
56         DC('contributor.technical_editor') : ('technical_editor', converters.str_to_person),
57         DC('publisher')      : ('publisher', converters.str_to_unicode),
58         DC('source')         : ('source_name', converters.str_to_unicode),
59         DC('source.URL')     : ('source_url', converters.str_to_unicode),
60         DC('identifier.url') : ('url', converters.str_to_unicode),
61         DC('relation.hasPart') : ('parts', converters.str_to_unicode_list),
62     }
63
64     @classmethod
65     def from_string(cls, xml):
66         from StringIO import StringIO
67         return cls.from_file(StringIO(xml))
68     
69     @classmethod
70     def from_file(cls, xml_file):
71         book_info = cls()
72         
73         try:
74             tree = ET.parse(xml_file)
75         except ExpatError, e:
76             raise ParseError(e)
77
78         description = tree.find('//' + book_info.RDF('Description'))
79         if description is None:
80             raise ParseError('no Description tag found in document')
81         
82         for element in description.findall('*'):
83             book_info.parse_element(element) 
84         
85         return book_info
86
87     def parse_element(self, element):
88         try:
89             attribute, converter = self.mapping[element.tag]
90             setattr(self, attribute, converter(element.text, getattr(self, attribute, None)))
91         except KeyError:
92             pass
93
94     def to_xml(self):
95         """XML representation of this object."""
96         ET._namespace_map[str(self.RDF)] = 'rdf'
97         ET._namespace_map[str(self.DC)] = 'dc'
98         
99         root = ET.Element(self.RDF('RDF'))
100         description = ET.SubElement(root, self.RDF('Description'))
101         
102         for tag, (attribute, converter) in self.mapping.iteritems():
103             if hasattr(self, attribute):
104                 e = ET.Element(tag)
105                 e.text = unicode(getattr(self, attribute))
106                 description.append(e)
107         
108         return unicode(ET.tostring(root, 'utf-8'), 'utf-8')
109
110
111 def parse(file_name):
112     return BookInfo.from_file(file_name)
113