Statistics
| Branch: | Tag: | Revision:

root / ncclient / content.py @ b58bb60b

History | View | Annotate | Download (7 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:`content` module provides methods for creating XML documents, parsing XML, and converting between different XML representations. It uses :mod:`~xml.etree.ElementTree` internally.
17
"""
18

    
19
from cStringIO import StringIO
20
from xml.etree import cElementTree as ET
21

    
22
from ncclient import NCClientError
23

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

    
28
### Namespace-related
29

    
30
#: Base NETCONf namespace
31
BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
32
#: ... and this is BASE_NS according to Cisco devices tested
33
CISCO_BS = 'urn:ietf:params:netconf:base:1.0'
34
#: namespace for Tail-f data model
35
TAILF_AAA_1_1 = 'http://tail-f.com/ns/aaa/1.1'
36
TAILF_EXECD_1_1 = 'http://tail-f.com/ns/execd/1.1'
37

    
38
try:
39
    register_namespace = ET.register_namespace
40
except AttributeError:
41
    def register_namespace(prefix, uri):
42
        from xml.etree import ElementTree
43
        # cElementTree uses ElementTree's _namespace_map, so that's ok
44
        ElementTree._namespace_map[uri] = prefix
45

    
46
# we'd like BASE_NS to be prefixed as "netconf"
47
register_namespace('netconf', BASE_NS)
48
register_namespace('aaa', TAILF_AAA_1_1)
49
register_namespace('execd', TAILF_EXECD_1_1)
50

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

    
53
#: Deprecated
54
multiqualify = lambda tag, nslist=(BASE_NS, CISCO_BS): [qualify(tag, ns) for ns in nslist]
55

    
56
unqualify = lambda tag: tag[tag.rfind('}')+1:]
57

    
58
### XML with Python data structures
59

    
60
class DictTree:
61

    
62
    @staticmethod
63
    def Element(spec):
64
        """DictTree -> Element
65

66
        :type spec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
67

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

    
92
    @staticmethod
93
    def XML(spec, encoding='UTF-8'):
94
        """DictTree -> XML
95

96
        :type spec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
97

98
        :arg encoding: chraracter encoding
99

100
        :rtype: string
101
        """
102
        return Element.XML(DictTree.Element(spec), encoding)
103

    
104
class Element:
105

    
106
    @staticmethod
107
    def DictTree(ele):
108
        """DictTree -> Element
109

110
        :type spec: :class:`~xml.etree.ElementTree.Element`
111
        :rtype: :obj:`dict`
112
        """
113
        return {
114
            'tag': ele.tag,
115
            'attributes': ele.attrib,
116
            'text': ele.text,
117
            'tail': ele.tail,
118
            'subtree': [ Element.DictTree(child) for child in ele.getchildren() ]
119
        }
120

    
121
    @staticmethod
122
    def XML(ele, encoding='UTF-8'):
123
        """Element -> XML
124

125
        :type spec: :class:`~xml.etree.ElementTree.Element`
126
        :arg encoding: character encoding
127
        :rtype: :obj:`string`
128
        """
129
        xml = ET.tostring(ele, encoding)
130
        if xml.startswith('<?xml'):
131
            return xml
132
        else:
133
            return '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml)
134

    
135
class XML:
136

    
137
    @staticmethod
138
    def DictTree(xml):
139
        """XML -> DictTree
140

141
        :type spec: :obj:`string`
142
        :rtype: :obj:`dict`
143
        """
144
        return Element.DictTree(XML.Element(xml))
145

    
146
    @staticmethod
147
    def Element(xml):
148
        """XML -> Element
149

150
        :type xml: :obj:`string`
151
        :rtype: :class:`~xml.etree.ElementTree.Element`
152
        """
153
        return ET.fromstring(xml)
154

    
155
dtree2ele = DictTree.Element
156
dtree2xml = DictTree.XML
157
ele2dtree = Element.DictTree
158
ele2xml = Element.XML
159
xml2dtree = XML.DictTree
160
xml2ele = XML.Element
161

    
162
### Other utility functions
163

    
164
iselement = ET.iselement
165

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

169
    :arg nslist: optional list of namespaces
170
    :type nslit: `string` `list`
171
    """
172
    if nslist:
173
        for qname in multiqualify(tag):
174
            found = ele.find(qname)
175
            if found is not None:
176
                return found
177
    else:
178
        return ele.find(tag)
179

    
180
def parse_root(raw):
181
    """Efficiently parses the root element of an XML document.
182

183
    :arg raw: XML document
184
    :type raw: string
185
    :returns: a tuple of `(tag, attributes)`, where `tag` is the (qualified) name of the element and `attributes` is a dictionary of its attributes.
186
    :rtype: `tuple`
187
    """
188
    fp = StringIO(raw[:1024]) # this is a guess but start element beyond 1024 bytes would be a bit absurd
189
    for event, element in ET.iterparse(fp, events=('start',)):
190
        return (element.tag, element.attrib)
191

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

195
    :arg tags: tag name or a list of allowable tag names
196
    :arg attrs: list of required attribute names, each item may be a list of allowable alternatives
197
    :arg text: textual content to match
198
    :type rep: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
199
    """
200
    ele = dtree2ele(rep)
201
    err = False
202
    if tags:
203
        if isinstance(tags, basestring):
204
            tags = [tags]
205
        if ele.tag not in tags:
206
            err = True
207
    if attrs:
208
        for req in attrs:
209
            if isinstance(req, basestring): req = [req]
210
            for alt in req:
211
                if alt in ele.attrib:
212
                    break
213
            else:
214
                err = True
215
    if text and ele.text != text:
216
        err = True
217
    if err:
218
        raise ContentError("Element [%s] does not meet requirements" % ele.tag)
219
    return ele