/**
 * @name CeL file function for XML
 * @fileoverview 本檔案包含了處理 XML file 的 functions。
 * @since
 */

// More examples: see /_test suite/test.js
'use strict';

// --------------------------------------------------------------------------------------------

// 不採用 if 陳述式，可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
	// module name
	// application.storage.format.EPUB
	name : 'data.XML',

	require : '',

	// 設定不匯出的子函式。
	// no_extend : '*',

	// 為了方便格式化程式碼，因此將 module 函式主體另外抽出。
	code : module_code
});

function module_code(library_namespace) {

	// nothing required

	/**
	 * null module constructor
	 * 
	 * @class XML 操作相關之 function。
	 */
	var _// JSDT:_module_
	= function() {
		// null module constructor
	};

	/**
	 * for JSDT: 有 prototype 才會將之當作 Class
	 */
	_// JSDT:_module_
	.prototype = {};

	/**
	 * <code>
	 parse XML document
	 http://www.adp-gmbh.ch/web/js/msxmldom/methods_properties.html
	 http://www.w3pop.com/learn/view/doc/transform_XML/
	 http://www.vacant-eyes.jp/Tips/txml/030.aspx
	 http://www.klstudio.com/post/94.html
	 http://xmljs.sourceforge.net/
	 ajaxslt	http://code.google.com/p/ajaxslt/

	 flag:	dcba in binary
	 isFP:
	 a==0	view as text
	 a==1	view as filename	If you want to parse a local file, You can use XSLT as well.
	 rX:
	 b==0	return dom.documentElement object
	 b==1	return dom object
	 fast:
	 c==0	normal speed
	 c==1	faster: ignore check

	 to use:
	 filtered_node=return_nodes.selectSingleNode("/tag1/tag2[tag3='1041']")
	 nodes.selectSingleNode("~").Text;
	 nodes.item(0).text;
	 node.getAttribute("~");
	 node.attributes(0).firstChild.nodeValue.valueOf()
	 node.attributes.item(0).firstChild.nodeValue.valueOf()
	 node.attributes.getNamedItem("~").nodeValue.valueOf()
	 ..

	 getXML():
	 loadXML(getU('全省空白價目表.xml')).getElementsByTagName("Worksheet").length


	 TODO:
	 可參考 JKL.ParseXML, http://doctype.googlecode.com/svn/trunk/goog/dom/xml.js
	 postXML()和parseXML(text/HTML application/xhtml+xml object/array)方法
	 MSXML2.XSLTemplate

	 libXmlRequest Library
	 r=document.implementation.createDocument("",XMLtext,null);
	 r.appendChild(r.createElement(XMLtext));


	 string = (new XMLSerializer()).serializeToString(xmlobject);

	 </code>
	 */
	function loadXML(XMLtext, flag) {
		var dom,
		// xmlDoc,
		isFP = flag % 2, rX, fast;

		if (window.DOMParser) {
			dom = (new DOMParser).parseFromString(XMLtext,
			// 'application/xml'
			'text/xml');
			if (!dom.documentElement
					|| dom.documentElement.tagName === 'parsererror') {
				throw new Error(
						dom.documentElement.firstChild.data
								+ '\n'
								+ dom.documentElement.firstChild.nextSibling.firstChild.data);
			}
			return dom;
		}

		if (typeof ActiveXObject === 'undefined') {
			dom = document.createElement('div');
			dom.innerHTML = XMLtext;
			return dom;
		}

		try {
			/**
			 * ActiveXObject is supported
			 * 
			 * フリースレッド DOM ドキュメントを使用すれば、ファイルを共有アプリケーション状態に取り込むことができます。
			 * 
			 * フリースレッド モデルの欠点の 1
			 * つは、未使用のメモリのクリーンアップにおける待ち時間が増大し、それ以降の操作のパフォーマンスに影響を及ぼすということです
			 * (実際にはクリーンアップが遅れているだけなのに、これをメモリ リークとして報告してくる人もいます)。
			 * 
			 * @see http://www.microsoft.com/japan/msdn/columns/xml/xml02212000.aspx
			 */
			dom = new ActiveXObject("Microsoft.FreeThreadedXMLDOM");
		} catch (e) {
			// CreateObject("Microsoft.XMLDOM");
			// MSXML3.DOMDocument,MSXML2.DOMDocument,MSXML.DOMDocument,
			// Msxml2.DOMDocument.6.0,Msxml2.DOMDocument.5.0,Msxml2.DOMDocument.4.0,MSXML4.DOMDocument,Msxml2.DOMDocument.3.0
			dom = new ActiveXObject("Microsoft.XMLDOM");
		}

		if (!dom)
			throw new Error(1, 'No parser!');

		flag >>= 1;
		rX = flag % 2;
		flag >>= 1;
		fast = flag % 2;

		// faster:
		// 既定の 「レンタル」 スレッディング モデルを使用する方法です (このスレッディング モデルでは、DOM ドキュメントは一度に 1
		// つのスレッドからしか使用できません)。
		// http://www.microsoft.com/japan/msdn/columns/xml/xml02212000.aspx
		if (fast)
			dom.validateOnParse = dom.resolveExternals = dom.preserveWhiteSpace = false;

		if (isFP) {
			// 'false'
			dom.async = false;
			// DTD Validation
			// dom.validateOnParse=true;
			dom.load(XMLtext);
		} else
			dom.loadXML(XMLtext);
		if (Number(dom.parseError)) {
			// return null;
			throw dom.parseError;
		}
		// with(dom.parseError)errorCode,reason,line
		return rX ? dom : dom.documentElement;
	}

	_.loadXML = loadXML;

	// untested
	// TODO:
	// (new XSLTProcessor()).importStylesheet(XMLF);
	// libXmlRequest Library
	// applyXSLT[generateCode.dLK]='loadXML';
	function applyXSLT(XMLF, XSLTF) {
		return loadXML(XSLTF, 1 + 2).transformNode(loadXML(XSLTF, 1 + 2));
	}

	// ↑XMLHttp set ==================

	/**
	 * XML declaration, xml header.
	 * 
	 * @param {String}[encoding]
	 *            encoding
	 * @param {String}[version]
	 *            XML version
	 * 
	 * @returns {String} XML declaration
	 */
	function XML_declaration(encoding, version) {
		var declaration = '<?xml version="' + (version || '1.0') + '"';
		if (encoding) {
			// e.g., "UTF-8", "UTF-16"
			declaration += ' encoding="' + encoding + '"';
		}
		return declaration + '?>';
	}

	_.XML_declaration = XML_declaration;

	/**
	 * 設定 node 之 attributes。
	 * 
	 * @param {Object}node
	 *            已設定過 tag name 之 node
	 * @param {String}attributes
	 *            attributes to set
	 * @param {Function}[normalizer]
	 *            only needed if you want to normalize the properties
	 * 
	 * @inner
	 */
	function set_attributes(node, attributes, normalizer) {
		if (!attributes) {
			// 可能為 undefined
			return;
		}

		var matched,
		// [ all attribute, name, "", '', no-quoted ]
		attribute_pattern = /([^\s=]+)(?:=(?:"((?:\\[\s\S]|[^"\\])+)"|'((?:\\[\s\S]|[^'\\])+)'|(\S+)))?/g;
		while (matched = attribute_pattern.exec(attributes)) {
			// delete matched.input;
			// console.log(matched);
			var value = matched[2] || matched[3];
			// unescape
			// value ? value.replace(/\\(.)/g, '$1') : matched[4];
			if (normalizer)
				value = normalizer(value, matched[1]);
			if (value || value === 0)
				node[matched[1]] = value;
		}
	}

	/**
	 * 將 node 下之 tag 當作 attribute。<br />
	 * 警告: 這會 lost 一些屬性。
	 * 
	 * @param {Object}nodes
	 *            node list
	 * @param {Object}[root]
	 *            XML document root to put value
	 * @returns {Object} XML document root
	 */
	function tag_to_attribute(nodes, root) {

		if (Array.isArray(nodes)) {
			if (!root)
				root = {};
			nodes.forEach(function(node) {
				tag_to_attribute(node, root);
			});

		} else if (typeof nodes === 'object') {
			if (!root)
				root = {};
			for ( var tag in nodes) {
				root[tag] = tag_to_attribute(nodes[tag]);
				break;
			}

		} else if (nodes || nodes === 0)
			root = nodes;

		return root;
	}

	_.tag_to_attribute = tag_to_attribute;

	// text declaration of XML.
	var PATTERN_XML_declaration = /^\s*<\?xml(?:\s[^<>?]*)?\?>\s*/,
	// XML 之 tag name 應允許 ":"。
	// 無 end-tag 之 node (empty-element tag) pattern。
	// http://www.w3.org/TR/REC-xml/#sec-starttags
	// [ , tag name, attributes ]
	PATTERN_EMPTY = /<([^\0-\,;->\s]+)(\s[^<>]*?)?\/>/,
	// end-tag 之 pattern。必須允許 ":","-"。
	// [ , last children, tag name ]
	PATTERN_END = /^([\S\s]*?)<\/([^\0-\,;->\s]+)\s*>/,
	//
	KEY_default_tag_name_mark = typeof Symbol === 'function' ? Symbol('tag name')
			: '$';

	_.KEY_default_tag_name_mark = KEY_default_tag_name_mark;

	/**
	 * parse XML to JSON.
	 * 
	 * TODO: new CeL.XML_Object(XML string), convert XML string to list of
	 * normal tag node: { type:'tag', tag_name:'', attributes:['
	 * attribute="value"',' attribute="value"'],
	 * attribute_hash:{attribute:"value", ...},
	 * child_nodes:['spaces',{child_node},'plain text', ...] }
	 * 
	 * commet node: { type:'commets', inner_text:'' }
	 * 
	 * @param {String}XML
	 *            XML string
	 * @param {Object}[options]
	 *            附加參數/設定選擇性/特殊功能與選項
	 * 
	 * @returns {Array|Object|String} XML nodes
	 */
	function XML_Object(XML, options) {
		TODO;
	}

	/**
	 * parse XML to JSON. Will trim spaces.
	 * 
	 * @param {String}XML
	 *            XML string
	 * @param {Object}[options]
	 *            附加參數/設定選擇性/特殊功能與選項<br />
	 *            {Boolean}options.node_normalizer(text): only needed if you
	 *            want to normalize the properties, {Boolean}options.numeralize:
	 *            only needed if you want to numeralize the properties
	 * 
	 * @returns {Array|Object|String} XML nodes
	 */
	function XML_to_JSON(XML, options) {
		if (!XML) {
			return XML;
		}

		// 前置處理。
		options = library_namespace.setup_options(options);
		var tag_name_mark = options.tag_name_mark;
		if (tag_name_mark === true) {
			tag_name_mark = KEY_default_tag_name_mark;
		}

		// console.log(XML);
		if (!options.preserve_declaration) {
			// 去除起首之 declaration。
			XML = XML.replace(PATTERN_XML_declaration, '');
		}

		var nodes = [ XML ],
		//
		normalizer = options.node_normalizer,
		//
		remove_spaces = !options.preserve_spaces;

		if (typeof normalizer !== 'function') {
			if (normalizer) {
				library_namespace.error('XML_to_JSON: Invalid normalizer: '
						+ normalizer);
			}
			normalizer = typeof options.numeralize === 'function'
			// treat as normalizer
			? options.numeralize
			// assert: normalizer === true
			: options.numeralize ? function(string) {
				return isNaN(string) ? string : +string;
			} : null;
		} else if (options.numeralize) {
			library_namespace
					.error('XML_to_JSON: Please numeralize node value in the options.node_normalizer!'
							+ normalizer);
		}
		if (remove_spaces) {
			normalizer = normalizer ? function(string) {
				return typeof string === 'string' ? string.trim()
						&& this(string) : string;
			}.bind(normalizer) : function(string) {
				return (typeof string !== 'string' || string.trim()) && string;
			};
		}

		for (var index = 0, matched; index < nodes.length; index++) {
			var this_node = nodes[index];
			if (typeof this_node !== 'string') {
				// assert: processed node
				continue;
			}

			// 'f<n/>e' → [ 'f', {n:null}, 'e' ]
			if (matched = this_node.match(PATTERN_EMPTY)) {
				// 本次要建構的 node。
				var node = {},
				//
				tail = this_node.slice(matched.index + matched[0].length);
				if (normalizer)
					tail = normalizer(tail);
				if (tail || tail === 0)
					nodes.splice(index + 1, 0, node, tail);
				else
					nodes.splice(index + 1, 0, node);
				tail = nodes[index].slice(0, matched.index);
				if (normalizer)
					tail = normalizer(tail);
				if (tail || tail === 0)
					nodes[index] = tail;
				else
					nodes.splice(index, 1);
				node[matched[1]] = null;
				if (tag_name_mark)
					node[tag_name_mark] = matched[1];
				set_attributes(node, matched[2], normalizer);
				// reset index to search next one
				index--;
				continue;
			}

			matched = this_node.match(PATTERN_END);
			if (!matched) {
				if (remove_spaces && !this_node.trim()) {
					nodes.splice(index--, 1);
				}
				// assert: no-function text. leave it alone.
				if (false && /[<>]/.test(this_node)) {
					library_namespace.error('XML_to_JSON: parse error: '
							+ this_node);
				}
				continue;
			}

			// 'f<n>c</n>e' → [ 'f', {n:'c'}, 'e' ]
			// [ 'f<N>', {n:'c'}, '</N>e' ] → [ 'f', {N : {n:'c'}}, 'e' ]

			// 前溯查找 node start.
			// tag_pattern: [ , attributes, first child ]
			for (var i = index, tag_pattern = new RegExp('<' + matched[2]
			// TODO: parse "<br />"
			+ '(\\s[^<>]*)?>([\\S\\s]*?)$'); i >= 0; i--) {
				this_node = nodes[i];
				if (typeof this_node !== 'string')
					continue;

				var tag_matched = (i === index ? matched[1] : this_node)
						.match(tag_pattern);
				if (!tag_matched) {
					// 再往前找。
					continue;
				}

				var more_tag_matched;
				// TODO: using lookbehind search?
				while (more_tag_matched = tag_matched[2].match(tag_pattern)) {
					// e.g., for '<g><g></g></g>'
					// 2 === '<'.length + '>'.length
					more_tag_matched.index += 2 + matched[2].length
							+ (tag_matched[1] ? tag_matched[1].length : 0);
					tag_matched = more_tag_matched;
				}
				// assert: more_tag_matched === null

				// assert: tail <= 0
				var tail = matched[0].length - nodes[index].length;

				// 設定好 node's 殘餘值。

				// 檢查是否需切割。

				// 切割 end: 'f</n>e'
				// → matched[1] = 'f' | matched[1].length
				// | '</n>' | matched[0].length
				// | nodes[index] = 'e'
				if (tail < 0) {
					// -(tail length) → {String}tail
					tail = nodes[index].slice(tail);
					if (normalizer)
						tail = normalizer(tail);
					if (i === index)
						// 直接添加一個當下一 node。
						nodes.splice(i + 1, 0, tail);
					else
						nodes[index] = tail;
					if (tail === 0) {
						// 預防之後的判斷失誤。
						tail = String(tail);
					}
				}

				var tag_start_from_head = tag_matched.index === 0;

				// 切割 start: 'f<n>e'
				// → nodes[i] = 'f' | tag_matched.index
				// | tag_matched[1] = '<n>' | nodes[i].length -
				// tag_matched[2].length
				// | tag_matched[2] = 'e'
				// 盡量晚點 copy。
				if (!tag_start_from_head) {
					// assert: tag_matched.index > 0
					// has head
					this_node = nodes[i].slice(0, tag_matched.index);
					if (normalizer) {
						this_node = normalizer(this_node);
					}
					if (this_node || this_node === 0) {
						nodes[i] = this_node;
					} else {
						// 前面是空白，可以切掉。
						tag_start_from_head = true;
					}
				}

				// 本次要建構的 node。
				var node = {},
				// 切割出 node
				children = nodes.splice(
				// 自此添加新 node。
				i + (tag_start_from_head ? 0 : 1),
				// 前後皆無東西，使其整組消失。
				index - i
				// 使 head 整組消失。
				+ (tag_start_from_head ? 1 : 0)
				// 不同 node 且有 tail 則少刪一個。
				- (i !== index && tail ? 1 : 0), node);
				// 去掉頭尾多切割出的部分。
				if (tag_start_from_head) {
					// head srction 從一開始就是 tag，因此直接將之除掉。
					children.shift();
				}
				if (i !== index && !tail) {
					// end srction 到結尾都是 end tag，因此直接將之除掉。
					children.pop();
				}

				// setup tag name & children
				if (remove_spaces) {
					tag_matched[2] = tag_matched[2].trim();
					matched[1] = matched[1].trim();
				}
				if ((this_node = tag_matched[2])
						&& (!normalizer || (this_node = normalizer(this_node)) || this_node === 0)) {
					// add first children @ last of head srction
					children.unshift(this_node);
				}
				if (i !== index
						&& (this_node = matched[1])
						&& (!normalizer || (this_node = normalizer(this_node)) || this_node === 0)) {
					// add last children @ head of end srction
					children.push(this_node);
				}
				// node's first property: node[tag name] = children
				node[matched[2]] = children.length === 0 ? null
						: children.length === 1 ? children[0] : children;
				if (tag_name_mark)
					node[tag_name_mark] = matched[2];
				set_attributes(node, tag_matched[1], normalizer);
				// reset index to search next one
				index = i + (tag_start_from_head ? 0 : 1);
				break;
			}

			if (false && i < 0) {
				// 回溯到第一個 node 都沒找到開始標記。
				library_namespace
						.error('XML_to_JSON: parse error: ' + nodes[i]);
			}
		}

		if (remove_spaces && typeof nodes.at(-1) === 'string'
				&& !(nodes[nodes.length - 1] = nodes.at(-1).trim())) {
			// 最後一個 node 為 spaces。
			nodes.pop();
		}
		if (nodes.length === 0)
			nodes = null;
		else if (nodes.length === 1)
			nodes = normalizer ? normalizer(nodes[0]) : nodes[0];

		if (options.tag_as_attribute)
			nodes = tag_to_attribute(nodes);

		return nodes;
	}

	// _.XML_to_JSON = XML_to_JSON;

	/**
	 * 以遞歸方式將 nodes 轉成 XML。
	 * 
	 * @example<code>

	 to_XML({a:12,b:34})
	 // <a b="34">12</a>

	 to_XML({a:[2,3,{r:{t:'e'}}],b:34})
	 // <a b="34">23<r><t>e</t></r></a>

	 * </code>
	 * 
	 * @param {Object}nodes
	 *            node list
	 * @param {Object}[options]
	 *            options<br />
	 *            {Boolean}options.no_empty: no empty-element tags.
	 * 
	 * @returns {String}XML
	 */
	function to_XML(nodes, options) {
		if (Array.isArray(nodes)) {
			return nodes.map(function(node) {
				return to_XML(node, options);
			}).join(options && options.separator || '');
		}

		if (typeof nodes === 'object') {
			var node = [], tag, name;
			for (name in nodes) {
				if (tag) {
					node.push(' ' + name + '="'
							+ String(nodes[name]).replace(/"/g, '\\"') + '"');
				} else {
					node.push('<' + name);
					tag = name;
				}
			}
			name = nodes[tag];
			name = name || name === 0
			// 用遞歸的方式處理children。
			? to_XML(name, options) : options && options.no_empty ? '' : name;
			node.push(name || name === '' ? '>' + name + '</' + tag + '>'
			// e.g., name = null, undefined
			: ' />');
			return node.join(options && options.separator || '');
		}

		return name || name === 0 ? String(nodes) : nodes;
	}

	/**
	 * 將 nodes 轉成 XML (frontend)
	 * 
	 * @param {Object}nodes
	 *            node list
	 * @param {Object}[options]
	 *            options<br />
	 *            {Boolean}options.declaration: 是否加上起首之 declaration。<br />
	 *            deprecated: options.no_declaration
	 * 
	 * @returns {String}XML
	 */
	function JSON_to_XML(nodes, options) {
		var XML = to_XML(nodes, options);

		if (options && (options === true || options.declaration)) {
			// 加上起首之 declaration。
			XML = XML_declaration("UTF-8")
					+ (options && options.separator || '') + XML;
		}

		return XML;
	}

	// _.JSON_to_XML = JSON_to_XML;

	// ------------------------------------------------------------------------

	// TODO

	function XML_parser(options) {
		/**
		 * <code>

		xml_parser = new XML_parser(options);

		// 維護一份堆疊
		options.provide_stack = false

		options.on_self_closing_tag(tag, stack)
		options.on_open_tag(tag, stack)
		options.on_close_tag(tag, stack, latest_tag)
		options.on_error(error, stack)
		options.on_end(tail, stack)
		value = options.attribute_normalizer(value, key)

		</code>
		 */

		Object.assign(this, options);
	}

	function XML_parser_parse(XML_text, options) {
		var provide_stack = this.provide_stack;
		var stack = [];

		var attribute_normalizer = this.attribute_normalizer;

		XML_text = String(XML_text);

		// matched:
		// [ all tag, is_close_tag, tag_name, attributes, is_self_closing_tag ]
		var PATTERN_next_tag = /<(\/?)([^<>\s"'=:]+)((?:\s+[^<>\s=]+(?:="[^"]*"|='[^']*'|=[^<>\s=]*)?)*\s*)(\/?)>/g;
		var matched, last_index = 0;
		while (matched = PATTERN_next_tag.exec(XML_text)) {
			// delete matched.input;
			// console.log(matched);
			if (!matched[4] && matched[3].endsWith('/')) {
				matched[4] = '/';
				matched[3] = matched[3].slice(0, -1);
			}

			// parse attributes
			var attributes = Object.create(null);
			// console.log(matched[3]);
			set_attributes(attributes, matched[3], attribute_normalizer);
			// console.log(attributes);

			// https://github.com/lddubeau/saxes/blob/master/src/saxes.ts#L381
			var tag = {
				name : matched[2],
				attributes : attributes,
				prefix : XML_text.slice(last_index, matched.index),
				is_self_closing : !!matched[4]
			};
			if (matched[1]) {
				// close tag
				var latest_tag = stack.pop();
				if (!latest_tag
				// Check the stack
				|| (provide_stack ? latest_tag.name : latest_tag) !== tag.name) {
					var error = stack.clone();
					if (latest_tag)
						error.push(latest_tag);
					error = error.map(function(tag) {
						return '<' + (provide_stack ? tag.name : tag) + '>';
					});
					// error.push('<' + tag.name + '>?');
					error = new Error('Close tag without open tag! '
					//
					+ error.join('') + '→' + matched[0] + '\n'
					// + 'This piece: '
					+ XML_text.slice(Math.max(0, last_index - 20),
					//
					matched.index + matched[0].length));
					if (!this.on_error)
						throw error;
					this.on_error(error, stack, latest_tag);
				}

				this.on_close_tag(tag, stack, latest_tag);
			} else if (matched[4]) {
				// self-closing tag
				// Do not push to stack.
				this.on_self_closing_tag(tag, stack);
			} else {
				// open tag
				stack.push(provide_stack ? tag : tag_name);
				this.on_open_tag(tag, stack);
			}
			last_index = matched.index + matched[0].length;
			library_namespace.debug(last_index + '/' + XML_text.length + ': '
					+ XML_text.slice(last_index, last_index + 200), 2);
		}

		if (stack.length !== 0) {
			var error = new Error('There are ' + stack.length
					+ ' element(s) left!');
			if (!this.on_error)
				throw error;
			this.on_error(error, stack);
		}

		this.on_end(XML_text.slice(last_index), stack);
	}

	Object.assign(XML_parser.prototype, {
		parse : XML_parser_parse
	});

	var KEY_children = 'children';
	// var KEY_TAGNAME = 0, KEY_ATTRIBUTES = 1, KEY_CHILDREN = 2;

	function parse_XML(XML_text, options) {
		function push_prefix(tag, stack) {
			var prefix = tag.prefix;
			delete tag.prefix;
			var children_list = XML_stack.at(-1);
			if (prefix)
				children_list.push(prefix);
			return children_list
		}

		function parse_XML__on_self_closing_tag(tag, stack) {
			// console.trace(tag);
			var children_list = push_prefix(tag, stack);
			children_list.push(tag);
		}

		function parse_XML__on_open_tag(tag, stack) {
			parse_XML__on_self_closing_tag(tag, stack);
			XML_stack.push(tag[KEY_children] = []);
		}

		function parse_XML__on_close_tag(tag, stack, latest_tag) {
			// console.trace(tag);
			var children_list = push_prefix(tag, stack);
			XML_stack.pop();
		}

		function parse_XML__on_end(tail, stack) {
			// console.trace(tail);
			if (XML_stack.length !== 1)
				throw new Error('There are ' + (XML_stack.length - 1)
						+ 'elements left!');
			if (tail)
				XML_stack[0].push(tail);
		}

		var XML_object = [], XML_stack = [ XML_object ];
		var xml_parser = new XML_parser(Object.assign(Object.create(null), {
			on_self_closing_tag : parse_XML__on_self_closing_tag,
			on_open_tag : parse_XML__on_open_tag,
			on_close_tag : parse_XML__on_close_tag,
			// on_error(error, stack)
			on_end : parse_XML__on_end,

			provide_stack : true
		}, options));
		xml_parser.parse(XML_text);
		return XML_object;
	}

	// ------------------------------------------------------------------------

	// TODO: XML search

	library_namespace.set_method(JSON, {
		to_XML : JSON_to_XML,
		from_XML : XML_to_JSON
	});

	Object.assign(_, {
		XML_parser : XML_parser,
		parse_XML : parse_XML
	});

	return (_// JSDT:_module_
	);
}
