Statistics
| Branch: | Tag: | Revision:

root / ncclient / xml_.py @ 564bee4f

History | View | Annotate | Download (7.1 kB)

1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

    
15

    
16
"""The :mod:`xml` module provides methods for creating XML documents, parsing
17
XML, and converting between different XML representations. It uses
18
:mod:`~xml.etree.ElementTree` internally.
19
"""
20

    
21
from cStringIO import StringIO
22
from xml.etree import cElementTree as ET
23

    
24
from ncclient import NCClientError
25

    
26
class ContentError(NCClientError):
27
    "Raised by methods of the :mod:`content` module in case of an error."
28
    pass
29

    
30
### Namespace-related
31

    
32
#: Base NETCONF namespace
33
BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
34
#: ... and this is BASE_NS according to Cisco devices tested
35
CISCO_BS = 'urn:ietf:params:netconf:base:1.0'
36
#: namespace for Tail-f data model
37
TAILF_AAA_1_1 = 'http://tail-f.com/ns/aaa/1.1'
38
#: namespace for Tail-f data model
39
TAILF_EXECD_1_1 = 'http://tail-f.com/ns/execd/1.1'
40
#: namespace for Cisco data model
41
CISCO_CPI_10 = 'http://www.cisco.com/cpi_10/schema'
42

    
43
try:
44
    register_namespace = ET.register_namespace
45
except AttributeError:
46
    def register_namespace(prefix, uri):
47
        from xml.etree import ElementTree
48
        # cElementTree uses ElementTree's _namespace_map, so that's ok
49
        ElementTree._namespace_map[uri] = prefix
50

    
51
register_namespace('netconf', BASE_NS)
52
register_namespace('aaa', TAILF_AAA_1_1)
53
register_namespace('execd', TAILF_EXECD_1_1)
54
register_namespace('cpi', CISCO_CPI_10)
55

    
56

    
57
qualify = lambda tag, ns=BASE_NS: tag if ns is None else '{%s}%s' % (ns, tag)
58

    
59
#: Deprecated
60
multiqualify = lambda tag, nslist=(BASE_NS, CISCO_BS): [qualify(tag, ns) for ns in nslist]
61

    
62
unqualify = lambda tag: tag[tag.rfind('}')+1:]
63

    
64
### XML with Python data structures
65

    
66
class DictTree:
67

    
68
    @staticmethod
69
    def Element(spec):
70
        """DictTree -> Element
71

72
        :type spec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
73

74
        :rtype: :class:`~xml.etree.ElementTree.Element`
75
        """
76
        if iselement(spec):
77
            return spec
78
        elif isinstance(spec, basestring):
79
            return XML.Element(spec)
80
        if not isinstance(spec, dict):
81
            raise ContentError("Invalid tree spec")
82
        if 'tag' in spec:
83
            ele = ET.Element(spec.get('tag'), spec.get('attrib', {}))
84
            ele.text = spec.get('text', '')
85
            ele.tail = spec.get('tail', '')
86
            subtree = spec.get('subtree', [])
87
            # might not be properly specified as list but may be dict
88
            if not isinstance(subtree, list):
89
                subtree = [subtree]
90
            for subele in subtree:
91
                ele.append(DictTree.Element(subele))
92
            return ele
93
        elif 'comment' in spec:
94
            return ET.Comment(spec.get('comment'))
95
        else:
96
            raise ContentError('Invalid tree spec')
97

    
98
    @staticmethod
99
    def XML(spec, encoding='UTF-8'):
100
        """DictTree -> XML
101

102
        :type spec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
103

104
        :arg encoding: chraracter encoding
105

106
        :rtype: string
107
        """
108
        return Element.XML(DictTree.Element(spec), encoding)
109

    
110
class Element:
111

    
112
    @staticmethod
113
    def DictTree(ele):
114
        """DictTree -> Element
115

116
        :type spec: :class:`~xml.etree.ElementTree.Element`
117
        :rtype: :obj:`dict`
118
        """
