mirror of
https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
synced 2025-08-30 01:05:58 +02: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_
|
||
);
|
||
}
|