Revision 9667bcb2 ncclient/xml_.py

b/ncclient/xml_.py
23 23

  
24 24
from ncclient import NCClientError
25 25

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

  
30 29
### Namespace-related
31 30

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

  
64 61
qualify = lambda tag, ns=BASE_NS_1_0: tag if ns is None else '{%s}%s' % (ns, tag)
65 62

  
66
multiqualify = lambda tag, nslist=(BASE_NS_1_0, CISCO_BS_1_0): [qualify(tag, ns) for ns in nslist]
67

  
68
unqualify = lambda tag: tag[tag.rfind('}')+1:]
69

  
70
### XML representations
71

  
72
class DictTree:
73

  
74
    @staticmethod
75
    def Element(spec):
76
        """DictTree -> Element
77

  
78
        :type spec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
79

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

  
104
    @staticmethod
105
    def XML(spec, encoding='UTF-8'):
106
        """DictTree -> XML
107

  
108
        :type spec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
109

  
110
        :arg encoding: chraracter encoding
111

  
112
        :rtype: string
113
        """
114
        return Element.XML(DictTree.Element(spec), encoding)
115

  
116
class Element:
117

  
118
    @staticmethod
119
    def DictTree(ele):
120
        """DictTree -> Element
121

  
122
        :type spec: :class:`~xml.etree.ElementTree.Element`
123
        :rtype: :obj:`dict`
124
        """
125
        return {
126
            'tag': ele.tag,
127
            'attributes': ele.attrib,
128
            'text': ele.text,
129
            'tail': ele.tail,
130
            'subtree': [ Element.DictTree(child) for child in ele.getchildren() ]
131
        }
132

  
133
    @staticmethod
134
    def XML(ele, encoding='UTF-8'):
135
        """Element -> XML
136

  
137
        :type spec: :class:`~xml.etree.ElementTree.Element`
138
        :arg encoding: character encoding
139
        :rtype: :obj:`string`
140
        """
141
        xml = ET.tostring(ele, encoding)
142
        if xml.startswith('<?xml'):
143
            return xml
144
        else:
145
            return '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml)
146

  
147
class XML:
148

  
149
    @staticmethod
150
    def DictTree(xml):
151
        """XML -> DictTree
152

  
153
        :type spec: :obj:`string`
154
        :rtype: :obj:`dict`
155
        """
156
        return Element.DictTree(XML.Element(xml))
157

  
158
    @staticmethod
159
    def Element(xml):
160
        """XML -> Element
161

  
162
        :type xml: :obj:`string`
163
        :rtype: :class:`~xml.etree.ElementTree.Element`
164
        """
165
        return ET.fromstring(xml)
166

  
167
dtree2ele = DictTree.Element
168
dtree2xml = DictTree.XML
169
ele2dtree = Element.DictTree
170
ele2xml = Element.XML
171
xml2dtree = XML.DictTree
172
xml2ele = XML.Element
173

  
174
### Other utility functions
63
#unqualify = lambda tag: tag[tag.rfind('}')+1:]
175 64

  
176
iselement = ET.iselement
177

  
178

  
179
NSLIST = [BASE_NS_1_0, CISCO_BS_1_0]
180

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

  
187
    :arg nslist: optional list of namespaces
188
    :type nslit: `string` `list`
65
def to_xml(ele, encoding="UTF-8"):
66
    """Element -> XML
67
    
68
    :type spec: :class:`~xml.etree.ElementTree.Element`
69
    :arg encoding: character encoding
70
    :rtype: :obj:`string`
189 71
    """
190
    if nslist:
191
        for qname in multiqualify(tag):
192
            found = ele.find(qname)
193
            if found is not None:
194
                return found
195
    else:
196
        return ele.find(tag)
72
    xml = ET.tostring(ele, encoding)
73
    return xml if xml.startswith('<?xml') else '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml)
74

  
75
def to_ele(x):
76
    """XML -> Element
77
    
78
    :type xml: :obj:`string`
79
    :rtype: :class:`~xml.etree.ElementTree.Element`
80
    """
81
    return x if iselement(x) else ET.fromstring(x)
82

  
83
iselement = ET.iselement
197 84

  
198 85
def parse_root(raw):
199 86
    """Efficiently parses the root element of an XML document.
......
203 90
    :returns: a tuple of `(tag, attributes)`, where `tag` is the (qualified) name of the element and `attributes` is a dictionary of its attributes.
204 91
    :rtype: `tuple`
205 92
    """
206
    fp = StringIO(raw[:1024]) # this is a guess but start element beyond 1024 bytes would be a bit absurd
93
    fp = StringIO(raw)
207 94
    for event, element in ET.iterparse(fp, events=('start',)):
208 95
        return (element.tag, element.attrib)
209 96

  
210
def validated_element(rep, tags=None, attrs=None, text=None):
97
def validated_element(x, tags=None, attrs=None):
211 98
    """Checks if the root element meets the supplied criteria. Returns a
212 99
    :class:`~xml.etree.ElementTree.Element` instance if so, otherwise raises
213 100
    :exc:`ContentError`.
214 101

  
215
    :arg tags: tag name or a list of allowable tag names
216
    :arg attrs: list of required attribute names, each item may be a list of allowable alternatives
102
    :arg tags: tag name or a sequence of allowable tag names
103
    :arg attrs: sequence of required attribute names, each item may be a list of allowable alternatives
217 104
    :arg text: textual content to match
218
    :type rep: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
105
    :type rep: :class:`~xml.etree.ElementTree.Element`
219 106
    """
220
    ele = dtree2ele(rep)
221
    err = False
107
    ele = to_ele(x)
222 108
    if tags:
223 109
        if isinstance(tags, basestring):
224 110
            tags = [tags]
225 111
        if ele.tag not in tags:
226
            err = True
112
            raise XMLError("Element [%s] does not meet requirement" % ele.tag)
227 113
    if attrs:
228 114
        for req in attrs:
229 115
            if isinstance(req, basestring): req = [req]
......
231 117
                if alt in ele.attrib:
232 118
                    break
233 119
            else:
234
                err = True
235
    if text and ele.text != text:
236
        err = True
237
    if err:
238
        raise ContentError("Element [%s] does not meet requirements" % ele.tag)
120
                raise XMLError("Element [%s] does not have required attributes" % ele.tag)
239 121
    return ele
122

  
123
def new_ele(tag, attrs={}, **extra):
124
    return ET.Element(tag, attrs, **extra)
125

  
126
def sub_ele(parent, tag, attrs={}, **extra):
127
    return ET.SubElement(parent, tag, attrs, **extra)

Also available in: Unified diff