diff --git a/README.md b/README.md index a6f1f82..dce4123 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Work is currently undergoing on implementing a ZIP decoder for this same purpose 1. Declare a `public UdonXML udonXml;` variable in your program. 2. Assign it the value of the UdonXML GameObject in your scene. -3. Parse your XML data with LoadXml `udonXml.LoadXml(inputData.ToCharArray());`. It will return an object. Notice it takes a char[] and not a string as input, so ToCharArray is required. +3. Parse your XML data with LoadXml `udonXml.LoadXml(inputData);`. It will return an object. 4. The object returned represents the root of the xml tree, use it when executing other nodes such as `GetNodeName` or `HasChildNodes`. ### Example demo @@ -59,7 +59,7 @@ public class UdonXMLTest : UdonSharpBehaviour public void Start() { // Parse and store the root node - var root = udonXml.LoadXml(EXAMPLE_DATA.ToCharArray()); + var root = udonXml.LoadXml(EXAMPLE_DATA); // Fetch the first node by index var books = udonXml.GetChildNode(root, 1); // Index 0 will be tag @@ -82,41 +82,51 @@ public class UdonXMLTest : UdonSharpBehaviour ## 📄 Documentation -### Parsing +### Loading - -#### 🔵 LoadXml(char[] input -Loads an XML structure into memory by parsing a char[] provided input. +#### 🔵 object LoadXml(string input) +Loads an XML structure into memory by parsing the provided input. Returns null in case of parse failure. +### Saving + +#### 🔵 string SaveXml(object data) +Saves the stored XML structure in memory to an XML document. + +Uses default indent of 4 spaces. Use `SaveXmlWithIdent` to override. + +#### 🔵 string SaveXmlWithIdent(object data, string indent) +Saves the stored XML structure in memory to an XML document with given indentation. + + ### Reading data -#### 🔵 HasChildNodes(object data) +#### 🔵 bool HasChildNodes(object data) Returns true if the node has child nodes. -#### 🔵 GetChildNodesCount(object data) +#### 🔵 int GetChildNodesCount(object data) Returns the number of children the current node has. -#### 🔵 GetChildNode(object data, int index) +#### 🔵 object GetChildNode(object data, int index) Returns the child node by the given index. -#### 🔵 GetChildNodeByName(object data, string nodeName) +#### 🔵 object GetChildNodeByName(object data, string nodeName) Returns the child node by the given name. If multiple nodes exists with the same type-name then the first one will be returned. -#### 🔵 GetNodeName(object data) +#### 🔵 string GetNodeName(object data) Returns the type-name of the node. -#### 🔵 GetNodeValue(object data) +#### 🔵 string GetNodeValue(object data) Returns the value of the node. -#### 🔵 HasAttribute(object data, string attrName) +#### 🔵 bool HasAttribute(object data, string attrName) Returns whether the node has a given attribute or not. -#### 🔵 GetAttribute(object data, string attrName) +#### 🔵 string GetAttribute(object data, string attrName) Returns the value of the attribute by given name. diff --git a/UdonXML.cs b/UdonXML.cs index 95e44da..1893384 100644 --- a/UdonXML.cs +++ b/UdonXML.cs @@ -28,6 +28,7 @@ * Version log: * 0.1.0: 2020-04-11; Initial version, parsing capability. * 0.1.1: 2020-04-12; Added support for !DOCTYPE, thereby allowing HTML to be parsed. + * 0.1.2: 2020-04-12; Many parsing issues fixed. Now possible to export XML data. * */ @@ -72,7 +73,7 @@ private object[] GenerateEmptyStruct() return emptyStruct; } - private string[] AddToStringArray(string[] a, string b) + private string[] AddLastToStringArray(string[] a, string b) { string[] n = new string[a.Length + 1]; for (int i = 0; i != a.Length; i++) @@ -84,7 +85,19 @@ private string[] AddToStringArray(string[] a, string b) return n; } - private object[] AddToObjectArray(object[] a, object b) + private object[] AddFirstToObjectArray(object[] a, object b) + { + object[] n = new object[a.Length + 1]; + for (int i = 0; i != a.Length; i++) + { + n[i + 1] = a[i]; + } + + n[0] = b; + return n; + } + + private object[] AddLastToObjectArray(object[] a, object b) { object[] n = new object[a.Length + 1]; for (int i = 0; i != a.Length; i++) @@ -96,7 +109,18 @@ private object[] AddToObjectArray(object[] a, object b) return n; } - private int[] AddToIntegerArray(int[] a, int b) + private object[] RemoveFirstObjectArray(object[] a) + { + object[] n = new object[a.Length - 1]; + for (int i = 0; i != a.Length - 1; i++) + { + n[i] = a[i + 1]; + } + + return n; + } + + private int[] AddLastToIntegerArray(int[] a, int b) { int[] n = new int[a.Length + 1]; for (int i = 0; i != a.Length; i++) @@ -254,21 +278,13 @@ private object[] Parse(char[] input) Debug.Log("OPENED TAG : " + nodeName); #endif state = 0; - - // Add tag - if (tagName.Trim().Length != 0) - { - tagNames = AddToStringArray(tagNames, tagName); - tagValues = AddToStringArray(tagValues, tagValue); - tagName = ""; - tagValue = ""; - hasTagSplitOccured = false; - } + tagName = ""; + tagValue = ""; object[] s = FindCurrentLevel(data, position); - position = AddToIntegerArray(position, ((object[]) s[2]).Length); + position = AddLastToIntegerArray(position, ((object[]) s[2]).Length); - s[2] = AddToObjectArray((object[]) s[2], GenerateEmptyStruct()); + s[2] = AddLastToObjectArray((object[]) s[2], GenerateEmptyStruct()); object[] children = (object[]) s[2]; object[] child = (object[]) children[children.Length - 1]; @@ -297,26 +313,20 @@ private object[] Parse(char[] input) } else if (c == '"') { - isWithinQuotes = !isWithinQuotes; - } - else if (c == ' ') - { - if (!hasNodeNameEnded) - { - hasNodeNameEnded = true; - } - else + if (isWithinQuotes) { // Add tag if (tagName.Trim().Length != 0) { - tagNames = AddToStringArray(tagNames, tagName); - tagValues = AddToStringArray(tagValues, tagValue); + tagNames = AddLastToStringArray(tagNames, tagName.Trim()); + tagValues = AddLastToStringArray(tagValues, tagValue); tagName = ""; tagValue = ""; hasTagSplitOccured = false; } } + + isWithinQuotes = !isWithinQuotes; } else if (c == '=' && !isWithinQuotes) { @@ -324,6 +334,11 @@ private object[] Parse(char[] input) } else { + if (c == ' ' && !hasNodeNameEnded) + { + hasNodeNameEnded = true; + } + if (hasNodeNameEnded) { // if tag name or tag value @@ -344,18 +359,139 @@ private object[] Parse(char[] input) } } +#if DEBUG Debug.Log("Level after parsing: " + level); +#endif return level != 0 ? null : data; } + private string Serialize(object[] data, string padding) + { + string output = ""; + + object[] work = new object[0]; + var current = data; + + var nodeChildren = (object[]) current[2]; + // Add all current children + foreach (object o in nodeChildren) + { + work = AddLastToObjectArray(work, new object[] {o, 0}); + } + + string nodeName, nodeValue, tagList; + object[] node, nodeTags, tagNames, tagValues; + int level; + object[] tempData; + + while (work.Length != 0) + { + current = (object[]) work[0]; + node = (object[]) current[0]; + level = (int) current[1]; + work = RemoveFirstObjectArray(work); + nodeName = (string) node[0]; + nodeTags = (object[]) node[1]; + tagNames = (object[]) nodeTags[0]; + tagValues = (object[]) nodeTags[1]; + nodeChildren = (object[]) node[2]; + nodeValue = (string) node[3]; +#if DEBUG + Debug.Log("[UdonXML] [Save] Work: " + nodeName + " " + level); +#endif + + // Add newline if not first line + if (output.Length != 0) + { + output += "\n"; + } + + // Add indentation + for (int i = 0; i != level; i++) + { + output += padding; + } + + // Compile tags list + tagList = ""; + for (int i = 0; i != tagNames.Length; i++) + { + tagList += " " + tagNames[i] + "=\"" + tagValues[i] + "\""; + } + + if (nodeChildren.Length == 0) + { + if (nodeValue.Trim().Length == 0) + { + if (nodeName == "?xml") + { + output += $"<{nodeName}{tagList}?>"; // ?xml has an extra ? at the end + } + else if (nodeName == "!DOCTYPE") + { + output += $"<{nodeName}{tagList}>"; // doc types are self closing without the usual slash + } + else + { + output += $"<{nodeName}{tagList} />"; + } + } + else + { + output += $"<{nodeName}{tagList}>{nodeValue}"; + } + } + else + { + output += $"<{nodeName}{tagList}>"; + if (nodeChildren[0] != null) + { + tempData = GenerateEmptyStruct(); + tempData[0] = "/" + nodeName; + tempData[2] = new object[] {null}; + work = AddFirstToObjectArray(work, new object[] {tempData, level}); + } + } + + // Add all current children + for (int i = nodeChildren.Length - 1; i >= 0; i--) + { + var o = nodeChildren[i]; + if (o != null) + { + work = AddFirstToObjectArray(work, new object[] {o, level + 1}); + } + } + } + + return output; + } + /** - * Loads an XML structure into memory by parsing a char[] provided input. + * Loads an XML structure into memory by parsing the provided input. * * Returns null in case of parse failure. */ - public object LoadXml(char[] input) + public object LoadXml(string input) + { + return Parse(input.ToCharArray()); + } + + /** + * Saves the stored XML structure in memory to an XML document. + * Uses default indent of 4 spaces. Use `SaveXmlWithIdent` to override. + */ + public string SaveXml(object data) + { + return SaveXmlWithIdent(data, " "); + } + + /** + * Saves the stored XML structure in memory to an XML document with given indentation. + */ + public string SaveXmlWithIdent(object data, string indent) { - return Parse(input); + return Serialize((object[]) data, indent); } /** diff --git a/UdonXMLTest.cs b/UdonXMLTest.cs index 8072743..0783b98 100644 --- a/UdonXMLTest.cs +++ b/UdonXMLTest.cs @@ -35,20 +35,20 @@ public class UdonXMLTest : UdonSharpBehaviour "; - private string EXAMPLE_DATA_2 = @" - - - Pride And Prejudice - 24.95 + private string EXAMPLE_DATA_2 = @" + + + Pride And Prejudice + 24.95 + + + The Handmaid's Tale + 29.95 + + + Sense and Sensibility + 19.95 - - The Handmaid's Tale - 29.95 - - - Sense and Sensibility - 19.95 - "; private string EXAMPLE_DATA_3 = @" @@ -566,17 +566,46 @@ public class UdonXMLTest : UdonSharpBehaviour "; + private string EXAMPLE_DATA_5 = @" + + + + + + + + + + + + + + + Hello worlds. + + + + + + + + + + + +"; + public void Start() { - var root = udonXml.LoadXml(EXAMPLE_DATA_4.ToCharArray()); + var root = udonXml.LoadXml(EXAMPLE_DATA_5); if (root == null) { Debug.Log("ROOT WAS NULL!!"); return; } - - for (var i1 = 0; i1 != udonXml.GetChildNodesCount(root); i1++) + + /*for (var i1 = 0; i1 != udonXml.GetChildNodesCount(root); i1++) { var level1 = udonXml.GetChildNode(root, i1); Debug.Log("name: " + udonXml.GetNodeName(level1) + " children: " + udonXml.GetChildNodesCount(level1)); @@ -592,6 +621,8 @@ public void Start() udonXml.GetChildNodesCount(level3) + " value: " + udonXml.GetNodeValue(level3)); } } - } + }*/ + + Debug.Log(udonXml.SaveXml(root)); } } \ No newline at end of file diff --git a/version.txt b/version.txt index 6da28dd..8294c18 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.1.1 \ No newline at end of file +0.1.2 \ No newline at end of file