1f42f4ac0ebee210f2c6f70b071eb477243ea4aa
[ncclient] / ncclient / xml_.py
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, parsing, and dealing
17 with XML. It uses :mod:`~xml.etree.ElementTree`."""
18
19 from cStringIO import StringIO
20 from xml.etree import cElementTree as ET
21
22 from ncclient import NCClientError
23
24 class XMLError(NCClientError):
25     pass
26
27 ### Namespace-related
28
29 #: Base NETCONF namespace
30 BASE_NS_1_0 = 'urn:ietf:params:xml:ns:netconf:base:1.0'
31 #: Namespace for Tail-f core data model
32 TAILF_AAA_1_1 = 'http://tail-f.com/ns/aaa/1.1'
33 #: Namespace for Tail-f execd data model
34 TAILF_EXECD_1_1 = 'http://tail-f.com/ns/execd/1.1'
35 #: Namespace for Cisco data model
36 CISCO_CPI_1_0 = 'http://www.cisco.com/cpi_10/schema'
37 #: Namespace for Flowmon data model
38 FLOWMON_1_0 = 'http://www.liberouter.org/ns/netopeer/flowmon/1.0'
39
40 try:
41     register_namespace = ET.register_namespace
42 except AttributeError:
43     def register_namespace(prefix, uri):
44         from xml.etree import ElementTree
45         # cElementTree uses ElementTree's _namespace_map, so that's ok
46         ElementTree._namespace_map[uri] = prefix
47
48 prefix_map = {
49     BASE_NS_1_0: 'nc',
50     TAILF_AAA_1_1: 'aaa',
51     TAILF_EXECD_1_1: 'execd',
52     CISCO_CPI_1_0: 'cpi',
53     FLOWMON_1_0: 'fm',
54 }
55
56 for (ns, pre) in prefix_map.items():
57     register_namespace(pre, ns)
58
59 qualify = lambda tag, ns=BASE_NS_1_0: tag if ns is None else '{%s}%s' % (ns, tag)
60
61 #unqualify = lambda tag: tag[tag.rfind('}')+1:]
62
63 def to_xml(ele, encoding="UTF-8"):
64     """Convert an :class:`~xml.etree.ElementTree.Element` to XML.
65     
66     :arg ele: the :class:`~xml.etree.ElementTree.Element`
67     :arg encoding: character encoding
68     :rtype: :obj:`string`
69     """
70     xml = ET.tostring(ele, encoding)
71     return xml if xml.startswith('<?xml') else '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml)
72
73 def to_ele(x):
74     """Convert XML to :class:`~xml.etree.ElementTree.Element`.
75     
76     :type xml: :obj:`string`
77     :rtype: :class:`~xml.etree.ElementTree.Element`
78     """
79     return x if iselement(x) else ET.fromstring(x)
80
81 iselement = ET.iselement
82
83 def parse_root(raw):
84     """Efficiently parses the root element of an XML document.
85
86     :arg raw: XML document
87     :returns: a tuple of `(tag, attrib)`, where *tag* is the (qualified)
88     name of the element and *attrib* is a dictionary of its attributes.
89     :rtype: `tuple`
90     """
91     fp = StringIO(raw)
92     for event, element in ET.iterparse(fp, events=('start',)):
93         return (element.tag, element.attrib)
94
95 def validated_element(x, tags=None, attrs=None):
96     """Checks if the root element of an XML document or
97     :class:`~xml.etree.ElementTree.Element` meets the supplied criteria. Raises
98     :exc:`XMLError` if it is not met.
99     
100     :arg x: the XML document or :class:`~xml.etree.ElementTree.Element` to validate
101     :arg tags: tag name or a sequence of allowable tag names
102     :arg attrs: sequence of required attribute names, each item may be a list of allowable alternatives
103     :returns: validated element
104     :rtype: :class:`~xml.etree.ElementTree.Element`
105     """
106     ele = to_ele(x)
107     if tags:
108         if isinstance(tags, basestring):
109             tags = [tags]
110         if ele.tag not in tags:
111             raise XMLError("Element [%s] does not meet requirement" % ele.tag)
112     if attrs:
113         for req in attrs:
114             if isinstance(req, basestring): req = [req]
115             for alt in req:
116                 if alt in ele.attrib:
117                     break
118             else:
119                 raise XMLError("Element [%s] does not have required attributes" % ele.tag)
120     return ele
121
122 def new_ele(tag, attrs={}, **extra):
123     return ET.Element(tag, attrs, **extra)
124
125 def sub_ele(parent, tag, attrs={}, **extra):
126     return ET.SubElement(parent, tag, attrs, **extra)