Statistics
| Branch: | Tag: | Revision:

root / ncclient / content.py @ 614a698d

History | View | Annotate | Download (6.5 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
        :rtype: :class:`~xml.etree.ElementTree.Element`
59
        """
60
        if iselement(spec):
61
            return spec
62
        elif isinstance(spec, basestring):
63
            return XML.Element(spec)
64
        if not isinstance(spec, dict):
65
            raise ContentError("Invalid tree spec")
66
        if 'tag' in spec:
67
            ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
68
            ele.text = spec.get('text', '')
69
            ele.tail = spec.get('tail', '')
70
            subtree = spec.get('subtree', [])
71
            # might not be properly specified as list but may be dict
72
            if isinstance(subtree, dict):
73
                subtree = [subtree]
74
            for subele in subtree:
75
                ele.append(DictTree.Element(subele))
76
            return ele
77
        elif 'comment' in spec:
78
            return ET.Comment(spec.get('comment'))
79
        else:
80
            raise ContentError('Invalid tree spec')
81
    
82
    @staticmethod
83
    def XML(spec, encoding='UTF-8'):
84
        """DictTree -> XML
85
        
86
        :type spec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
87
        :arg encoding: chraracter encoding
88
        :rtype: string
89
        """
90
        return Element.XML(DictTree.Element(spec), encoding)
91

    
92
class Element:
93
    
94
    @staticmethod
95
    def DictTree(ele):
96
        """DictTree -> Element
97
        
98
        :type spec: :class:`~xml.etree.ElementTree.Element`
99
        :rtype: :obj:`dict`
100
        """
101
        return {
102
            'tag': ele.tag,
103
            'attributes': ele.attrib,
104
            'text': ele.text,
105
            'tail': ele.tail,
106
            'subtree': [ Element.DictTree(child) for child in root.getchildren() ]
107
        }
108
    
109
    @staticmethod
110
    def XML(ele, encoding='UTF-8'):
111
        """Element -> XML
112
        
113
        :type spec: :class:`~xml.etree.ElementTree.Element`
114
        :arg encoding: character encoding
115
        :rtype: :obj:`string`
116
        """
117
        xml = ET.tostring(ele, encoding)
118
        if xml.startswith('<?xml'):
119
            return xml
120
        else:
121
            return '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml)
122

    
123
class XML:
124
    
125
    @staticmethod
126
    def DictTree(xml):
127
        """XML -> DictTree
128
        
129
        :type spec: :obj:`string`
130
        :rtype: :obj:`dict`
131
        """
132
        return Element.DictTree(XML.Element(xml))
133
    
134
    @staticmethod
135
    def Element(xml):
136
        """XML -> Element
137
        
138
        :type xml: :obj:`string`
139
        :rtype: :class:`~xml.etree.ElementTree.Element`
140
        """
141
        return ET.fromstring(xml)
142

    
143
dtree2ele = DictTree.Element
144
dtree2xml = DictTree.XML
145
ele2dtree = Element.DictTree
146
ele2xml = Element.XML
147
xml2dtree = XML.DictTree
148
xml2ele = XML.Element
149

    
150
### Other utility functions
151

    
152
iselement = ET.iselement
153

    
154
def find(ele, tag, nslist=[]):
155
    """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.
156
    
157
    :arg nslist: optional list of namespaces
158
    """
159
    if nslist:
160
        for qname in multiqualify(tag):
161
            found = ele.find(qname)
162
            if found is not None:
163
                return found
164
    else:
165
        return ele.find(tag)
166

    
167
def parse_root(raw):
168
    """Efficiently parses the root element of an XML document.
169
    
170
    :type raw: string
171
    :returns: a tuple of `(tag, attributes)`, where `tag` is the (qualified) name of the element and `attributes` is a dictionary of its attributes.
172
    """
173
    fp = StringIO(raw[:1024]) # this is a guess but start element beyond 1024 bytes would be a bit absurd
174
    for event, element in ET.iterparse(fp, events=('start',)):
175
        return (element.tag, element.attrib)
176

    
177
def validated_element(rep, tag=None, attrs=None, text=None):
178
    """Checks if the root element meets the supplied criteria. Returns a :class:`~xml.etree.ElementTree.Element` instance if so, otherwise raises :exc:`ContentError`.
179
    
180
    :arg tag: tag name or a list of allowable tag names
181
    :arg attrs: list of required attribute names, each item may be a list of allowable alternatives
182
    :arg text: textual content to match
183
    :type rep: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
184
    :see: :ref:`dtree`
185
    """
186
    ele = dtree2ele(rep)
187
    err = False
188
    if tag:
189
        if isinstance(tag, basestring): tag = [tag]
190
        if ele.tag not in tags:
191
            err = True
192
    if attrs:
193
        for req in attrs:
194
            if isinstance(req, basestring): req = [req]
195
            for alt in req:
196
                if alt in ele.attrib:
197
                    break
198
            else:
199
                err = True
200
    if text and ele.text != text:
201
        err = True
202
    if err:
203
        raise ContentError("Element [%s] does not meet requirements" % ele.tag)
204
    return ele