119
        return {
120
            'tag': ele.tag,
121
            'attributes': ele.attrib,
122
            'text': ele.text,
123
            'tail': ele.tail,
124
            'subtree': [ Element.DictTree(child) for child in ele.getchildren() ]
125
        }
126

    
127
    @staticmethod
128
    def XML(ele, encoding='UTF-8'):
129
        """Element -> XML
130

131
        :type spec: :class:`~xml.etree.ElementTree.Element`
132
        :arg encoding: character encoding
133
        :rtype: :obj:`string`
134
        """
135
        xml = ET.tostring(ele, encoding)
136
        if xml.startswith('<?xml'):
137
            return xml
138
        else:
139
            return '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml)
140

    
141
class XML:
142

    
143
    @staticmethod
144
    def DictTree(xml):
145
        """XML -> DictTree
146

147
        :type spec: :obj:`string`
148
        :rtype: :obj:`dict`
149
        """
150
        return Element.DictTree(XML.Element(xml))
151

    
152
    @staticmethod
153
    def Element(xml):
154
        """XML -> Element
155

156
        :type xml: :obj:`string`
157
        :rtype: :class:`~xml.etree.ElementTree.Element`
158
        """
159
        return ET.fromstring(xml)
160

    
161
dtree2ele = DictTree.Element
162
dtree2xml = DictTree.XML
163
ele2dtree = Element.DictTree
164
ele2xml = Element.XML
165
xml2dtree = XML.DictTree
166
xml2ele = XML.Element
167

    
168
### Other utility functions
169

    
170
iselement = ET.iselement
171

    
172
def find(ele, tag, nslist=[]):
173
    """If *nslist* is empty, same as :meth:`xml.etree.ElementTree.Element.find`.
174
    If it is not, *tag* is interpreted as an unqualified name and qualified
175
    using each item in *nslist* (with a :const:`None` item in *nslit* meaning no
176
    qualification is done). The first match is returned.
177

178
    :arg nslist: optional list of namespaces
179
    :type nslit: `string` `list`
180
    """
181
    if nslist:
182
        for qname in multiqualify(tag):
183
            found = ele.find(qname)
184
            if found is not None:
185
                return found
186
    else:
187
        return ele.find(tag)
188

    
189
def parse_root(raw):
190
    """Efficiently parses the root element of an XML document.
191

192
    :arg raw: XML document
193
    :type raw: string
194
    :returns: a tuple of `(tag, attributes)`, where `tag` is the (qualified) name of the element and `attributes` is a dictionary of its attributes.
195
    :rtype: `tuple`
196
    """
197
    fp = StringIO(raw[:1024]) # this is a guess but start element beyond 1024 bytes would be a bit absurd
198
    for event, element in ET.iterparse(fp, events=('start',)):
199
        return (element.tag, element.attrib)
200

    
201
def validated_element(rep, tags=None, attrs=None, text=None):
202
    """Checks if the root element meets the supplied criteria. Returns a
203
    :class:`~xml.etree.ElementTree.Element` instance if so, otherwise raises
204
    :exc:`ContentError`.
205

206
    :arg tags: tag name or a list of allowable tag names
207
    :arg attrs: list of required attribute names, each item may be a list of allowable alternatives
208
    :arg text: textual content to match
209
    :type rep: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
210
    """
211
    ele = dtree2ele(rep)
212
    err = False
213
    if tags:
214
        if isinstance(tags, basestring):
215
            tags = [tags]
216
        if ele.tag not in tags:
217
            err = True
218
    if attrs:
219
        for req in attrs:
220
            if isinstance(req, basestring): req = [req]
221
            for alt in req:
222
                if alt in ele.attrib:
223
                    break
224
            else:
225
                err = True
226
    if text and ele.text != text:
227
        err = True
228
    if err:
229
        raise ContentError("Element [%s] does not meet requirements" % ele.tag)
230
    return ele