XML processing with JavaScript in the context of a browser is somewhat simplified as it can
leverage the DOM API already part of the browser for dealing with HTML which is a dialect
of XML. This is the approach we present in this section. To use JavaScript outside of a web browsing context,
then one should consider using the jsdom
[70] package for Node.js.
Parsing a string containing an XML structure to get the corresponding DOM structure is done by calling
new DOMParser().parseFromString(xmlStr,"text/xml")
which returns a DOM structure that can be manipulated using similar calls as in Java
.
For example, children
to get the list of children elements,
attributes
to get a mapping of attribute names with the corresponding values,
appendChild(node)
to add a new child to the current element, etc.
Once the DOM structure has been modified, a string with the XML structure can be obtained by calling
new XMLSerializer().serializeToString(document)
The resulting string is a correct XML structure, but is not indented so some further processing is needed to get a more readable display. One way is to use an XSLT transformation within the browser, but browsers often differ on how they handle such transformations. See this example for such an approach. We instead use a specialized string formatter.
We illustrate JavaScript XML processing through a very simple web page (see Figure 10.1) that embeds our running example, compacting and expanding XML structures.
Figure 10.1. Display of the webpage for exercising JavaScript compaction and expansion.
![]() |
The left part shows the XML content and the right part shows the compact form once the
user has clicked on the compact
button.
Figure 10.2. HTML to create the web page for compaction and expansion
1 <!DOCTYPE html> <html lang="en"> <head><title>Compaction and expansion of XML</title> 5 <script src="https://code.jquery.com/jquery-latest.min.js"></script> <script src="DOMCompact.js"></script> <script src="CompactTokenizer.js"></script> <script src="DOMExpand.js"></script> <script src="FormatXML.js"></script> 10 <style> textarea{font-family: 'Courier New', Courier, monospace;font-size: large;} .center {text-align: center;} </style> </head> 15 <body>
<h1>Compaction and expansion of XML</h1> <table> <tr><th>XML</th><th>Compact form</th></tr> <tr> 20 <td><textarea name="xml" id="xml" cols="50" rows="20"></textarea></td>
<td><textarea name="text" id="text" cols="50" rows="20"></textarea></td>
</tr> <tr> <td class="center">
25 <input type="button" id="compact" value="compact"> <input type="button" id="pprint" value="pretty-print"> </td> <td class="center"><input type="button" id="expand" value="expand"></td> </tr> 30 </table> </body> </html>
|
|
|
|
|
Text box in which the XML content can be pasted. |
|
Text box where the compaction will appear. |
|
Buttons for launching the compaction, expansion and pretty-print of the expanded XML. |
Example 10.26. [DOMCompact.js
] Text compaction of the cellar book (Example 2.2) with JavaScript using the DOM model
Compare this program with Example 8.1.
1 // taken from https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType const ELEMENT_NODE = 1;const TEXT_NODE = 3; const COMMENT_NODE = 8; 5 function domCompact(node, indent){
const nodeType = node.nodeType;
switch (nodeType) { case ELEMENT_NODE:
10 let out=[]; // create list of compacted strings indent+=" ".repeat(node.nodeName.length+1) const attributes = node.attributes;
for (let i = 0; i < attributes.length; i++) { const attr = attributes[i]; 15 out.push(`@${attr.nodeName}[${attr.nodeValue}]`); } const children=node.childNodes;
for (let i = 0; i < children.length; i++) { const child = children[i]; 20 // skip comment and empty text nodes if (child.nodeType==COMMENT_NODE || (child.nodeType==TEXT_NODE && child.textContent.trim().length==0))continue; out.push(domCompact(child,indent)); 25 } return `${node.nodeName}[${out.join("\n"+indent)}]`;
case TEXT_NODE:
return node.textContent.trim() default: 30 console.log("Should never happen: bad nodeType",nodeType) } } $(document).ready(function(){ $("#compact").click(function(){
35 const xmlStr=$("#xml").val(); const doc=new DOMParser().parseFromString(xmlStr,"text/xml"); $("#text").val(domCompact(doc.documentElement,"")); }) })
|
Constants for relevant DOM node types. |
|
Start of function to produce a compact string from a |
|
Determines the node type and switch to the appropriate code. Only element and text types are dealt with. |
|
If the node is an element, a list of compact string is initialized for adding attributes and children nodes. The new indentation value is computed for the children nodes. |
|
Each attribute is added with its name preceded by |
|
Each child node is added with a recursive call to
|
|
Returns a new string in which the compacted strings for the attributes and children are joined with a new line and the appropriate indentation. |
|
A text node is the trimmed content of the node. |
|
The callback function for the |
Parsing a compact form in order to create an XML document follows an organization similar to the one we have shown in Chapter 9 and Section 10.1.3. We first define an object for a specialized tokenizer which returns only meaningful units that will be used to create the XML elements and attributes of the resulting XML document.
Example 10.27. [CompactTokenizer.js
] JavaScript specialized string scanner that
returns tokens of compact form
Compare this program with Example 9.1.
1 function CompactTokenizer(source){this.token="";
this.tokens=source.split(/(\[|\]|\n|@|[^\[\]\n@]+)/) // separate all tokens
.filter((e)=>e.length>0 && !e.match(/^\n? *$/) // keep only non empty tokens 5 ); this.nextToken = function(){
return this.token = this.tokens.length==0 ? null : this.tokens.shift(); } 10 this.skip=function(sym){
if(this.token==sym) return this.nextToken(); else 15 throw new ArgumentError("skip:"+sym+" expected but "+ this.token+ " found"); } }
|
Constructor for building a specialized tokenizer for the string given as source. |
|
Keeps the current token. |
|
Builds the array of all tokens by splitting
according to important elements of the inputs. Here we are interested in
the content matched by the regular expressions, so we
capture the content matched. However as the
input is matched by the regular expression, empty strings between each
match are produced. These empty matches and those containing only spaces
are removed by the call to |
|
Retrieves the first element of the |
|
Checks that the current token corresponds to the parameter. If this is the case, returns the next token, otherwise raises an exception. In principle, this exception should never be raised but this function might be useful (and it was!) for debugging purposes. |
The creation of a new XML document is done by a call to the
domExpand
function which launches a series of recursive calls to expand
.
Example 10.28. [DOMExpand.js
] JavaScript compact form parsing in order to create
an XML document
A sample input for this program is Example 5.8 to yield Example 2.2. Compare this program with Example 9.2.
1 function expand(st,doc,defaultNS,elementName){let elem; const colonPos=elementName.indexOf(":")
if (colonPos>0) // qualified element name 5 elem = doc.createElementNS(elementName.substring(0,colonPos), elementName.substring(colonPos+1)) else elem=doc.createElementNS(defaultNS,elementName); st.skip("["); 10 while (st.token=="@"){ // process attributes
const attName=st.skip("@"); st.nextToken(); const attValue=st.skip("["); if (attName=="xmlns")defaultNS=attValue; // set new default namespace 15 elem.setAttribute(attName,attValue); st.nextToken(); st.skip("]") } while (st.token!="]"){
20 const str=st.token.trim(); st.nextToken(); if (st.token=="["){ elem.appendChild(expand(st,doc,defaultNS,str))
} else { 25 elem.appendChild(doc.createTextNode(str)) } } st.skip("]"); return elem; 30 } function domExpand(s){
let st=new CompactTokenizer(s); let rootName; 35 while(st.nextToken()!="[")rootName=st.token; let doc= new Document() doc=expand(st,doc,"",rootName); return doc; } 40 $(document).ready(function(){
$("#expand").click(function(){
const text=$("#text").val(); const xmlStr=new XMLSerializer().serializeToString(domExpand(text)) 45 $("#xml").val(xmlStr) }) $("#pprint").click(function(){
const xmlStr=$("#xml").val(); $("#xml").val(formatXml(xmlStr)) 50 }) })
|
Defines the function with the following parameters: the |
|
Creates a new element checking if the element name is qualified, in which case it sets the appropriate namespace on creation otherwise it uses the default namespace. |
|
Each attribute is created with a call to |
|
Loops on each child. Gets the string that follows, which is then trimmed. |
|
If the current token is an opening bracket, then we create a new element
by a recursive call to |
|
The function receives a string with the compact form for which the |
|
Associates callback functions with the |
|
Function that gets the value of the expand string (the right text box), gives it to the
|
|
Function that gets the value of the raw XML and formats it using a function that it not shown here. |