/**
* @class Ext.data.writer.Xml
* This class is used to write {@link Ext.data.Model} data to the server in an XML format.
* The {@link #documentRoot} property is used to specify the root element in the XML document.
* The {@link #record} option is used to specify the element name for each record that will
* make up the XML document.
*/
Ext.define('Ext.data.writer.Xml', {
extend: 'Ext.data.writer.Writer',
alternateClassName: 'Ext.data.XmlWriter',
alias: 'writer.xml',
config: {
/**
* @cfg {String} documentRoot The name of the root element of the document. Defaults to
* `'xmlData'`. If there is more than 1 record and the root is not specified, the default
* document root will still be used to ensure a valid XML document is created.
*
* If the {@link #record} mapping includes a root element name, eg: "SystemInfo>Operation",
* and the selector includes the root element name, then you must configure this as `false`
*/
documentRoot: 'xmlData',
/**
* @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty
* and a root is required to form a valid XML document.
*/
defaultDocumentRoot: 'xmlData',
/**
* @cfg {String} header A header to use in the XML document (such as setting the encoding
* or version). Defaults to `''`.
*/
header: '',
/**
* @cfg {String} record The name of the node to use for each record. Defaults to
* the owning {@link Ext.data.proxy.Proxy Proxy}'s {@link Ext.data.reader.Xml Reader}'s
* {@link Ext.data.reader.Xml#record} setting, or `'record'`.
*/
record: 'record'
},
// To break simple XPath selectors like "SystemInfo>SystemName"
// into ["SystemInfo", "SystemName"]
selectorRe: /[^>\s]+/g,
writeRecords: function(request, data) {
var me = this,
xml = [],
i = 0,
len = data.length,
root = me.getDocumentRoot(),
recordName = me.getRecord(),
// Convert eg 'Items>Item' into ['Items', 'Item']
record = recordName.match(this.selectorRe),
recLen = record.length,
// Need a containing element if there are multiple data records and
// it's not a compound record selector
needsRoot = data.length !== 1 && recLen === 1,
transform;
transform = this.getTransform();
if (transform) {
data = transform(data, request);
}
// may not exist
xml.push(me.getHeader() || '');
if (!root && needsRoot) {
root = me.getDefaultDocumentRoot();
}
// May not exist if configured as false, and the record selector is rooted, eg "Items>Item"
if (root) {
xml.push('<', root, '>');
}
// Output record nodes' wrapping, eg "<Items>" from record "Items>Item"->["Items", "Item"]
for (i = 0; i < recLen - 1; i++) {
xml.push('<', record[i], '>');
}
recordName = record[i];
for (i = 0; i < len; ++i) {
this.objectToElement(recordName, data[i], xml);
}
// Close record nodes' wrapping, eg "</Items>" from record "Items>Item"->["Items", "Item"]
for (i = recLen - 2; i > -1; i--) {
xml.push('</', record[i], '>');
}
if (root) {
xml.push('</', root, '>');
}
request.setXmlData(xml.join(''));
return request;
},
/**
* Serializes an object to XML.
* Properties will be serialized as child elements unless their first character is `'@'`
*
* For example:
*
* myWriter.objectToElement('SystemComponent', {
* "@SystemNumber": '10118795',
* "SystemInfo>SystemName": 'Phase Noise Measurement System',
* AssetId: 'DE3208',
* AgilentModel: 'E5505A',
* SerialNumber: 'US44101357',
* }, []).join('');
*
* becomes
*
* <SystemComponent SystemNumber="10118795">
* <SystemInfo>
* <SystemName>Phase Noise Measurement System</SystemName>
* </SystemInfo>
* <AssetId>DE3208</AssetId>
* <AgilentModel>E5505A</AgilentModel>
* <SerialNumber>US44101357</SerialNumber>
* </SystemComponent>
*
* @param {String} name The element name for the object.
* @param {Object} o The object to serialize.
* @param {Array} [output] The array into which to serialize the object.
* @return {undefined}
*/
objectToElement: function(name, o, output) {
var subOutput = [],
key, datum, subKeys, subKeyLen, subObject, subObjects, lastObject, lastKey, i;
if (!output) {
output = [];
}
// Open the record node, eg "<Item"
// Stop there because some child properties may be attributes.
output.push('<', name);
for (key in o) {
datum = o[key];
// Attribute node
if (key[0] === '@') {
output.push(' ', key.substr(1), '="', datum, '"');
}
// Child element node
else {
// Object properties become child elements
if (typeof datum === 'object') {
this.objectToElement(key, datum, subOutput);
}
else {
// Is it a selector?
subKeys = key.match(this.selectorRe);
// key was eg "foo > bar".
// Ensure looks like contains {foo: {bar: {}}}
if ((subKeyLen = subKeys.length) > 1) {
subObjects = subObjects || {};
for (subObject = subObjects, i = 0; i < subKeyLen; i++) {
lastObject = subObject;
lastKey = subKeys[i];
subObject = subObject[lastKey] || (subObject[lastKey] = {});
}
// lastObject is now the bar property in the above example
lastObject[lastKey] = datum;
}
else {
subOutput.push('<', key, '>', datum, '</', key, '>');
}
}
}
}
output.push('>');
output.push.apply(output, subOutput);
// Output any embedded nodes that were specified by child element mappings
// like "SystemInfo>SystemName"
if (subObjects) {
for (key in subObjects) {
datum = subObjects[key];
this.objectToElement(key, datum, output);
}
}
// Close the record node, eg "</Item>"
output.push('</', name, '>');
return output;
}
});