mirror of
				https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
				synced 2025-11-04 05:15:23 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			840 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			840 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						||
 * @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_
 | 
						||
	);
 | 
						||
}
 |