SweetButty 0.1

Sections

About

Code

 /**
 * @filename
 *    sweetButty01.js
 *
 * @author
 *    Julian Turner, Ripley, Derbyshire, England
 *    http://www.baconbutty.com
 *
 * @copyright 
 *    (c) Julian Turner 2007
 *
 * @version
 *    0.1 (developed July 2007)
 *
 *    Called version 0.1, because it is not quite
 *    fully compliant with SweetXML 0.2
 *
 * @fileoverview
 *    sweetButty01.js enables ``quick and dirty`` parsing
 *    of SweetXML files.
 *
 *    SweetXML is the creation of Paul Phillips Cantrell
 *    http://innig.net/software/sweetxml/index.html
 * 
 * @dependencies
 *    sweetButty01.js has no dependencies. 
 */

Licence

Page1

Licence Terms - SweetButty 1.0

The licence terms are as follows, which you are deemed to agree to by downloading, running, or modifying SweetButty 1.0:-

  1. SweetButty is free software.
  2. You may copy, run and modify SweetButty 1.0 where this constitues use for domestic purposes, or use for the purposes of a not-for profit organisation, and not use as part of a business.
  3. You may copy, run and modify SweetButty 1.0 as part of a business, but only for reasonable evaluation purposes.
  4. If you make any copy or modification, you must reproduce on it my copyright notice and these licence terms; and, if you are distributing a modification, you must identify by comments in the code, what the modifications are.
  5. You may not distribute copies of SweetButty 1.0, whether alone, or as part of any other program, except on these licence terms.
  6. You may not sell copies of SweetButty 1.0, whether alone, or as part of any other program.
  7. By downloading SweetButty 1.0 you agree that it is copied, run and modified by you at your sole risk, and that it is provided as is, without any promise, warranty, condition, or guarantee of any kind relating to its condition, compliance with description, fitness for purpose, or freedom from bugs or errors.
  8. I assume no duty of care to you in relation to SweetButty 1.0, and disclaim and exclude any liability to you (whether in statute, contract, tort, negligence or otherwise howsoever) for any loss, damage, or liability you may suffer through copying, running, modifying or distributing SweetButty 1.0 or doing anything else with SweetButty 1.0 (whether permitted by this licence or not).
  9. This licence may be terminated by me, and the terms of this licence may be substituted or replaced by me, by notice given by means of an update to this blog entry, or any other page on this web site which deals with SweetButty 1.0.

classSweetButty - Start

Code

