Search Unity

Lightweight UnityScript XML parser

Discussion in 'Scripting' started by Flimgoblin, Jan 5, 2010.

  1. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    I've created a generic XML parser (in pure UnityScript - no libraries required) that in theory should work with any XML input. No validation or XML file generation and I expect it'll break in interesting ways if you give it bad input.

    It's also not been tested to death by any means (though it seems to behave well enough on an RSS feed I fed it).

    http://www.roguishness.com/unity/

    I've used it here to power an RSS reader-based "newspaper" for the RockPaperShotgunity project (well, if I can get it integrated to the main thing properly):

    http://www.roguishness.com/unity/rps/newspaper.html

    Any comments, bug reports, etc. are more than welcome.

    PS. I've put it under LGPL because when I looked the Creative Commons website told me to, but tbh I'm happy to license it out to people under whatever terms you need it - just get in touch :)

    Edit: should say it's now under MIT too on the page, though again if you need another license...

    PPS. In case you're wondering "Why?" - if you use System.XML et al you'll throw a meg of bloat onto your final app - not ideal for a web download, or possibly even for an iphone app I guess. This thing is 8k of javascript, but being that small it's pretty bare bones.
     
  2. rouhee

    rouhee

    Joined:
    Dec 23, 2008
    Posts:
    194
    very nice! 8)
     
  3. previz

    previz

    Joined:
    Jan 3, 2010
    Posts:
    4
    Looks exactly what I was looking for.
     
  4. GhostDog

    GhostDog

    Joined:
    Nov 11, 2009
    Posts:
    103
    Awesome flim...thank you. How about an MIT license?
     
  5. polytropoi

    polytropoi

    Joined:
    Aug 16, 2006
    Posts:
    681
    thanks for sharing this! very useful...
     
  6. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
  7. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
  8. MikeHergaarden

    MikeHergaarden

    Joined:
    Mar 9, 2008
    Posts:
    1,027
    Awesome thanks! This cut our preloader down from 1.1mb compressed to 250kb compressed.

    Did you try writing the script in #pragma strict mode btw? I wasn't able to loop over a parsed XMl file's Hashtable in #pragma strict mode so I removed the pragma.
     
  9. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    Ahh, I'm lazy so no ;) will give it a go and see if I can get it running in strict mode.


    Edit:
    Wasn't too hard as it turns out, had to use a couple of local variables to cast (is there an easier way to cast in Unityscript other than "var a:SomeType=ob;"?) to Array where appropriate.

    v0.4 linked from the page now.

    Let me know if that works for you :)

    If you're looping on the hashtables I think you want:
    Code (csharp):
    1.  
    2. for(var p:DictionaryEntry in myHash)
    3.  
     
  10. MikeHergaarden

    MikeHergaarden

    Joined:
    Mar 9, 2008
    Posts:
    1,027
    Yeah, I already concerted it to strict myself too and only needed to change one Array cast indeed.

    The problem I'm having with strict mode is when using it though, I can't loop trough a nested array because of a Boo.Lang.Hash entry. My XML is like:

    Code (csharp):
    1.  
    2. <messagebundle>
    3. <msg name="ADJUDANT">OBERFELDWEBEL</msg>
    4. <msg name="ALIEN">AUSSERIRDISCH</msg>
    5. <msg name="AQUA">WASSER</msg>
    6. </messagebundle>
    7.  
    This is the code that works (not strict)
    Code (csharp):
    1.  
    2. var node : Hashtable = parser.Parse(File.ReadAllText(dataPath));
    3. var textList = node["messagebundle"][0]["msg"];    
    4. for(var k : int =0;k<textList.length;k++){
    5. var aNode = textList[k];
    6. var name : String = aNode["@name"];
    7. var text : String = aNode["_text"];
    8. }
    I'd like this to work in strict too, but I got problems accessing the nested Hashtable, it refuses to access it. When I cast it to Hashtable/Boo.Lang.Hash it's simply empty after the cast and the line in the second for loop will complain it's argumment null. Without casting it'll obviously complain it cant convert the vars.

    This doesn't work:
    Code (csharp):
    1. for(var p:DictionaryEntry in node) {
    2.     Debug.Log(p+": "+p.Key+"  "+p.Value);
    3.     if(p.Key=="messagebundle"){
    4.         Debug.LogWarning("MessageBundle contents..");
    5.         var node2 : Hashtable = p.Value; // OR:  (p.Value as Hashtable);
    6.         for(var h : DictionaryEntry in node2) {
    7.             Debug.Log(h+": "+h.Key+" and "+h.Value); //Either nullreff or empty
    8.         }
    9.     }
    10. }
    11.  
     
  11. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    OK, I see the problem.

    Code (csharp):
    1.  
    2.     var arr:Array=((node["messagebundle"] as Array)[0] as Hashtable)["msg"];  
    3.     for(var k : int =0;k<arr.length;k++){
    4.         var aNode:Hashtable = arr[k];
    5.         var name : String = aNode["@name"];
    6.         var text : String = aNode["_text"];
    7.     }
    8.  

    This works, but that's some horrible casting going on... problem being the Hashtables and Arrays are all coming out as plain Objects from the slice operator. Hmm does Unityscript do generics for strict?

    Might be worth writing some sort of query for extracting stuff:
    Code (csharp):
    1.  
    2. var node:XMLNode=Parser.parse(<stuff>);
    3. var arr=node.GetArray("messagebundle.[0].msg");
    4.  
    Would only really be worthwhile for deeply nested stuff in #pragma strict projects mind, though I doubt it'd be that much code added to do so...

    A more powerful query string set something like jquery or xpath stuff might be nice though (e.g.
    Code (csharp):
    1.  
    2. var arr=node.GetStringArray("..@name");
    3.  
    to grab every name attribute in the document)


    PS. thanks for the 'as <type>' tip :)
     
  12. MikeHergaarden

    MikeHergaarden

    Joined:
    Mar 9, 2008
    Posts:
    1,027
    Great, thanks! I tried a lot of combinations with Hashtable and Boo.Lang.Has (which is the same..), but didn't try Array. This works perfectly..got all of yesterdays files in strict mode again.

    I just added some other file that works with hashtables a lot, for that file I do want a proper solution to access the Hashtables so I might look at such a function indeed.
     
  13. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    Decided to just go for the fairly simple option, rather than trying any crazy xpath emulation.

    v0.5 uploaded

    The node is no longer a Hash, it's now an XMLNode - which is a subclass of Hash so works the same as before if you want, but it has 3 convenience methods:

    GetNode("path>0>to>0>array>0>node>0");
    GetNodeList("path>0>to>0>array");
    GetValue("path>0>to>0>array>0>node>0>@attribute");

    Some better examples on the download page.
     
  14. MikeHergaarden

    MikeHergaarden

    Joined:
    Mar 9, 2008
    Posts:
    1,027
    Once again, thanks for the XML file. Its surprising no one else made such a complete XML parser yet (for the public?).

    Now that I've saved quite some Mb's with the XML parser, adding JSOn to my webplayer is even worse +1.1mb compressed(3.3mb uncompressed).

    A test case here:

    Code (csharp):
    1. var testJ : String;
    2. //These work :)
    3. // testJ = "{\"response\":\"blaa\"}";  
    4. //testJ = "{\"response\":[]}";
    5. //testJ = "{\"response\":true}";
    6. // testJ = "{\"ticket\":\"2494a00b22dcedb439ce1cfb76108f45\"}";
    7.  
    8. //Don't work
    9. testJ = "{\"response\":{\"ticket\":\"2494a00b22dcece1cfb76108f45\"}}";
    10. //testJ = "{\"response\":{\"timestamp\":1266880210,\"date\":\"2010-02-22T23:10:10+00:00\",\"timezone\":\"Europe/London\"}}";
    11. var hash = JSON.ParseJSON(testJ);  
    12. Debug.LogWarning(hash["response"]);
    13.  
    True/false values *do* seem to work here. Did you already look at implementing nested "{...}" yet? Did you simply not yet have the time to add that or is it complicated?

    I'm trying to get this one working:

    Code (csharp):
    1. {"response":{"ticket":"2494a00b22dcece1cfb76108f45"}}
    It could be as simple as a recursive call to ParseJSON I guess?
     
  15. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    Cool, it does indeed seem to work with literals, both true/false and numerical (probably with literal strings too, which is wrong according to the spec but , well, tough :p).

    Don't remember putting that in there (hence the note) but looking at the code it falls out of the last else clause in the behaviour when not quoted.

    It should have been happy with nested hashes but a hash directly within a hash had a stupid bug*. This is now fixed.

    JSON parser v0.2

    * it reset a key right before using it - duh! - my very limited test set had only had hashes within arrays within hashes, though with those there's a ghost extra blank element after the hash... might fix it sometime.
     
  16. fvdsn

    fvdsn

    Joined:
    Nov 15, 2009
    Posts:
    7
    I don't really understand how to use your library :(

    In the following example:
    Code (csharp):
    1.  
    2. parser = new XMLParser();
    3.     var node:XMLNode =parser.Parse("<foo attr=\"value\"> text <child0> c0 </child0> <child1> c1 </child1> </foo>");
    4.     Debug.Log(node["@attr"]);   // expecting 'value', getting null
    5.     Debug.Log(node["_text"]);   // expecting 'text' , getting  ''
    6.     for (var n:XMLNode in node["foo"]){
    7.         Debug.Log(n["_text"]);  // expecting c0 .. c1, getting text
    8.     }
    9.  
    Can you show me what I'm doing wrong ? How can i get 'value' and 'text' from node ?

    And why isn't this valid ?
    Code (csharp):
    1.  
    2.  var node2:XMLNode = node["foo"][0]; //Type Object does not support slicing
    3.  
     
  17. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    Hmm, can't seem to reproduce your second issue there..

    The reason you're getting Null is because 'node' is actually an imaginary container node, rather than the outermost node.

    node["foo"] is an array of all the 'foo' nodes at the top level of the XML document (of which there is one) and node["foo"][0] is your outermost XML node in your document.

    So to get the details you want:

    Code (csharp):
    1.  
    2. Debug.Log(node["foo"][0]["_text"]);
    3. Debug.Log(node["foo"][0]["@attr"]);
    4.  
    If you change your two child attributes to just 'child' instead of 'child0' and 'child1' you can do:

    Code (csharp):
    1.  
    2. for(var n:XMLNode in node["foo"][0]["child"]){
    3.   Debug.Log(n["_text"]);
    4. }
    5.  
    or if you're on the iphone/using #pragma strict you'd want:

    Code (csharp):
    1.  
    2. Debug.Log(node.GetValue("foo>0>_text"));
    3. Debug.Log(node.GetValue("foo>0>@attr"));
    4.  
    5. for(var n:XMLNode in node.GetNodeList("foo>0>child")){
    6. Debug.Log(n.GetValue("_text"));
    7. }
    8.  

    Full listing I tested with:

    Code (csharp):
    1.  
    2. function ParseTest2(){
    3.  var parser = new XMLParser();
    4.    var node:XMLNode =parser.Parse("<foo attr=\"value\"> text <child> c0 </child> <child> c1 </child> </foo>");
    5.     var node2:XMLNode = node["foo"][0];
    6.    Debug.Log(node2["@attr"]);  
    7.    Debug.Log(node2["_text"]);    
    8.    Debug.Log(node.GetValue("foo>0>_text"));
    9.    Debug.Log(node.GetValue("foo>0>@attr"));
    10.  
    11.    for(var n:XMLNode in node.GetNodeList("foo>0>child")){
    12.      Debug.Log(n.GetValue("_text"));
    13.    }
    14.  
    Hope that helps.

    Cheers,
    Flim
     
  18. Nissen

    Nissen

    Joined:
    Jan 18, 2010
    Posts:
    4
    Thank you very much for providing this really useful library! :)

    I have a specific problem with it, however, and I have tried a lot of different approaches, none of them giving me the result I'm seeking. I would simply like to be able to enumerate each node in the XML-data without having to know them beforehand. Consider the following example:

    Code (csharp):
    1. <a>
    2.  <b/>
    3.  <c>
    4.   <d/>
    5.  </c>
    6.  <e/>
    7. </a>
    In the example, I would like a solution that provides me with the node list:
    - a
    -- b
    -- c
    --- d
    -- e
    You can think of it as a XML pretty-printer.
    Is it possible to create a graceful solution for this? Thanks in advance :)

    Best regards,
    Nissen
     
  19. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    I wrote a variable dumping util function, if you just want to see what's in the objects created, not the prettiest but you can probably adapt it to look nicer.

    Code (csharp):
    1.  
    2. public static function print_r(val,d:int):String{
    3.     var str:String="";
    4.  
    5.     for(var i=0;i<d;i++){
    6.         str+=" ";
    7.     }
    8.     if(val instanceof String){
    9.         str=str+" '"+val+"'";
    10.     }else if(val instanceof Hashtable){
    11.         str+="{\n";
    12.         for(var p:DictionaryEntry in val){
    13.             str+=p.Key+" => "+print_r(p.Value,d+1)+", ";
    14.         }
    15.         str+=" }";
    16.     }else if(val instanceof Array){
    17.         str+="[\n";
    18.         for(p in val){
    19.             str+=print_r(p,d+1)+", ";
    20.         }
    21.         str+=" }";
    22.     }
    23.     return str;
    24. }
    25.  
    26.  
    Usage: print_r(thing,0);

    Suspect it'll explode if you try #pragma strict but you never know.
     
  20. Nissen

    Nissen

    Joined:
    Jan 18, 2010
    Posts:
    4
    Thank you very much for your reply, it is much appreciated. However, I don't think your solution works for my problem.

    If I have the following XML file:

    Code (csharp):
    1. <a>
    2.  <b/>
    3.  <c/>
    4.  <b/>
    5. </a>
    I will get the list [a, b, b, c] because each b-node will be grouped in the hash map, thus losing the sequence ordering.

    I can't figure out if there is an elegant way to handle this problem with the way the parsing is implemented. Hopefully there is, because my Unity project depends on being able to load XML :)

    PS. Sorry for my late reply, I'm currently at Breakpoint 2010 ;)
     
  21. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    Ahh yeah the way it groups the nodes loses that original ordering - there's currently no "all children" method which would preserve it. I'll ponder how I can add something like that in.

    In the mean time Is that ordering of your XML vital and if so are you able to restructure your XML before use or is it a case of some arbitrary XML which you need to parse/print in the correct way?
     
  22. Nissen

    Nissen

    Joined:
    Jan 18, 2010
    Posts:
    4
    Well, it definitely isn't pretty, but it gets the job done. I made a new bare bones XML parser that suits my needs (ie. order preservation). It also maintains the original XML tree structure.

    Code (csharp):
    1. class XMLNode {
    2.     var tagName : String;
    3.     var parentNode : XMLNode;
    4.     var children : Array;
    5.     var attributes : Boo.Lang.Hash;
    6.    
    7.     function XMLNode() {
    8.         tagName = "NONE";
    9.         parentNode = null;
    10.         children = new Array();
    11.         attributes = new Boo.Lang.Hash();
    12.     }
    13. };
    Code (csharp):
    1. class XMLReader {
    2.     private var TAG_START : char = "<"[0];
    3.     private var TAG_END     : char = ">"[0];
    4.     private var SPACE : char = " "[0];
    5.     private var QUOTE : char = "\""[0];
    6.     private var SLASH : char = "/"[0];
    7.     private var EQUALS : char = "="[0];
    8.     private var BEGIN_QUOTE : String = "" + EQUALS + QUOTE;
    9.    
    10.     function read(xml : String) : XMLNode {
    11.         var index : int = 0;
    12.         var lastIndex : int = 0;
    13.         var rootNode : XMLNode = XMLNode();
    14.         var currentNode : XMLNode = rootNode;
    15.        
    16.         while (true) {
    17.             index = xml.IndexOf(TAG_START, lastIndex);
    18.             if (index < 0 || index >= xml.Length)   break;
    19.             index++; // skip the tag-char
    20.                
    21.             lastIndex = xml.IndexOf(TAG_END, index);
    22.             if (lastIndex < 0 || lastIndex >= xml.Length) break;
    23.            
    24.             var tagLength = lastIndex - index;
    25.             var xmlTag : String = xml.Substring(index, tagLength);
    26.            
    27.             // The tag starts with "</", it is thus an end tag
    28.             if (xmlTag[0] == SLASH) {
    29.                 currentNode = currentNode.parentNode;
    30.                 continue;
    31.             }
    32.            
    33.             var openTag = true;
    34.             // The tag ends in "/>", it is thus a closed tag
    35.             if (xmlTag[tagLength - 1] == SLASH) {
    36.                 xmlTag = xmlTag.Substring(0, tagLength - 1); // cut away the slash
    37.                 openTag = false;
    38.             }
    39.            
    40.             //s += readTag(xmlTag, indent);
    41.             var node : XMLNode = parseTag(xmlTag);
    42.             node.parentNode = currentNode;
    43.             currentNode.children.Push(node);
    44.            
    45.             if (openTag) {
    46.                 currentNode = node;
    47.             }
    48.         };
    49.        
    50.         return rootNode;
    51.     }
    52.  
    53.     function parseTag(xmlTag : String) : XMLNode {
    54.         var node : XMLNode = new XMLNode();
    55.    
    56.         var nameEnd : int = xmlTag.IndexOf(SPACE, 0);
    57.         if (nameEnd < 0) {
    58.             node.tagName = xmlTag;
    59.             return node;
    60.         }
    61.        
    62.         var tagName : String = xmlTag.Substring(0, nameEnd);
    63.         node.tagName = tagName;
    64.        
    65.         var attrString : String = xmlTag.Substring(nameEnd, xmlTag.Length - nameEnd);
    66.         return parseAttributes(attrString, node);
    67.     }
    68.    
    69.     function parseAttributes(xmlTag : String, node : XMLNode) : XMLNode {
    70.         var index : int = 0;
    71.         var attrNameIndex : int = 0;
    72.         var lastIndex : int = 0;
    73.        
    74.         while (true) {
    75.             index = xmlTag.IndexOf(BEGIN_QUOTE, lastIndex);
    76.             if (index < 0 || index > xmlTag.Length) break;
    77.            
    78.             attrNameIndex = xmlTag.LastIndexOf(SPACE, index);
    79.             if (attrNameIndex < 0 || attrNameIndex > xmlTag.Length) break;
    80.             attrNameIndex++; // skip space char
    81.             var attrName : String = xmlTag.Substring(attrNameIndex, index - attrNameIndex);
    82.            
    83.             index += 2; // skip the equal and quote chars
    84.                
    85.             lastIndex = xmlTag.IndexOf(QUOTE, index);
    86.             if (lastIndex < 0 || lastIndex > xmlTag.Length) break;
    87.            
    88.             var tagLength : int = lastIndex - index;
    89.             var attrValue : String = xmlTag.Substring(index, tagLength);
    90.             node.attributes[attrName] = attrValue;
    91.         };
    92.        
    93.         return node;
    94.     }
    95.  
    96. }
    Usage:
    Code (csharp):
    1. var parser : XMLReader = XMLReader();
    2. var node : XMLNode = parser.read(xmlString);
    3. printXML(node, 0);
    4.  
    5. public function printXML(node : XMLNode, indent : int) {
    6.     indent++;
    7.     for (var n : XMLNode in node.children) {
    8.         var attr : String = " ";
    9.         for(var p : DictionaryEntry in n.attributes){
    10.             attr += "[" + p.Key + ": " + p.Value + "] ";
    11.         }
    12.         Debug.Log(String("-"[0], indent-1) + " " + n.tagName + attr);
    13.         printXML(n, indent);
    14.     }
    15. }
    The following is the example XML data:
    Code (csharp):
    1. <behavior>
    2.     <move dir="1" type="int"/>
    3.     <move dir="4" type="float"/>
    4.     <sequence>
    5.         <move dir="1" type="int"/>
    6.         <move dir="4" type="int"/>
    7.         <move dir="2" type="int"/>
    8.         <move dir="3" type="string"/>
    9.         <move dir="1" type="int"/>
    10.     </sequence>
    11.     <move dir="4" type="int"/>
    12.     <move dir="1" type="int"/>
    13.     <move dir="4" type="long"/>
    14.     <move dir="1" type="short"/>
    15.     <move dir="3" type="int"/>
    16. </behavior>;
    17.  
    From which the code produces the following result:
    Code (csharp):
    1.  behavior
    2. - move [type: int] [dir: 1]
    3. - move [type: float] [dir: 4]
    4. - sequence
    5. -- move [type: int] [dir: 1]
    6. -- move [type: int] [dir: 4]
    7. -- move [type: int] [dir: 2]
    8. -- move [type: string] [dir: 3]
    9. -- move [type: int] [dir: 1]
    10. - move [type: int] [dir: 4]
    11. - move [type: int] [dir: 1]
    12. - move [type: long] [dir: 4]
    13. - move [type: short] [dir: 1]
    14. - move [type: int] [dir: 3]
    Regards, Nissen :)
     
  23. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    Cool, nice work :)
     
  24. chirhotec

    chirhotec

    Joined:
    Mar 30, 2010
    Posts:
    47
    Can this be used with C#?

    I was able to get my C# scripts to recognize the XMLParser / XMLNode classes, by placing them in Standard Assets (not sure why they didn't work when loaded into an Editor folder).

    I'm trying to use with C#:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class xmlSimpleTest : MonoBehaviour {
    6.     void Start () {
    7.         XMLParser parser = new XMLParser();
    8.         XMLNode node = parser.Parse("<example><value type=\"String\">Foobar</value><value type=\"Int\">3</value></example>");
    9.         print(node["example"]["1"]);
    10.     }
    11. }
    12.  
    But end up with an error:

    How hard would it be to convert these scripts to C#? I imagine the biggest issue would be that your XMLNode is based on Boo.Lang.Hash.
     
    Last edited: Sep 30, 2010
  25. karn9872

    karn9872

    Joined:
    Sep 26, 2010
    Posts:
    14
    Thanks for the great script!

    One issue, while upgrading my project to Unity 3.0, I ran into a problem with the XML parser. It served me well prior to 3.0, but line 147(?) and 156(?) in XMLParser.js:

    Code (csharp):
    1. currentNode["_text"]+=textValue;
    caused this error:

    Code (csharp):
    1. Unhandled Exception: System.ExecutionEngineException: Attempting to JIT compile method '(wrapper dynamic-method) Boo.Lang.Runtime.RuntimeServices:RuntimeServices$o p_Addition$System.String$System.String (object,object[])' while running with --aot-only.
    2.  
    I changed the lines to:

    Code (csharp):
    1. currentNode["_text"]=currentNode["_text"].ToString()+textValue;
    Maybe there is a better way to handle this, but it seems to work.
     
  26. chirhotec

    chirhotec

    Joined:
    Mar 30, 2010
    Posts:
    47
    I converted your scripts to C#. Hashtable for XMLNode, ArrayList for XMLNodeList. Also, I didn't see any real use for having the parser extend MonoBehaviour, so I extended object, and made the class static.

    Here is your modified usage example:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class xmlSimpleTest : MonoBehaviour {
    6.     void Start () {
    7.         XMLNode node = XMLParser.Parse("<example><value type=\"String\">Foobar</value><value type=\"Int\">3</value></example>");
    8.         print(node.GetValue("example>0>value>1>@type"));
    9.     }
    10. }
    11.  
    And attached are the C# scripts.

    Tested in Unity 3.0
     

    Attached Files:

    Last edited: Oct 1, 2010
  27. blockimperium

    blockimperium

    Joined:
    Jan 21, 2008
    Posts:
    452
    I converted Nissen's XML Parser over to C#. Will add some XPath magic to the system shortly.

    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class XMLNode
    7. {
    8.     public String                           tagName;
    9.     public XMLNode                          parentNode;
    10.     public ArrayList                        children;
    11.     public Dictionary<String, String>       attributes;
    12.    
    13.     public XMLNode ()
    14.     {
    15.         tagName = "NONE";
    16.         parentNode = null;
    17.         children = new ArrayList();
    18.         attributes = new Dictionary<String, String>();
    19.     }
    20. }
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. public class XMLReader
    8. {
    9.     private static char TAG_START = '<';
    10.     private static char TAG_END = '>';
    11.     private static char SPACE = ' ';
    12.     private static char QUOTE = '"';
    13.     private static char SLASH = '/';
    14.     private static char EQUALS = '=';
    15.     private static String BEGIN_QUOTE = "" + EQUALS + QUOTE;
    16.    
    17.     public XMLReader ()
    18.     {
    19.     }
    20.    
    21.     public XMLNode read( String xml )
    22.     {
    23.         int index = 0;
    24.         int lastIndex = 0;
    25.         XMLNode rootNode = new XMLNode();
    26.         XMLNode currentNode = rootNode;
    27.        
    28.         while ( true )
    29.         {
    30.             index = xml.IndexOf(TAG_START, lastIndex );
    31.            
    32.             if ( index < 0 || index >= xml.Length )
    33.             {
    34.                 break;
    35.             }
    36.            
    37.             index++;
    38.            
    39.             lastIndex = xml.IndexOf(TAG_END, index);
    40.             if ( lastIndex < 0 || lastIndex >= xml.Length )
    41.             {
    42.                 break;
    43.             }
    44.            
    45.             int tagLength = lastIndex - index;
    46.             String xmlTag = xml.Substring( index, tagLength );
    47.            
    48.             // if the tag starts with a </ then it is an end tag
    49.             //
    50.             if (xmlTag[0] == SLASH)
    51.             {
    52.                 currentNode = currentNode.parentNode;
    53.                 continue;
    54.             }
    55.            
    56.             bool openTag = true;
    57.            
    58.             // if the tag ends in /> the tag can be considered closed
    59.             if ( xmlTag[tagLength - 1] == SLASH)
    60.             {
    61.                 // cut away the slash
    62.                 xmlTag = xmlTag.Substring( 0, tagLength - 1 );
    63.                 openTag = false;
    64.             }
    65.            
    66.            
    67.             XMLNode node = parseTag( xmlTag );
    68.             node.parentNode = currentNode;
    69.             currentNode.children.Add( node );
    70.            
    71.             if ( openTag )
    72.             {
    73.                 currentNode = node;
    74.             }
    75.            
    76.         }
    77.        
    78.         return rootNode;
    79.     }
    80.    
    81.    
    82.     public XMLNode parseTag( String xmlTag )
    83.     {
    84.         XMLNode node = new XMLNode();
    85.        
    86.         int nameEnd = xmlTag.IndexOf(SPACE, 0);
    87.         if ( nameEnd < 0 )
    88.         {
    89.             node.tagName = xmlTag;
    90.             return node;
    91.         }
    92.        
    93.         String tagName = xmlTag.Substring( 0, nameEnd );
    94.         node.tagName = tagName;
    95.        
    96.         String attrString = xmlTag.Substring( nameEnd, xmlTag.Length - nameEnd );
    97.         return parseAttributes( attrString, node );
    98.     }
    99.    
    100.     public XMLNode parseAttributes( String xmlTag, XMLNode node )
    101.     {
    102.         int index = 0;
    103.         int attrNameIndex = 0;
    104.         int lastIndex = 0;
    105.        
    106.         while ( true )
    107.         {
    108.             index = xmlTag.IndexOf(BEGIN_QUOTE, lastIndex);
    109.             if ( index < 0 || index > xmlTag.Length)
    110.             {
    111.                 break;
    112.             }
    113.            
    114.             attrNameIndex = xmlTag.LastIndexOf(SPACE, index );
    115.             if ( attrNameIndex < 0 || attrNameIndex > xmlTag.Length )
    116.             {
    117.                 break;
    118.             }
    119.            
    120.            
    121.             attrNameIndex++;
    122.             String attrName = xmlTag.Substring( attrNameIndex, index - attrNameIndex );
    123.            
    124.             // skip the equal and quote character
    125.             //
    126.             index +=2;
    127.            
    128.             lastIndex = xmlTag.IndexOf(QUOTE, index);
    129.             if ( lastIndex < 0 || lastIndex > xmlTag.Length )
    130.             {
    131.                 break;
    132.             }
    133.            
    134.             int tagLength = lastIndex - index;
    135.             String attrValue = xmlTag.Substring( index, tagLength );
    136.            
    137.             node.attributes[attrName] = attrValue;         
    138.         }
    139.        
    140.         return node;
    141.     }
    142.    
    143.     public void printXML( XMLNode node, int indent )
    144.     {
    145.         indent ++;
    146.        
    147.         foreach ( XMLNode n in node.children )
    148.         {
    149.             String attr = " ";
    150.             foreach( KeyValuePair<String, String> p in n.attributes )
    151.             {
    152.                 attr += "[" + p.Key + ": " + p.Value + "] ";
    153.                 //Debug.Log( attr );
    154.             }
    155.            
    156.             String indentString = "";
    157.             for ( int i=0; i< indent; i++ )
    158.             {
    159.                 indentString += "-";
    160.             }
    161.            
    162.             Debug.Log( "" + indentString + " " + n.tagName + attr );
    163.             printXML(n, indent );
    164.         }
    165.     }  
    166. }
    167.  
     
  28. blockimperium

    blockimperium

    Joined:
    Jan 21, 2008
    Posts:
    452
    Added some limited XPath functionality. If you need to search via raw paths and not attributes, that code is done

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. public class XPath
    8. {
    9.     private ArrayList scratchPad = new ArrayList();
    10.    
    11.  
    12.     /**
    13.      * All path operations assume that the rootNode passed in is the root of the document. This
    14.      * allows one to get a particular part of the tree and perform XPath operations on just that part
    15.      * of the tree without having to do any more expensive pathing. If you want the "normal" xpath operations
    16.      * pass in the root of the XML DOM to this function.
    17.      **/
    18.     public ArrayList parse( XMLNode rootNode, String xpath )
    19.     {      
    20.         // if there are no attributes use the fast method
    21.         //
    22.         if (! (xpath.Contains("[") ) )
    23.         {
    24.             return nodesMatchingPath( rootNode, xpath );
    25.         }
    26.        
    27.        
    28.         ArrayList foundNodes = new ArrayList();
    29.        
    30.         char[] charSeparaters = new char[] {'/'};
    31.         String[] pathElements = xpath.Split( charSeparaters, StringSplitOptions.RemoveEmptyEntries );
    32.                
    33.         foreach( String path in pathElements )
    34.         {
    35.             foundNodes = nodesMatchingName( rootNode, path );          
    36.         }
    37.        
    38.         return foundNodes;
    39.     }
    40.    
    41.    
    42.  
    43.    
    44.     private void walkChildrenMatchingNameAndValue( XMLNode rootNode, String nodeName, Dictionary<String, String> attributes )
    45.     {      
    46.         if ( rootNode.tagName.CompareTo( nodeName ) == 0 )
    47.         {
    48.             foreach( KeyValuePair<String, String> p in attributes )
    49.             {
    50.             }
    51.            
    52.            
    53.             scratchPad.Add( rootNode );
    54.         }
    55.        
    56.         foreach ( XMLNode node in rootNode.children )
    57.         {
    58.             walkChildrenMatchingName( node, nodeName );
    59.         }      
    60.     }  
    61.    
    62.     public ArrayList nodesMatchingPath( XMLNode rootNode, String path )
    63.     {
    64.         scratchPad = new ArrayList();
    65.        
    66.         walkChildrenMatchingPath( rootNode, path );
    67.        
    68.         return scratchPad;
    69.     }
    70.    
    71.     private void walkChildrenMatchingPath( XMLNode rootNode, String path )
    72.     {
    73.         if ( rootNode == null )
    74.         {
    75.             return;        
    76.         }
    77.            
    78.         if ( rootNode.path.CompareTo( path ) == 0 )
    79.         {
    80.             scratchPad.Add( rootNode );
    81.         }
    82.        
    83.         foreach ( XMLNode node in rootNode.children )
    84.         {
    85.             walkChildrenMatchingPath( node, path );
    86.         }      
    87.     }  
    88.    
    89.    
    90.     public ArrayList nodesMatchingName( XMLNode rootNode, String nodeName )
    91.     {      
    92.         scratchPad = new ArrayList();
    93.        
    94.         walkChildrenMatchingName( rootNode, nodeName );
    95.        
    96.         return scratchPad;
    97.     }
    98.    
    99.     private void walkChildrenMatchingName( XMLNode rootNode, String nodeName )
    100.     {
    101.         if ( rootNode == null )
    102.         {
    103.             return;
    104.         }
    105.        
    106.         if ( rootNode.tagName.CompareTo( nodeName ) == 0 )
    107.         {
    108.             scratchPad.Add( rootNode );
    109.         }
    110.        
    111.         foreach ( XMLNode node in rootNode.children )
    112.         {
    113.             walkChildrenMatchingName( node, nodeName );
    114.         }      
    115.     }  
    116.  
    117.  
    118. }
    119.  

    Usage

    Code (csharp):
    1.  
    2.         TextAsset fileText = (TextAsset)Resources.Load("config.xml");
    3.                
    4.         XMLReader reader = new XMLReader();
    5.         XMLNode rootNode = reader.read( fileText.text );
    6.                 XPath xpath = new XPath();
    7.         Debug.Log("There are [" + xpath.parse( rootNode, "/cards/card" ).Count + "] Nodes matching path");
    8.  
    The source XML file looks like:

    Code (csharp):
    1.  
    2. <cards>
    3.     <card suite="A" image="A.png" />
    4.     <card suite="A" image="Apple.png" />
    5. </cards>
    6.  
     
  29. abacus

    abacus

    Joined:
    Oct 22, 2010
    Posts:
    2
    Hi,

    thanks for some great code!

    I found a small problem when I had empty XML-tags where the slash and the node name didn't have a space between them. For example, <examplenodename /> would work, but <examplenodename/> did not.

    I fixed this in XMLParser.js by replacing
    Code (csharp):
    1.  
    2. 132         if(inElement){
    3. 133             if(collectNodeName){
    4. 134                 if(c==SPACE){
    5. 135                     collectNodeName=false;
    6. 136                 }else if(c==GT){
    7. 137                     collectNodeName=false;
    8. 138                     inElement=false;
    9. 139                 }
    10.  
    with this:
    Code (csharp):
    1.  
    2. 132         if(inElement){
    3. 133             if(collectNodeName){
    4. 134                 if(c==SPACE){
    5. 135                     collectNodeName=false;
    6. 136                 }else if(c==GT){
    7. 137                     collectNodeName=false;
    8. 138                     inElement=false;
    9. 139 *                   }else if(c==SLASH  cn==GT){
    10. 140 *                       collectNodeName=false;
    11. 141 *                       i--;
    12. 142                 }
    13.  

    Hope someone finds this useful. I have included the updated file as well.
     

    Attached Files:

  30. Varnae

    Varnae

    Joined:
    Aug 16, 2010
    Posts:
    3
    I'm trying to access all the children of a node regardless of their name and want to be able to return their name. Given the following XML:

    <test>
    <Amy />​
    <Bob />​
    <Cathy />​
    </test>

    I would want to be able to iterate over all children of test and be able to output the strings "Amy", "Bob", and "Cathy."

    Is this possible?
     
  31. diabloroxx

    diabloroxx

    Joined:
    Jan 20, 2010
    Posts:
    71
    Is there a solution for this error? Or have I also done something wrong.
     
  32. chirhotec

    chirhotec

    Joined:
    Mar 30, 2010
    Posts:
    47
    Sorry, its been too long for me to remember. Based on re-reading that post, I think the problem was due to trying to mix JavaScript XML implementation and C# usage. In the end, I just converted it all to C#.

    Your xpath code seems to made use of XMLNode.path, which doesn't exist in your previously provided XMLNode code. Any chance you could supply an updated version?
     
  33. diabloroxx

    diabloroxx

    Joined:
    Jan 20, 2010
    Posts:
    71
    Yeah, I figured it out and made it work. Thanks.
     
  34. llavigne

    llavigne

    Joined:
    Dec 27, 2007
    Posts:
    977
    Can you share the final, working XML parser in c# ?
     
  35. explrCre8

    explrCre8

    Joined:
    Oct 16, 2009
    Posts:
    6
    Thank you for this XML parser, this is fantastically useful and just what I needed. I do however have one question - a very easy one I'm sure - about looping through an XML document and placing the values in an array.

    For example, here's my very simple XML file:
    Code (csharp):
    1. <?xml version="1.0"?>
    2. <MainVendorList>
    3.     <vendor1>
    4.         <title> This is an XML Vendor Title </title>
    5.         <position> (12,0,77) </position>
    6.     </vendor1>
    7.  
    8.     <vendor2>
    9.         <title> Sam's Ceramics N Stuff</title>
    10.        <position> (12,0,77) </position>
    11.    </vendor2>
    12.  
    13.    <vendor3>
    14.        <title> Superconductors R' Us</title>
    15.        <position> (20,0,45) </position>
    16.     </vendor3> 
    17. </MainVendorList>
    using this thread as a guide, I'm able to access the 'Value' and 'Title' values as below:

    Code (csharp):
    1.  
    2. public var vendorXMLFile:TextAsset;
    3. public var xmlDataNode:XMLNode;
    4. private var aTitles:Array = new Array();
    5.  
    6. function Start()
    7. {
    8.    var parser=new XMLParser();
    9.    xmlDataNode=parser.Parse(vendorXMLFile.text);
    10.  
    11.   Debug.Log(xmlDataNode.GetValue("MainVendorList>0>vendor1>0>title>0>_text");
    12.   Debug.Log(xmlDataNode.GetValue("MainVendorList>0>vendor1>0>position>0>_text");
    13. }
    14.  
    However I want to iterate through the XML, but confused on how to concatenate. For example - can't do:
    Code (csharp):
    1.  
    2. for(var i:int=0; i<4; i++)
    3. {
    4.   var temp:String =  xmlDataNode.GetValue("MainVendorList>0>vendor"+i">0>position>0>_text"
    5.    aTitles.Push(temp);
    6. }
    7.  
    So my question is how to iterate through and gather all the 'titles' and 'position' nodes. I'm new to UnityScript so any advice is greatly appreciated. Thank you!
     
    Last edited: Dec 2, 2010
  36. Jehu

    Jehu

    Joined:
    Aug 19, 2010
    Posts:
    16
    I have converted the above XML parser to C#.

    This makes it faster, and now it works on the iPhone as well - as the Boo script data type is no longer present (ie Boo script doesn't work on iPhone).

    See here for post.

    For convenience I have attached the C# version of XML Parser here too.
     

    Attached Files:

  37. llavigne

    llavigne

    Joined:
    Dec 27, 2007
    Posts:
    977


    It's only a reader, correct?
     
  38. ashcn2001

    ashcn2001

    Joined:
    Dec 8, 2010
    Posts:
    4
    hi get a problem when i want to know how many of my models under the bundle element.

    var totalnumber : int =node["Product"][0]["Assetbundles"][0]["Bundle"].Count;
    i got this problem:

    NullReferenceException: Object reference not set to an instance of an object
    Boo.Lang.Runtime.RuntimeServices.Dispatch (System.Object target, System.String cacheKeyName, System.Type[] cacheKeyTypes, System.Object[] args, Boo.Lang.Runtime.DynamicDispatching.DispatcherFactory factory)
    Boo.Lang.Runtime.RuntimeServices.Dispatch (System.Object target, System.String cacheKeyName, System.Object[] args, Boo.Lang.Runtime.DynamicDispatching.DispatcherFactory factory)
    Boo.Lang.Runtime.RuntimeServices.GetSlice (System.Object target, System.String name, System.Object[] args)

    ****************************************************************************************************************
    actually i can get the right number of how many models i have but it throw out this error i don't know where is wrong so strange. anyone can help?


    the list is :

    <Product>
    <Assetbundles>
    <Bundle>
    <name>Box</name>
    <introduce>this is just box,nothing else</introduce>
    <pic>box_pic.jpg</pic>
    <model>box.unity3d</model>
    <link>http://www.yahoo.com.cn</link>
    </Bundle>
    <Bundle>
    <name>boxandcylinder</name>
    <introduce>this is just box and also a cylinder here</introduce>
    <pic>boxandcylinder_pic.jpg</pic>
    <model>boxandcylinder.unity3d</model>
    <link>http://www.yahoo.com.cn</link>
    </Bundle>
    <Bundle>
    <name>capsult</name>
    <introduce>this is capsult</introduce>
    <pic>capsult_pic.jpg</pic>
    <model>capsult.unity3d</model>
    <link>http://www.yahoo.com.cn</link>
    </Bundle>
    </Bundle>
    </Assetbundles>
    </Product>
     
  39. herle88

    herle88

    Joined:
    Feb 15, 2011
    Posts:
    1
    First of all, thanks so much for this script, was very usefull. Now, for the users that build the game for web player and the JSON parser doesn't work here's the solution:

    Replace the line: "public static function ParseJSON(json):Hashtable{" with "public static function ParseJSON(json : String):Hashtable{".

    The problem is the variable "json" is not casted and it fails. But with that everything it's ok! this is maybe for the next update.

    Cheers and thanks Flim!
     
  40. minevr

    minevr

    Joined:
    Mar 4, 2008
    Posts:
    1,018
  41. cemC

    cemC

    Joined:
    Dec 23, 2010
    Posts:
    214
    i want to ask a Question:

    we can change everything (pytnon,C or C++ ) For Unity3D by using this converter?
     
  42. tupakapoor

    tupakapoor

    Joined:
    Dec 15, 2010
    Posts:
    1
    thought i should point out that trying to call XMLParser didn't work for me until I put the XMLParser folder in the Standard Assets folder. that is all :)
     
  43. palamangelus

    palamangelus

    Joined:
    Jun 8, 2010
    Posts:
    28
    I saw the print_r example that was offered, but is there an adaptation that allows for serializing an xml string from a particular node (or even to spit the whole thing back out)?

    For instance, I have a doc:

    <a><b><newroot><d>sometext</d></newroot></b></a>.

    After parsing, I'd like the following:

    newRootNode = node["a"][0]["b"][0]["newroot"][0];
    Debug.Log (newRootNode.xml);

    where newRootNode.xml is a string that looks like: <newroot><d><sometext</d></newroot>

    Thanks
     
  44. grimmy

    grimmy

    Joined:
    Feb 2, 2009
    Posts:
    409
    I just get Type 'Object' does not support slicing.??!?

    Code (csharp):
    1. parser = new XMLParser();
    2. var node = parser.Parse( raw );
    3. var tick = node["roar"][0]["@tick"];//Type 'Object' does not support slicing.
    Am I doing something wrong?
     
  45. ersaurabh101

    ersaurabh101

    Joined:
    Oct 8, 2010
    Posts:
    412
    Hi,

    sorry i m a beginner, really needs a hand... i want to call a prefab via xml..is there any example i can read ?? or can any 1 give guide me right coding sample

    Let us start from here -

    i have a prefab called - prefab circle [ a java script named - "script circle" is assigned to it]

    now what code do i type in "script circle" , in assets folder i have created a file named item.xml, what code do i write there ?? [x,y,z,rotation,scale to read from xml]

    please help
     
    Last edited: Mar 29, 2011
  46. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    Good feature, will consider adding that but there's a danger of it moving on from being a "Lightweight" unity xml parser...
     
  47. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    What's the XML in 'raw'?
     
  48. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
  49. Flimgoblin

    Flimgoblin

    Joined:
    Nov 25, 2009
    Posts:
    89
    Take a look at the example in the answer here:
    http://answers.unity3d.com/question...c-parts-of-an-xml-and-feed-them-into-dialogue

    Not a complete solution but it should point you in the right direction.
     
  50. afalk

    afalk

    Joined:
    Jun 21, 2010
    Posts:
    164
    Great information and some wonderful code bits! Have to dive in and start experimenting!