update
This commit is contained in:
@@ -0,0 +1,517 @@
|
||||
// ========================================================================
|
||||
// XML.ObjTree -- XML source code from/to JavaScript object like E4X
|
||||
// ========================================================================
|
||||
|
||||
if (typeof (XML) == 'undefined') XML = function () { };
|
||||
|
||||
// constructor
|
||||
|
||||
XML.ObjTree = function () {
|
||||
return this;
|
||||
};
|
||||
|
||||
// class variables
|
||||
|
||||
XML.ObjTree.VERSION = "0.23";
|
||||
|
||||
// object prototype
|
||||
|
||||
XML.ObjTree.prototype.xmlDecl = '<?xml version="1.0" encoding="UTF-8" ?>\n';
|
||||
XML.ObjTree.prototype.attr_prefix = '-';
|
||||
|
||||
// method: parseXML( xmlsource )
|
||||
|
||||
XML.ObjTree.prototype.parseXML = function (xml) {
|
||||
var root;
|
||||
if (window.DOMParser) {
|
||||
var xmldom = new DOMParser();
|
||||
// xmldom.async = false; // DOMParser is always sync-mode
|
||||
var dom = xmldom.parseFromString(xml, "application/xml");
|
||||
if (!dom) return;
|
||||
root = dom.documentElement;
|
||||
} else if (window.ActiveXObject) {
|
||||
xmldom = new ActiveXObject('Microsoft.XMLDOM');
|
||||
xmldom.async = false;
|
||||
xmldom.loadXML(xml);
|
||||
root = xmldom.documentElement;
|
||||
}
|
||||
if (!root) return;
|
||||
return this.parseDOM(root);
|
||||
};
|
||||
|
||||
// method: parseHTTP( url, options, callback )
|
||||
|
||||
XML.ObjTree.prototype.parseHTTP = function (url, options, callback) {
|
||||
var myopt = {};
|
||||
for (var key in options) {
|
||||
myopt[key] = options[key]; // copy object
|
||||
}
|
||||
if (!myopt.method) {
|
||||
if (typeof (myopt.postBody) == "undefined" &&
|
||||
typeof (myopt.postbody) == "undefined" &&
|
||||
typeof (myopt.parameters) == "undefined") {
|
||||
myopt.method = "get";
|
||||
} else {
|
||||
myopt.method = "post";
|
||||
}
|
||||
}
|
||||
if (callback) {
|
||||
myopt.asynchronous = true; // async-mode
|
||||
var __this = this;
|
||||
var __func = callback;
|
||||
var __save = myopt.onComplete;
|
||||
myopt.onComplete = function (trans) {
|
||||
var tree;
|
||||
if (trans && trans.responseXML && trans.responseXML.documentElement) {
|
||||
tree = __this.parseDOM(trans.responseXML.documentElement);
|
||||
}
|
||||
__func(tree, trans);
|
||||
if (__save) __save(trans);
|
||||
};
|
||||
} else {
|
||||
myopt.asynchronous = false; // sync-mode
|
||||
}
|
||||
var trans;
|
||||
if (typeof (HTTP) != "undefined" && HTTP.Request) {
|
||||
myopt.uri = url;
|
||||
var req = new HTTP.Request(myopt); // JSAN
|
||||
if (req) trans = req.transport;
|
||||
} else if (typeof (Ajax) != "undefined" && Ajax.Request) {
|
||||
var req = new Ajax.Request(url, myopt); // ptorotype.js
|
||||
if (req) trans = req.transport;
|
||||
}
|
||||
if (callback) return trans;
|
||||
if (trans && trans.responseXML && trans.responseXML.documentElement) {
|
||||
return this.parseDOM(trans.responseXML.documentElement);
|
||||
}
|
||||
}
|
||||
|
||||
// method: parseDOM( documentroot )
|
||||
|
||||
XML.ObjTree.prototype.parseDOM = function (root) {
|
||||
if (!root) return;
|
||||
|
||||
this.__force_array = {};
|
||||
if (this.force_array) {
|
||||
for (var i = 0; i < this.force_array.length; i++) {
|
||||
this.__force_array[this.force_array[i]] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
var json = this.parseElement(root); // parse root node
|
||||
if (this.__force_array[root.nodeName]) {
|
||||
json = [json];
|
||||
}
|
||||
if (root.nodeType != 11) { // DOCUMENT_FRAGMENT_NODE
|
||||
var tmp = {};
|
||||
tmp[root.nodeName] = json; // root nodeName
|
||||
json = tmp;
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
// method: parseElement( element )
|
||||
|
||||
XML.ObjTree.prototype.parseElement = function (elem) {
|
||||
// COMMENT_NODE
|
||||
if (elem.nodeType == 7) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TEXT_NODE CDATA_SECTION_NODE
|
||||
if (elem.nodeType == 3 || elem.nodeType == 4) {
|
||||
var bool = elem.nodeValue.match(/[^\x00-\x20]/);
|
||||
if (bool == null) return; // ignore white spaces
|
||||
return elem.nodeValue;
|
||||
}
|
||||
|
||||
var retval;
|
||||
var cnt = {};
|
||||
|
||||
// parse attributes
|
||||
if (elem.attributes && elem.attributes.length) {
|
||||
retval = {};
|
||||
for (var i = 0; i < elem.attributes.length; i++) {
|
||||
var key = elem.attributes[i].nodeName;
|
||||
if (typeof (key) != "string") continue;
|
||||
var val = elem.attributes[i].nodeValue;
|
||||
if (!val) continue;
|
||||
key = this.attr_prefix + key;
|
||||
if (typeof (cnt[key]) == "undefined") cnt[key] = 0;
|
||||
cnt[key]++;
|
||||
this.addNode(retval, key, cnt[key], val);
|
||||
}
|
||||
}
|
||||
|
||||
// parse child nodes (recursive)
|
||||
if (elem.childNodes && elem.childNodes.length) {
|
||||
var textonly = true;
|
||||
if (retval) textonly = false; // some attributes exists
|
||||
for (var i = 0; i < elem.childNodes.length && textonly; i++) {
|
||||
var ntype = elem.childNodes[i].nodeType;
|
||||
if (ntype == 3 || ntype == 4) continue;
|
||||
textonly = false;
|
||||
}
|
||||
if (textonly) {
|
||||
if (!retval) retval = "";
|
||||
for (var i = 0; i < elem.childNodes.length; i++) {
|
||||
retval += elem.childNodes[i].nodeValue;
|
||||
}
|
||||
} else {
|
||||
if (!retval) retval = {};
|
||||
for (var i = 0; i < elem.childNodes.length; i++) {
|
||||
var key = elem.childNodes[i].nodeName;
|
||||
if (typeof (key) != "string") continue;
|
||||
var val = this.parseElement(elem.childNodes[i]);
|
||||
if (!val) continue;
|
||||
if (typeof (cnt[key]) == "undefined") cnt[key] = 0;
|
||||
cnt[key]++;
|
||||
this.addNode(retval, key, cnt[key], val);
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
};
|
||||
|
||||
// method: addNode( hash, key, count, value )
|
||||
|
||||
XML.ObjTree.prototype.addNode = function (hash, key, cnts, val) {
|
||||
if (this.__force_array[key]) {
|
||||
if (cnts == 1) hash[key] = [];
|
||||
hash[key][hash[key].length] = val; // push
|
||||
} else if (cnts == 1) { // 1st sibling
|
||||
hash[key] = val;
|
||||
} else if (cnts == 2) { // 2nd sibling
|
||||
hash[key] = [hash[key], val];
|
||||
} else { // 3rd sibling and more
|
||||
hash[key][hash[key].length] = val;
|
||||
}
|
||||
};
|
||||
|
||||
// method: writeXML( tree )
|
||||
|
||||
XML.ObjTree.prototype.writeXML = function (tree) {
|
||||
var xml = this.hash_to_xml(null, tree);
|
||||
return this.xmlDecl + xml;
|
||||
};
|
||||
|
||||
// method: hash_to_xml( tagName, tree )
|
||||
|
||||
XML.ObjTree.prototype.hash_to_xml = function (name, tree) {
|
||||
var elem = [];
|
||||
var attr = [];
|
||||
for (var key in tree) {
|
||||
if (!tree.hasOwnProperty(key)) continue;
|
||||
var val = tree[key];
|
||||
if (key.charAt(0) != this.attr_prefix) {
|
||||
if (typeof (val) == "undefined" || val == null) {
|
||||
elem[elem.length] = "<" + key + " />";
|
||||
} else if (typeof (val) == "object" && val.constructor == Array) {
|
||||
elem[elem.length] = this.array_to_xml(key, val);
|
||||
} else if (typeof (val) == "object") {
|
||||
elem[elem.length] = this.hash_to_xml(key, val);
|
||||
} else {
|
||||
elem[elem.length] = this.scalar_to_xml(key, val);
|
||||
}
|
||||
} else {
|
||||
attr[attr.length] = " " + (key.substring(1)) + '="' + (this.xml_escape(val)) + '"';
|
||||
}
|
||||
}
|
||||
var jattr = attr.join("");
|
||||
var jelem = elem.join("");
|
||||
if (typeof (name) == "undefined" || name == null) {
|
||||
// no tag
|
||||
} else if (elem.length > 0) {
|
||||
if (jelem.match(/\n/)) {
|
||||
jelem = "<" + name + jattr + ">\n" + jelem + "</" + name + ">\n";
|
||||
} else {
|
||||
jelem = "<" + name + jattr + ">" + jelem + "</" + name + ">\n";
|
||||
}
|
||||
} else {
|
||||
jelem = "<" + name + jattr + " />\n";
|
||||
}
|
||||
return jelem;
|
||||
};
|
||||
|
||||
// method: array_to_xml( tagName, array )
|
||||
|
||||
XML.ObjTree.prototype.array_to_xml = function (name, array) {
|
||||
var out = [];
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
var val = array[i];
|
||||
if (typeof (val) == "undefined" || val == null) {
|
||||
out[out.length] = "<" + name + " />";
|
||||
} else if (typeof (val) == "object" && val.constructor == Array) {
|
||||
out[out.length] = this.array_to_xml(name, val);
|
||||
} else if (typeof (val) == "object") {
|
||||
out[out.length] = this.hash_to_xml(name, val);
|
||||
} else {
|
||||
out[out.length] = this.scalar_to_xml(name, val);
|
||||
}
|
||||
}
|
||||
return out.join("");
|
||||
};
|
||||
|
||||
// method: scalar_to_xml( tagName, text )
|
||||
|
||||
XML.ObjTree.prototype.scalar_to_xml = function (name, text) {
|
||||
if (name == "#text") {
|
||||
return this.xml_escape(text);
|
||||
} else {
|
||||
return "<" + name + ">" + this.xml_escape(text) + "</" + name + ">\n";
|
||||
}
|
||||
};
|
||||
|
||||
// method: xml_escape( text )
|
||||
|
||||
XML.ObjTree.prototype.xml_escape = function (text) {
|
||||
return (text + '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
};
|
||||
|
||||
/*
|
||||
// ========================================================================
|
||||
|
||||
=head1 NAME
|
||||
|
||||
XML.ObjTree -- XML source code from/to JavaScript object like E4X
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
var xotree = new XML.ObjTree();
|
||||
var tree1 = {
|
||||
root: {
|
||||
node: "Hello, World!"
|
||||
}
|
||||
};
|
||||
var xml1 = xotree.writeXML( tree1 ); // object tree to XML source
|
||||
alert( "xml1: "+xml1 );
|
||||
|
||||
var xml2 = '<?xml version="1.0"?><response><error>0</error></response>';
|
||||
var tree2 = xotree.parseXML( xml2 ); // XML source to object tree
|
||||
alert( "error: "+tree2.response.error );
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
XML.ObjTree class is a parser/generater between XML source code
|
||||
and JavaScript object like E4X, ECMAScript for XML.
|
||||
This is a JavaScript version of the XML::TreePP module for Perl.
|
||||
This also works as a wrapper for XMLHTTPRequest and successor to JKL.ParseXML class
|
||||
when this is used with prototype.js or JSAN's HTTP.Request class.
|
||||
|
||||
=head2 JavaScript object tree format
|
||||
|
||||
A sample XML source:
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<family name="Kawasaki">
|
||||
<father>Yasuhisa</father>
|
||||
<mother>Chizuko</mother>
|
||||
<children>
|
||||
<girl>Shiori</girl>
|
||||
<boy>Yusuke</boy>
|
||||
<boy>Kairi</boy>
|
||||
</children>
|
||||
</family>
|
||||
|
||||
Its JavaScript object tree like JSON/E4X:
|
||||
|
||||
{
|
||||
'family': {
|
||||
'-name': 'Kawasaki',
|
||||
'father': 'Yasuhisa',
|
||||
'mother': 'Chizuko',
|
||||
'children': {
|
||||
'girl': 'Shiori'
|
||||
'boy': [
|
||||
'Yusuke',
|
||||
'Kairi'
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Each elements are parsed into objects:
|
||||
|
||||
tree.family.father; # the father's given name.
|
||||
|
||||
Prefix '-' is inserted before every attributes' name.
|
||||
|
||||
tree.family["-name"]; # this family's family name
|
||||
|
||||
A array is used because this family has two boys.
|
||||
|
||||
tree.family.children.boy[0]; # first boy's name
|
||||
tree.family.children.boy[1]; # second boy's name
|
||||
tree.family.children.girl; # (girl has no other sisiters)
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 xotree = new XML.ObjTree()
|
||||
|
||||
This constructor method returns a new XML.ObjTree object.
|
||||
|
||||
=head2 xotree.force_array = [ "rdf:li", "item", "-xmlns" ];
|
||||
|
||||
This property allows you to specify a list of element names
|
||||
which should always be forced into an array representation.
|
||||
The default value is null, it means that context of the elements
|
||||
will determine to make array or to keep it scalar.
|
||||
|
||||
=head2 xotree.attr_prefix = '@';
|
||||
|
||||
This property allows you to specify a prefix character which is
|
||||
inserted before each attribute names.
|
||||
Instead of default prefix '-', E4X-style prefix '@' is also available.
|
||||
The default character is '-'.
|
||||
Or set '@' to access attribute values like E4X, ECMAScript for XML.
|
||||
The length of attr_prefix must be just one character and not be empty.
|
||||
|
||||
=head2 tree = xotree.parseXML( xmlsrc );
|
||||
|
||||
This method loads an XML document using the supplied string
|
||||
and returns its JavaScript object converted.
|
||||
|
||||
=head2 tree = xotree.parseDOM( domnode );
|
||||
|
||||
This method parses a DOM tree (ex. responseXML.documentElement)
|
||||
and returns its JavaScript object converted.
|
||||
|
||||
=head2 tree = xotree.parseHTTP( url, options );
|
||||
|
||||
This method loads a XML file from remote web server
|
||||
and returns its JavaScript object converted.
|
||||
XMLHTTPRequest's synchronous mode is always used.
|
||||
This mode blocks the process until the response is completed.
|
||||
|
||||
First argument is a XML file's URL
|
||||
which must exist in the same domain as parent HTML file's.
|
||||
Cross-domain loading is not available for security reasons.
|
||||
|
||||
Second argument is options' object which can contains some parameters:
|
||||
method, postBody, parameters, onLoading, etc.
|
||||
|
||||
This method requires JSAN's L<HTTP.Request> class or prototype.js's Ajax.Request class.
|
||||
|
||||
=head2 xotree.parseHTTP( url, options, callback );
|
||||
|
||||
If a callback function is set as third argument,
|
||||
XMLHTTPRequest's asynchronous mode is used.
|
||||
|
||||
This mode calls a callback function with XML file's JavaScript object converted
|
||||
after the response is completed.
|
||||
|
||||
=head2 xmlsrc = xotree.writeXML( tree );
|
||||
|
||||
This method parses a JavaScript object tree
|
||||
and returns its XML source generated.
|
||||
|
||||
=head1 EXAMPLES
|
||||
|
||||
=head2 Text node and attributes
|
||||
|
||||
If a element has both of a text node and attributes
|
||||
or both of a text node and other child nodes,
|
||||
text node's value is moved to a special node named "#text".
|
||||
|
||||
var xotree = new XML.ObjTree();
|
||||
var xmlsrc = '<span class="author">Kawasaki Yusuke</span>';
|
||||
var tree = xotree.parseXML( xmlsrc );
|
||||
var class = tree.span["-class"]; # attribute
|
||||
var name = tree.span["#text"]; # text node
|
||||
|
||||
=head2 parseHTTP() method with HTTP-GET and sync-mode
|
||||
|
||||
HTTP/Request.js or prototype.js must be loaded before calling this method.
|
||||
|
||||
var xotree = new XML.ObjTree();
|
||||
var url = "http://example.com/index.html";
|
||||
var tree = xotree.parseHTTP( url );
|
||||
xotree.attr_prefix = '@'; // E4X-style
|
||||
alert( tree.html["@lang"] );
|
||||
|
||||
This code shows C<lang=""> attribute from a X-HTML source code.
|
||||
|
||||
=head2 parseHTTP() method with HTTP-POST and async-mode
|
||||
|
||||
Third argument is a callback function which is called on onComplete.
|
||||
|
||||
var xotree = new XML.ObjTree();
|
||||
var url = "http://example.com/mt-tb.cgi";
|
||||
var opts = {
|
||||
postBody: "title=...&excerpt=...&url=...&blog_name=..."
|
||||
};
|
||||
var func = function ( tree ) {
|
||||
alert( tree.response.error );
|
||||
};
|
||||
xotree.parseHTTP( url, opts, func );
|
||||
|
||||
This code send a trackback ping and shows its response code.
|
||||
|
||||
=head2 Simple RSS reader
|
||||
|
||||
This is a RSS reader which loads RDF file and displays all items.
|
||||
|
||||
var xotree = new XML.ObjTree();
|
||||
xotree.force_array = [ "rdf:li", "item" ];
|
||||
var url = "http://example.com/news-rdf.xml";
|
||||
var func = function( tree ) {
|
||||
var elem = document.getElementById("rss_here");
|
||||
for( var i=0; i<tree["rdf:RDF"].item.length; i++ ) {
|
||||
var divtag = document.createElement( "div" );
|
||||
var atag = document.createElement( "a" );
|
||||
atag.href = tree["rdf:RDF"].item[i].link;
|
||||
var title = tree["rdf:RDF"].item[i].title;
|
||||
var tnode = document.createTextNode( title );
|
||||
atag.appendChild( tnode );
|
||||
divtag.appendChild( atag );
|
||||
elem.appendChild( divtag );
|
||||
}
|
||||
};
|
||||
xotree.parseHTTP( url, {}, func );
|
||||
|
||||
=head2 XML-RPC using writeXML, prototype.js and parseDOM
|
||||
|
||||
If you wish to use prototype.js's Ajax.Request class by yourself:
|
||||
|
||||
var xotree = new XML.ObjTree();
|
||||
var reqtree = {
|
||||
methodCall: {
|
||||
methodName: "weblogUpdates.ping",
|
||||
params: {
|
||||
param: [
|
||||
{ value: "Kawa.Net xp top page" }, // 1st param
|
||||
{ value: "http://www.kawa.net/" } // 2nd param
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
var reqxml = xotree.writeXML( reqtree ); // JS-Object to XML code
|
||||
var url = "http://example.com/xmlrpc";
|
||||
var func = function( req ) {
|
||||
var resdom = req.responseXML.documentElement;
|
||||
xotree.force_array = [ "member" ];
|
||||
var restree = xotree.parseDOM( resdom ); // XML-DOM to JS-Object
|
||||
alert( restree.methodResponse.params.param.value.struct.member[0].value.string );
|
||||
};
|
||||
var opt = {
|
||||
method: "post",
|
||||
postBody: reqxml,
|
||||
asynchronous: true,
|
||||
onComplete: func
|
||||
};
|
||||
new Ajax.Request( url, opt );
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Yusuke Kawasaki http://www.kawa.net/
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (c) 2005-2006 Yusuke Kawasaki. All rights reserved.
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the Artistic license. Or whatever license I choose,
|
||||
which I will do instead of keeping this documentation like it is.
|
||||
|
||||
=cut
|
||||
// ========================================================================
|
||||
*/
|
||||
Reference in New Issue
Block a user