Statistics
| Branch: | Tag: | Revision:

root / ncclient / content.py @ 4f650d54

History | View | Annotate | Download (6.4 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
from cStringIO import StringIO
16
from xml.etree import cElementTree as ET
17

    
18
from ncclient import NCClientError
19

    
20
class ContentError(NCClientError):
21
    "Raised by methods of the :mod:`content` module in case of an error."
22
    pass
23

    
24
### Namespace-related
25

    
26
# : Base NETCONf namespace
27
BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
28
# : ... and this is BASE_NS according to Cisco devices tested
29
CISCO_BS = 'urn:ietf:params:netconf:base:1.0'
30

    
31
try:
32
    register_namespace = ET.register_namespace
33
except AttributeError:
34
    def register_namespace(prefix, uri):
35
        from xml.etree import ElementTree
36
        # cElementTree uses ElementTree's _namespace_map, so that's ok
37
        ElementTree._namespace_map[uri] = prefix
38

    
39
# we'd like BASE_NS to be prefixed as "netconf"
40
register_namespace('netconf', BASE_NS)
41

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

    
44
# deprecated
45
multiqualify = lambda tag, nslist=(BASE_NS, CISCO_BS): [qualify(tag, ns) for ns in nslist]
46

    
47
unqualify = lambda tag: tag[tag.rfind('}')+1:]
48

    
49
### XML with Python data structures
50

    
51
class DictTree:
52

    
53
    @staticmethod
54
    def Element(spec):
55
        """DictTree -> Element
56

57
        :type spec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
58

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

    
83
    @staticmethod
84
    def XML(spec, encoding='UTF-8'):
85
        """DictTree -> XML
86

87
        :type spec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
88

89
        :arg encoding: chraracter encoding
90

91
        :rtype: string
92
        """
93
        return Element.XML(DictTree.Element(spec), encoding)
94

    
95
class Element:
96

    
97
    @staticmethod
98
    def DictTree(ele):
99
        """DictTree -> Element
100

101
        :type spec: :class:`~xml.etree.ElementTree.Element`
102
        :rtype: :obj:`dict`
103
        """
104
        return {
105
            'tag': ele.tag,
106
            'attributes': ele.attrib,
107
            'text': ele.text,
108
            'tail': ele.tail,
109
            'subtree': [ Element.DictTree(child) for child in root.getchildren() ]
110
        }
111

    
112
    @staticmethod
113
    def XML(ele, encoding='UTF-8'):
114
        """Element -> XML
115

116
        :type spec: :class:`~xml.etree.ElementTree.Element`
117
        :arg encoding: character encoding
118
        :rtype: :obj:`string`
119
        """
120
        xml = ET.tostring(ele, encoding)
121
        if xml.startswith('<?xml'):
122
            return xml
123
        else:
124
            return '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml)
125

    
126
class XML:
127

    
128
    @staticmethod
129
    def DictTree(xml):
130
        """XML -> DictTree
131

132
        :type spec: :obj:`string`
133
        :rtype: :obj:`dict`
134
        """
135
        return Element.DictTree(XML.Element(xml))
136

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

141
        :type xml: :obj:`string`
142
        :rtype: :class:`~xml.etree.ElementTree.Element`
143
        """
144
        return ET.fromstring(xml)
145

    
146
dtree2ele = DictTree.Element
147
dtree2xml = DictTree.XML
148
ele2dtree = Element.DictTree
149
ele2xml = Element.XML
150
xml2dtree = XML.DictTree
151
xml2ele = XML.Element
152

    
153
### Other utility functions
154

    
155
iselement = ET.iselement
156

    
157
def find(ele, tag, nslist=[]):
158
    """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`. The first match is returned.
159

160
    :arg nslist: optional list of namespaces
161
    """
162
    if nslist:
163
        for qname in multiqualify(tag):
164
            found = ele.find(qname)
165
            if found is not None:
166
                return found
167
    else:
168
        return ele.find(tag)
169

    
170
def parse_root(raw):
171
    """Efficiently parses the root element of an XML document.
172

173
    :type raw: string
174
    :returns: a tuple of `(tag, attributes)`, where `tag` is the (qualified) name of the element and `attributes` is a dictionary of its attributes.
175
    """
176
    fp = StringIO(raw[:1024]) # this is a guess but start element beyond 1024 bytes would be a bit absurd
177
    for event, element in ET.iterparse(fp, events=('start',)):
178
        return (element.tag, element.attrib)
179

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

183
    :arg tag: tag name or a list of allowable tag names
184
    :arg attrs: list of required attribute names, each item may be a list of allowable alternatives
185
    :arg text: textual content to match
186
    :type rep: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
187
    """
188
    ele = dtree2ele(rep)
189
    err = False
190
    if tag:
191
        if isinstance(tag, basestring): tag = [tag]
192
        if ele.tag not in tags:
193
            err = True
194
    if attrs:
195
        for req in attrs:
196
            if isinstance(req, basestring): req = [req]
197
            for alt in req:
198
                if alt in ele.attrib:
199
                    break
200
            else:
201
                err = True
202
    if text and ele.text != text:
203
        err = True
204
    if err:
205
        raise ContentError("Element [%s] does not meet requirements" % ele.tag)
206
    return ele