Add new function to replace the xml2obj
authorZhuoX Li <zhuox.li@intel.com>
Mon, 1 Dec 2014 09:01:20 +0000 (17:01 +0800)
committerZhuoX Li <zhuox.li@intel.com>
Wed, 3 Dec 2014 02:38:22 +0000 (10:38 +0800)
Change-Id: Id2d208fd2cfa9ab64e19827775bfdb852c650504

common/manifest.py
common/mapping.py
common/utils.py

index f6de044..9922805 100644 (file)
@@ -24,7 +24,7 @@
 import os
 import gzip
 
-from common.utils import xml2obj
+from common.utils import xml_to_obj
 
 HEADER = """<?xml version="1.0" encoding="UTF-8"?>
 <manifest>
@@ -54,7 +54,7 @@ def get_package_vcs_tag(primary_md):
     ret_data = []
     xml_file = gzip.open(primary_md)
 
-    primary = xml2obj(xml_file)
+    primary = xml_to_obj(xml_file)
 
     for package in primary.package:
         vcs_tag = package.version.vcs
@@ -72,7 +72,7 @@ def get_repo_primary_md(path_snapshot_base, repo):
     # return as dict in {'package_name': "VCS Tag"}
     ret_data = None
 
-    build_info = xml2obj(open(os.path.join(path_snapshot_base,
+    build_info = xml_to_obj(open(os.path.join(path_snapshot_base,
                                           'build.xml')))
 
     for buildtarget in build_info.buildtargets.buildtarget:
index b26e356..92c4827 100644 (file)
@@ -20,7 +20,7 @@
 """Handle XML data with Python objects"""
 
 import os
-from common.utils import xml2obj
+from common.utils import xml_to_obj
 from xml.sax import SAXException
 
 class MappingError(Exception):
@@ -37,7 +37,7 @@ class Mapping(object):
 
         with open(mapping_file) as filei:
             try:
-                self.mapping_obj = xml2obj(''.join(filei.readlines()))
+                self.mapping_obj = xml_to_obj(''.join(filei.readlines()))
             except SAXException, err:
                 raise MappingError("Fatal: parsing of xml file %s failed: %s" \
                                    % (mapping_file, str(err)))
index 28475c2..f48ecd1 100644 (file)
@@ -24,8 +24,8 @@ import os
 import glob
 import subprocess
 import re
-import xml.sax.handler
 import shutil
+import xml.etree.cElementTree as ET
 
 from common import runner
 
@@ -162,54 +162,55 @@ def set_permissions(tpath, modes=(0644, 0755)):
             path = os.path.join(root, fname)
             os.chmod(path, os.stat(path).st_mode | modes[fname in dirs])
 
-def make_latest_link(snapshot_path):
-    """ make the latest repo link to the new repo
-        snapshot_path (str): the local path to snapshot
+def xml_to_obj(xml_f):
     """
-    latest = os.path.join(snapshot_path, "../latest")
-    rel_path = os.path.basename(snapshot_path)
-    if os.path.lexists(latest):
-        os.unlink(latest)
-    os.symlink(rel_path, latest)
-
-# The function xml2obj is from http://code.activestate.com/recipes/534109/
-# and is distributed under the PSF License.
-#
-# Author: Wai Yip Tung
-# Last Updated: 2007/10/19
-# Version no: 1.3
-# License: PSF License
-# Source: http://code.activestate.com/recipes/534109/
-
-def xml2obj(src):
+    A simple function to converts XML data
+    Xml data:
+    <?xml version="1.0" ?>
+    <build id="1.0">
+      <id>test</id>
+      <archs name='atom'>
+        <arch name='lz'>ia32</arch>
+        <arch>ai32</arch>
+        <arch>ai32</arch>
+      </archs>
+      <repo>repos/atom/packages</repo>
+      <repo type="source"></repo>
+      <repo type="debug">repos/atom/debug</repo>
+    </build>
+    Change result:
+    {archs:{arch:[{name:u'lz', data:u'ia32'}, u'ai32', u'ai32'], name:u'atom'},
+            id:[u'1.0', u'test'],
+            repo:[u'repos/atom/packages', {type:u'source'},
+                  {type:u'debug', data:u'repos/atom/debug'}]}
+    stack = [(node, node.children, [node.children.data]),(node, node.children, [])....]
     """