var classSweetButty /*: Function*/ = (function()
{

Notes

Purpose

Encapsulates functionality to provide simple parsing of SweetXML files.

Constructor

classSweetButty

Extends

Nothing

Public Methods

setIndentUnit(indentUnit : String) : void

Specify what characters the SweetButty file uses for indentation (e.g. 4 spaces or a tab character).

process (sweetXMLMarkup : String) : Object

Parses, and returns the root Object of the parsed tree.

Requires

Nothing.

Remarks

The class returns an Object tree for the parsed nodes. The root Object is added by the parser in addition to the nodes within the SweetXML markup.

Each Object in the tree has the following properties:-

nodeType : Number

This gives you:

1 = Element

3 = Text

4 = CDATA-Section

8 = Comment

nodeName : String

This gives you <tagName>, #text, #comment or #cdata-section.

nodeValue : String

This gives you the text of a Text, CDATASection and Comment node, but an empty string for Element nodes.

[0] ... [N] : Node

These are the childNodes, but accessed through a direct accessor, rather than a childNodes collection.

<attribute-name> : String

The value of any attribute on the Node.

<node-name> : Node or String

There are 4 possibilities here:-

  1. If the node has a single child with that name, which itself has a single text node, then you will get the value of that text node.
  2. If the node has a single child with that name, which itself has multiple sub-child nodes, then you get child node object.
  3. If the node has multiple children with that name, each of which has a single text node, then you get an array of strings.
  4. If the node has multiple children with that name, each of which has sub-child nodes, then you get an array of child nodes.

See examples below if this sounds a little confusing.

Example

None

classSweetButty() : void

Code

function classSweetButty() /*: void*/
{
	this.indentUnit /*: String*/;

	this.initialise();
}

var constr /*: Function*/ = classSweetButty;

Notes

Purpose

Constructor function.

Arguments

None

Returns

void

Requires

this::initialise

Remarks

None

Example

None

initialise() : void

Code

function initialise () /*: void*/
{
	this.indentUnit = "\t";
}

AddMethod(constr, initialise);

Notes

Purpose

Initialisation function

Arguments

None

Returns

void

Requires

this::AddMethod

Remarks

Specifies that the default indent unit will be a tab character.

Example

None

setIndentUnit(indentUnit : String) : void

Code

function setIndentUnit(
	indentUnit /*: String*/
) /*: void*/
{
	this.indentUnit = indentUnit;
}

AddMethod(constr, setIndentUnit);

Notes

Purpose

You need to tell the parser what indent unit the SweetButty file is using: either TAB or 4 spaces.

Arguments

indentUnit : String

A TAB or 4 spaces

Returns

void

Requires

this::AddMethod

Remarks

None

Example

None

parse(sweetXMLMarkup : String) : Object

Code

function parse(
	markup /*: String*/
) /*: Node*/
{	

	var nodes /*: Array.<Node>*/ = this.splitIntoNodes(markup);

	var rootNode /*: Node*/ = this.nodesToTree(nodes);

	this.createTreeShortcuts(rootNode);

	return rootNode;
}

AddMethod(constr, parse);

Notes

Purpose

The primary parsing function.

Arguments

sweetXMLMarkup : String

The SweetXML markup.

Returns

Node

Requires

this::AddMethod
this::createTreeShortcuts
this::nodesToTree
this::parse
this::splitIntoNodes

Remarks

None

Example

None

NODE_REGEXP

Code

var NODE_INDENT = "(\\t+| +)*";
var NODE_TAG_NAME = "([^ \\t\\r\\n\"'=\:\!\#]*)";
var NODE_ATTR_DIVIDER = "([ \\t]*[\\r\\n]+[ \\t]*\\|[ \\t]*|[ \\t]+)";
var NODE_ATTR = "[^ \\t\\r\\n\"'=]+=(\"[^\"]*\"|'[^']*'|[\\w\\-\\.]+)";
var NODE_ATTRS = "((" + NODE_ATTR_DIVIDER + NODE_ATTR + ")*)";
var NODE_INLINE = "[ \\t]*(\\:)?";
var NODE_TEXT = "[ \\t]*(!?\"[^\"]*\"|!?'[^']*'|!?[\\w\\-\\.]+)?";
var NODE_COMMENT = "([ \\t]*\\#[^\\r\\n]+)?";
var NODE_PATTERN = 
	NODE_INDENT + 
	NODE_TAG_NAME + 
	NODE_ATTRS + 
	NODE_INLINE + 
	NODE_TEXT + 
	NODE_COMMENT + 
	"[ \\t]*(\\r\\n|\\n\\r|\\r|\\n|$)";

var NODE_REGEXP = new RegExp(NODE_PATTERN, "gi");

Notes

Purpose

A range of Regular Expressions for parsing the SweetXML node grammar.

ATTR_REGEXP

Code

var ATTR_PATTERN = "([^ \\t\\r\\n\"'=]+)=(\"([^\"]*)\"|'([^']*)'|([\\w\\-\\.]+))";
var ATTR_REGEXP = new RegExp(ATTR_PATTERN, "gi");

Notes

Purpose

A range of Regular Expressions for parsing the SweetXML attributes grammar.

TEXT_REGEXP

Code

var TEXT_PATTERN = "[ \\t]*(!?\"([^\"]*)\"|!?'([^']*)'|!?([\\w\\-\\.]+))";
var TEXT_REGEXP = new RegExp(TEXT_PATTERN);

Notes

Purpose

A range of Regular Expressions for parsing the SweetXML text nodes grammar.

I have extended the SweetXML grammar, by allowing the `` character as a prefix to identify CDATASections.

COMMENT_REGEXP

Code

var COMMENT_PATTERN = "[ \\t]*\\#([^\\r\\n]+)";
var COMMENT_REGEXP = new RegExp(COMMENT_PATTERN);

Notes

Purpose

A range of Regular Expressions for parsing the SweetXML comment nodes grammar.

Node() : void

Code

function Node()
{
	this.isNode /*: Boolean*/ = true;

	this.depth /*: int*/ = 0;
	this.length /*: int*/ = 0;

	this.nodeType /*: int*/ = 0;
	this.nodeName /*: String*/ = "";
	this.nodeValue /*: String*/ = "";


	this.parentNode /*: Node*/ = null;
}

Notes

Purpose

A simple Object constructor for the Node objects created by the parser.

Arguments

None

Returns

void

Requires

Nothing

Remarks

None

Example

None

splitIntoNodes(sweetXMLMarkup : String) : Array.<Node>

Code

function splitIntoNodes(
	markup /*: String*/
) /*: Array.<Node>*/
{	
	var execResult /*: Array*/;

	var nodes /*: Array.<Node>*/ = [];
	var node /*: Node*/;

	var nodeIndentPart /*: String*/ = "";
	var nodeNamePart /*: String*/ = "";
	var nodeAttributesPart /*: String*/ = ""; 
	var nodeInlinePart /*: String*/ = "";
	var nodeTextPart /*: String*/ = "";
	var nodeCommentPart /*: String*/ = "";

	var indentUnitLength /*: String*/ = this.indentUnit.length;

	var attrExecResult /*: Array*/ = [];
	var attrName /*: String*/ = "";
	var attrValue /*: String*/ = "";

	var textExecResult /*: Array*/ = [];
	var text /*: String*/ = "";
	var textNode /*: Object*/;

	var commentExecResult /*: Array*/ = [];
	var commentNode /*: Object*/;
	
	var overflow /*: int*/ = 1000;

	while(execResult = this.exec(NODE_REGEXP, markup))
	{
		/* Clear*/
		node = null;
		textNode = null;
		commentNode = null;

		/* At end */
		if (execResult.index == markup.length)
		{
			break;
		}

		if (!overflow--)
		{
			break;
		}

		/* Empty */
		if (!this.trim(execResult[0]))
		{
			continue;
		}

		//alert(this.execResultToString(execResult));

		/* Get Node Parts*/
		nodeIndentPart = execResult[1];
		nodeNamePart = this.trim(execResult[2]);
		nodeAttributesPart = this.trim(execResult[3]); 
		nodeInlinePart = this.trim(execResult[7]);
		nodeTextPart = this.trim(execResult[8]);
		nodeCommentPart = this.trim(execResult[9]);

		/* Node*/
		node = new Node();

		/* Calculate Depth*/
		node.depth = Math.ceil(nodeIndentPart.length / indentUnitLength) + 1;

		//alert(nodeIndentPart.length);
		
		/* Node Type*/
		if (nodeNamePart)
		{
			node.nodeType = 1;
			node.nodeName = nodeNamePart;
		}
		else if (nodeTextPart)
		{
			if (/^!/.test(nodeTextPart))
			{
				node.nodeType = 4;
				node.nodeName = "#cdata-section";
			}
			else
			{
				node.nodeType = 3;
				node.nodeName = "#text";
			}
		}
		else if (nodeCommentPart)
		{
			node.nodeType = 8;
			node.nodeName = "#comment";
		}
		else
		{
			continue;
		}

		/* Node Attributes*/
		if (node.nodeType == 1 && nodeAttributesPart)
		{
			while(attrExecResult = this.exec(ATTR_REGEXP, nodeAttributesPart))
			{
				attrName = attrExecResult[1]; 
				attrValue = attrExecResult[3] || attrExecResult[4] || attrExecResult[5];
				attrValue = this.unescapeAttrHTML(attrValue);
	
				node[attrName] = attrValue;
			}			
		}

		/* Inline Extras */
		if (node.nodeType == 1 && !nodeInlinePart)
		{
			nodes[nodes.length] = node;
			continue;
		}

		/* Look for inline parts */
		nodes[nodes.length] = node;

		/* Text */
		if (nodeTextPart)
		{
			textExecResult = this.exec(TEXT_REGEXP, nodeTextPart)
			
			if ((node.nodeType == 3 || node.nodeType == 4) && !textExecResult)
			{
				continue;
			}

			text = textExecResult[2] || textExecResult[3] || textExecResult[4];
			text = this.unescapeAttrHTML(text);

			if (node.nodeType == 1)
			{
				textNode = new Node();
				textNode.depth = node.depth + 1; 

				if (/^!/.test(nodeTextPart))
				{
					textNode.nodeType = 4;
					textNode.nodeName = "#cdata-section";
				}
				else
				{
					textNode.nodeType = 3;
					textNode.nodeName = "#text";
				}

				textNode.nodeValue = text;

				nodes[nodes.length] = textNode;
			}
			else if (node.nodeType == 3 || node.nodeType == 4)
			{
				node.nodeValue = text;
			}
		}

		/* Comment */
		if (nodeCommentPart)
		{
			commentExecResult = this.exec(COMMENT_REGEXP, nodeCommentPart);
	
			text = this.trim(commentExecResult[1]);
			text = this.unescapeAttrHTML(text);

			if (node.nodeType == 1)
			{
				commentNode = new Node();
				commentNode.depth = node.depth + 1; 
				commentNode.nodeType = 8;
				commentNode.nodeName = "#comment";
				commentNode.nodeValue = text;

				nodes[nodes.length] = commentNode;
			}
			else if (node.nodeType == 3 || node.nodeType == 4)
			{
				commentNode = new Node();
				commentNode.depth = node.depth; 
				commentNode.nodeType = 8;
				commentNode.nodeName = "#comment";
				commentNode.nodeValue = text;

				nodes[nodes.length] = commentNode;
			}
			else if (node.nodeType == 8)
			{
				node.nodeValue = text;
			}

		}	
	}

	return nodes;
}

AddMethod(constr, splitIntoNodes);

Notes

Purpose

This is the first (and main) part of the 3-part parsing operation.

It uses the regular expressions to parse the SweetXML and creates a single flat Array of Node objects in tree depth-first order.

The depth and parent/child relationships are worked out in the next function: nodesToTree.

Arguments

sweetXMLMarkup : String

The SweetXML text file.

Returns

Array.<Node>

A single flat Array of Node objects in tree depth-first order.

Requires

this::AddMethod
this::Node
this::execResultToString
this::splitIntoNodes
this::trim
this::unescapeAttrHTML

Remarks

None

Example

None

nodesToTree(nodes : Array.<Node>) : Node

Code

function nodesToTree(
	nodes /*: Array.<Node>*/
) /*: Node*/
{	
	var root /*: Node*/ = new Node();
	root.depth = 0;
	root.nodeType = 1;
	root.nodeName = "root";

	var node /*: Node*/;
	var parents /*: Array*/ = [root];
	var parent /*: Object*/;
	var currentParent /*: Object*/ = root;
	var depth /*: int*/ = 0;
	var lastDepth /*: int*/ = 1;
	var lastElement /*: Object*/ = root;
	var depthDifference /*: int*/ = 0;
	var nodeName /*: String*/;

	for (var i = 0; i < nodes.length; i++)
	{
		node = nodes[i];
		
		if (node.depth == lastDepth)
		{
			currentParent[currentParent.length++] = node;
			node.parentNode = currentParent;
		}
		else if (node.depth > lastDepth)
		{
			node.depth = lastDepth + 1;
			lastDepth = node.depth;

			if (currentParent != lastElement)
			{
				currentParent = lastElement;
				parents.push(lastElement);
			}

			currentParent[currentParent.length++] = node;
			node.parentNode = currentParent;
		} 
		else
		{
			depthDifference = lastDepth - node.depth;
			
			while (depthDifference--)
			{
				parents.pop();
			}
	
			currentParent = parents[parents.length - 1];
			lastDepth = node.depth;

			currentParent[currentParent.length++] = node;
			node.parentNode = currentParent;
		}

		if (node.nodeType == 1)
		{
			lastElement = node;
		}

		//if (node.nodeType == 3 || node.nodeType == 4)
		//{
		//	for (var j = 0; j < parents.length; j++)
		//	{
		//		parents[j].nodeValue += node.nodeValue;
		//	}
		//}
	}

	nodes.unshift(root);
	return root;

	//if (root.length == 0)
	//{
	//	return null;
	//}
	//else if (root.length == 1)
	//{
	//	root[0].parentNode = null;
	//	return root[0];
	//}
	//else
	//{
	//	return root;
	//}
}

AddMethod(constr, nodesToTree);

Notes

Purpose

This organises the Nodes into a tree structure.

The result is then passed to createTreeShortcuts.

Arguments

nodes : Array.<Node>

The result of splitIntoNodes.

Returns

Node

The root node of the tree. This function will always create a root node above the root node of the parsed node tree, just to allow you to parse SweetXML fragments as well.

Requires

this::AddMethod
this::Node
this::nodesToTree

Remarks

None

Example

None

createTreeShortcuts(rootNode : Node) : void

Code

function createTreeShortcuts (
	node /*: Node*/
) /*: Node*/
{
	//alert(node.nodeName + " " + node.length);

	var child /*: Node*/ = node[0];
	var parent /*: Node*/ = node.parentNode;
	var nodeName /*: String*/ = node.nodeName;

	if (parent && node.length == 1 && child && (child.nodeType == 3 || child.nodeType == 4))
	{
		if (typeof parent[nodeName] == "undefined")
		{
			parent[nodeName] = child.nodeValue;
		}
		else if (typeof parent[nodeName] == "string")
		{
			var a /*: Array*/ = [parent[nodeName]];
			a.push(child.nodeValue);
			parent[nodeName] = a;
		}
		else if (typeof parent[nodeName] == "object")
		{
			var parentProperty = parent[nodeName];

			if (parentProperty.isNode)
			{
				var a /*: Array*/ = [parent[nodeName]];
				a.push(node);
				parent[nodeName] = a;
			}
			else
			{
				if (typeof (parentProperty[0]) == "string")
				{
					parentProperty.push(child.nodeValue);
				}
				else
				{
					parentProperty.push(node);
				}
			}
		}	

		return;
	}
	else if (parent)
	{
		if (typeof parent[nodeName] == "undefined")
		{
			parent[nodeName] = node;
		}
		else if (typeof parent[nodeName] == "string")
		{
			var a /*: Array*/ = [parent[nodeName]];
			a.push(node);
			parent[nodeName] = a;
		}
		else if (typeof parent[nodeName] == "object")
		{
			var parentProperty = parent[nodeName];

			if (parentProperty.isNode)
			{
				var a /*: Array*/ = [parent[nodeName]];
				a.push(node);
				parent[nodeName] = a;
			}
			else
			{
				parentProperty.push(node);
			}
		}
	}

	if (node.length == 0)
	{
		return;
	}

	for (var i = 0; i < node.length; i++)
	{
		child = node[i];

		if (child.nodeType == 1)
		{
			this.createTreeShortcuts(child);
		}
	}
}

AddMethod(constr, createTreeShortcuts);

Notes

Purpose

This is is a nice-to-have function which enables you to access childNodes by nodeName as a property of the the parentNode.

Arguments

rootNode : Node

The Node returned from nodesToTree.

Returns

void

The rootNode is operated on by reference.

Requires

this::AddMethod

Remarks

Given:

contacts
	contact
		name:George Bush
	contact
		name:Tony Blair	

the following are equivalent:

root.contacts[0][0];
root.contacts[0].name;
root.contacts.contact[0][0]
root.contacts.contact[0].name

Example

None

trim(s : String) : String

Code

function trim(
	s /*: String*/
) /*: String*/
{
	return s.replace(/^\s\s*/, "").replace(/\s*\s$/,"");
}

AddMethod(constr, trim);

Notes

Purpose

Trims whitespace from start and end of String.

Arguments

s : String

The String to trim.

Returns

String

The trimmed String.

Requires

this::AddMethod

Remarks

None

Example

None

escapeAttrHTML(string : String) : String

Code

function escapeAttrHTML(
	string /*: String*/
) /*: String*/
{
	return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
}

AddMethod(constr, escapeAttrHTML);

Notes

[NOT CURRENTLY USED]

Purpose

The characters ' " < > & are escaped in SweetButty strings in the same way as HTML attributes.

This would be used to do the escaping, prior to serialising the node tree.

Arguments

string : String

String to escape.

Returns

String

Escaped string.

Requires

this::AddMethod

Remarks

The characters ' " < > & are converted to &apos; etc.

Example

None

exec(re : RegExp, input : String) : Array

Code

function exec(
	re /*: RegExp*/,
	input /*: String*/
) /*: Array*/
{
	var execResult /*: Array*/ = re.exec(input);

	if (execResult === null)
	{
		return execResult;
	}

	for (var i /*: int*/ = 0; i < 10; i++)
	{
		if (typeof execResult[i] === "undefined" || execResult[i] == "undefined")
		{
			execResult[i] = "";
		}
	}

	return execResult;
}
AddMethod(constr, exec);

Notes

Purpose

Calls the exec method of the RegExp object on the supplied input.

Purpose is to convert unmatched parentheses that return undefined into blank strings, as blank strings are preferred.

Arguments

re : RegExp

The RegExp object on which the exec method is called.

input : String

The String in respect of which the exec method is called.

Returns

Array

The Array returned by RegExp.exec with undefined items converted to blank Strings.

Requires

this::AddMethod

Remarks

None

Example

None

unescapeAttrHTML(string : String) : String

Code

function unescapeAttrHTML(
	string /*: String*/
) /*: String*/
{
	return string.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&apos;/g, "'").replace(/&amp;/g, "&");
}

AddMethod(constr, unescapeAttrHTML);

Notes

Purpose

As SweetButty escapes the contents of attributes and text nodes in the same way as XML, this is used to unescape during the parsing process.

Arguments

string : String

String to unescape.

Returns

String

Unescaped String.

Requires

this::AddMethod

Remarks

None

Example

None

escapeHTML(string : String) : String

Code

function escapeHTML(
	string /*: String*/
) /*: String*/
{
	return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

AddMethod(constr, escapeHTML);

Notes

[NOT CURRENTLY USED]

Purpose

Converts < > & to &lt; &gt; &amp;.

Arguments

string : String

The string to escape.

Returns

String

The escaped string.

Requires

this::AddMethod

Remarks

None

Example

None

unescapeHTML(string : String) : String

Code

function unescapeHTML(
	string /*: String*/
) /*: String*/
{
	return string.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
}

AddMethod(constr, unescapeHTML);

Notes

[NOT CURRENTLY USED]

Purpose

Converts &lt; &gt; &amp; to < > &.

Arguments

string : String

The string to unescape.

Returns

String

The unescaped string.

Requires

this::AddMethod

Remarks

None

Example

None

execResultToString(execResult : Array) : String

Code

function execResultToString(
	execResult /*: Array*/
) /*: String*/
{
	if (!execResult)
	{
		return "";
	}
	var a = ["Result","======="];

	for (var key in execResult)
	{
		a.push(key + " : |" + execResult[key] + "|");
	}

	a.push("Substr : |" + execResult.input.substring(execResult.index, execResult.lastIndex) + "|");

	return a.join("\r\n");
}

AddMethod(constr, execResultToString);

Notes

Purpose

Debugging. Formats the Array returned by the RegExp.exec method.

Arguments

execResult : Array

The Array returned by the RegExp.exec method

Returns

String

The properties of the Array on separate lines.

Requires

this::AddMethod

Remarks

None

Example

None

nodeListToString(nodes : Array.<Nodes>) : String

Code

function nodeListToString(
	nodes /*: Array.<Nodes>*/
) /*: String*/
{
	var a /*: Array.<String>*/ = [];
	var node;

	for (var i /*: String*/ = 0; i < nodes.length; i++)
	{
		node = nodes[i];
		var nodeString /*: String*/ = [];
		
		for (var k in node)
		{
			nodeString.push(k + "            ".substring(0,12 - k.length) + "\t: " + "\"" + node[k] + "\"");
		}

		a.push(nodeString.join("\r\n"));
	}

	return a.join("\r\n\r\n==============================\r\n");
}

AddMethod(constr, nodeListToString);

Notes

Purpose

Debugging. Formats the Array of Nodes returned by parseToNodes as a String.

Arguments

nodes : Array.<Nodes>

The Array returned by parseToNodes.

Returns

String

Formatted string, consisting of each Node and its properties.

Requires

this::AddMethod

Remarks

None

Example

None

classSweetXML - END

Code

return constr;

})();

