Revision a14c36f9 ncclient/content.py

b/ncclient/content.py
18 18

  
19 19
from ncclient import NCClientError
20 20

  
21
class ContentError(NCClientError): pass
21
class ContentError(NCClientError):
22
    pass
22 23

  
23 24
### Namespace-related ###
24 25

  
......
45 46

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

  
48
def namespaced_find(ele, tag, workaround=True):
49
    """`workaround` is for Cisco implementations (at least the one tested), 
50
    which uses an incorrect namespace.
49
### XML with Python data structures
50

  
51
def to_element(spec):
52
    "TODO: docstring"
53
    if iselement(spec):
54
        return spec
55
    elif isinstance(spec, basestring):
56
        return ET.XML(spec)
57
    if not isinstance(spec, dict):
58
        raise ContentError("Invalid tree spec")
59
    if 'tag' in spec:
60
        ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
61
        ele.text = spec.get('text', '')
62
        ele.tail = spec.get('tail', '')
63
        subtree = spec.get('subtree', [])
64
        # might not be properly specified as list but may be dict
65
        if isinstance(subtree, dict):
66
            subtree = [subtree]
67
        for subele in subtree:
68
            ele.append(XMLConverter.build(subele))
69
        return ele
70
    elif 'comment' in spec:
71
        return ET.Comment(spec.get('comment'))
72
    else:
73
        raise ContentError('Invalid tree spec')
74

  
75
def from_xml(xml):
76
    return ET.fromstring(xml)
77

  
78
def to_xml(repr, encoding='utf-8'):
79
    "TODO: docstring"
80
    xml = ET.tostring(to_element(repr), encoding)
81
    # some etree versions don't include xml decl with utf-8
82
    # this is a problem with some devices
83
    return (xml if xml.startswith('<?xml')
84
            else '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml))
85

  
86

  
87
## Utility functions
88

  
89
iselement = ET.iselement
90

  
91
def namespaced_find(ele, tag, strict=False):
92
    """In strict mode, doesn't work around Cisco implementations sending incorrectly
93
    namespaced XML. Supply qualified name if using strict mode.
51 94
    """
52 95
    found = None
53
    if not workaround:
96
    if strict:
54 97
        found = ele.find(tag)
55 98
    else:
56 99
        for qname in multiqualify(tag):
......
59 102
                break
60 103
    return found
61 104

  
62

  
63
### Build XML using Python data structures ###
64

  
65
class XMLConverter:
66
    """Build an ElementTree.Element instance from an XML tree specification
67
    based on nested dictionaries. TODO: describe spec
68
    """
69
    
70
    def __init__(self, spec):
71
        "TODO: docstring"
72
        self._root = XMLConverter.build(spec)
73
    
74
    def tostring(self, encoding='utf-8'):
75
        "TODO: docstring"
76
        xml = ET.tostring(self._root, encoding)
77
        # some etree versions don't include xml decl with utf-8
78
        # this is a problem with some devices
79
        return (xml if xml.startswith('<?xml')
80
                else '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml))
81
    
82
    @property
83
    def tree(self):
84
        "TODO: docstring"
85
        return self._root
86
    
87
    @staticmethod
88
    def build(spec):
89
        "TODO: docstring"
90
        if iselement(spec):
91
            return spec
92
        elif isinstance(spec, basestring):
93
            return ET.XML(spec)
94
        # assume isinstance(spec, dict)
95
        if 'tag' in spec:
96
            ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
97
            ele.text = spec.get('text', '')
98
            ele.tail = spec.get('tail', '')
99
            subtree = spec.get('subtree', [])
100
            # might not be properly specified as list but may be dict
101
            if isinstance(subtree, dict):
102
                subtree = [subtree]
103
            for subele in subtree:
104
                ele.append(XMLConverter.build(subele))
105
            return ele
106
        elif 'comment' in spec:
107
            return ET.Comment(spec.get('comment'))
108
        # TODO elif DOM rep
109
        else:
110
            raise ContentError('Invalid tree spec')
105
def parse_root(raw):
106
    '''Internal use.
107
    Parse the top-level element from XML string.
111 108
    
112
    @staticmethod
113
    def fromstring(xml):
114
        return XMLConverter.parse(ET.fromstring(xml))
115
    
116
    @staticmethod
117
    def parse(root):
118
        return {
119
            'tag': root.tag,
120
            'attributes': root.attrib,
121
            'text': root.text,
122
            'tail': root.tail,
123
            'subtree': [ XMLConverter.parse(child) for child in root.getchildren() ]
124
        }
125

  
126
## utility functions
127

  
128
iselement = ET.iselement
129

  
130
# def isdom(x): return True # TODO
131

  
132
def ensured(rep, req_tag, req_attrs=None):
133
    if isinstance(rep, basestring):
134
        rep = ET.XML(rep)
135
    if iselement(rep) and (rep.tag not in (req_tag, qualify(req_tag): 
109
    Returns a `(tag, attributes)` tuple, where `tag` is a string representing
110
    the qualified name of the root element and `attributes` is an
111
    `{attribute: value}` dictionary.
112
    '''
113
    fp = StringIO(raw[:1024]) # this is a guess but start element beyond 1024 bytes would be a bit absurd
114
    for event, element in ET.iterparse(fp, events=('start',)):
115
        return (element.tag, element.attrib)
116

  
117
def root_ensured(rep, req_tag, req_attrs=None):
118
    rep = to_element(rep)
119
    if rep.tag not in (req_tag, qualify(req_tag)):
136 120
        raise ContentError("Required root element [%s] not found" % req_tag)
137 121
    if req_attrs is not None:
138 122
        pass # TODO

Also available in: Unified diff