Here is the description of the algorithm which generates XML from values entered in the form.

XML generation

Follow the steps :

  1. Enter values in the form.
  2. Click on "generate XML" to get the XML generated from the form.
form :

Benefits

Using that generation of XML from XPath/value pairs, it is possible to create generically any kind of XML from any kind of web form.

All you have to do is to fill the "name" attribute of the input fields of the form with the XPath expression of the value in the XML to generate.

Here the algorithm is written in javascript, il is added inside the web page and the XML is generated in the client web browser. it is also possible to send a request with all the XPath/value pairs as parameters to the server and generate the XML on the server. In order to do that, I advise you to send it as a multipart request in order to keep the order of the fields, it will be a lot easier to read.

Explanation

Algorithm of generation of XML from XPath/value pairs

The purpose of that algorithm is to generate a XML document from the values entered in the form. Those values are associated to a XPath expression which indicates where that value must be put in the XML. In order to respect the namespaces declared in the schema, I will also need a map of prefixes and namespaces, so the prototype of the function to create is :

function addFormElm(xpath,value,xmlDoc,namespaces) {}
The XPath associated to that value is very simple, 4 cases are possible. The first three cases only vary by the end of the expression, and the 4th one is the case of multiple markups with the same name :
  1. "/root/@name" which means I have to create an attribute "name" under a "root" markup.
  2. "/root/name/text()" which means I have to create a text node in a "name" markup under a "root" markup.
  3. "/root/name" which means the same thing than above, but the way it has been declared in the specification is not the same, and its representation in the form is different(a text input instead of a textarea).
  4. "/root/element[3]/..." which means I have to create 3 (there is no 0 index in XPath) markups "element" under a "root" markup.
