PDA

View Full Version : [2.x] Ext.ux.XmlWriter, V0.3



evant
11 Jun 2008, 5:40 AM
Hi all.

Here's a small utility class that assists in constructing XML documents,

Sample Usage:


var xml = new Ext.ux.XmlWriter(
{
indent: true,
indentSize: 4,
namespace: 'Ext',
root: 'Root'
}
);
xml.attribute(
{
'attr1': 'value1',
'attr2': 'value2',
'encodeMe': '&><'
}
);
xml.startElement('Person',
{
name: 'Evan',
hobby: 'Coding'
}
);
xml.attribute('birthMonth', 'March');
xml.startElement('Location');
xml.startElement('City');
xml.writeData('Sydney');
xml.endElement();
xml.startElement('Country');
xml.writeData('Australia');
xml.endElements(); //close all open elements
var s = xml.toString();


The output is:



<Ext:Root attr1="value1" attr2="value2" encodeMe="&amp;&gt;&lt;">
<Ext:Person name="Evan" hobby="Coding" birthMonth="March">
<Ext:Location>
<Ext:City>Sydney</Ext:City>
<Ext:Country>Australia</Ext:Country>
</Ext:Location>
</Ext:Person>
</Ext:Root>




/* Version 0.3 */

Ext.namespace('Ext.ux');

/**
* @class Ext.ux.XmlWriter
* A utility class used to construct valid XML documents.
* @constructor
* @param {Object} config The configuration options for this object.
*/
Ext.ux.XmlWriter = function(config)
{
Ext.apply(this, config);
this.clear();
this.hasNS = (this.namespace && this.namespace != '');
if (config.root)
this.startElement(config.root);
};