-    A simple function to converts XML data into native Python object.
-    """
-
-    non_id_char = re.compile('[^_0-9a-zA-Z]')
-    def _name_mangle(name):
-        """Split name though regular expressions"""
-        return non_id_char.sub('_', name)
+    node_char = re.compile('[^_0-9a-zA-Z]')
+    def _name_regular(name):
+        """Split name though regular"""
+        return node_char.sub('_', name)
 
-    class DataNode(object):
-        """A custom class used to wrap XML attributes and child elements"""
+    class XMLNode(object):
+        """Wrap XML attributes and child elements"""
         def __init__(self):
             self._attrs = {}    # XML attributes and child elements
-            self.data = None    # child text data
-        def __len__(self):
-            # treat single element as a list of 1
-            return 1
-        def __setitem__(self, key, value):
-            self._attrs[key] = value
+            self.data = None    # text data
+        def add_attr(self, name, value):
+            """Add XML attribute"""
+            if name in self._attrs:
+                # multiple attribute of the same name are represented by a list
+                children = self._attrs[name]
+                if not isinstance(children, list):
+                    self._attrs[name] = [children, value]
+                else:
+                    children.append(value)
+            else:
+                self._attrs[name] = value
         def __getitem__(self, key):
             if isinstance(key, basestring):
                 return self._attrs.get(key, None)
             else:
                 return [self][key]
-        def __delitem__(self, key):
-            self._attrs.pop(key)
-        def __contains__(self, name):
-            return name in self._attrs
         def __nonzero__(self):
             return bool(self._attrs or self.data)
         def __getattr__(self, name):
@@ -217,62 +218,61 @@ def xml2obj(src):
                 # need to do this for Python special methods???
                 raise AttributeError(name)
             return self._attrs.get(name, None)
-        def _add_xml_attr(self, name, value):
-            """Add XML attribute"""
-            if name in self._attrs:
-                # multiple attribute of the same name are represented by a list
-                children = self._attrs[name]
-                if not isinstance(children, list):
-                    children = [children]
-                    self._attrs[name] = children
-                children.append(value)
-            else:
-                self._attrs[name] = value
-        def __str__(self):
-            return self.data or ''
-        def __repr__(self):
-            items = sorted(self._attrs.items())
-            if self.data:
-                items.append(('data', self.data))
-            return u'{%s}' % ', '.join([u'%s:%s' % (k, repr(v))
-                                       for k, v in items])
-
-    class TreeBuilder(xml.sax.handler.ContentHandler):
-        """A wrapper class of ContentHandler"""
-        def __init__(self):
-            xml.sax.handler.ContentHandler.__init__(self)
-            self.stack = []
-            self.root = DataNode()
-            self.current = self.root
-            self.text_parts = []
-        # pylint:  disable-msg=C0103
-        def startElement(self, name, attrs):
-            self.stack.append((self.current, self.text_parts))
-            self.current = DataNode()
-            self.text_parts = []
-            # xml attributes --> python attributes
-            for k, v in attrs.items():
-                self.current._add_xml_attr(_name_mangle(k), v)
-        def endElement(self, name):
-            text = ''.join(self.text_parts).strip()
-            if text:
-                self.current.data = text
-            if self.current._attrs:
-                obj = self.current
+    #Get tree root element
+    if isinstance(xml_f, basestring):
+        root = ET.fromstring(xml_f)
+    else:
+        root = ET.parse(xml_f).getroot()
+    stack = []
+    stack.append((root, root.getchildren(), []))
+
+    tag = None
+    xml_node = None
+    # convert xml data from sub nodes start
+    while stack:
+        cur_node, child_nodes, data_list = stack[-1]
+
+        if tag and xml_node:
+            data_list.append((tag, xml_node))
+            tag = None
+            xml_node = None
+
+        if child_nodes:
+            child_node = child_nodes[0]
+            child_nodes.remove(child_node)
+            stack.append((child_node, child_node.getchildren(), []))
+            continue
+
+        xml_node = XMLNode()
+
+        # add current node attrib into XMLNode
+        for key in cur_node.attrib:
+            xml_node.add_attr(_name_regular(key), cur_node.attrib[key])
+
+        # Add previous node attrib into XMLNode
+        for (previous_tag, node) in data_list:
+            xml_node.add_attr(_name_regular(previous_tag), node)
+
+        # Add current node text info XMLNode's data
+        if cur_node.text and ''.join(cur_node.text.strip()):
+            if xml_node:
+                xml_node.data = cur_node.text
             else:
-                # a text only node is simply represented by the string
-                obj = text or ''
-            self.current, self.text_parts = self.stack.pop()
-            self.current._add_xml_attr(_name_mangle(name), obj)
-        # pylint: enable-msg=C0103
-
-        def characters(self, content):
-            self.text_parts.append(content)
-
-    builder = TreeBuilder()
+                xml_node = cur_node.text
+        tag = cur_node.tag
+        # remove namespaces of node.tag
+        if tag[0] == '{':
+            url, tag = tag[1:].split('}')
+        stack.pop()
+        if not stack:
+            return xml_node
 
-    if isinstance(src, basestring):
-        xml.sax.parseString(src, builder)
-    else:
-        xml.sax.parse(src, builder)
-    return builder.root._attrs.values()[0]
+def make_latest_link(snapshot_path):
+    """ make the latest repo link to the new repo
+        snapshot_path (str): the local path to snapshot
+    """
+    latest = os.path.join(snapshot_path, "../latest")
+    rel_path = os.path.basename(snapshot_path)
+    if os.path.lexists(latest):
+        os.unlink(latest)
+    os.symlink(rel_path, latest)