I will have to browse the XML tree in order to see if the elements declared in the XPath expression are already created or no. So I need a variable currentElement which will contain the node I am actually on. Then I will start by splitting the XPath expression around the "/" character. I also need a variable addedValue for the third ending case, explained below.
function addFormElm(xpath,value,xmlDoc,namespaces) {
	var currentElement = null;
	var addedValue = null;
	
	var xpathParts = xpath.split("/");
	for (var i=1 ; i < xpathParts.length ; i++) {
		var nodeName = xpathParts[i];

The conditions which will lead to the different cases depend on the value of variable nodeName. Those conditions are written from the most constrained to the least constrained, so the algorithm is not written chronogically and I begin by the ending possibilites.

Ending cases
A nodeName equals to "text()" :
		//case of a text node : this element name is text(), second ending
		// example in the request : /network/noise/neuron[2]/sample[1]/text()=1, 2, 3, 4, 5, 6, 7, 8, 9
		// the hexadecimal characters have already been decoded
		if (nodeName == "text()") {
			addedValue = replaceTextNodesDirectlyUnder(currentElement,value);
The function used "replaceTextNodesDirectlyUnder" is :
function replaceTextNodesDirectlyUnder(element,text) {
	var children = element.childNodes;
	for (var i = 0; i < children.length; i++) {
		if (children.item(i).nodeType == 3) {
			element.removeChild(children.item(i));
		}
	}
	var textNode = element.ownerDocument.createTextNode(text);
	element.appendChild(textNode);
	return textNode;
}
So it removes all the text nodes (whose type is 3) directly under current node and appends a new one with content text. So, if there was a text node, its value is replaced by new one. Then I treat the case of a nodeName containing attribute declaration "@" :
		//case of finding an attribute child (1st possible ending)
		} else if (nodeName.match("@")) {
			//remove the '@' at the beginning of the node name
			nodeName = nodeName.replace(/^@/,"");
			addedValue = xmlDoc.createAttribute(nodeName);
			addedValue.value = value;
			currentElement.setAttributeNode(addedValue);

For the moment I only treat the clear ending cases. I will now have to browse the XML document and create the missing markups.

Browse the XML tree
When I find a nodeName, I have to detect its namespace and its localName :
		//case of finding an element child, have to browse further the tree
		} else {
			var namespaceURI = getNamespaceURIFromNodeName(nodeName,namespaces);
			//when using method getElementsByTagNameNS(), node name specified
			// must be localName, i.e. without prefix
			var localName = removePrefix(nodeName);
The function getNamespaceURIFromNodeName(), implemented in dom_utils.js, retrieves the namespace (if declared) from the map "namespaces", argument of that function. The function removePrefix() removes any string before a ":" character in the nodeName. At start, currentElement is null and I have to create the root markup of the XML document if not already created :
			//start of the xpath browsing, init currentNode to the root
			if (!currentElement) {
				if(!xmlDoc.documentElement) {
					//create the root node
					var rootElement = xmlDoc.createElementNS(namespaceURI,nodeName);
					xmlDoc.appendChild(rootElement);
				}
				currentElement = xmlDoc.documentElement;

Then I have to treat the case of multiple markups with the same name, detected with the presence of brackets "[2]" in the nodeName.

Multiple markups with the same name
That case is the most complicated one. I will have to retrieve the number of markups with the same name i have to add :
				//case of several elements with the same name : element[2]
			} else if (nodeName.match(/\[/)) {
				var indexOfOpeningBracket = nodeName.indexOf("[");
				var indexOfClosingBracket = nodeName.indexOf("]");
				//WARNING : the index of the elements in XPath standardisation begins from 1 so this childNumber is index+1
				var childNumber =  parseInt(nodeName.substring(indexOfOpeningBracket + 1,indexOfClosingBracket));
At this moment, nodeName still contains the suffix "[2]" so I remove it from nodeName and I update the localName variable :
				nodeName = nodeName.substring(0,indexOfOpeningBracket);
				localName = removePrefix(nodeName);
Last step is to create the missing elements, if needed, and update the currentElement variable :
				//if this child doesn't exist, create it
				var alreadyCreatedChildNumber = currentElement.getElementsByTagNameNS(namespaceURI,localName).length;
				for (var j = alreadyCreatedChildNumber; j < childNumber; j++) {
					var element = xmlDoc.createElementNS(namespaceURI,nodeName);
					currentElement.appendChild(element);
				}
				currentElement = currentElement.getElementsByTagNameNS(namespaceURI,localName).item(childNumber-1);

Finally, I arrive to the usual case, the nodeName only contains the name of a markup to create if does not exist.

Usual case
namespacesURI and localName variables are already defined above, and there must be only one element of that name under currentElement :
				//usual case, finding a single element
			} else {
				// if this element doesn't exist, it has to be created
				if (currentElement.getElementsByTagNameNS(namespaceURI,localName).length == 0) {
					var element = xmlDoc.createElementNS(namespaceURI,nodeName);
					currentElement.appendChild(element);
				}
				// continues the xpath browsing
				currentElement = currentElement.getElementsByTagNameNS(namespaceURI,localName).item(0);
			}
		}
	}

Here, I still do not take care of the third ending case : "/root/name" which means the XPath expression is just finished, and I have to do something with the value associated !

Last ending case
The for loop on the XPath expression is over but the variable addedValue is still empty, I have to create a text node under currentElement :
	//if we finish that parsing without adding anything, that means we are in the third ending case, i.e. a string data node
	if (addedValue == null && currentElement != null) {
		addedValue = replaceTextNodesDirectlyUnder(currentElement,value);
	}
And the algorithm is finished !! Final content is :
function addFormElm(xpath,value,xmlDoc,namespaces) {
	var currentElement = null;
	var addedValue = null;
	
	var xpathParts = xpath.split("/");
	for (var i = 1; i < xpathParts.length; i++) {
		var nodeName = xpathParts[i];
		
		//case of a text node : this element name is text(), second ending
		// example in the request : /network/noise/neuron[2]/sample[1]/text()=1, 2, 3, 4, 5, 6, 7, 8, 9
		// the hexadecimal characters have already been decoded
		if (nodeName == "text()") {
			addedValue = replaceTextNodesDirectlyUnder(currentElement,value);
			
			//case of finding an attribute child (1st possible ending)
		} else if (nodeName.match("@")) {
			//remove the '@' at the beginning of the node name
			nodeName = nodeName.replace(/^@/,"");
			addedValue = xmlDoc.createAttribute(nodeName);
			addedValue.value = value;
			currentElement.setAttributeNode(addedValue);
			
			//case of finding an element child, have to browse further the tree
		} else {
			var namespaceURI = getNamespaceURIFromNodeName(nodeName,namespaces);
			//when using method getElementsByTagNameNS(), node name specified must be localName, i.e. without prefix
			var localName = removePrefix(nodeName);
			
			//start of the xpath browsing, init currentNode to the root
			if (!currentElement) {
				if(!xmlDoc.documentElement) {
					//create the root node
					var rootElement = xmlDoc.createElementNS(namespaceURI,nodeName);
					xmlDoc.appendChild(rootElement);
				}
				currentElement = xmlDoc.documentElement;
				
				//case of several elements with the same name : element[2]
			} else if (nodeName.match(/\[/)) {
				var indexOfOpeningBracket = nodeName.indexOf("[");
				var indexOfClosingBracket = nodeName.indexOf("]");
				//WARNING : the index of the elements in XPath standardisation begins from 1 so this childNumber is index+1
				var childNumber =  parseInt(nodeName.substring(indexOfOpeningBracket + 1,indexOfClosingBracket));
				
				nodeName = nodeName.substring(0,indexOfOpeningBracket);
				localName = removePrefix(nodeName);
				//if this child doesn't exist, create it
				var alreadyCreatedChildNumber = currentElement.getElementsByTagNameNS(namespaceURI,localName).length;
				for (var j = alreadyCreatedChildNumber; j < childNumber; j++) {
					var element = xmlDoc.createElementNS(namespaceURI,nodeName);
					currentElement.appendChild(element);
				}
				currentElement = currentElement.getElementsByTagNameNS(namespaceURI,localName).item(childNumber-1);
				
				//usual case, finding a single element
			} else {
				// if this element doesn't exist, it has to be created
				if (currentElement.getElementsByTagNameNS(namespaceURI,localName).length == 0) {
					var element = xmlDoc.createElementNS(namespaceURI,nodeName);
					currentElement.appendChild(element);
				}
				// continues the xpath browsing
				currentElement = currentElement.getElementsByTagNameNS(namespaceURI,localName).item(0);
			}
		}
	}
	
	//if we finish that parsing without adding anything, that means we are in the third ending case, i.e. a string data node
	if (addedValue == null && currentElement != null) {
		addedValue = replaceTextNodesDirectlyUnder(currentElement,value);
	}
}

Participation

All those developments are released under Cecill licence.

The project Forms Generator is available on a svn, contact me for more information. In the future, a project might be created on sourceforge.

Projects svg 3d, XSD to RelaxNG converter, javascript SAX parser, javascript RelaxNG validator, javascript datatype library are published on Google code. Project javascript SAX parser is also published on Github.

Requirements

In order to have javascript working as expected, please use last version of firefox.

For the applets, you will need to have java installed on your computer.