Domanda

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.

È stato utile?

Soluzione

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.

Altri suggerimenti

If you would like to filter only the children's layer try this:

enter image description here

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
           xmlns:s="library://ns.adobe.com/flex/spark" 
           xmlns:mx="library://ns.adobe.com/flex/mx" 
           minWidth="955" minHeight="600" creationComplete="init(event)">
<fx:Script>
    <![CDATA[
        import mx.controls.Alert;
        import mx.events.FlexEvent;
        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>);

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

        protected function init(event:FlexEvent):void
        {
            var thisXml:XMLList = filter(checkXml);
            Alert.show(thisXml.toXMLString());
        }

    ]]>
</fx:Script>

</s:Application>

EDIT

If you want to filter the items at the both levels separated, you can use this function instead:

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

In this case the filter wipes the second node "cat 2" and all the dupplicates among the children in the rest nodes.

EDIT2

If you want to filter both layers and don't want to lose the 2-dimensional structure, do something like this:

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

            var secondLevel:XMLListCollection = new XMLListCollection();

            var seen_child:Object={};

            for each (var xmlItem:XML in firstLevel)
            {
                var tempXMLList:XMLList = xmlItem.item.(!seen_child[@name.valueOf()]&&(seen_child[@name.valueOf()]=true));
                xmlItem.setChildren(tempXMLList);
                secondLevel.addItem(xmlItem);
            }

            return secondLevel.source;
        }

The result:

enter image description here

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top