//makeModulePublicClass(classSweetXML);

Notes

Two cannibals eating a clown. One says to the other: "Does this taste funny to you.".

Copyright

Page1

SweetXML 0.2

SweetXML 0.2 and this grammar is the creation of Paul Phillips Cantrell.

Link to SweetXML homepage

sweetXML

Page1

sweetXml
    : directives
      line (EOL line)*
    ;

directives

Page1

directives
    : (nestedAngleBraces | S | EOL)*
    ;

nestedAngleBraces

Page1

protected
nestedAngleBraces
    : '<' (nestedAngleBraces | ~('<'|'>') )* '>'
    ;
    

line

Page1

line
    : indent
      element?
      S*
      comment?
    ;

indent

Page1

indent
    : S*
    ;

element

Page1

element
    : tag
    | quotedText;

tag

Page1

tag
    : NAME attributes? S* inlineText?
    ;

attributes

Page1

protected
attributes
    : S+ attribute attributes?
    ;

attribute

Page1

attribute
    :  NAME S* '=' S* text
    ;

inlineText

Page1

inlineText
    : ':' S* (text)?
    ;

text

Page1

text
    : quotedText
    | unquotedText
    ;

quotedText

Page1

quotedText
    : '"' (~('"'))* '"'
    | '\'' (~('\''))* '\''
    ;
    

unquotedText

Page1

unquotedText
    : (NAME_START | NAME_CONT)+
    ;

comment

Page1

comment
    : '#' (~(EOL))*;
    

NAME

Page1

NAME
    : NAME_START (NAME_START | NAME_CONT)*
    ;

NAME_START

Page1

fragment
NAME_START
    : 'A'..'Z' | 'a'..'z' | '_'
    | '\u00C0'..'\u00D6' | '\u00D8'..'\u00F6' | '\u00F8'..'\u02FF' | '\u0370'..'\u037D'
    | '\u037F'..'\u1FFF' | '\u200C'..'\u200D' | '\u2070'..'\u218F' | '\u2C00'..'\u2FEF'
    | '\u3001'..'\uD7FF' | '\uF900'..'\uFDCF' | '\uFDF0'..'\uFFFD'
    ;

NAME_CONT

Page1

fragment
NAME_CONT
    : '0'..'9' | '-' | '.'
    | '\u00B7' | '\u0300'..'\u036F' | '\u203F'..'\u2040'
    ;

S

Page1

S  : ' '
    | '\t';

EOL

Page1

EOL
    : '\r\n'
    | '\r'
    | '\n'
    ;