(function () {
// Helper Function: returns the subject's cast type
function getType(subject) {
return Object.prototype.toString.call(subject).match(/^\[object (\S+)\]$/)[1].toLowerCase();
}
// Helper Function: returns true if the subject has the specified property and it is not part of its inheritence chain
function has(subject, key) {
return Object.prototype.hasOwnProperty.call(subject, key);
}
// Helper Function: removes EOL characters from text
function trim(text) {
return text.replace(/[\r\n]+$/g, '');
}
// Helper Function: Escapes text to be YAML compliant
function yamlText(text) {
// empty text
if (text === '') {
return '""';
}
// text contains characters that require quoting
if (/(?:^[!&*-?#|>@`"' ])|[{}\[\]\(\),:\t\r\n\\\/]|(?: $)/g.test(text)) {
return '"' + text.replace(
/[\\"\u0000-\u001F\u2028\u2029]/g,
function (chr) {
return {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t'}[chr] || '\\u' + (chr.charCodeAt(0) + 0x10000).toString(16).substr(1);
}
) + '"';
}
// text can be left as-is
return text;
}
// Helper Function: Formats the specified collection item to be YAML compliant
function yamlItem(data, indent) {
// text
if (typeof data == 'string') {
return ' ' + yamltext(data);
}
// null, boolean, numbers
if (data === null || data === !!data || isFinite(data)) {
return ' ' + data;
}
// collections
data = yaml(data, indent + 2);
if (data != undefined) {
if (data === '[]' || data === '{}') {
return ' ' + data;
}
return '\n' + data;
}
}
// YAML conversion entry point
function yaml(data, indent) {
var type = getType(data),
index,
value,
hasChild,
result = '';
// no data, return
if (data === undefined) {
return;
}
// text
if (type == 'string') {
return yamlText(data);
}
// null, boolean, numbers
if (data == null || type == 'boolean' || isFinite(data)) {
return '' + data;
}
// collections
if (type == 'array' || type == 'object') {
// loop over each item in collection
for (index in data) {
if (has(data, index)) {
// convert to YAML item and append to result
value = yamlItem(data[index], indent);
if (value != null) {
hasChild = true;
result += Array(indent + 1).join(' ') + (type == 'array' ? '-' : yamlText(index) + ':') + trim(value) + '\n';
}
}
}
// return result
return trim(hasChild ? result : type == 'array' ? '[]' : '{}') + '\n';
}
}
// Helper Function: Recursively walks an XML document, processing it into a proper JS collection
function xmlWalk(node, parent) {
var result = {
attributes: {},
children: {},
value: node.text
},
index,
attr,
child;
// loop over all attributes and add them to the result's attributes collection
for (index = 0; index < node.attributes.length; index += 1) {
attr = node.attributes[index];
result.attributes[attr.nodeName] = attr.text || true;
}
// loop over all children and add them to the result's attributes collection
for (index = 0; index < node.childNodes.length; index += 1) {
child = node.childNodes[index];
if (child.nodeType === 1) {
xmlWalk(child, result.children);
}
}
// if theres no attributes and no children nodes, store this node's value as being its text
if (node.childNodes.length === 0 && node.attributes.length === 0) {
result = node.text || true;
}
// store the node based on the parent:
// Parent does not have entry for the node name: add entry with result as its value
if (!has(parent, node.nodeName)) {
parent[node.nodeName] = result;
// parent's entry for node name is an array: append result
} else if (getType(parent[node.nodeName]) == 'array') {
parent[node.nodeName].push(result);
// parent's entry for node name is not an array: convert to array and append result
} else {
parent[node.nodeName] = [parent[node.nodeName], result];
}
}
// Exposed Function: Converts an XML string into YAML
xml2yaml = function (data) {
// initial XML parser
var xml = new ActiveXObject("Msxml2.DOMDocument.6.0");
xml.async = false;
// load xml into parser
try {
var loaded = xml.loadXML(data);
} catch (e) {
throw new Error('INVALID_XML');
}
if (!loaded || xml.documentElement == null) {
throw new Error('INVALID_XML');
}
// walk the XML, converting it to a more managable JS object
xmlWalk(xml.documentElement, data = {});
// convert the JS object to YAML
return yaml(data, 0);
};
// Exposed Function: Converts a JSON string into a YAML
json2yaml = function (data) {
// parse JSON into JS object
data = String(data).replace(/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, function(chr) {
return '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
});
if (!/^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
throw new Error("INVALID_JSON");
}
try {
data = eval('(' + data + ')');
} catch (e) {
throw new Error("INVALID_JSON");
}
// convert JS object to YAML
return yaml(data, 0);
};
}());