From 04ed4ac050de268d1469c379f6bf60a46bfc333f Mon Sep 17 00:00:00 2001 From: Tony Mack Date: Wed, 16 Nov 2011 21:56:32 -0500 Subject: [PATCH] replaced Element.get_elements() with XmlNode.get_instance(). replaced Element.add_elements() with XmlNodde.add_instance() --- sfa/rspecs/elements/element.py | 50 +------ sfa/rspecs/elements/versions/pgv2Link.py | 9 +- sfa/rspecs/elements/versions/pgv2Node.py | 45 +++--- sfa/rspecs/elements/versions/pgv2Services.py | 28 +++- .../elements/versions/pgv2SliverType.py | 2 +- sfa/rspecs/elements/versions/sfav1Node.py | 67 +++++---- sfa/rspecs/elements/versions/sfav1Sliver.py | 2 +- sfa/rspecs/versions/pgv2.py | 20 ++- sfa/rspecs/versions/sfav1.py | 8 +- sfa/util/xml.py | 132 +++++++++++------- 10 files changed, 190 insertions(+), 173 deletions(-) diff --git a/sfa/rspecs/elements/element.py b/sfa/rspecs/elements/element.py index 5789a9cc..a2febfc0 100644 --- a/sfa/rspecs/elements/element.py +++ b/sfa/rspecs/elements/element.py @@ -11,47 +11,9 @@ class Element(dict): if key in fields: self[key] = fields[key] - @staticmethod - def get_elements(xml, xpath, element_class=None, fields=None): - """ - Search the specifed xml node for elements that match the - specified xpath query. - Returns a list of objects instanced by the specified element_class. - """ - if not element_class: - element_class = Element - if not fields and hasattr(element_class, 'fields'): - fields = element_class.fields - elems = xml.xpath(xpath) - objs = [] - for elem in elems: - if not fields: - obj = element_class(elem.attrib, elem) - else: - obj = element_class({}, elem) - for field in fields: - if field in elem.attrib: - obj[field] = elem.attrib[field] - objs.append(obj) - return objs - - @staticmethod - def add_elements(xml, name, objs, fields=None): - """ - Adds a child node to the specified xml node based on - the specified name , element class and object. - """ - if not isinstance(objs, list): - objs = [objs] - elems = [] - for obj in objs: - if not obj: - continue - if not fields: - fields = obj.keys() - elem = xml.add_element(name) - for field in fields: - if field in obj and obj[field]: - elem.set(field, unicode(obj[field])) - elems.append(elem) - return elems + def __getattr__(self, attr): + if hasattr(self, attr): + return getattr(self, attr) + elif self.element is not None and hasattr(self.element, attr): + return getattr(self.element, attr) + raise AttributeError, "Element class has no attribute %s" % attr diff --git a/sfa/rspecs/elements/versions/pgv2Link.py b/sfa/rspecs/elements/versions/pgv2Link.py index e496d166..65513d0f 100644 --- a/sfa/rspecs/elements/versions/pgv2Link.py +++ b/sfa/rspecs/elements/versions/pgv2Link.py @@ -9,14 +9,14 @@ class PGv2Link: @staticmethod def add_links(xml, links): for link in links: - link_elems = Element.add(xml, 'link', link, ['component_name', 'component_id', 'client_id']) - link_elem = link_elems[0] + + link_elem = xml.add_instance('link', link, ['component_name', 'component_id', 'client_id']) # set component manager element if 'component_manager' in link and link['component_manager']: cm_element = link_elem.add_element('component_manager', name=link['component_manager']) # set interface_ref elements for if_ref in [link['interface1'], link['interface2']]: - Element.add(link_elem, 'interface_ref', if_ref, Interface.fields) + link_elem.add_instance('interface_ref', if_ref, Interface.fields) # set property elements prop1 = link_elem.add_element('property', source_id = link['interface1']['component_id'], dest_id = link['interface2']['component_id'], capacity=link['capacity'], @@ -55,7 +55,8 @@ class PGv2Link: link[attrib] = prop[attrib] # get interfaces - interfaces = Element.get(Interface, link_elem, './default:interface_ref | ./interface_ref') + iface_elems = link_elem.xpath('./default:interface_ref | ./interface_ref') + interfaces = [iface_elem.get_instance(Interface) for iface_elem in iface_elems] if len(interfaces) > 1: link['interface1'] = interfaces[0] link['interface2'] = interfaces[1] diff --git a/sfa/rspecs/elements/versions/pgv2Node.py b/sfa/rspecs/elements/versions/pgv2Node.py index 4836cab6..eb0518fa 100644 --- a/sfa/rspecs/elements/versions/pgv2Node.py +++ b/sfa/rspecs/elements/versions/pgv2Node.py @@ -1,7 +1,6 @@ from sfa.util.plxrn import PlXrn, xrn_to_hostname from sfa.util.xrn import Xrn from sfa.util.xml import XpathFilter -from sfa.rspecs.elements.element import Element from sfa.rspecs.elements.node import Node from sfa.rspecs.elements.sliver import Sliver from sfa.rspecs.elements.location import Location @@ -19,19 +18,21 @@ class PGv2Node: node_elems = [] for node in nodes: node_fields = ['component_manager_id', 'component_id', 'client_id', 'sliver_id', 'exclusive'] - elems = Element.add_elements(xml, 'node', node, node_fields) - node_elem = elems[0] + node_elem = xml.add_instance('node', node, node_fields) node_elems.append(node_elem) # set component name if node.get('component_id'): component_name = xrn_to_hostname(node['component_id']) node_elem.set('component_name', component_name) - # set hardware types - Element.add_elements(node_elem, 'hardware_type', node.get('hardware_types', []), HardwareType.fields) - # set location - location_elems = Element.add_elements(node_elem, 'location', node.get('location', []), Location.fields) + # set hardware types + for hardware_type in node.get('hardware_types', []): + node_elem.add_instance('hardware_type', hardware_type, HardwareType.fields) + # set location + if node.get('location'): + node_elem.add_instance('location', node['location'], Location.fields) # set interfaces - interface_elems = Element.add_elements(node_elem, 'interface', node.get('interfaces', []), ['component_id', 'client_id', 'ipv4']) + for interface in node.get('interfaces', []): + node_elem.add_instance('interface', interface, ['component_id', 'client_id', 'ipv4']) # set available element if node.get('boot_state', '').lower() == 'boot': available_elem = node_elem.add_element('available', now='True') @@ -72,17 +73,29 @@ class PGv2Node: nodes.append(node) if 'component_id' in node_elem.attrib: node['authority_id'] = Xrn(node_elem.attrib['component_id']).get_authority_urn() + + # get hardware types + hardware_type_elems = node_elem.xpath('./default:hardwate_type | ./hardware_type') + node['hardware_types'] = [hw_type.get_instnace(HardwareType) for hw_type in hardware_type_elems] + + # get location + location_elems = node_elem.xpath('./default:location | ./location') + locations = [location_elem.get_instance(Location) for location_elem in location_elems] + if len(locations) > 0: + node['location'] = locations[0] - node['hardware_types'] = Element.get_elements(node_elem, './default:hardwate_type | ./hardware_type', HardwareType) - location_elems = Element.get_elements(node_elem, './default:location | ./location', Location) - if len(location_elems) > 0: - node['location'] = location_elems[0] - node['interfaces'] = Element.get_elements(node_elem, './default:interface | ./interface', Interface) + # get interfaces + iface_elems = node_elem.xpath('./default:interface | ./interface') + node['interfaces'] = [iface_elem.get_instance(Interface) for iface_elem in iface_elems] + + # get services node['services'] = PGv2Services.get_services(node_elem) + + # get slivers node['slivers'] = PGv2SliverType.get_slivers(node_elem) - available_elem = Element.get_elements(node_elem, './default:available | ./available', fields=['now']) - if len(available_elem) > 0 and 'name' in available_elem[0]: - if available_elem[0].get('now', '').lower() == 'true': + available_elems = node_elem.xpath('./default:available | ./available') + if len(available_elems) > 0 and 'name' in available_elems[0].attrib: + if available_elems[0].attrib.get('now', '').lower() == 'true': node['boot_state'] = 'boot' else: node['boot_state'] = 'disabled' diff --git a/sfa/rspecs/elements/versions/pgv2Services.py b/sfa/rspecs/elements/versions/pgv2Services.py index 9eb1ed9a..9fadc56c 100644 --- a/sfa/rspecs/elements/versions/pgv2Services.py +++ b/sfa/rspecs/elements/versions/pgv2Services.py @@ -8,21 +8,35 @@ class PGv2Services: def add_services(xml, services): if not services: return - for service in services: service_elem = xml.add_element('services') - Element.add_elements(service_elem, 'install', service.get('install', []), Install.fields) - Element.add_elements(service_elem, 'execute', service.get('execute', []), Execute.fields) - Element.add_elements(service_elem, 'login', service.get('login', []), Login.fields) + child_elements = {'install': Install.fields, + 'execute': Execute.fields, + 'login': Login.fields} + for (name, fields) in child_elements.items(): + objects = service.get(name) + if not objects: + continue + if isinstance(objects, basestring): + service_elem.add_instance(name, objects, fields) + elif isinstance(objects, list): + for obj in objects: + service_elem.add_instance(name, obj, fields) @staticmethod def get_services(xml): services = [] for services_elem in xml.xpath('./default:services | ./services'): service = Services(services_elem.attrib, services_elem) - service['install'] = Element.get_elements(service_elem, './default:install | ./install', Install) - service['execute'] = Element.get_elements(service_elem, './default:execute | ./execute', Execute) - service['login'] = Element.get_elements(service_elem, './default:login | ./login', Login) + # get install + install_elems = xml.xpath('./default:install | ./install') + service['install'] = [install_elem.get_instance(Install) for install_elem in install_elems] + # get execute + execute_elems = xml.xpath('./default:execute | ./execute') + service['execute'] = [execute_elem.get_instance(Execute) for execute_elem in execute_elems] + # get login + login_elems = xml.xpath('./default:login | ./login') + service['login'] = [login_elem.get_instance(Login) for login_elem in login_elems] services.append(service) return services diff --git a/sfa/rspecs/elements/versions/pgv2SliverType.py b/sfa/rspecs/elements/versions/pgv2SliverType.py index c0715321..4271d211 100644 --- a/sfa/rspecs/elements/versions/pgv2SliverType.py +++ b/sfa/rspecs/elements/versions/pgv2SliverType.py @@ -10,7 +10,7 @@ class PGv2SliverType: if not isinstance(slivers, list): slivers = [slivers] for sliver in slivers: - sliver_elem = Element.add_elements(xml, 'sliver_type', sliver, ['type', 'client_id']) + sliver_elem = xml.add_instance('sliver_type', sliver, ['type', 'client_id']) PGv2SliverType.add_sliver_attributes(sliver_elem, sliver.get('pl_tags', [])) @staticmethod diff --git a/sfa/rspecs/elements/versions/sfav1Node.py b/sfa/rspecs/elements/versions/sfav1Node.py index 9933eceb..61f80564 100644 --- a/sfa/rspecs/elements/versions/sfav1Node.py +++ b/sfa/rspecs/elements/versions/sfav1Node.py @@ -19,19 +19,19 @@ class SFAv1Node: @staticmethod def add_nodes(xml, nodes): - network_elems = Element.get_elements(xml, '//network', fields=['name']) + network_elems = xml.xpath('//network') if len(network_elems) > 0: network_elem = network_elems[0] elif len(nodes) > 0 and nodes[0].get('component_manager_id'): - network_urn = nodes[0]['component_manager_id'] - network_elems = Element.add_elements(xml, 'network', {'name': Xrn(network_urn).get_hrn()}) - network_elem = network_elems[0] + network_urn = nodes[0]['component_manager_id'] + network_elem = xml.add_element('network', name = Xrn(network_urn).get_hrn()[0]) + else: + network_elem = xml node_elems = [] for node in nodes: node_fields = ['component_manager_id', 'component_id', 'boot_state'] - elems = Element.add_elements(network_elem, 'node', node, node_fields) - node_elem = elems[0] + node_elem = network_elem.add_instance('node', node, node_fields) node_elems.append(node_elem) # determine network hrn @@ -43,17 +43,20 @@ class SFAv1Node: if 'component_id' in node and node['component_id']: component_name = xrn_to_hostname(node['component_id']) node_elem.set('component_name', component_name) - hostname_tag = node_elem.add_element('hostname') - hostname_tag.set_text(component_name) + hostname_elem = node_elem.add_element('hostname') + hostname_elem.set_text(component_name) # set site id if 'authority_id' in node and node['authority_id']: node_elem.set('site_id', node['authority_id']) - location_elems = Element.add_elements(node_elem, 'location', - node.get('location', []), Location.fields) - interface_elems = Element.add_elements(node_elem, 'interface', - node.get('interfaces', []), ['component_id', 'client_id', 'ipv4']) + # add locaiton + location = node.get('location') + if location: + node_elem.add_instance('location', location, Location.fields) + + for interface in node.get('interfaces', []): + node_elem.add_instance('interface', interface, ['component_id', 'client_id', 'ipv4']) #if 'bw_unallocated' in node and node['bw_unallocated']: # bw_unallocated = etree.SubElement(node_elem, 'bw_unallocated', units='kbps').text = str(int(node['bw_unallocated'])/1000) @@ -95,21 +98,6 @@ class SFAv1Node: node_elems = xml.xpath(xpath) return SFAv1Node.get_node_objs(node_elems) - # xxx Thierry : an ugly hack to get the tests to pass again - # probably this needs to be trashed - # the original code returned the tag, - # but we prefer the father node instead as it already holds data - # initially this was to preserve the nodename... - # xxx I don't get the ' | //default:node/default:sliver' ... - @staticmethod - def get_nodes_with_slivers_thierry(xml): - # dropping the '' - xpath = '//node[count (sliver)>0]' - node_elems = xml.xpath(xpath) - # we need to check/recompute the node data - - return node_elems - @staticmethod def get_nodes_with_slivers(xml): xpath = '//node[count(sliver)>0] | //default:node[count(default:sliver)>0]' @@ -124,16 +112,25 @@ class SFAv1Node: node = Node(node_elem.attrib, node_elem) if 'site_id' in node_elem.attrib: node['authority_id'] = node_elem.attrib['site_id'] - location_objs = Element.get_elements(node_elem, './default:location | ./location', Location) - if len(location_objs) > 0: - node['location'] = location_objs[0] - bwlimit_objs = Element.get_elements(node_elem, './default:bw_limit | ./bw_limit', BWlimit) - if len(bwlimit_objs) > 0: - node['bwlimit'] = bwlimit_objs[0] - node['interfaces'] = Element.get_elements(node_elem, './default:interface | ./interface', Interface) + # get location + location_elems = node_elem.xpath('./default:location | ./location') + locations = [loc_elem.get_instance(Location) for loc_elem in location_elems] + if len(locations) > 0: + node['location'] = locations[0] + # get bwlimit + bwlimit_elems = node_elem.xpath('./default:bw_limit | ./bw_limit') + bwlimits = [bwlimit_elem.get_instance(BWlimit) for bwlimit_elem in bwlimit_elems] + if len(bwlimits) > 0: + node['bwlimit'] = bwlimits[0] + # get interfaces + iface_elems = node_elem.xpath('./default:interface | ./interface') + ifaces = [iface_elem.get_instance(Interface) for iface_elem in iface_elems] + node['interfaces'] = ifaces + # get services node['services'] = PGv2Services.get_services(node_elem) + # get slivers node['slivers'] = SFAv1Sliver.get_slivers(node_elem) -#thierry node['tags'] = SFAv1PLTag.get_pl_tags(node_elem, ignore=Node.fields.keys()) + # get tags node['tags'] = SFAv1PLTag.get_pl_tags(node_elem, ignore=Node.fields) nodes.append(node) return nodes diff --git a/sfa/rspecs/elements/versions/sfav1Sliver.py b/sfa/rspecs/elements/versions/sfav1Sliver.py index d0a7592b..e8fc0b7b 100644 --- a/sfa/rspecs/elements/versions/sfav1Sliver.py +++ b/sfa/rspecs/elements/versions/sfav1Sliver.py @@ -12,7 +12,7 @@ class SFAv1Sliver: if not isinstance(slivers, list): slivers = [slivers] for sliver in slivers: - sliver_elem = Element.add_elements(xml, 'sliver', sliver, ['name'])[0] + sliver_elem = xml.add_instance('sliver', sliver, ['name']) SFAv1Sliver.add_sliver_attributes(sliver_elem, sliver.get('tags', [])) if sliver.get('sliver_id'): sliver_id_leaf = Xrn(sliver.get('sliver_id')).get_leaf() diff --git a/sfa/rspecs/versions/pgv2.py b/sfa/rspecs/versions/pgv2.py index ceff971b..60387dcc 100644 --- a/sfa/rspecs/versions/pgv2.py +++ b/sfa/rspecs/versions/pgv2.py @@ -1,6 +1,6 @@ from copy import deepcopy from StringIO import StringIO -from sfa.util.xrn import urn_to_sliver_id +from sfa.util.xrn import Xrn, urn_to_sliver_id from sfa.util.plxrn import hostname_to_urn, xrn_to_hostname from sfa.rspecs.baseversion import BaseVersion from sfa.rspecs.elements.versions.pgv2Link import PGv2Link @@ -20,18 +20,16 @@ class PGv2(BaseVersion): namespaces = dict(extensions.items() + [('default', namespace)]) # Networks - def get_network(self): - network = None - nodes = self.xml.xpath('//default:node[@component_manager_id][1]', namespaces=self.namespaces) - if nodes: - network = nodes[0].get('component_manager_id') - return network - def get_networks(self): - networks = self.xml.xpath('//default:node[@component_manager_id]/@component_manager_id', namespaces=self.namespaces) - return set(networks) + networks = set() + nodes = self.xml.xpath('//default:node[@component_manager_id]', namespaces=self.namespaces) + for node in nodes: + if 'component_manager_id' in node: + network_urn = node.get('component_manager_id') + network_hrn = Xrn(network_urn).get_hrn()[0] + networks.add({'name': network_hrn}) + return list(networks) - # Nodes def get_nodes(self, filter=None): diff --git a/sfa/rspecs/versions/sfav1.py b/sfa/rspecs/versions/sfav1.py index 85aa86e6..39a53483 100644 --- a/sfa/rspecs/versions/sfav1.py +++ b/sfa/rspecs/versions/sfav1.py @@ -23,12 +23,16 @@ class SFAv1(BaseVersion): # Network def get_networks(self): - return Element.get_elements(self.xml, '//network', Element) + network_elems = self.xml.xpath('//network') + networks = [network_elem.get_instance(fields=['name', 'slice']) for \ + network_elem in network_elems] + return networks + def add_network(self, network): network_tags = self.xml.xpath('//network[@name="%s"]' % network) if not network_tags: - network_tag = etree.SubElement(self.xml.root, 'network', name=network) + network_tag = self.xml.add_element('network', name=network) else: network_tag = network_tags[0] return network_tag diff --git a/sfa/util/xml.py b/sfa/util/xml.py index bb298a3f..d6fd1963 100755 --- a/sfa/util/xml.py +++ b/sfa/util/xml.py @@ -3,6 +3,7 @@ from types import StringTypes from lxml import etree from StringIO import StringIO from sfa.util.faults import InvalidXML +from sfa.rspecs.elements.element import Element class XpathFilter: @staticmethod @@ -37,32 +38,64 @@ class XpathFilter: xpath = '[' + xpath + ']' return xpath -class XmlNode: - def __init__(self, node, namespaces): - self.node = node - self.text = node.text +class XmlElement: + def __init__(self, element, namespaces): + self.element = element + self.text = element.text self.namespaces = namespaces - self.attrib = node.attrib + self.attrib = element.attrib def xpath(self, xpath, namespaces=None): if not namespaces: namespaces = self.namespaces - elems = self.node.xpath(xpath, namespaces=namespaces) - return [XmlNode(elem, namespaces) for elem in elems] + elems = self.element.xpath(xpath, namespaces=namespaces) + return [XmlElement(elem, namespaces) for elem in elems] def add_element(self, tagname, **kwds): - element = etree.SubElement(self.node, tagname, **kwds) - return XmlNode(element, self.namespaces) + element = etree.SubElement(self.element, tagname, **kwds) + return XmlElement(element, self.namespaces) def append(self, elem): - if isinstance(elem, XmlNode): - self.node.append(elem.node) + if isinstance(elem, XmlElement): + self.element.append(elem.element) else: - self.node.append(elem) + self.element.append(elem) def getparent(self): - return XmlNode(self.node.getparent(), self.namespaces) + return XmlElement(self.element.getparent(), self.namespaces) + + def get_instance(self, instance_class=None, fields=[]): + """ + Returns an instance (dict) of this xml element. The instance + holds a reference this xml element. + """ + if not instance_class: + instance_class = Element + if not fields and hasattr(instance_class, 'fields'): + fields = instance_class.fields + + if not fields: + instance = instance_class(self.attrib, self) + else: + instance = instance_class({}, self) + for field in fields: + if field in self.attrib: + instance[field] = self.attrib[field] + return instance + + def add_instance(self, name, instance, fields=[]): + """ + Adds the specifed instance(s) as a child element of this xml + element. + """ + if not fields and hasattr(instance, 'keys'): + fields = instance.keys() + elem = self.add_element(name) + for field in fields: + if field in instance and instance[field]: + elem.set(field, unicode(instance[field])) + return elem def remove_elements(self, name): """ @@ -72,36 +105,36 @@ class XmlNode: if not element_name.startswith('//'): element_name = '//' + element_name - elements = self.node.xpath('%s ' % name, namespaces=self.namespaces) + elements = self.element.xpath('%s ' % name, namespaces=self.namespaces) for element in elements: parent = element.getparent() parent.remove(element) def remove(self, element): - if isinstance(element, XmlNode): - self.node.remove(element.node) + if isinstance(element, XmlElement): + self.element.remove(element.element) else: - self.node.remove(element) + self.element.remove(element) def get(self, key, *args): - return self.node.get(key, *args) + return self.element.get(key, *args) - def items(self): return self.node.items() + def items(self): return self.element.items() def set(self, key, value): - self.node.set(key, value) + self.element.set(key, value) def set_text(self, text): - self.node.text = text + self.element.text = text def unset(self, key): - del self.node.attrib[key] + del self.element.attrib[key] def iterchildren(self): - return self.node.iterchildren() + return self.element.iterchildren() def toxml(self): - return etree.tostring(self.node, encoding='UTF-8', pretty_print=True) + return etree.tostring(self.element, encoding='UTF-8', pretty_print=True) def __str__(self): return self.toxml() @@ -115,7 +148,7 @@ class XML: self.schema = None if isinstance(xml, basestring): self.parse_xml(xml) - if isinstance(xml, XmlNode): + if isinstance(xml, XmlElement): self.root = xml self.namespaces = xml.namespaces elif isinstance(xml, etree._ElementTree) or isinstance(xml, etree._Element): @@ -147,7 +180,7 @@ class XML: else: self.namespaces['default'] = 'default' - self.root = XmlNode(root, self.namespaces) + self.root = XmlElement(root, self.namespaces) # set schema for key in self.root.attrib.keys(): if key.endswith('schemaLocation'): @@ -210,48 +243,43 @@ class XML: namespaces = self.namespaces return self.root.xpath(xpath, namespaces=namespaces) - def set(self, key, value, node=None): - if not node: - node = self.root - return node.set(key, value) + def set(self, key, value, element=None): + if not element: + element = self.root + return element.set(key, value) - def remove_attribute(self, name, node=None): - if not node: - node = self.root - node.remove_attribute(name) + def remove_attribute(self, name, element=None): + if not element: + element = self.root + element.remove_attribute(name) - def add_element(self, name, **kwds): + def add_element(self, *args, **kwds): """ Wrapper around etree.SubElement(). Adds an element to specified parent node. Adds element to root node is parent is not specified. """ - parent = self.root - xmlnode = parent.add_element(name, *kwds) - return xmlnode + return self.root.add_element(*args, **kwds) - def remove_elements(self, name, node = None): + def remove_elements(self, name, element = None): """ Removes all occurences of an element from the tree. Start at specified root_node if specified, otherwise start at tree's root. """ - if not node: - node = self.root + if not element: + element = self.root + + element.remove_elements(name) - node.remove_elements(name) + def add_instance(self, *args, **kwds): + return self.root.add_instance(*args, **kwds) - def attributes_list(self, elem): - # convert a list of attribute tags into list of tuples - # (tagnme, text_value) - opts = [] - if elem is not None: - for e in elem: - opts.append((e.tag, str(e.text).strip())) - return opts + def get_instance(self, *args, **kwds): + return self.root.get_instnace(*args, **kwds) def get_element_attributes(self, elem=None, depth=0): if elem == None: - elem = self.root_node + elem = self.root if not hasattr(elem, 'attrib'): # this is probably not an element node with attribute. could be just and an # attribute, return it @@ -283,7 +311,7 @@ class XML: return self.toxml() def toxml(self): - return etree.tostring(self.root.node, encoding='UTF-8', pretty_print=True) + return etree.tostring(self.root.element, encoding='UTF-8', pretty_print=True) # XXX smbaker, for record.load_from_string def todict(self, elem=None): -- 2.43.0