Reputation: 91
How to convert a flat/hierarchical data to XML format in Flex. The following is my Hierarchical Data:(Table format)
Asia India Chennai TN Category1 Product1 100
Asia India Mumbai MH Category1 Product1 100
Asia India Calcutta CT Category1 Product1 100
Asia India Calcutta CT Category2 Product2 200
EMEA UK London LN Category3 Product1 123
EMEA UK London LN Category3 Product2 455
EMEA UK Reading RN Category1 Product1 500
EMEA UK Reading RN Category1 Product2 430
I need to format/convert this to XML format so that I can populate that resulting xml as dataprovider to a Tree control.
Asia
India
Chennai
TN
Category1
Product1
100
Mumbai
MH
Category1
Product1
100
such a tree structure.
Upvotes: 2
Views: 1010
Reputation: 4684
I would make a simple converter to get a real XML structure. The complexity of the solution depends on the source text you have.
Here is my implementation.
Suppose your hierarchical data is good structured and each element has a certain amount of whitespaces before. In my case I use 4 whitespaces as one level shift.
I load the data from a text file, which looks like this:
Asia
India
Chennai
TN
Category1
Product1
100
Mumbai
MH
Category2
Product2
200
Category3
Product3
300
Product4
400
Then I go through each string and analyse its level. The resulting tree looks like this:
// Application
<?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.events.FlexEvent;
private const SPACEDELIMITER:int = 4;
private var loader:URLLoader;
private var ar:Array = new Array();
[Bindable]private var data:XML = <Root/>;
private var inputStr:String;
protected function init(event:FlexEvent):void
{
loader = new URLLoader(new URLRequest("com/treexml/tree.txt"));
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.addEventListener(Event.COMPLETE, completeHandler);
}
private function completeHandler(event:Event):void
{
inputStr = URLLoader(event.target).data;
parseString();
}
private function parseString():void
{
var levels:Array = new Array();
ar = inputStr.split("\r\n");
var reg:RegExp = /[a-z0-9]/gi;
var globalShift:int = String(ar[0]).search(reg);
for (var i:int = 0; i < ar.length; i++)
{
var item:String = ar[i];
var shift:int = item.search(reg); //amount of witespaces before the text
var level:int = (shift - globalShift)/SPACEDELIMITER; //level of the node
var label:String = item.substring(shift, item.length);
levels[level] = i; //id of the last element for the given level
var node:XML = new XML();
node = <child id = {i} label = {label}/>;
if (level == 0)
data.appendChild(node);
else
data..child.(@id == levels[level - 1]).appendChild(node);
}
}
]]>
</fx:Script>
<mx:Tree width="250"
height="400"
dataProvider="{data.child}"
labelField="@label"/>
</s:Application>
//EDIT
If your data is not aligned by means of whitespaces, try to use another edition of the parseString function.
Suppose your data looks like:
Asia India Chennai TN Category1 Product1 100
Asia India Mumbai MH Category1 Product1 100
Asia India Calcutta CT Category1 Product1 100
Asia India Calcutta CT Category2 Product2 200
EMEA UK London LN Category3 Product1 123
EMEA UK London LN Category3 Product2 455
EMEA UK Reading RN Category1 Product1 500
EMEA UK Reading RN Category1 Product2 430
The function is:
private function parseString():void
{
ar = inputStr.split("\r\n");
var map:Dictionary = new Dictionary();
var delimiter:String = "***";
var id:int = 0;
for (var i:int = 0; i < ar.length; i++)
{
if(ar[i].length){//if it's not an empty string
var itemArray:Array = ar[i].replace(/\s{2,}/g, ' ').split(" ");//collapse multiple spaces as one using RegEx
var key:String = "";
var prevkey:String = "";
for (var j:int = 0; j< itemArray.length; j++)
{
prevkey = key;
key += itemArray[j] + delimiter;
if (map[key] == null)
{
map[key] = id;
var node:XML = <child id = {id} label = {itemArray[j]}/>;
if (j == 0)
data.appendChild(node);
else
data..child.(@id == map[prevkey]).appendChild(node);
id++;
}
}
}
}
}
Upvotes: 2
Reputation: 51847
Here's a not so efficient approach using the recursive function form this answer:
var data:String = '';//this is just to keep the data in the editor, you would dinamically load this usually
data += 'Asia India Chennai TN Category1 Product1 100\n';
data += 'Asia India Mumbai MH Category1 Product1 100\n';
data += 'Asia India Calcutta CT Category1 Product1 100\n';
data += 'Asia India Calcutta CT Category2 Product2 200\n';
data += 'EMEA UK London LN Category3 Product1 123\n';
data += 'EMEA UK London LN Category3 Product2 455\n';
data += 'EMEA UK Reading RN Category1 Product1 500\n';
data += 'EMEA UK Reading RN Category1 Product2 430\n';
trace(cleanNodes(parseTableString(data).*));
function parseTableString(str:String):XML{
var lines:Array = str.split('\n');
var xml:XML = <root />;
for(var i:int = 0; i < lines.length; i++){
//collapse multiple spaces to 1 using RegEx, then split
var nodes:Array = lines[i].replace(/\s{2,}/g, ' ').split(' ');
for(var j:int = 0 ; j < nodes.length; j++){
if(nodes[j].length){
var node:String = '<item name="'+nodes[j]+'"/>';
if(j > 0){//if the node has parents
var pre:String = '';//create enclosing tags
var post:String = '';
for(var k:int = 0 ; k < j; k++){//build up parent hierarchy
pre += '<item name="'+nodes[k]+'">';
post += '</item>';
}
node = pre+node+post;//concatenate as a full node
xml.appendChild(new XML(node));//add full node (will add duplicates)
}
}
}
}
return xml;
}
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;
}
Which outputs:
<root>
<item name="Asia">
<item name="India">
<item name="Chennai">
<item name="TN">
<item name="Category1">
<item name="Product1">
<item name="100"/>
</item>
</item>
</item>
</item>
<item name="Mumbai">
<item name="MH">
<item name="Category1">
<item name="Product1">
<item name="100"/>
</item>
</item>
</item>
</item>
<item name="Calcutta">
<item name="CT">
<item name="Category1">
<item name="Product1">
<item name="100"/>
</item>
</item>
<item name="Category2">
<item name="Product2">
<item name="200"/>
</item>
</item>
</item>
</item>
</item>
</item>
<item name="EMEA">
<item name="UK">
<item name="London">
<item name="LN">
<item name="Category3">
<item name="Product1">
<item name="123"/>
</item>
<item name="Product2">
<item name="455"/>
</item>
</item>
</item>
</item>
<item name="Reading">
<item name="RN">
<item name="Category1">
<item name="Product1">
<item name="500"/>
</item>
<item name="Product2">
<item name="430"/>
</item>
</item>
</item>
</item>
</item>
</item>
</root>
Personally I think @Anton's answer it better since only adds nodes when it needs (as opposed to mine which creates duplicates, then removes them). I've added a RegEx to collapse multiple whitespaces into one into his answer as well.
Upvotes: 2
Reputation: 58735
If the only reason for converting to XML is for use as a Tree dataprovider, then you don't need to do that. A much more efficient way to approach is to use tree data descriptors. This lets you keep your data provider flat, and the descriptor lets you describe its structure so the tree knows what objects are children of each other. http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf69084-7b69.html
Upvotes: 1