pax_global_header 0000666 0000000 0000000 00000000064 13135025736 0014517 g ustar 00root root 0000000 0000000 52 comment=67a470dad7319c88b6ab086562d0ba48a866d722
pixl-xml-1.0.13/ 0000775 0000000 0000000 00000000000 13135025736 0013353 5 ustar 00root root 0000000 0000000 pixl-xml-1.0.13/README.md 0000664 0000000 0000000 00000037426 13135025736 0014646 0 ustar 00root root 0000000 0000000 # Overview
This module provides a lightweight, fast, easy-to-use XML parser which generates a simplified object / array tree. This can be very useful for parsing XML configuration files and the like. It is 100% pure JavaScript and has no dependencies.
* Pure JavaScript, no dependencies
* Very fast parser (About 6X faster than [xml2js](https://www.npmjs.com/package/xml2js))
* Low memory usage (About 60% less than [xml2js](https://www.npmjs.com/package/xml2js))
* Fully synchronous operation, no callbacks
* Can parse XML strings, Buffers or load from files
* Can preserve or flatten attributes
* Can convert all keys to lower-case
* Can serialize objects back to pretty-printed or compact XML
# Usage
Use [npm](https://www.npmjs.com/) to install the module:
```
npm install pixl-xml
```
Then use `require()` to load it in your code:
```javascript
var XML = require('pixl-xml');
```
# Simplified API
The simplified API provides basic `XML.parse()` and `XML.stringify()` standalone functions for parsing and serializing XML. Also see the [Object-Oriented API](#object-oriented-api) below, for more control over your XML.
Parse some XML by passing a string to `XML.parse()`:
```javascript
var xml_string = '' +
'Hello' +
'Complex' +
'';
var doc = XML.parse( xml_string );
console.log( doc );
```
That would produce an object like the following:
```javascript
{
"Simple": "Hello",
"Node": {
"Key": "Value",
"_Data": "Complex"
}
}
```
Note that the outermost element is omitted from the object (`` in this case). Also, for complex elements that have both attributes (or sub-elements) *and* plain data, the plain data goes into a `_Data` property. Multiple elements with the same name at the same level are converted to an array.
You can also pass in a path to an XML file on disk, and it'll be loaded, then parsed:
```javascript
var config = XML.parse( 'conf/config.xml' );
console.log( config );
```
Parsing errors will be thrown as exceptions, so you'd better wrap `parse()` calls in a try/catch for safety:
```javascript
var doc = null;
try {
doc = XML.parse( 'my_xml_file.xml' );
}
catch (err) {
console.log("XML Parser Error: " + err);
}
console.log( doc );
```
## Options
You can pass an optional 2nd argument to `parse()`, which can be an object containing any of the following properties:
### preserveAttributes
This optional property, when set to `true`, will cause all XML attributes to be kept separate in their own sub-object called `_Attribs` for each element. For example, consider this snippet:
```javascript
var xml_string = '' +
'Hello' +
'Complex' +
'';
var doc = XML.parse( xml_string, { preserveAttributes: true } );
console.log( doc );
```
With the `preserveAttributes` flag set to true, this would produce the following object:
```javascript
{
"Simple": "Hello",
"Node": {
"_Attribs": {
"Key": "Value"
},
"_Data": "Content"
}
}
```
Notice the `Key` attribute of the `` element is now kept in an `_Attribs` sub-object. The only real purpose for this is if you expect to write the XML back out again (see [Composing XML](#composing-xml) below).
### lowerCase
This optional property, when set to `true`, will cause all keys to be lower-cased as the XML is parsed. This affects both elements and attributes. Example:
```javascript
var xml_string = '' +
'Hello' +
'Complex' +
'';
var doc = XML.parse( xml_string, { lowerCase: true } );
console.log( doc );
```
With the `lowerCase` flag set to true, this would produce the following object:
```javascript
{
"simple": "Hello",
"node": {
"key": "Value",
"_data": "Content"
}
}
```
Note that the values themselves are not touched -- only the keys are lower-cased.
### preserveDocumentNode
If you want the outermost root node (also called the document node) preserved when parsing, set the `preserveDocumentNode` property to true when parsing. Example:
```js
var xml_string = '' +
'Hello' +
'Complex' +
'';
var doc = XML.parse( xml_string, { preserveDocumentNode: true } );
console.log( doc );
```
With the `preserveDocumentNode` flag set to true, this would produce the following object:
```js
{
"Document": {
"Simple": "Hello",
"Node": {
"Key": "Value",
"_Data": "Complex"
}
}
}
```
### preserveWhitespace
If you want to preserve whitespace before and after text inside elements, set the `preserveWhitespace` flag to a true value. Note that this has no effect on attributes (whitespace is always preserved there), nor does it effect whitespace *between* complex elements. Example:
```js
var xml_string = ' ' +
' Hello ' +
' Complex ' +
' ';
var doc = XML.parse( xml_string, { preserveWhitespace: true } );
console.log( doc );
```
With the `preserveWhitespace` flag set to true, this would produce the following object:
```js
{
"Simple": " Hello ",
"Node": {
"Key": " Value ",
"_Data": " Complex "
}
}
```
Notice that the whitespace before/after all the opening and closing tags has no effect on the parsed object. It only has effect *inside* elements that also contain a text value.
### forceArrays
By default single elements are not represented as arrays, until another element with the same appears at the same level in the XML tree. However, if you want to force every element into an array all the time, even when there is only a single element with a given name, set the `forceArrays` property to true. Example:
```js
var xml_string = '' +
'Hello' +
'Complex' +
'';
var doc = XML.parse( xml_string, { forceArrays: true } );
console.log( doc );
```
With the `forceArrays` flag set to true, this would produce the following object:
```js
{
"Simple": [
"Hello"
],
"Node": [
{
"Key": "Value",
"_Data": "Complex"
}
]
}
```
Please note that this feature applies only to elements, not attributes.
## Composing XML
To compose XML back to a string, call `XML.stringify()` and pass in your pre-parsed XML object, and an outer wrapper element name. It helps to parse using the [preserveAttributes](#preserveattributes) option for this, as it will honor the `_Attribs` sub-objects and convert them back into real XML attributes. Example:
```javascript
var xml_string = XML.stringify( doc, 'Document' );
console.log( xml_string );
```
This would produce something like:
```xml
Content
Hello
```
Note that elements and attributes may lose their original ordering, as hashes have an undefined key order. However, to keep things consistent, they are both alphabetically sorted when serialized. See [Preserve Sort Order](#preserve-sort-order) below for a possible workaround.
If you are composing an XML document which has the document root node preserved (see [preserveDocumentNode](#preserveDocumentNode) above), simply omit the name parameter, and only pass in the object. Example:
```js
var xml_string = XML.stringify( doc );
```
### Compact XML
To produce compact XML output (i.e. without indentation nor EOLs) you simply have to call `stringify()` with three additional arguments. Pass in `0` indicating a zero indent level, and then two empty strings, one representing the indentation character (defaults to `\t`) and finally the EOL character (defaults to `\n`). Example:
```javascript
var xml_string = XML.stringify( doc, 'Document', 0, "", "" );
console.log( xml_string );
```
This would produce something like:
```xml
ContentHello
```
If you are composing an XML document which has the document root node preserved (see [preserveDocumentNode](#preserveDocumentNode) above), simply pass in an empty string for the name parameter. Example:
```js
var xml_string = XML.stringify( doc, "", 0, "", "" );
```
### Preserve Sort Order
Most modern JavaScript engines including Node.js seem to magically preserve hash key order, although this goes against the ECMAScript specification. If you want to take your chances and skip the alphabetic sort, and instead rely on natural key order, pass `false` as the 6th parameter when composing:
```js
var xml_string = XML.stringify( doc, 'Document', 0, "\t", "\n", false );
```
This will render elements and attributes in whatever order they come out of their hashes, which is up to your JavaScript runtime engine.
# Object-Oriented API
In addition to the [Simplified API](#simplified-api), an object-oriented API is also available. Using this, you instantiate an `XML.Parser` class instance, and use that to parse, manipulate and serialize XML. The constructor accepts up to two arguments, the raw XML string, and an optional object with configuration options.
After constructing the `XML.Parser` object, and no error was thrown, call `getTree()` to get a reference to the simplified XML structure in memory, manipulate it if you want, then call `compose()` to serialize the object tree back into XML.
The main reason for using this API is that it preserves any PI ([Processing Instruction](https://en.wikipedia.org/wiki/Processing_Instruction)) and DTD ([Document Type Definition](https://en.wikipedia.org/wiki/Document_type_definition)) elements in the source XML file, and they will be serialized into the output. Example:
```js
var xml_string = '' +
'Hello' +
'Complex' +
'';
var parser = null;
try {
parser = new XML.Parser( xml_string, { preserveAttributes: true } );
}
catch (err) {
throw err;
}
var doc = parser.getTree();
doc.Simple = "Hello, I changed this.";
console.log( parser.compose() );
```
This would produce the following output:
```xml
Complex
Hello, I changed this.
```
Notice that the PI element with its `encoding` attribute was preserved and serialized.
To manipulate the PI nodes, access the `piNodeList` property on your parser object. It is an array of strings, each one representing one raw PI node sans the surrounding angle brackets, e.g. `?xml version="1.0"?`. Similarly, any DTD nodes are stored in a `dtdNodeList` property (also an array). Feel free to change these to customize your serialized XML documents.
```js
parser.piNodeList = [ '?xml version="1.0" encoding="UTF-8"?' ];
parser.dtdNodeList = [ '!DOCTYPE MyAppConfig SYSTEM "/dtds/AppConfig.dtd"' ];
console.log( parser.compose() );
```
## Custom XML Formatting
To produce custom XML formatting using the object-oriented API, you can pass two arguments to the `compose()` method. The first argument represents the indentation character (defaults to `\t`) and the second argument represents the EOL character (defaults to `\n`).
For example, to produce compact XML (i.e. all crammed onto one line) set both arguments to empty strings. Example:
```js
console.log( parser.compose("", "") );
```
This would produce the following output:
```xml
ComplexHello, I changed this.
```
# Utility Functions
Here are a few utility functions that are provided in the package:
## encodeEntities
```
STRING encodeEntities( STRING )
```
This function will take a string, and encode the three standard XML entities, ampersand (`&`), left-angle-bracket (`<`) and right-angle-bracket (`>`), into their XML-safe counterparts. It returns the result. Example:
```javascript
var text = '&';
console.log( XML.encodeEntities(text) );
// Would output: <Hello>&<There>
```
## encodeAttribEntities
```
STRING encodeAttribEntities( STRING )
```
This function does basically the same thing as [encodeEntities](#encodeentities), but it also includes encoding for single-quotes (`'`) and double-quotes (`"`). It is used for encoding an XML string for composing into an attribute value. It returns the result. Example:
```javascript
var text = '"&"';
console.log( XML.encodeAttribEntities(text) );
// Would output: <Hello>"&"<There>
```
## decodeEntities
```
STRING decodeEntities( STRING )
```
This function decodes all the standard XML entities back into their original characters. This includes ampersand (`&`), left-angle-bracket (`<`), right-angle-bracket (`>`), single-quote (`'`) and double-quote (`"`). It is used when parsing XML element and attribute values. Example:
```javascript
var text = '<Hello>"&"<There>';
console.log( XML.decodeEntities(text) );
// Would output: "&"
```
## alwaysArray
```
ARRAY alwaysArray( MIXED )
```
This function will wrap anything passed to it into an array and return the array, unless the item passed is already an array, in which case it is simply returned verbatim.
```javascript
var arr = XML.alwaysArray( maybe_array );
```
## hashKeysToArray
```
ARRAY hashKeysToArray( OBJECT )
```
This function returns all the hash keys as an array. Useful for sorting and then iterating over the sorted list.
```javascript
var my_hash = { foo: "bar", baz: 12345 };
var keys = XML.hashKeysToArray( my_hash ).sort();
for (var idx = 0, len = keys.length; idx < len; idx++) {
var key = keys[idx];
// do something with key and my_hash[key]
}
```
## isaHash
```
BOOLEAN isaHash( MIXED )
```
This function returns `true` if the provided argument is a hash (object), `false` otherwise.
```javascript
var my_hash = { foo: "bar", baz: 12345 };
var is_hash = XML.isaHash( my_hash );
```
## isaArray
```
BOOLEAN isaArray( MIXED )
```
This function returns `true` if the provided argument is an array (or is array-like), `false` otherwise.
```javascript
var my_arr = [ "foo", "bar", 12345 ];
var is_arr = XML.isaArray( my_arr );
```
## numKeys
```
INTEGER numKeys( OBJECT )
```
This function returns the number of keys in the specified hash.
```javascript
var my_hash = { foo: "bar", baz: 12345 };
var num = XML.numKeys( my_hash ); // 2
```
## firstKey
```
STRING firstKey( OBJECT )
```
This function returns the first key of the hash when iterating over it. Note that hash keys are stored in an undefined order.
```javascript
var my_hash = { foo: "bar", baz: 12345 };
var key = XML.firstKey( my_hash ); // foo or baz
```
# Known Issues
* Serialized XML doesn't exactly match parsed XML.
* Unicode XML entities are not decoded when parsed.
# License
The MIT License
Copyright (c) 2004 - 2016 Joseph Huckaby
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
pixl-xml-1.0.13/package.json 0000664 0000000 0000000 00000000770 13135025736 0015645 0 ustar 00root root 0000000 0000000 {
"name": "pixl-xml",
"version": "1.0.13",
"description": "A simple module for parsing and composing XML.",
"author": "Joseph Huckaby ",
"homepage": "https://github.com/jhuckaby/pixl-xml",
"license": "MIT",
"main": "xml.js",
"repository": {
"type": "git",
"url": "https://github.com/jhuckaby/pixl-xml"
},
"bugs": {
"url": "https://github.com/jhuckaby/pixl-xml/issues"
},
"keywords": [
"xml"
],
"dependencies": {},
"devDependencies": {}
}
pixl-xml-1.0.13/xml.js 0000664 0000000 0000000 00000043307 13135025736 0014520 0 ustar 00root root 0000000 0000000 /*
JavaScript XML Library
Plus a bunch of object utility functions
Usage:
var XML = require('pixl-xml');
var myxmlstring = '' +
'Hello' +
'Content' +
'';
var tree = XML.parse( myxmlstring, { preserveAttributes: true });
console.log( tree );
tree.Simple = "Hello2";
tree.Node._Attribs.Key = "Value2";
tree.Node._Data = "Content2";
tree.New = "I added this";
console.log( XML.stringify( tree, 'Document' ) );
Copyright (c) 2004 - 2015 Joseph Huckaby
Released under the MIT License
This version is for Node.JS, converted in 2012.
*/
var fs = require('fs');
var util = require('util');
var isArray = Array.isArray || util.isArray; // support for older Node.js
var xml_header = '';
var sort_args = null;
var re_valid_tag_name = /^\w[\w\-\:\.]*$/;
var XML = exports.XML = exports.Parser = function XML(args, opts) {
// class constructor for XML parser class
// pass in args hash or text to parse
if (!args) args = '';
if (isa_hash(args)) {
for (var key in args) this[key] = args[key];
}
else this.text = args || '';
// options may be 2nd argument as well
if (opts) {
for (var key in opts) this[key] = opts[key];
}
// stringify buffers
if (this.text instanceof Buffer) {
this.text = this.text.toString();
}
if (!this.text.match(/^\s*)) {
// try as file path
var file = this.text;
this.text = fs.readFileSync(file, { encoding: 'utf8' });
if (!this.text) throw new Error("File not found: " + file);
}
this.tree = {};
this.errors = [];
this.piNodeList = [];
this.dtdNodeList = [];
this.documentNodeName = '';
if (this.lowerCase) {
this.attribsKey = this.attribsKey.toLowerCase();
this.dataKey = this.dataKey.toLowerCase();
}
this.patTag.lastIndex = 0;
if (this.text) this.parse();
}
XML.prototype.preserveDocumentNode = false;
XML.prototype.preserveAttributes = false;
XML.prototype.preserveWhitespace = false;
XML.prototype.lowerCase = false;
XML.prototype.forceArrays = false;
XML.prototype.patTag = /([^<]*?)<([^>]+)>/g;
XML.prototype.patSpecialTag = /^\s*([\!\?])/;
XML.prototype.patPITag = /^\s*\?/;
XML.prototype.patCommentTag = /^\s*\!--/;
XML.prototype.patDTDTag = /^\s*\!DOCTYPE/;
XML.prototype.patCDATATag = /^\s*\!\s*\[\s*CDATA/;
XML.prototype.patStandardTag = /^\s*(\/?)([\w\-\:\.]+)\s*([\s\S]*)$/;
XML.prototype.patSelfClosing = /\/\s*$/;
XML.prototype.patAttrib = new RegExp("([\\w\\-\\:\\.]+)\\s*=\\s*([\\\"\\'])([^\\2]*?)\\2", "g");
XML.prototype.patPINode = /^\s*\?\s*([\w\-\:]+)\s*(.*)$/;
XML.prototype.patEndComment = /--$/;
XML.prototype.patNextClose = /([^>]*?)>/g;
XML.prototype.patExternalDTDNode = new RegExp("^\\s*\\!DOCTYPE\\s+([\\w\\-\\:]+)\\s+(SYSTEM|PUBLIC)\\s+\\\"([^\\\"]+)\\\"");
XML.prototype.patInlineDTDNode = /^\s*\!DOCTYPE\s+([\w\-\:]+)\s+\[/;
XML.prototype.patEndDTD = /\]$/;
XML.prototype.patDTDNode = /^\s*\!DOCTYPE\s+([\w\-\:]+)\s+\[(.*)\]/;
XML.prototype.patEndCDATA = /\]\]$/;
XML.prototype.patCDATANode = /^\s*\!\s*\[\s*CDATA\s*\[([^]*)\]\]/;
XML.prototype.attribsKey = '_Attribs';
XML.prototype.dataKey = '_Data';
XML.prototype.parse = function(branch, name) {
// parse text into XML tree, recurse for nested nodes
if (!branch) branch = this.tree;
if (!name) name = null;
var foundClosing = false;
var matches = null;
// match each tag, plus preceding text
while ( matches = this.patTag.exec(this.text) ) {
var before = matches[1];
var tag = matches[2];
// text leading up to tag = content of parent node
if (before.match(/\S/)) {
if (typeof(branch[this.dataKey]) != 'undefined') branch[this.dataKey] += ' '; else branch[this.dataKey] = '';
branch[this.dataKey] += !this.preserveWhitespace ? trim(decode_entities(before)) : decode_entities(before);
}
// parse based on tag type
if (tag.match(this.patSpecialTag)) {
// special tag
if (tag.match(this.patPITag)) tag = this.parsePINode(tag);
else if (tag.match(this.patCommentTag)) tag = this.parseCommentNode(tag);
else if (tag.match(this.patDTDTag)) tag = this.parseDTDNode(tag);
else if (tag.match(this.patCDATATag)) {
tag = this.parseCDATANode(tag);
if (typeof(branch[this.dataKey]) != 'undefined') branch[this.dataKey] += ' '; else branch[this.dataKey] = '';
branch[this.dataKey] += !this.preserveWhitespace ? trim(decode_entities(tag)) : decode_entities(tag);
} // cdata
else {
this.throwParseError( "Malformed special tag", tag );
break;
} // error
if (tag == null) break;
continue;
} // special tag
else {
// Tag is standard, so parse name and attributes (if any)
var matches = tag.match(this.patStandardTag);
if (!matches) {
this.throwParseError( "Malformed tag", tag );
break;
}
var closing = matches[1];
var nodeName = this.lowerCase ? matches[2].toLowerCase() : matches[2];
var attribsRaw = matches[3];
// If this is a closing tag, make sure it matches its opening tag
if (closing) {
if (nodeName == (name || '')) {
foundClosing = 1;
break;
}
else {
this.throwParseError( "Mismatched closing tag (expected " + name + ">)", tag );
break;
}
} // closing tag
else {
// Not a closing tag, so parse attributes into hash. If tag
// is self-closing, no recursive parsing is needed.
var selfClosing = !!attribsRaw.match(this.patSelfClosing);
var leaf = {};
var attribs = leaf;
// preserve attributes means they go into a sub-hash named "_Attribs"
// the XML composer honors this for restoring the tree back into XML
if (this.preserveAttributes) {
leaf[this.attribsKey] = {};
attribs = leaf[this.attribsKey];
}
// parse attributes
this.patAttrib.lastIndex = 0;
while ( matches = this.patAttrib.exec(attribsRaw) ) {
var key = this.lowerCase ? matches[1].toLowerCase() : matches[1];
attribs[ key ] = decode_entities( matches[3] );
} // foreach attrib
// if no attribs found, but we created the _Attribs subhash, clean it up now
if (this.preserveAttributes && !num_keys(attribs)) {
delete leaf[this.attribsKey];
}
// Recurse for nested nodes
if (!selfClosing) {
this.parse( leaf, nodeName );
if (this.error()) break;
}
// Compress into simple node if text only
var num_leaf_keys = num_keys(leaf);
if ((typeof(leaf[this.dataKey]) != 'undefined') && (num_leaf_keys == 1)) {
leaf = leaf[this.dataKey];
}
else if (!num_leaf_keys) {
leaf = '';
}
// Add leaf to parent branch
if (typeof(branch[nodeName]) != 'undefined') {
if (isa_array(branch[nodeName])) {
branch[nodeName].push( leaf );
}
else {
var temp = branch[nodeName];
branch[nodeName] = [ temp, leaf ];
}
}
else if (this.forceArrays && (branch != this.tree)) {
branch[nodeName] = [ leaf ];
}
else {
branch[nodeName] = leaf;
}
if (this.error() || (branch == this.tree)) break;
} // not closing
} // standard tag
} // main reg exp
// Make sure we found the closing tag
if (name && !foundClosing) {
this.throwParseError( "Missing closing tag (expected " + name + ">)", name );
}
// If we are the master node, finish parsing and setup our doc node
if (branch == this.tree) {
if (typeof(this.tree[this.dataKey]) != 'undefined') delete this.tree[this.dataKey];
if (num_keys(this.tree) > 1) {
this.throwParseError( 'Only one top-level node is allowed in document', first_key(this.tree) );
return;
}
this.documentNodeName = first_key(this.tree);
if (this.documentNodeName && !this.preserveDocumentNode) {
this.tree = this.tree[this.documentNodeName];
}
}
};
XML.prototype.throwParseError = function(key, tag) {
// log error and locate current line number in source XML document
var parsedSource = this.text.substring(0, this.patTag.lastIndex);
var eolMatch = parsedSource.match(/\n/g);
var lineNum = (eolMatch ? eolMatch.length : 0) + 1;
lineNum -= tag.match(/\n/) ? tag.match(/\n/g).length : 0;
this.errors.push({
type: 'Parse',
key: key,
text: '<' + tag + '>',
line: lineNum
});
// Throw actual error (must wrap parse in try/catch)
throw new Error( this.getLastError() );
};
XML.prototype.error = function() {
// return number of errors
return this.errors.length;
};
XML.prototype.getError = function(error) {
// get formatted error
var text = '';
if (!error) return '';
text = (error.type || 'General') + ' Error';
if (error.code) text += ' ' + error.code;
text += ': ' + error.key;
if (error.line) text += ' on line ' + error.line;
if (error.text) text += ': ' + error.text;
return text;
};
XML.prototype.getLastError = function() {
// Get most recently thrown error in plain text format
if (!this.error()) return '';
return this.getError( this.errors[this.errors.length - 1] );
};
XML.prototype.parsePINode = function(tag) {
// Parse Processor Instruction Node, e.g.
if (!tag.match(this.patPINode)) {
this.throwParseError( "Malformed processor instruction", tag );
return null;
}
this.piNodeList.push( tag );
return tag;
};
XML.prototype.parseCommentNode = function(tag) {
// Parse Comment Node, e.g.
var matches = null;
this.patNextClose.lastIndex = this.patTag.lastIndex;
while (!tag.match(this.patEndComment)) {
if (matches = this.patNextClose.exec(this.text)) {
tag += '>' + matches[1];
}
else {
this.throwParseError( "Unclosed comment tag", tag );
return null;
}
}
this.patTag.lastIndex = this.patNextClose.lastIndex;
return tag;
};
XML.prototype.parseDTDNode = function(tag) {
// Parse Document Type Descriptor Node, e.g.
var matches = null;
if (tag.match(this.patExternalDTDNode)) {
// tag is external, and thus self-closing
this.dtdNodeList.push( tag );
}
else if (tag.match(this.patInlineDTDNode)) {
// Tag is inline, so check for nested nodes.
this.patNextClose.lastIndex = this.patTag.lastIndex;
while (!tag.match(this.patEndDTD)) {
if (matches = this.patNextClose.exec(this.text)) {
tag += '>' + matches[1];
}
else {
this.throwParseError( "Unclosed DTD tag", tag );
return null;
}
}
this.patTag.lastIndex = this.patNextClose.lastIndex;
// Make sure complete tag is well-formed, and push onto DTD stack.
if (tag.match(this.patDTDNode)) {
this.dtdNodeList.push( tag );
}
else {
this.throwParseError( "Malformed DTD tag", tag );
return null;
}
}
else {
this.throwParseError( "Malformed DTD tag", tag );
return null;
}
return tag;
};
XML.prototype.parseCDATANode = function(tag) {
// Parse CDATA Node, e.g.
var matches = null;
this.patNextClose.lastIndex = this.patTag.lastIndex;
while (!tag.match(this.patEndCDATA)) {
if (matches = this.patNextClose.exec(this.text)) {
tag += '>' + matches[1];
}
else {
this.throwParseError( "Unclosed CDATA tag", tag );
return null;
}
}
this.patTag.lastIndex = this.patNextClose.lastIndex;
if (matches = tag.match(this.patCDATANode)) {
return matches[1];
}
else {
this.throwParseError( "Malformed CDATA tag", tag );
return null;
}
};
XML.prototype.getTree = function() {
// get reference to parsed XML tree
return this.tree;
};
XML.prototype.compose = function(indent_string, eol) {
// compose tree back into XML
if (typeof(eol) == 'undefined') eol = "\n";
var tree = this.tree;
if (this.preserveDocumentNode) tree = tree[this.documentNodeName];
var raw = compose_xml( tree, this.documentNodeName, 0, indent_string, eol );
var body = raw.replace(/^\s*\<\?.+?\?\>\s*/, '');
var xml = '';
if (this.piNodeList.length) {
for (var idx = 0, len = this.piNodeList.length; idx < len; idx++) {
xml += '<' + this.piNodeList[idx] + '>' + eol;
}
}
else {
xml += xml_header + eol;
}
if (this.dtdNodeList.length) {
for (var idx = 0, len = this.dtdNodeList.length; idx < len; idx++) {
xml += '<' + this.dtdNodeList[idx] + '>' + eol;
}
}
xml += body;
return xml;
};
//
// Static Utility Functions:
//
var parse_xml = exports.parse = function parse_xml(text, opts) {
// turn text into XML tree quickly
if (!opts) opts = {};
opts.text = text;
var parser = new XML(opts);
return parser.error() ? parser.getLastError() : parser.getTree();
};
var trim = exports.trim = function trim(text) {
// strip whitespace from beginning and end of string
if (text == null) return '';
if (text && text.replace) {
text = text.replace(/^\s+/, "");
text = text.replace(/\s+$/, "");
}
return text;
};
var encode_entities = exports.encodeEntities = function encode_entities(text) {
// Simple entitize exports.for = function for composing XML
if (text == null) return '';
if (text && text.replace) {
text = text.replace(/\&/g, "&"); // MUST BE FIRST
text = text.replace(//g, ">");
}
return text;
};
var encode_attrib_entities = exports.encodeAttribEntities = function encode_attrib_entities(text) {
// Simple entitize exports.for = function for composing XML attributes
if (text == null) return '';
if (text && text.replace) {
text = text.replace(/\&/g, "&"); // MUST BE FIRST
text = text.replace(//g, ">");
text = text.replace(/\"/g, """);
text = text.replace(/\'/g, "'");
}
return text;
};
var decode_entities = exports.decodeEntities = function decode_entities(text) {
// Decode XML entities into raw ASCII
if (text == null) return '';
if (text && text.replace && text.match(/\&/)) {
text = text.replace(/\<\;/g, "<");
text = text.replace(/\>\;/g, ">");
text = text.replace(/\"\;/g, '"');
text = text.replace(/\&apos\;/g, "'");
text = text.replace(/\&\;/g, "&"); // MUST BE LAST
}
return text;
};
var compose_xml = exports.stringify = function compose_xml(node, name, indent, indent_string, eol, sort) {
// Compose node into XML including attributes
// Recurse for child nodes
if (typeof(indent_string) == 'undefined') indent_string = "\t";
if (typeof(eol) == 'undefined') eol = "\n";
if (typeof(sort) == 'undefined') sort = true;
var xml = "";
// If this is the root node, set the indent to 0
// and setup the XML header (PI node)
if (!indent) {
indent = 0;
xml = xml_header + eol;
if (!name) {
// no name provided, assume content is wrapped in it
name = first_key(node);
node = node[name];
}
}
// Setup the indent text
var indent_text = "";
for (var k = 0; k < indent; k++) indent_text += indent_string;
if ((typeof(node) == 'object') && (node != null)) {
// node is object -- now see if it is an array or hash
if (!node.length) { // what about zero-length array?
// node is hash
xml += indent_text + "<" + name;
var num_keys = 0;
var has_attribs = 0;
for (var key in node) num_keys++; // there must be a better way...
if (node["_Attribs"]) {
has_attribs = 1;
var sorted_keys = sort ? hash_keys_to_array(node["_Attribs"]).sort() : hash_keys_to_array(node["_Attribs"]);
for (var idx = 0, len = sorted_keys.length; idx < len; idx++) {
var key = sorted_keys[idx];
xml += " " + key + "=\"" + encode_attrib_entities(node["_Attribs"][key]) + "\"";
}
} // has attribs
if (num_keys > has_attribs) {
// has child elements
xml += ">";
if (node["_Data"]) {
// simple text child node
xml += encode_entities(node["_Data"]) + "" + name + ">" + eol;
} // just text
else {
xml += eol;
var sorted_keys = sort ? hash_keys_to_array(node).sort() : hash_keys_to_array(node);
for (var idx = 0, len = sorted_keys.length; idx < len; idx++) {
var key = sorted_keys[idx];
if ((key != "_Attribs") && key.match(re_valid_tag_name)) {
// recurse for node, with incremented indent value
xml += compose_xml( node[key], key, indent + 1, indent_string, eol, sort );
} // not _Attribs key
} // foreach key
xml += indent_text + "" + name + ">" + eol;
} // real children
}
else {
// no child elements, so self-close
xml += "/>" + eol;
}
} // standard node
else {
// node is array
for (var idx = 0; idx < node.length; idx++) {
// recurse for node in array with same indent
xml += compose_xml( node[idx], name, indent, indent_string, eol, sort );
}
} // array of nodes
} // complex node
else {
// node is simple string
xml += indent_text + "<" + name + ">" + encode_entities(node) + "" + name + ">" + eol;
} // simple text node
return xml;
};
var always_array = exports.alwaysArray = function always_array(obj, key) {
// if object is not array, return array containing object
// if key is passed, work like XMLalwaysarray() instead
if (key) {
if ((typeof(obj[key]) != 'object') || (typeof(obj[key].length) == 'undefined')) {
var temp = obj[key];
delete obj[key];
obj[key] = new Array();
obj[key][0] = temp;
}
return null;
}
else {
if ((typeof(obj) != 'object') || (typeof(obj.length) == 'undefined')) { return [ obj ]; }
else return obj;
}
};
var hash_keys_to_array = exports.hashKeysToArray = function hash_keys_to_array(hash) {
// convert hash keys to array (discard values)
var array = [];
for (var key in hash) array.push(key);
return array;
};
var isa_array = exports.isaArray = function isa_array(arg) {
// determine if arg is an array or is array-like
return isArray(arg);
};
var isa_hash = exports.isaHash = function isa_hash(arg) {
// determine if arg is a hash
return( !!arg && (typeof(arg) == 'object') && !isa_array(arg) );
};
var first_key = exports.firstKey = function first_key(hash) {
// return first key from hash (unordered)
for (var key in hash) return key;
return null; // no keys in hash
};
var num_keys = exports.numKeys = function num_keys(hash) {
// count the number of keys in a hash
var count = 0;
for (var a in hash) count++;
return count;
};