summaryrefslogtreecommitdiff
path: root/mammoth/docx/xmlparser.py
blob: 2cfe1e0f2c251bed61460941426c677d4236030e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import collections
import xml.sax


XmlElementBase = collections.namedtuple("XmlElement", ["name", "attributes", "children"])
class XmlElement(XmlElementBase):
    def find_child_or_null(self, name):
        return self.find_child(name) or _null_xml_element
    
    def find_child(self, name):
        for child in self.children:
            if child.name == name:
                return child
        
    
    def find_children(self, name):
        return XmlElementList(filter(
            lambda child: child.node_type == node_types.element and child.name == name,
            self.children
        ))


class XmlElementList(object):
    def __init__(self, elements):
        self._elements = elements
        
    def __iter__(self):
        return iter(self._elements)
    
    def find_children(self, name):
        children = []
        for element in self._elements:
            for child in element.find_children(name):
                children.append(child)
        return XmlElementList(children)


class NullXmlElement(object):
    attributes = {}
    
    def find_child_or_null(self, name):
        return self
    
    def find_child(self, name):
        return None


_null_xml_element = NullXmlElement()


XmlText = collections.namedtuple("XmlText", ["value"])


def element(name, attributes=None, children=None):
    return XmlElement(name, attributes or {}, children or [])

text = XmlText


class node_types(object):
    element = 1
    text = 3


XmlElement.node_type = node_types.element
XmlText.node_type = node_types.text



def parse_xml(fileobj, namespace_mapping=None):
    if namespace_mapping is None:
        namespace_prefixes = {}
    else:
        namespace_prefixes = dict((uri, prefix) for prefix, uri in namespace_mapping)
    
    handler = Handler(namespace_prefixes)
    parser = xml.sax.make_parser()
    parser.setFeature(xml.sax.handler.feature_namespaces, True)
    parser.setContentHandler(handler)
    parser.parse(fileobj)
    return handler.root()


class Handler(xml.sax.handler.ContentHandler):
    def __init__(self, namespace_prefixes):
        self._namespace_prefixes = namespace_prefixes
        self._element_stack = [RootElement()]
        self._character_buffer = []
    
    def root(self):
        return self._element_stack[0].children[0]
    
    def startElementNS(self, name, qname, attrs):
        self._flush_character_buffer()
        attributes = dict((self._read_name(key), value) for key, value in attrs.items())
        element = XmlElement(self._read_name(name), attributes, [])
        self._element_stack[-1].children.append(element)
        self._element_stack.append(element)
    
    def endElementNS(self, name, qname):
        self._flush_character_buffer()
        self._element_stack.pop()
        
    def characters(self, content):
        self._character_buffer.append(content)

    def _flush_character_buffer(self):
        if self._character_buffer:
            text = "".join(self._character_buffer)
            self._element_stack[-1].children.append(XmlText(text))
            self._character_buffer = []
            
    def _read_name(self, name):
        uri, local_name = name
        if uri is None:
            return local_name
        else:
            prefix = self._namespace_prefixes.get(uri)
            if prefix is None:
                return "{%s}%s" % (uri, local_name)
            else:
                return "%s:%s" % (prefix, local_name)

class RootElement(object):
    def __init__(self):
        self.children = []