Skip to content

Commit

Permalink
Many parsing issues fixed, allow exporting XML
Browse files Browse the repository at this point in the history
  • Loading branch information
Foorack committed Apr 12, 2020
1 parent b9707bb commit f160f18
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 61 deletions.
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <books> node by index
var books = udonXml.GetChildNode(root, 1); // Index 0 will be <?xml> tag
Expand All @@ -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.


Expand Down
194 changes: 165 additions & 29 deletions UdonXML.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
*/

Expand Down Expand Up @@ -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++)
Expand All @@ -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++)
Expand All @@ -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++)
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -297,33 +313,32 @@ 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)
{
hasTagSplitOccured = true;
}
else
{
if (c == ' ' && !hasNodeNameEnded)
{
hasNodeNameEnded = true;
}

if (hasNodeNameEnded)
{
// if tag name or tag value
Expand All @@ -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}</{nodeName}>";
}
}
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);
}

/**
Expand Down
Loading

0 comments on commit f160f18

Please sign in to comment.