xmlapache-flexxmllist

How to convert XMLList to XML in FLEX


I had already referred this following link but could not find the solution for my scenario.

Actionscript 3 - How can i convert from XMLList to XML?

My xml variable is as follows:

private var checkXml:XML = new XML(
<universe>
<item name="cat 2">
     <item name = "All"/>
     <item name = "item 1"/>
     <item name = "item 2"/>
  </item>
  <item name="cat 2">
     <item name = "All"/>
     <item name = "item 3"/>
     <item name = "item 4"/>
     <item name = "item 5"/>
     <item name = "item 5"/>
  </item>
  <item name="cat 3">
     <item name = "All 33"/>
     <item name = "item 34"/>
     <item name = "item 44"/>
  </item>
</universe>);

I use a filter function to remove the duplicate values in above xml as:

private function filter(xmlSample:XML):XMLList 
{
     var seen:Object={};
     return xmlSample..@name.(!seen[valueOf()]&&(seen[valueOf()]=true));
}

which returns XMLList data.When I use this to get an XML format as:

var thisXml:XMLList = filter(checkXml);
Alert.show(thisXml.toXMLString());

I do not get the output in XML format;I get it this way.:

     cat 2
     All
     item 1
     item 2
     item 3
     item 4
     item 5
     cat 3
     All 33
     item 34
     item 44

How to get the same in XML format in Flex like that of my XML variable "checkXml".,so that I can retain all the parent nodes and child nodes as it is thereby the duplicates getting removed.


Solution

  • Here's a quick suggetion:

    function clean(xml:XML):XML{
        var paths:Dictionary = new Dictionary(true);//keep track of paths
        var result = new XML("<"+xml.localName()+" />");//make new xml
        for each(var child:XML in xml.*){//travers 1st level
            var path:String = child.parent().localName()+"/"+child.localName()+"@"+child.@name;//get a path (I formatted it like)
            if(!paths[path]) {//if it's a new node
                paths[path] = child;//store it in the dictionary
                result.appendChild(child.copy());//add it to the result
            }else {//otherwise copy children
                for each(var clone:XML in child.*)//check for duplicates, otherwise insert, NOTE this does not merge child nodes yet :(
                    if(result[child.localName()][0].*.(@name == clone.@name).length() == 0) result[child.localName()][0].appendChild(clone);
            }
        }
        trace(result.toXMLString());
        return result;
    }
    

    With the basic xml you have it does the job, but it's not very flexible. It should merge child nodes for duplicates and perhaps should be recursive, but I've got no time atm.

    Update: I've got two more versions for you. One that goes into the child nodes a level:

    function clean(xml:XML):XML{
        var paths:Dictionary = new Dictionary(true);//keep track of 
        var result = new XML("<"+xml.localName()+" />");
        for each(var child:XML in xml.*){
            var path:String = child.parent().localName()+"/"+child.localName()+"@"+child.@name;
            if(!paths[path]) {
                paths[path] = child;
                result.appendChild(child.copy());
            }else
                for each(var clone:XML in child.*)
                    if(result[child.localName()][0].*.(@name == clone.@name).length() == 0) result[child.localName()][0].appendChild(clone);
                    else result[child.localName()][0].*.(@name == clone.@name)[0].appendChild(clone.*);
        }
        return result;
    }
    

    so an xml like this:

    var data:XML = <universe>
    <item name="cat 2">
         <item name = "All">
            <item name="item child 1" />
            <item name="item child 3">
                <item name="item grandchild 1" />
            </item>
         </item>
         <item name = "item 1">
            <item name = "item child 1" />
         </item>
         <item name = "item 2"/>
      </item>
      <item name="cat 2">
         <item name = "All">
            <item name="item child 2" />
            <item name="item child 3">
                <item name="item grandchild 2" />
            </item>
         </item>
         <item name = "item 3"/>
         <item name = "item 4"/>
         <item name = "item 5"/>
         <item name = "item 5"/>
      </item>
      <item name="cat 3">
         <item name = "All 33"/>
         <item name = "item 34"/>
         <item name = "item 44"/>
      </item>
    </universe>;
    

    produces an output like this:

    <universe>
      <item name="cat 2">
        <item name="All">
          <item name="item child 1"/>
          <item name="item child 3">
            <item name="item grandchild 1"/>
          </item>
          <item name="item child 2"/>
          <item name="item child 3">
            <item name="item grandchild 2"/>
          </item>
        </item>
        <item name="item 1">
          <item name="item child 1"/>
        </item>
        <item name="item 2"/>
        <item name="item 3"/>
        <item name="item 4"/>
        <item name="item 5"/>
      </item>
      <item name="cat 3">
        <item name="All 33"/>
        <item name="item 34"/>
        <item name="item 44"/>
      </item>
    </universe>
    

    Note that attributes in the root node are lost if any. Probably the best option is still to use recursion, like so:

    function cleanNodes(nodes:XMLList):XML{
        var parent:XML = nodes.parent();
        var result:XML = new XML("<"+parent.localName()+" />");//copy parent node name
        for each(var a:XML in parent.attributes()) result['@'+a.name()] = parent.attribute(a.name());//and attributes
        //merge duplicates at one level
        var found:Dictionary = new Dictionary(true);
        for each(var child:XML in nodes){
            var name:String = child.@name;
            if(!found[name]) {
                found[name] = child;
                result.appendChild(child);
            }else{//merge
                found[name].appendChild(child.*);
            }
        }
        //recurse
        for each(var kid:XML in result.*){//for each child node
            if(kid.*.length() > 0){//if it has children
                var clean:XML = cleanNodes(kid.*);//get a clean copy of each child node
                delete result.*[kid.childIndex()];//remove the original
                result.appendChild(clean);        //add the cleaned one (replace)
            }
        }
        return result;  
    }
    

    I'm not sure if this is the cleanest/most elegant solution, but it works:

    trace(cleanNodes(data.*));
    

    produces:

    <universe>
      <item name="cat 2">
        <item name="item 2"/>
        <item name="item 3"/>
        <item name="item 4"/>
        <item name="item 5"/>
        <item name="All">
          <item name="item child 1"/>
          <item name="item child 2"/>
          <item name="item child 3">
            <item name="item grandchild 1"/>
            <item name="item grandchild 2"/>
          </item>
        </item>
        <item name="item 1">
          <item name="item child 1"/>
        </item>
      </item>
      <item name="cat 3">
        <item name="All 33"/>
        <item name="item 34"/>
        <item name="item 44"/>
      </item>
    </universe>
    

    Notice nodes with the same names got collapsed recursively (like "All" and "item child 3"), but unfortunately the node order the way a bit.