There is no need to parse a XML file into any other format (such as an Array), because it's already been parsed and has more functionality than any parsed format would have.
For example, assuming your above XML data has been assigned or loaded into a variable named "NPCActions", you can get a list of all of the child "qstchk" nodes with a simple command like this:
[source]
var qstchkNodes:XMLList=NPCActions.child("qstchk");
[/source]
The XMLList object works as an array (with some minor differences, such as length() instead of length), and each list item is a "qstchk" node (in a XML object) in the order in which it appears in the XML data.
The XML object stores references to all portions of the XML document so that updating a child node automatically updates the entire XML document. It's also the reason why you can traverse upwards and downwards through the tree -- all the data is there. Parsing it into another format like an array is, in 99% of cases, extra and completely unnecessary work.
For starters, lets say you're using the XML internally (not loading it into the Flash player);;
[source]
var NPCActions:XML=
<NPCACTIONS>
<text face="TRUE" ID="0" loc="LEFT">Good day sir. Loving morning out there.</text>
<qstchk ID="0" status="0" oper=">=">
<TRUE>
<qstchk ID="0" status="1" oper="==">
<TRUE>
<text face="TRUE" ID="0" loc="LEFT">I see you have my 5 Iris'. Thank you very much. Here is your reward.</text>
<qstset id="0" status="2"></qstset>
</TRUE>
<FALSE>
<text face="TRUE" ID="0" loc="LEFT">You've given me my Iris', I cannot help you anymore.</text>
</FALSE>
</TRUE>
<FALSE>
<choices num="2" ques="I've got something I could use your help with. Do you mind assisting me?">
<opt id="0" text="I'd be more than willing to help, what can I do?">
<text face="TRUE" ID="0" loc="LEFT">Excellant. I'm in need of some flowers. Can you collect me 5 Iris'. Thanks.</text>
<qstset id="0" status="0">
</opt>
<opt id="1" text="I'm sorry, I haven't the time right now. Maybe later">
<text face="TRUE" ID="0" loc="LEFT">Very well, maybe next time.</text>
</opt>
</choices>
</FALSE>
</qstchk>
</NPCACTIONS>
[/source]
I've fixed some of the XML formatting errors -- all attribute values must be in quotes, and an XML document should always be in a single enclosing node (NPCACTIONS in this case).
The best way to begin using this data is to make an automated trigger handler to operate on all child nodes found at a specific location:
[source]
public function processNPCNode(node:XML):void {
var childList:XMLList=node.children();
for (var count:uint=0; count<childList.length(); count++) {
var currentNode:XML=childList[count] as XML;
var nodeName:String=currentNode.localName();
nodeName=nodeName.toLowerCase(); //process nodes even if capitalization isn't correct -- remove if you want exact matches only
switch (nodeName) {
case "qstchk" :
this.handleQuestCheck(currentNode);
break;
case "text" :
this.handleQuestText(currentNode);
break;
case "qstset":
this.handleQuestSet(currentNode);
break;
case "choices":
this.handleQuestChoices(currentNode);
break;
default :
trace ("NPC node type \""+nodeName+"\" not recognized!");
break;
}
}
}
[/source]
In the above example I've defined a bunch of handlers for each of the types of node. How these work is of course entirely up to you, but here's an example of how I might implement them:
[source]
private var _currentNPCAction:XML=null; //Stores the pointer to the current action being processed; should be set to null only at startup
public function processNPCNode(node:XML, currentStatus:int):void {
var childList:XMLList=node.children();
for (var count:uint=0; count<childList.length(); count++) {
var currentNode:XML=childList[count] as XML;
var nodeName:String=currentNode.localName();
nodeName=nodeName.toLowerCase(); //process nodes even if capitalization isn't correct
switch (nodeName) {
case "qstchk" :
this.handleQuestCheck(currentNode, currentStatus);
break;
case "text" :
this.handleQuestText(currentNode, currentStatus);
break;
case "qstset":
this.handleQuestSet(currentNode, currentStatus);
break;
case "choices":
this.handleQuestChoices(currentNode, currentStatus);
break;
default :
trace ("NPC node type \""+nodeName+"\" not recognized!");
break;
}
}
}
public function handleQuestCheck(currentNode:XML, currentStatus:int):void {
var actionID:int=int(currentNode.@ID);
var status:int=int(currentNode.@status);
var comparator:String=String(currentNode.@oper);
var resultXML:XML=this.getResultNode(currentStatus, status, comparator, currentNode);
_currentNPCAction=resultXML;
if (resultXML!=null) {
this.processNPCNode(resultXML);
}
}
public function handleQuestText(currentNode:XML, currentStatus:int):void {
var actionID:int=int(currentNode.@ID);
var loc:String=new String(currentNode.@loc);
var face:Boolean=this.getBoolean(String(currentNode.@face));
var text:String=new String(currentNode.children().toString());
//Update the NPC text using the data above...
}
public function handleQuestSet(currentNode:XML, currentStatus:int):void {
var actionID:int=int(currentNode.@ID);
var status:int=int(currentNode.@status);
//Update the current quest status using above data...
}
public function handleQuestChoices(currentNode:XML, currentStatus:int):void {
var numChoices:int=int(currentNode.@num);
//var numChoices:int=int(currentNode.child("opt").length()); //Alternative that doesn't require a "num" attribute in the XML data
var question:String=new String(currentNode.@ques);
var optionsList:XMLList=currentNode.child("opt") as XMLList;
//optionsList[0] is the first answer, optionsList[1] is the next answer, and so on.
//The option ID is accessible as optionsList[#].@id -- most likely you will want to convert this to an integer as I have: int(optionsList[#].@id)
//The answer text for each answer is accessible as optionsList[#].@text -- I advise forcing this to be a string before using it: String(optionsList[#].@text)
_currentNPCAction=currentNode; //This is our current action, subsequent actions must be based on it.
//Now shoe the question and hold on to the current answer list. Once selected, invoke the onQuestChoiceSelected method below with the selected answer/option ID...
}
public function onQuestChoiceSelected (optionID:int):void {
var questionNode:XML=_currentNPCAction;
//Find the matching answer by ID...
var optionsList:XMLList=questionNode.child("opt") as XMLList;
for (var count:uint=0; count<optionsList.length(); count++) {
var currentOption:XML=optionsList[count] as XML;
var currentID:int=int(currentOption.@id);
if (currentID==optionID) {
this.processNPCNode(currentOption);
return;
}
}
trace ("No matching answer to the question could be found!"); //Probably bad XML data
}
private function getBoolean(data:String):Boolean {
data=data.toLowerCase();
data=data.split(" ").join("");
switch (data) {
case "true": return (true); break;
case "false": return (false); break;
case "t": return (true); break;
case "f": return (false); break;
case "1": return (true); break;
case "0": return (false); break;
default: return (false); break;
}
return (false);
}
private function getResultNode(currentStatus:int, definedStatus:int, comparator:String, parentNode:XML):XML {
var trueNode:XML=parentNode.child("TRUE")[0].children()[0] as XML;
var falseNode:XML=parentNode.child("FALSE")[0].children()[0] as XML;
switch (comparator) {
case "==" :
if (currentStatus==definedStatus) {
return (trueNode);
} else {
return (falseNode);
}
break;
case ">=" :
if (currentStatus>=definedStatus) {
return (trueNode);
} else {
return (falseNode);
}
break;
case "<=" :
if (currentStatus<=definedStatus) {
return (trueNode);
} else {
return (falseNode);
}
break;
case "!=" :
if (currentStatus!=definedStatus) {
return (trueNode);
} else {
return (falseNode);
}
break;
case "<" :
if (currentStatus<definedStatus) {
return (trueNode);
} else {
return (falseNode);
}
break;
case ">" :
if (currentStatus>definedStatus) {
return (trueNode);
} else {
return (falseNode);
}
break;
default:
return (null); //unrecognized comparator
break;
}
return (null);
}
public function get currentNPCAction():XML {
if (_currentNPCAction==null) {
_currentNPCAction=NPCActions; //Only set to top-level when no other action has been processed
}
return (_currentNPCAction);
}
this.processNPCNode(this.currentNPCAction, currentStatus);
[/source]
This is somewhat incomplete and there may be some errors (I didn't check it through an AS3 compiler), but you should have the bare bones of a good parsing system. If nothing else, I hope this helps to explain how XML and XMLList are used directly and why using an additional array to hold such data is not necessary most of the time.