Source: xmlparser.js

/**
 * @author Ani Sinanaj
 * @module xmlparser
 * @dependencies: xmlparser/extract, xmlparser/proxies
 */

/**
 * Dependencies
 */
var Extract = require('T/support/xmlparser/extract').extract;
var DefaultProxies = require('T/support/xmlparser/proxies');

/**
 * Global variables
 */
var viewCount = 0; // just a counter to have the number of created views
var customProxies = {};
var container = null;

/**
 * Parse the given string of `xml`.
 *
 * @param {String} xml
 * @return {Object}
 * @api public
 */
function parse(xml, opts) {
	opts = opts || {};
	var fontTransform = _.extend({
		italic: {
			fontStyle: 'italic'
		},
		bold: {
			fontWeight: 'bold'
		}
	}, opts.fontTransform);

	DefaultProxies.opts = opts;
	DefaultProxies.fontTransform = fontTransform;

	var proxies = _.extend({}, DefaultProxies.proxies, customProxies, opts.proxies);

	container = opts.container || container || Ti.UI.createScrollView({layout: "vertical", height: Ti.UI.SIZE, width: Ti.UI.SIZE});
	var currentLabel; // variable to use for constucting multi style labels
	var androidHtml = "";
	var tempAttributes = [];

	exports.container = container;

	// strip comments and whitespaces
	xml = xml.trim();
	xml = xml.replace(/<!--[\s\S]*?-->/g, '').replace(new RegExp("\\n" ,"g"), '<br />');

	// start processing
	if (OS_IOS) tag(xml);
	else if (OS_ANDROID) androidTag(xml);

	//finalize currentLabel if it's not null
	finalizeLabel();
	return container;

	/**
	 * Tag.
	 */
	function tag(data) {
		var re = /^<([\w-:.]+)\s*/;
		var m = re.exec(data);
		var el;

		// starts with simple text, could have children later
		if (!m) {
			// check if there are children
			var c = /<([\w-:.]+)\s*/.exec(data);
			var text = data;

			if(c != null && c[0] != null) {
				// split, process, loop
				text = text.substr(0,c.index);
				data = data.substr(c.index);

			} else {
				data = "";
			}

			el = proxy({
				name: "span",
				attributes: {},
				start: 0,
				end: data.length,
				text: text,
				content: data
			}); // create proxy

			// loop
			if(0 != data.length) tag(data);
			return;
		}

		// case when content starts with a child
		var block 	= Extract(data,m[1]);
		var child 	= re.exec(block.content);
		var content = block.content;

		// if the block has at least a child, iterate in it.
		data = data.replace(data.slice(block.start, block.end - block.start), '');
		if (!!child) {
			el = proxy(block); // create proxy
			tag(block.content);
		}

		// if the block doesn't start with a child but has text
		if (!child && !!content.length) {
			if (block.text.length != block.content.length) {
				// Maybe don't override the content. Check if it doesn't break anything
				el = proxy(_.extend(_.clone(block), {text: "", content: ""}));
				tag(block.content);

			} else {
				el = proxy(block); // create proxy∑
			}
		}

		// i.e. <br/>
		if (!child && !content.length) el = proxy(block);

		if (el && el.end && _.isFunction(el.end)) {
			finalizeLabel();
			container.addTo = null;
			el.end(container);
		}

		// continue with the rest of the xml
		tag(data);
		return;
	}

	function androidTag(data) {
		var re = /^<([\w-:.]+)\s*/;
		var m = re.exec(data);

		// case when content starts with a child
		var block 	= Extract(data,m[1]);

		if (null == proxies[block.name]) block.name = "span";

		if (proxies[block.name].type == exports.TYPE_TEXT) {
			block.content = data.substr(block.start, block.end);
		}
		data = data.replace(data.slice(block.start, block.end - block.start), '');
		proxy(block);

		if (0 != data.length) {
			androidTag(data);
			return;
		}
		return;
	}

	/**
	 * Strip.
	 */

	function strip(val) {
		return val.replace(/^['"]|['"]$/g, '');
	}

	/**
	 * .
	 */

	function proxy(element) {
		viewCount++;

		if (null == proxies[element.name]) element.name = "span";

		if (proxies[element.name].type == exports.TYPE_TEXT && _.isFunction(proxies[element.name].handler)) {
			if (null == currentLabel) currentLabel = {text: "", attributes: []};

			if (OS_IOS) {
				// fix t.attributes ranges
				var t = proxies[element.name].handler(element, container);
				currentLabel.text += t.text;
				cascadingAttributes(t, element);
			} else {
				androidHtml += element.content;
			}

		} else if (proxies[element.name].type == exports.TYPE_CUSTOM && _.isFunction(proxies[element.name].handler)) {
			// check if currentLabel is null
			finalizeLabel();
			proxies[element.name].handler(element, container);
		}

		return proxies[element.name];
	}

	function cascadingAttributes(e, element) {
		if (e.text == "") {
			tempAttributes = concatAttributes(tempAttributes, e.attributes);
		} else {
			var start = currentLabel.text.length - e.text.length, length = e.text.length;
			tempAttributes = concatAttributes(tempAttributes, e.attributes);

			_.each(tempAttributes, function(a) {
				a.range = [start, length];
				currentLabel.attributes.push(a);
			});

			if(element.content.length == element.text.length) tempAttributes = [];
		}
	}

	function concatAttributes(haystack, needles) {
		var attrs = [];
		_.each(haystack, function(attr) {
			_.each(needles, function(needle, index) {
				if (attr.type == needle.type) {
					switch(attr.type) {
						case Ti.UI.ATTRIBUTE_FONT:
							attr.value = _.extend(attr.value, needle.value);
							needles.splice(index,1);
							break;
					}
				}
			});
		});
		return haystack.concat(needles);
	}

	function finalizeLabel() {
		if (null == currentLabel && OS_IOS) return;
		if ("" == androidHtml && OS_ANDROID) return;

		if (opts.lineSpacing && OS_IOS) {
			currentLabel.attributes.push({
				type: Ti.UI.ATTRIBUTE_PARAGRAPH_STYLE,
				value: {
					lineSpacing: opts.lineSpacing
				},
				range: [0,currentLabel.text.length]
			});
		}

		if (opts.characterSpacing && OS_IOS) {
			currentLabel.attributes.push({
				type: Ti.UI.ATTRIBUTE_KERN,
				value: opts.characterSpacing,
				range: [0,currentLabel.text.length]
			});
		}

		var labelProperties = null;

		if (OS_IOS) {
			var as = Ti.UI.createAttributedString(currentLabel);
			labelProperties = _.extend({
				attributedString: as,
				font: {fontSize: 14}
			}, opts.textStyle);
		} else {
			labelProperties = _.extend({
				lineSpacing: {add: opts.lineSpacing, multiply: 1.2},
				html: androidHtml,
				font: {fontSize: 14}
			}, opts.textStyle);
		}
		var label = Ti.UI.createLabel(labelProperties);
		label.addEventListener("link", linkHandler);
		// add label to container

		if (container.addTo) container.addTo.add(label);
		else container.add(label);

		currentLabel = null;
		androidHtml = "";
		label = null;
	}

	function linkHandler(e) {
		if (null != opts.linkHandler && _.isFunction(opts.linkHandler)) {
			opts.linkHandler(e);
			return;
		}

		if (e.url) Ti.Platform.openURL(e.url);
	}
}

/**
 * Expose `parse`.
 */
exports.process = parse;

/**
 * Public methods
 */

// Use this method to set your own proxies
exports.overrideProxies = function(p) {
	_.extend(customProxies, p);
};

// Use this method to set your own container view
exports.setContainer = function(view) {
	container = view;
};

// A getter for the container
exports.getContainer = function() {
	return container;
};

/**
 * Setting type constants
 */
exports.TYPE_TEXT = 0;
exports.TYPE_CUSTOM = 1;