/**
* @fileoverview
* Utilities to handle XML and JSON data. Borrowed from WebShow.XML and WebShow.JSON.
* @author Partridge Jiang
*/
var
/**
* Class to handle JSON file and data.
* @class JsonUtility
*/
JsonUtility = {
/** @private */
DEF_LOAD_TIMEOUT: 20000,
/** @private */
FILE_EXT_JSON: '.json',
/** @private */
FILE_EXT_JSON_JS_WRAPPER: '.jsonjs',
/** @private */
DEF_SERIALIZE_TO_JSCODE_VARNAME: '__jsonvar__',
/** Parse JSON object from text, return object. */
parse: function(text)
{
/*
if (window.JSON) // native support for JSON in browser
return JSON.parse(text);
else // try use JSON2 lib
return
*/
return JSON.parse(text);
},
/** Parse a JavaScript var wrapped JSON string */
parseFromJsWrappedCode: function(code, jsVarName)
{
if (!jsVarName)
jsVarName = JsonUtility.DEF_SERIALIZE_TO_JSCODE_VARNAME;
eval(code);
return JsonUtility.parse(eval(jsVarName));
},
/** Load JSON file of URL and return result in callback(jsonObj, isSuccess). */
load: function(url, callback, options)
// options: object include fields:
// timeout: Integer, in ms
{
// check file ext first, if is a Xml-Js-Wrapper, call corresponding method
var ext = WebShow.URL.getFileExt(url);
if (ext.toLowerCase() == JsonUtility.FILE_EXT_JSON_JS_WRAPPER)
return JsonUtility.loadJsWrapper(url, callback, options);
else
{
return JsonUtility.loadJson(url, callback, options);
}
},
/** Load a JS wrapper JSON file, content like "var _varname = {};" */
// callback(jsonObj, isSuccess)
loadJsWrapper: function(url, callback, options)
// options: object include fields:
// wrapperVarName: string
// timeout: Integer, in ms
{
var loptions = options || {};
var wrapperVarName = loptions.wrapperVarName || JsonUtility.DEF_SERIALIZE_TO_JSCODE_VARNAME;
if (!loptions.timeout)
loptions.timeout = JsonUtility.DEF_LOAD_TIMEOUT;
/** @ignore */
var runCallBack = function(jsonObj, isSuccess)
{
if (callback)
callback(jsonObj, isSuccess);
};
/** @ignore */
var wrapperCallback = function(src, isSuccess)
{
var jsonObj = null;
if (isSuccess)
{
var wrapperVar = eval(wrapperVarName);
// get wrappered JSON
if (wrapperVar)
{
if (typeof(wrapperVar) == 'string') // wrapped a JSON string
jsonObj = JsonUtility.parse(wrapperVar);
else // a object
jsonObj = wrapperVar;
}
runCallBack(jsonObj, true);
}
else
runCallBack(null, false);
};
WebShow.ScriptLoader.load(url, wrapperCallback, loptions);
},
/** Load a file that containers JSON string only, use WebShow.XHRLoader for AJAX loading. */
loadJson: function(url, callback, options)
{
loptions = options || {};
if (!loptions.timeout)
loptions.timeout = JsonUtility.DEF_LOAD_TIMEOUT;
loptions.resultType = WebShow.FileLoader.ResultType.TEXT;
loptions.extractBody = false;
/** @ignore */
var wrapperCallback = function(content, isSuccess, url)
{
if (isSuccess)
{
var jsonObj = JsonUtility.parse(content);
callback(jsonObj, true);
}
else
callback(null, false);
};
var xhrLoader = new WebShow.XHRLoader();
xhrLoader.load(url, wrapperCallback, loptions);
},
/** Serialize a object to JSON string.
* NOTE: this function only works in browser support JSON.stringify.
* @param {Object} srcObj Source JavaScript object.
* @param {Object} options Options for serializing:
* {
* prettyPrint: {Bool} whether format output JSON or use a compact one,
* indentSpaces: {Int} indent space count for formatting
* }
* @returns {String} JSON string.
*/
serializeToStr: function(srcObj, options)
{
//return Object.toJSON(srcObj);
if (options && options.prettyPrint)
return JSON.stringify(srcObj, null, options.indentSpaces || 2);
else
return JSON.stringify(srcObj);
},
/** Serialize a object to JSON string wrapped in JavaScript var*/
serializeToJsCode: function(srcObj, jsVarName, quoteChar)
{
if (!quoteChar)
quoteChar = "'";
if (!jsVarName)
jsVarName = JsonUtility.DEF_SERIALIZE_TO_JSCODE_VARNAME;
var jsonStr = JsonUtility.serializeToStr(srcObj);
var sreg = new RegExp(quoteChar, 'g');
jsonStr = jsonStr.replace(sreg, '\\' + quoteChar);
return 'var ' + jsVarName + ' = ' + quoteChar + jsonStr + quoteChar;
}
};
var
/**
* Class to handle XML file and data.
* This part is partly borrowed from Javascript - The Definitive Guide, 5th Ed
* and add some code of myself, mainly for Safari (do not support xmldoc.load)
* and including file handle
* @class XmlUtility
*/
XmlUtility = {
/** @private */
DEF_INCLUDE_TAG_NAME: 'include',
/** @private */
DEF_INCLUDE_SRC_ATTRIB: 'src',
/** @private */
DEF_LOAD_TIMEOUT: 20000,
/** @private */
DEF_BYPASS_ROOT_ELEM: true,
/** @private */
DEF_SERIALIZE_TO_JSCODE_VARNAME: '__xmlStr__',
/** @private */
FILE_EXT_XML: '.xml',
/** @private */
FILE_EXT_XML_JS_WRAPPER: '.xmljs',
/** Create a new XML document */
newDocument: function(rootTagName, namespaceURL)
{
if (!rootTagName) rootTagName = "";
if (!namespaceURL) namespaceURL = "";
if (document.implementation && document.implementation.createDocument) {
// This is the W3C standard way to do it
var doc = document.implementation.createDocument(namespaceURL,
rootTagName, null);
//console.log('Here: ', doc);
return doc;
/*
if (doc.load)
return doc;
else // Safari adn Google Chrome do not support xmldoc.load
{
var xmlhttp = new XMLHttpRequest();
doc = xmlhttp;
return doc;
}
*/
}
else { // This is the IE way to do it
// Create an empty document as an ActiveX object
// If there is no root element, this is all we have to do
var doc = new ActiveXObject("MSXML2.DOMDocument");
// If there is a root tag, initialize the document
if (rootTagName) {
// Look for a namespace prefix
var prefix = "";
var tagname = rootTagName;
var p = rootTagName.indexOf(':');
if (p != -1) {
prefix = rootTagName.substring(0, p);
tagname = rootTagName.substring(p+1);
}
/*
// If we have a namespace, we must have a namespace prefix
// If we don't have a namespace, we discard any prefix
if (namespaceURL) {
if (!prefix) prefix = "a0"; // What Firefox uses
}
else prefix = "";
*/
// Create the root element (with optional namespace) as a
// string of text
var text = "<" + (prefix?(prefix+":"):"") + tagname +
(namespaceURL?
((prefix? " xmlns:": " xmlns") + prefix + '="' + namespaceURL +'"')
:"") +
"/>";
// And parse that text into the empty document
doc.loadXML(text);
}
return doc;
}
},
/** parse Xml DOM from text, return XMLDocument */
parse: function(text)
{
if (typeof DOMParser != "undefined") {
// Mozilla Firefox, Google Chrome and related browsers
return (new DOMParser( )).parseFromString(text, "application/xml");
}
else if (typeof ActiveXObject != "undefined") {
// Internet Explorer.
var doc = XmlUtility.newDocument( ); // Create an empty document
doc.loadXML(text); // Parse text into it
return doc; // Return it
}
else {
// As a last resort, try loading the document from a data: URL
// This is supposed to work in Safari. Thanks to Manos Batsis and
// his Sarissa library (sarissa.sourceforge.net) for this technique.
var url = "data:text/xml;charset=utf-8," + encodeURIComponent(text);
var request = new XMLHttpRequest( );
request.open("GET", url, false);
request.send(null);
return request.responseXML;
}
},
/** load XML document from url. */
load: function(url, callback, options) // return XMLDocument, handle include tags
// ATTENTION, currently load can only handle one-level include, that is,
// load(A) while include B, B include C, B but not C will be included in A.
// This limination is to avoid circulate include
// options: object include fields:
// disableInclude: boolean, whether handle include tag, default true
// includeTagName: string,
// srcAttribName: string,
// bypassRootElem: boolean, whether insert the documentElement of the included xml doc
// timeout: Integer, in ms
{
// check file ext first, if is a Xml-Js-Wrapper, call corresponding method
var ext = WebShow.URL.getFileExt(url);
if (ext.toLowerCase() == XmlUtility.FILE_EXT_XML_JS_WRAPPER)
return XmlUtility.loadHelper.loadJsWrapper(url, callback, options);
if (!options)
options = {disableInclude: false};
if (!options.disableInclude)
return XmlUtility.loadHelper.loadAndHandleIncludeElement(url, callback, options);
else
return XmlUtility.loadHelper.loadSimple(url, callback, options.timeout);
},
/** @private */
loadHelper: {
/////////////////////////////////////////////////////////////////////
// Internal method for load to use
///////////////
/** @private */
loadSimple: function(url, callback, loadTimeout)
// return XMLDocument, if assigned callback, it runs async
// callback(xmlDoc, isloadSuccess)
// this function will try use XHR method in IE,
// as IE can not load local XML file via XHR
{
var async = (callback != null);
if ((loadTimeout === null) || (loadTimeout === undefined))
loadTimeout = XmlUtility.DEF_LOAD_TIMEOUT;
if (!(document.implementation && document.implementation.createDocument)) // ie
{
return XmlUtility.loadHelper.loadSimpleViaDoc(url, callback);
}
else // other browse
{
var req = new XMLHttpRequest();
var timeoutHandle = null;
if (loadTimeout)
timeoutHandle = setTimeout(function() // timeout, load failed
{
runCallBack(null, false);
}, loadTimeout);
/** @ignore */
var runCallBack = function(xmldoc, isSuccess)
{
if (timeoutHandle) // clear timeout handler
clearTimeout(timeoutHandle);
if (callback)
callback(xmldoc, isSuccess);
};
if (async)
{
/** @ignore */
req.onreadystatechange = function(){
if (req.readyState == 4)
{
//if (req.status == 200)
var xmldoc = req.responseXML;
if (xmldoc && xmldoc.documentElement) // check documentElement, otherwise opera will cause error
{
runCallBack(xmldoc, true);
}
else
runCallBack(null, false);
}
};
}
try
{
//console.log('open request', req);
req.open('get', url, async);
req.send(null);
}
catch(e)
{
WebShow.reportError(e);
runCallBack(null, false); // load failed
}
var doc = req.responseXML;
if (doc && doc.documentElement)
return doc;
else
return null;
}
},
/** @private */
loadSimpleViaDoc: function(url, callback) // use for IE
{
var tagInitial = 'dummy_dummy_dummy_dummy';
var async = (callback != null);
// Create a new document with the previously defined function
var xmldoc = XmlUtility.newDocument(tagInitial);
xmldoc.async = async;
if (async)
{
/** @ignore */
xmldoc.onreadystatechange = function( ) {
if (xmldoc.readyState == 4)
{
if ((!xmldoc.documentElement) || (xmldoc.documentElement.tag == tagInitial))
callback(xmldoc, false);
else
callback(xmldoc, true);
}
};
}
xmldoc.load(url); // Load and parse
return xmldoc; // Return the document
},
/** @private */
loadAndHandleIncludeElement: function(url, callback, options)
// options: object include fields:
// includeTagName: string,
// srcAttribName: string,
// bypassRootElem: boolean, whether insert the documentElement of the included xml doc
// timeout: Integer, in ms
{
var loadTimeout;
if (options)
loadTimeout = options.timeout;
else
loadTimeout = null;
var sync = (callback == null);
if (sync)
{
var xmldoc = XmlUtility.loadHelper.loadSimple(url, null, loadTimeout);
if (xmldoc && xmldoc.documentElement)
{
XmlUtility.loadHelper.handleIncludedXmlInElement(xmldoc.documentElement, null, options);
return xmldoc;
}
else
return null;
}
else
return XmlUtility.loadHelper.loadSimple(url, function(xmldoc, isSuccess)
{
if (!isSuccess)
callback(xmldoc, false);
else // handle include element
{
XmlUtility.loadHelper.handleIncludedXmlInElement(xmldoc.documentElement, function(count)
{
callback(xmldoc, true); // what ever include, cann always return xmlDoc
}, options);
}
}, loadTimeout);
},
/** @private */
loadJsWrapper: function(url, callback, options)
// options: object include fields:
// wrapperVarName: string
// timeout: Integer, in ms
{
options = options || {};
var wrapperVarName = options.wrapperVarName || XmlUtility.DEF_SERIALIZE_TO_JSCODE_VARNAME;
var loadTimeout = options.timeout;
if ((loadTimeout === null) || (loadTimeout === undefined))
loadTimeout = XmlUtility.DEF_LOAD_TIMEOUT;
/** @ignore */
var runCallBack = function(xmldoc, isSuccess)
{
/*
if (timeoutHandle) // clear timeout handler
clearTimeout(timeoutHandle);
*/
if (callback)
callback(xmldoc, isSuccess);
};
/** @ignore */
var wrapperCallback = function(src, isSuccess)
{
/*
if (timeoutHandle) // clear timeout handler
clearTimeout(timeoutHandle);
*/
if (isSuccess)
{
// get wrappered XML string
var xmlContent = eval(wrapperVarName);
// parse xml content
var doc = XmlUtility.parse(xmlContent);
runCallBack(doc, true);
}
else
runCallBack(null, false);
};
/*
var timeoutHandle = null;
if (loadTimeout)
timeoutHandle = setTimeout(function() // timeout, load failed
{
runCallBack(null, false);
}, loadTimeout);
*/
options.timeout = loadTimeout;
WebShow.ScriptLoader.load(url, wrapperCallback, options);
},
/** @private */
// for include tag handle
getIncludeElements: function(rootElement, includeTagName)
{
var result = [];
if (!includeTagName)
includeTagName = XmlUtility.DEF_INCLUDE_TAG_NAME;
return rootElement.getElementsByTagName(includeTagName);
},
/** @private */
handleIncludedXmlInElement: function(rootElement, callback, options)
// callback(includeHandleCount), if not assigned, run in sync mode
// options: object include fields:
// includeTagName: string,
// srcAttribName: string,
// bypassRootElem: boolean, whether insert the documentElement of the included xml doc
{
if (!options)
options = {};
var includeTagName = options.includeTagName || XmlUtility.DEF_INCLUDE_TAG_NAME;
var srcAttribName = options.srcAttribName || XmlUtility.DEF_INCLUDE_SRC_ATTRIB;
var bypassRootElem = (options.bypassRootElem != null)? options.bypassRootElem : XmlUtility.DEF_BYPASS_ROOT_ELEM;
var sync = (callback == null);
var docURL = XmlUtility.getXmlDocUrl(rootElement.ownerDocument);
var includeElems = XmlUtility.loadHelper.getIncludeElements(rootElement, includeTagName);
if (!srcAttribName)
var srcURL = null;
var includeElem = null;
var finishedLoadCount = 0;
var totalLoadCount = includeElems.length;
if (totalLoadCount <= 0)
{
if (callback)
callback(0);
return null;
}
var includedDoc;
for (var i = includeElems.length - 1; i >= 0; --i)
{
includeElem = includeElems[i];
srcURL = includeElem.getAttribute(srcAttribName);
if (srcURL) // srcLoc not null
{
srcURL = WebShow.URL.mergePath(srcURL, docURL);
if (sync)
{
includedDoc = XmlUtility.loadHelper.loadSimple(srcURL);
if (includedDoc)
XmlUtility.loadHelper.insertIncludedSection(includedDoc, includeElem, bypassRootElem);
}
else // async
XmlUtility.loadHelper.loadSimple(srcURL, (function(originIncludeElem, doc, isSuccess)
{
if (isSuccess && doc)
{
// replace include tag in parent xml
includedDoc = doc;
XmlUtility.loadHelper.insertIncludedSection(includedDoc, originIncludeElem, bypassRootElem);
}
//
finishedLoadCount++;
if (finishedLoadCount == totalLoadCount) // all include xml finished
{
callback(finishedLoadCount);
}
}).bind(this, includeElem));
}
}
return rootElement;
},
/** @private */
insertIncludedSection: function(includedDoc, originIncludeElem, bypassRootElem)
{
var
elems = [];
// fill element array that shoul be inserted to origin document
if (bypassRootElem)
{
var child = includedDoc.documentElement.firstChild;
while (child)
{
if (child.nodeType == Node.ELEMENT_NODE) // child element
elems.push(child);
child = child.nextSibling;
}
}
else
elems.push(includedDoc.documentElement);
// insert elements
var elem;
var originDoc = originIncludeElem.ownerDocument;
var docElem = originDoc.documentElement;
for (var i = 0, l = elems.length; i < l; ++i)
{
var elem = elems[i];
if (originDoc.importNode)
elem = originDoc.importNode(elem, true);
originIncludeElem.parentNode.insertBefore(elem, originIncludeElem);
}
// delete old include tag
originIncludeElem.parentNode.removeChild(originIncludeElem);
return originDoc;
}
///////////////
// Internal method for load to use END
/////////////////////////////////////////////////////////////////////
},
/**
* Serialize a XML node content to string.
* @param {Object} node
* @param {Object} options Options for serializing:
* {
* prettyPrint: {Bool} whether format output JSON or use a compact one,
* }
*/
serializeNode: function(node, options)
{
if (options && options.prettyPrint)
{
return XmlUtility.serializeNodePretty(node);
}
if (typeof XMLSerializer != "undefined")
return (new XMLSerializer( )).serializeToString(node);
else if (node.xml) return node.xml;
else throw "XML.serialize is not supported or can't serialize " + node;
},
/** Try serialize node content to string in pretty mode. Not always possible in some browser. */
serializeNodePretty: function(node)
{
try
{
return XML((new XMLSerializer( )).serializeToString(node)).toXMLString(); // firefox
// TODO: E4X is deprecated in firefox 21, so need other methods
}
catch(e)
{
return XmlUtility.serializeNode(node);
}
},
/** wrap XML string to a JavaScript var,
* like "var xmlStr = '<xml><data></data></xml>';"
*/
serializeNodeToJsCode: function(node, jsVarName, quoteChar)
{
if (!quoteChar)
quoteChar = "'";
if (!jsVarName)
jsVarName = XmlUtility.DEF_SERIALIZE_TO_JSCODE_VARNAME;
var xmlString = XmlUtility.serializeNode(node);
// escape all original quoteChars
var sreg = new RegExp(quoteChar, 'g');
xmlString = xmlString.replace(sreg, '\\' + quoteChar);
return 'var ' + jsVarName + ' = ' + quoteChar + xmlString + quoteChar + ';';
},
/** Parse XML from a JavaScript string var */
parseFromJsWrappedCode: function(code, jsVarName)
{
eval(code);
var xmlText = eval(jsVarName);
return XmlUtility.parse(xmlText);
},
// utils functions
/** Get url of a XML document */
getXmlDocUrl: function(xmlDoc)
{
return xmlDoc.url // IE
|| xmlDoc.documentURI // firefox
|| xmlDoc.URL // Safari
|| xmlDoc.location; // Opera
}
};