Ext.extend(Ext.ux.XmlWriter, Object,
{
/**
*@cfg {Number} indentSize The number of spaces to ident new nodes. Only applies if {@link #indent} is true. Defaults to 4.
*/
indentSize: 4,

/**
*@cfg {Boolean} indent Indicates whether child nodes should be indented. Defaults to true.
*/
indent: true,

/**
*@cfg {String} namespace The default namespace of the XML document. Defaults to ''.
*/
namespace: '',

/**
*@cfg {String} quoteChar The quote character to used to surround attribute values. Defaults to '"'.
*/
quoteChar: '"',

/**
*@cfg {String} root The name of the root node. If not specified, a root node must manually be created.
*/
root: null,

/**
*@cfg {Boolean} isDebug When in debug mode, the writer will raise an alert if an invalid operation has occurred. Defaults to false.
*/
isDebug: false,

/**
* Clears all data in the writer.
* @return {Ext.ux.XmlWriter} this
*/
clear: function()
{
this.root = this.current = null;
return this;
},

/**
* Append an attribute to the active node. If the attribute already exists it will be overwritten.
* @param {String/Object} key The key for the attribute, or an object of multiple attributes.
* @param {String} value (optional) The value for the attribute.
* @return {Ext.ux.XmlWriter} this
*/
attribute: function(key, value)
{
if (this.current)
{
//object of values
if (typeof key == 'object')
{
for (var i in key)
this.setAttribute(i, key[i]);
}
else
this.setAttribute(key, value);
}
else
this.error('No current element.');

return this;
},

/**
* Append text data to the current node.
* @param {String} data The data to append.
* @return {Ext.ux.XmlWriter} this
*/
writeData: function(data)
{
this.pushData(data, false);
return this;
},

/**
* Append CDATA text to the current node.
* @param {String} data The data to append.
* @return {Ext.ux.XmlWriter} this
*/
writeCData: function(data)
{
this.pushData(data, true);
return this;
},

/**
* Create a new element.
* @param {String} tagName The name of the tag.
* @param {Object} (optional) attributes An object containing attributes to append to this node. See {@link #attribute}.
* @return {Ext.ux.XmlWriter} this
*/
startElement: function(tagName, attributes)
{
var err = false;
if (this.current)
{
if (!this.current.data)
{
var el = this.manufacture(tagName, this.current);
this.current.children.push(el);
this.current = el;
}
else
{
this.error('Node already contains text');
err = true;
}
}
else
this.current = this.root = this.manufacture(tagName);

if (!err && attributes)
this.attribute(attributes);

return this;
},

/**
* Close an existing element.
* @return {Ext.ux.XmlWriter} this
*/
endElement: function()
{
if (this.current)
this.current = this.current.parent;
else
this.error('No current element');

return this;
},

/**
* Closes any open elements.
* @return {Ext.ux.XmlWriter} this
*/
endElements: function()
{
this.current = null;
return this;
},

/**
* Retrieves the string representation of this document. If it is in an invalid state an empty string is returned.
* @return {String} The string representation of this object.
*/
toString: function()
{
if (this.current)
{
this.error('Document malformed');
return '';
}

return (this.root ? this.print(this.root, 0) : '');
},

//private
setAttribute: function(key, value)
{
this.current.attributes[key] = this.sanitizeValue(value);
this.current.hasAttributes = true;
},

//private
error: function(msg)
{
if (this.isDebug)
{
if (console)
console.log(msg);
else
alert(msg);
}
},

//private
pushData: function(data, isCData)
{
if (this.current && this.current.children.length == 0 && !this.current.data)
this.current.data = (isCData ? '<![CDATA[' : '') + data + (isCData ? ']]>' : '');
else
this.error('Invalid write state');
},

//private
print: function(o, depth)
{
var out = [this.getTag(o, depth, false)];
if (o.hasAttributes)
{
var attrs = o.attributes;
for (var attr in attrs)
out.push(' ', attr, '=', this.quoteChar, attrs[attr], this.quoteChar);
}

if (o.children.length > 0)
{
out.push('>')
Ext.each(o.children, function(child)
{
out.push(this.print(child, depth + 1));
}, this);
out.push(this.getTag(o, depth, true));
}
else if (o.data)
out.push('>', o.data, this.getTag(o, depth, true));
else
out.push(' />');

return out.join('');
},

//private
getTag: function(o, depth, isEnd)
{
var out = [];
if (o.children.length > 0 || !isEnd)
out.push(this.doIndent(depth, isEnd));

out.push('<', (isEnd ? '/' : ''));
if (this.hasNS)
out.push(this.namespace, ':');

out.push(o.tagName, (isEnd ? '>' : ''));
return out.join('');
},

//private
manufacture: function(tagName, parent)
{
return {tagName: tagName, parent: parent, attributes: {}, children: []};
},

//private
sanitizeValue: function(value)
{
if (!value)
return '';

if (Ext.isArray(value))
{
var val = '';
Ext.each(value, function(el, idx)
{
if (idx > 0)
val += ',';

val += el;
}
);
value = val;
}
else
value = value.toString();


value = value.replace(/&/g, '&amp;');
value = value.replace(/</g, '&lt;');
value = value.replace(/>/g, '&gt;');
value = value.replace(/"/g, '&quot;');
value = value.replace(/'/g, '&apos;');

return value;
},

//private
doIndent: function(depth, isEnd)
{
if (this.indent && (depth > 0 || isEnd))
{
var out = ['\r\n'];
for (var i = 0, len = this.indentSize * depth; i < len; ++i)
out.push(' ');

return out.join('');
}
return '';
}
}
);

mabello
13 Jun 2008, 12:36 PM
Nice one evant :)

galdaka
14 Jun 2008, 12:13 AM
Excellent work!!

@evant: Can you specify in what type of work (Or put an example) can you apply this extension?

I think that is excellent extension for add rows in client side XML grid

danh2000
15 Jun 2008, 6:52 PM
Nice work.

I think a good addition would be an endElements method, then instead of:


xml.endElement();
xml.endElement();
xml.endElement();
xml.endElement();

You could just write:


xml.endElements();

and it would close all open tags.

evant
15 Jun 2008, 7:23 PM
Thanks Dan.

I've made the update as per your request.

I've also updated the attribute method, so it can now take an object of key value pairs:



xml.attribute(
{
'attr1': 'value1',
'attr2': 'value2',
'encodeMe': '&><'
}
);


It now also supports chaining:



var xml = new Ext.ux.XmlWriter();
var s = xml.startElement('foo').startElement('bar').startElement('baz').endElements().toString();

danh2000
17 Jun 2008, 1:27 AM
Good work.

The method chaining is also a great addition - it works a treat in builder type classes.

:D

evant
17 Jun 2008, 5:07 AM
A few minor changes for 0.3:

1) The config now takes a 'root' property to automatically create the root tag.
2) startElement now takes an optional attributes config object.
3) Fixed a small bug with the encoding.

mystix
17 Jun 2008, 6:24 AM
just a thought -- would it be more efficient if you used XTemplates ?
might make things a whole lot simpler.

[edit]
since JSON can be directly translated into XML (and vice versa), it might be easier to simply make XmlWriter consume a JSON object and spit out it's XML equivalent. thoughts?