03-18-2011 10:42 AM - edited 03-18-2011 10:46 AM
Im having a hard time creating/updating an XML object. Whenever I add a child node using the XML.appendChild() function it is not adding a node but an attribute and so when I try to search for the child it is not finding it and thus places every node under the top level as an attribute.
Here is the code I am using:
public function addKey(key:String, value:String):void {
//break key into individual strings
var children:Array = key.split('.');
//variable to hold the node that corresponds to key
var finalKey:XML;
//using the array created by key.split to climb down the XML object
for (var i:uint = 0; i < children.length; ++i) {
if (i == 0) {
trace("First child in array: "+children[0]);
if( _workingCopy.child(children[i]).length() == 0 ){
trace("Element: "+children[i]+" is not contained in XML document......adding");
_workingCopy.appendChild(children[i]);
finalKey = _workingCopy.child(children[i])[0];
} else {
trace("Element: "+children[i]+" is contained in XML document......retrieving");
finalKey = _workingCopy.child(children[i])[0];
trace("Element: "+children[i]+" has "+finalKey.children().length()+" children");
}
} else {
if( finalKey.child(children[i]).length() == 0 ){
trace("Element: "+children[i]+" is not contained in "+finalKey.name()+" document......adding");
finalKey.appendChild(children[i]);
trace( finalKey.toXMLString() );
trace( _workingCopy.toXMLString() );
finalKey = finalKey.child(children[i])[0];
} else {
trace("Element: "+children[i]+" is contained in "+finalKey.name()+" document......retrieving");
finalKey = finalKey.child(children[i])[0];
}
}
}
trace();
//finalKey.@value = value;
}
Here is the data I am calling it with and my inital file structure:
private var _settingsTemplate:XML = new XML("<data>"+
"<events>0</events>"+
"<current>0</current>"+
"<currentEvent>"+
"</currentEvent>"+
"<event>"+
"</event>"+
"</data>");
trace( _workingCopy.toXMLString() );
addKey("event.1.type","test");
addKey("event.2.name","test");
addKey("event.2.type","test");
addKey("event.3.name","test");
addKey("currentEvent.name","updated sample event");
trace( _workingCopy.toXMLString() );
Here is the output i get:
<data> <events>0</events> <current>0</current> <currentEvent/> <event/> </data> First child in array: event Element: event is contained in XML document......retrieving Element: event has 0 children Element: 1 is not contained in event document......adding <event>1</event> <data> <events>0</events> <current>0</current> <currentEvent/> <event>1</event> </data> TypeError: Error #1009: Cannot access a property or method of a null object reference.
I read that appendChild adds the node to the end of the children's list, but that does not seem to be what's happening. Also, I cannot add nodes to the the top level data node, it always adds the node to the last child node. Even if I wrap the children[] object with XML() it still has the same effect.
Solved! Go to Solution.
03-18-2011 11:20 AM
During my initial tests I am slightly confused by this variable and where it comes from: _workingCopy
I am sure you are creating and setting it somewhere, but that is outside of your code example at this point.
I am hoping to draft up something that might help you out.
03-18-2011 11:24 AM
For all intensive purposes you can set _workingCopy with this code:
_workingCopy = _settingsTemplate.copy();
03-18-2011 11:49 AM - edited 03-18-2011 12:21 PM
OK, CMY, what is your expected output?
Thanks for the clarification on that variable... it is what I figured, but I didn't want to make assumptions.
Maybe I am a little confused, but wouldn't you be better off using something like this?
It is a lot more readable, in my opinion.
private var _settingsTemplate:XML = new XML( <data>
<events>0</events>
<current>0</current>
<currentEvent />
<event />
</data> );
private var _workingCopy:XML = _settingsTemplate.copy();
public function addKey( _xml_node:XML ):void
{
_workingCopy.appendChild( _xml_node );
}
public function updateNode( _xml_node:String, _replace_with:XML ):void
{
_workingCopy.replace( _xml_node, _replace_with );
}
Just tracing out the _workingCopy first will produce the following output:
trace(); trace( 'Before xml changes' ); trace( _workingCopy.toXMLString() ); // outputs: Before xml changes <data> <events>0</events> <current>0</current> <currentEvent/> <event/> </data> *********************/
Add a bunch of new keys and update a node, then trace out the _workingCopy to produce the following output:
addKey( XML( <event type="test" /> ) ); addKey( XML( <event name="test" type="test" /> ) ); addKey( XML( <event name="test" /> ) ); updateNode( 'currentEvent', XML( <currentEvent name="updated sample event" /> ) ); trace(); trace( 'After xml changes:' ); trace( _workingCopy.toXMLString() ); // outputs: /********************* After xml changes: <data> <events>0</events> <current>0</current> <currentEvent name="updated sample event"/> <event/> <event type="test"/> <event name="test" type="test"/> <event name="test"/> </data> *********************/
03-18-2011 11:56 AM
In checking out this portion of the code
//using the array created by key.split to climb down the XML object
for (var i:uint = 0; i < children.length; ++i) {
You're not "climbing down the XML object" you're just looping over the "key" that is passed it to addKey. So it's looping over event.1.type not _workingCopy 's children, right?
03-18-2011 02:48 PM
@kdittyr
That would be easier to do and it works, which i guess is all that matters, but it still doesn't explain why the issue was occuring. Also, the data to be added is entered by the user, so the input string has to be built which would still not work as appendChild is not creating a node and if I manually type the < or > they are trated as part of the string and converted.
ie:
//name
finalKey.appendChild('<'+children[i]+'>');
finalKey.appendChild("<"+children[i]+">");
gives
<name> <name>
@lampei
Yes it is looping over the string that is passed in to create nodes and insert them in to the XML tree
03-18-2011 03:03 PM - edited 03-18-2011 03:29 PM
You need to type cast as XML and you won't have that output error ( <name> ):
finalKey.appendChild( XML( '<' + children[ i ] + '>' ) ); finalKey.appendChild( XML( "<" + children[ i ] + ">" ) );
As for why you are getting the original error... well, you need to subtract one from the length, otherwise you are looking for something that is actually a null object, or out of range.
for( var i:uint = 0; i < children.length; ++i ) should be: var _length:int = children.length - 1; // removing "extra" loop for( var i:uint = 0; i < _length; ++i )
Because we use zero indexing in software development the first element is in the 0 position.
If there were 5 children, the last index would actually be 4, not 5. Make sense?
I am not sure that this is creating the output that you are expecting, though. Would you mind posting a sample of what you are trying to create? This is now outputting:
<data>
<events>0</events>
<current>0</current>
<currentEvent/>
<event>
1
2
2
3
</event>
</data>
03-18-2011 03:30 PM - edited 03-18-2011 03:33 PM
Wrapping it in the XML tag with the included < > solved the problem of creating the node. I also found a workaround to getting the child, when initially added it does not show up under children, but it does show up under the elements list:
//finalKey = finalKey.child(children[i])[0]; //Does not work finalKey = finalKey.elements(children[i])[0]; //Works
And upon each subsequent search of children the nodes popup. Thanks for you assistance.
The error about the null object was not due to a looping error it was because the child node was not being added correctly. Before it was not creating a node at all, and after I correctly created the node, it was not populating in the child list, but using the elements list fixed that. I don't know if it's a bug or not, but it's working.
03-18-2011 03:33 PM
No problem
Glad I could help out in some way!
03-18-2011 11:13 PM
Hi CMY--
Glad to see you got this working and thanks for posting your final solution. It looks like you used the settings manager class I posted a while back as a starting point. When I posted it , I hadn't had a need for addKey yet, but I did add a version of it in the last update to one of my desktop apps. It uses appendChild like you were originally trying to do. Here it is for future reference, if you're interested:
public function addKey(key:String, label:String, value:String):void
{
var children:Array = key.split('.');
var finalKey:XML = new XML();
var insert:XML = <{children[children.length - 1]} label={label} value={value}></{children[children.length - 1]}>;
for (var i:uint = 0; i < children.length - 1; ++i)
{
if (i == 0)
{
finalKey = XML(newSettings.child(children[i])[0]);
}
else
{
finalKey = XML(finalKey.child(children[i])[0]);
}
}
finalKey.appendChild(insert);
}
One thing I haven't done yet, but have it on the roadmap, is to encapsulate the for loop that finds the final key into a function so it isn't repeated everywhere.
Chris