UNPKG

@mitre/jsonix

Version:

Jsonix (JSON interfaces for XML) is a JavaScript library which allows converting between XML and JSON structures.

1,610 lines (1,560 loc) 216 kB
var _jsonix_factory = function (_jsonix_xmldom, _jsonix_xmlhttprequest, _jsonix_fs) { // Complete Jsonix script is included below var Jsonix = { singleFile: true }; Jsonix.Util = {}; Jsonix.Util.extend = function (destination, source) { destination = destination || {}; if (source) { /*jslint forin: true */ for (var property in source) { var value = source[property]; if (value !== undefined) { destination[property] = value; } } /** * IE doesn't include the toString property when iterating over an * object's properties with the for(property in object) syntax. * Explicitly check if the source has its own toString property. */ /* * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative * prototype object" when calling hawOwnProperty if the source object is * an instance of window.Event. */ // REWORK // Node.js var sourceIsEvt = typeof window !== 'undefined' && window !== null && typeof window.Event === "function" && source instanceof window.Event; if (!sourceIsEvt && source.hasOwnProperty && source.hasOwnProperty('toString')) { destination.toString = source.toString; } } return destination; }; Jsonix.Class = function () { var Class = function () { this.initialize.apply(this, arguments); }; var extended = {}; var empty = function () { }; var parent, initialize, Type; for (var i = 0, len = arguments.length; i < len; ++i) { Type = arguments[i]; if (typeof Type == "function") { // make the class passed as the first argument the superclass if (i === 0 && len > 1) { initialize = Type.prototype.initialize; // replace the initialize method with an empty function, // because we do not want to create a real instance here Type.prototype.initialize = empty; // the line below makes sure that the new class has a // superclass extended = new Type(); // restore the original initialize method if (initialize === undefined) { delete Type.prototype.initialize; } else { Type.prototype.initialize = initialize; } } // get the prototype of the superclass parent = Type.prototype; } else { // in this case we're extending with the prototype parent = Type; } Jsonix.Util.extend(extended, parent); } Class.prototype = extended; return Class; }; Jsonix.XML = { XMLNS_NS: 'http://www.w3.org/2000/xmlns/', XMLNS_P: 'xmlns' }; Jsonix.DOM = { isDomImplementationAvailable: function () { if (typeof _jsonix_xmldom !== 'undefined') { return true; } else if (typeof document !== 'undefined' && Jsonix.Util.Type.exists(document.implementation) && Jsonix.Util.Type.isFunction(document.implementation.createDocument)) { return true; } else { return false; } }, createDocument: function () { // REWORK // Node.js if (typeof _jsonix_xmldom !== 'undefined') { return new (_jsonix_xmldom.DOMImplementation)().createDocument(); } else if (typeof document !== 'undefined' && Jsonix.Util.Type.exists(document.implementation) && Jsonix.Util.Type.isFunction(document.implementation.createDocument)) { return document.implementation.createDocument('', '', null); } else if (typeof ActiveXObject !== 'undefined') { return new ActiveXObject('MSXML2.DOMDocument'); } else { throw new Error('Error created the DOM document.'); } }, serialize: function (node) { Jsonix.Util.Ensure.ensureExists(node); // REWORK // Node.js if (typeof _jsonix_xmldom !== 'undefined') { return (new (_jsonix_xmldom).XMLSerializer()).serializeToString(node); } else if (Jsonix.Util.Type.exists(XMLSerializer)) { return (new XMLSerializer()).serializeToString(node); } else if (Jsonix.Util.Type.exists(node.xml)) { return node.xml; } else { throw new Error('Could not serialize the node, neither XMLSerializer nor the [xml] property were found.'); } }, parse: function (text) { Jsonix.Util.Ensure.ensureExists(text); if (typeof _jsonix_xmldom !== 'undefined') { return (new (_jsonix_xmldom).DOMParser()).parseFromString(text, 'application/xml'); } else if (typeof DOMParser != 'undefined') { return (new DOMParser()).parseFromString(text, 'application/xml'); } else if (typeof ActiveXObject != 'undefined') { var doc = Jsonix.DOM.createDocument('', ''); doc.loadXML(text); return doc; } else { var url = 'data:text/xml;charset=utf-8,' + encodeURIComponent(text); var request = new XMLHttpRequest(); request.open('GET', url, false); if (request.overrideMimeType) { request.overrideMimeType("text/xml"); } request.send(null); return request.responseXML; } }, load: function (url, callback, options) { var request = Jsonix.Request.INSTANCE; request.issue( url, function (transport) { var result; if (Jsonix.Util.Type.exists(transport.responseXML) && Jsonix.Util.Type.exists(transport.responseXML.documentElement)) { result = transport.responseXML; } else if (Jsonix.Util.Type.isString(transport.responseText)) { result = Jsonix.DOM.parse(transport.responseText); } else { throw new Error('Response does not have valid [responseXML] or [responseText].'); } callback(result); }, function (transport) { throw new Error('Could not retrieve XML from URL [' + url + '].'); }, options); }, xlinkFixRequired: null, isXlinkFixRequired: function () { if (Jsonix.DOM.xlinkFixRequired === null) { if (typeof navigator === 'undefined') { Jsonix.DOM.xlinkFixRequired = false; } else if (!!navigator.userAgent && (/Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor))) { var doc = Jsonix.DOM.createDocument(); var el = doc.createElement('test'); el.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', 'urn:test'); doc.appendChild(el); var testString = Jsonix.DOM.serialize(doc); Jsonix.DOM.xlinkFixRequired = (testString.indexOf('xmlns:xlink') === -1); } else { Jsonix.DOM.xlinkFixRequired = false; } } return Jsonix.DOM.xlinkFixRequired; } }; Jsonix.Request = Jsonix .Class({ // REWORK factories: [function () { return new XMLHttpRequest(); }, function () { return new ActiveXObject('Msxml2.XMLHTTP'); }, function () { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }, function () { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }, function () { return new ActiveXObject('Microsoft.XMLHTTP'); }, function () { // Node.js if (typeof _jsonix_xmlhttprequest !== 'undefined') { var XMLHttpRequest = _jsonix_xmlhttprequest.XMLHttpRequest; return new XMLHttpRequest(); } else { return null; } }], initialize: function () { }, issue: function (url, onSuccess, onFailure, options) { Jsonix.Util.Ensure.ensureString(url); if (Jsonix.Util.Type.exists(onSuccess)) { Jsonix.Util.Ensure.ensureFunction(onSuccess); } else { onSuccess = function () { }; } if (Jsonix.Util.Type.exists(onFailure)) { Jsonix.Util.Ensure.ensureFunction(onFailure); } else { onFailure = function () { }; } if (Jsonix.Util.Type.exists(options)) { Jsonix.Util.Ensure.ensureObject(options); } else { options = {}; } var transport = this.createTransport(); var method = Jsonix.Util.Type.isString(options.method) ? options.method : 'GET'; var async = Jsonix.Util.Type.isBoolean(options.async) ? options.async : true; var proxy = Jsonix.Util.Type.isString(options.proxy) ? options.proxy : Jsonix.Request.PROXY; var user = Jsonix.Util.Type.isString(options.user) ? options.user : null; var password = Jsonix.Util.Type.isString(options.password) ? options.password : null; if (Jsonix.Util.Type.isString(proxy) && (url.indexOf("http") === 0)) { url = proxy + encodeURIComponent(url); } if (Jsonix.Util.Type.isString(user)) { transport.open(method, url, async, user, password); } else { transport.open(method, url, async); } if (Jsonix.Util.Type.isObject(options.headers)) { for (var header in options.headers) { if (options.headers.hasOwnProperty(header)) { transport.setRequestHeader(header, options.headers[header]); } } } var data = Jsonix.Util.Type.exists(options.data) ? options.data : null; if (!async) { transport.send(data); this.handleTransport(transport, onSuccess, onFailure); } else { var that = this; if (typeof window !== 'undefined') { transport.onreadystatechange = function () { that.handleTransport(transport, onSuccess, onFailure); }; window.setTimeout(function () { transport.send(data); }, 0); } else { transport.onreadystatechange = function () { that.handleTransport(transport, onSuccess, onFailure); }; transport.send(data); } } return transport; }, handleTransport: function (transport, onSuccess, onFailure) { if (transport.readyState == 4) { if (!transport.status || (transport.status >= 200 && transport.status < 300)) { onSuccess(transport); } if (transport.status && (transport.status < 200 || transport.status >= 300)) { onFailure(transport); } } }, createTransport: function () { for (var index = 0, length = this.factories.length; index < length; index++) { try { var transport = this.factories[index](); if (transport !== null) { return transport; } } catch (e) { // TODO log } } throw new Error('Could not create XML HTTP transport.'); }, CLASS_NAME: 'Jsonix.Request' }); Jsonix.Request.INSTANCE = new Jsonix.Request(); Jsonix.Request.PROXY = null; Jsonix.Schema = {}; Jsonix.Model = {}; Jsonix.Util.Type = { exists: function (value) { return (typeof value !== 'undefined' && value !== null); }, isUndefined: function (value) { return typeof value === 'undefined'; }, isString: function (value) { return typeof value === 'string'; }, isBoolean: function (value) { return typeof value === 'boolean'; }, isObject: function (value) { return typeof value === 'object'; }, isFunction: function (value) { return typeof value === 'function'; }, isNumber: function (value) { return (typeof value === 'number') && !isNaN(value); }, isNumberOrNaN: function (value) { return (value === +value) || (Object.prototype.toString.call(value) === '[object Number]'); }, isNaN: function (value) { return Jsonix.Util.Type.isNumberOrNaN(value) && isNaN(value); }, isArray: function (value) { // return value instanceof Array; return !!(value && value.concat && value.unshift && !value.callee); }, isDate: function (value) { return !!(value && value.getTimezoneOffset && value.setUTCFullYear); }, isRegExp: function (value) { return !!(value && value.test && value.exec && (value.ignoreCase || value.ignoreCase === false)); }, isNode: function (value) { return (typeof Node === "object" || typeof Node === "function") ? (value instanceof Node) : (value && (typeof value === "object") && (typeof value.nodeType === "number") && (typeof value.nodeName === "string")); }, isEqual: function (a, b, report) { var doReport = Jsonix.Util.Type.isFunction(report); // TODO rework var _range = function (start, stop, step) { var args = slice.call(arguments); var solo = args.length <= 1; var start_ = solo ? 0 : args[0]; var stop_ = solo ? args[0] : args[1]; var step_ = args[2] || 1; var len = Math.max(Math.ceil((stop_ - start_) / step_), 0); var idx = 0; var range = new Array(len); while (idx < len) { range[idx++] = start_; start_ += step_; } return range; }; var _keys = Object.keys || function (obj) { if (Jsonix.Util.Type.isArray(obj)) { return _range(0, obj.length); } var keys = []; for (var key in obj) { if (obj.hasOwnProperty(key)) { keys[keys.length] = key; } } return keys; }; // Check object identity. if (a === b) { return true; } // Check if both are NaNs if (Jsonix.Util.Type.isNaN(a) && Jsonix.Util.Type.isNaN(b)) { return true; } // Different types? var atype = typeof a; var btype = typeof b; if (atype != btype) { if (doReport) { report('Types differ [' + atype + '], [' + btype + '].'); } return false; } // Basic equality test (watch out for coercions). if (a == b) { return true; } // One is falsy and the other truthy. if ((!a && b) || (a && !b)) { if (doReport) { report('One is falsy, the other is truthy.'); } return false; } // Check dates' integer values. if (Jsonix.Util.Type.isDate(a) && Jsonix.Util.Type.isDate(b)) { return a.getTime() === b.getTime(); } // Both are NaN? if (Jsonix.Util.Type.isNaN(a) && Jsonix.Util.Type.isNaN(b)) { return false; } // Compare regular expressions. if (Jsonix.Util.Type.isRegExp(a) && Jsonix.Util.Type.isRegExp(b)) { return a.source === b.source && a.global === b.global && a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; } if (Jsonix.Util.Type.isNode(a) && Jsonix.Util.Type.isNode(b)) { var aSerialized = Jsonix.DOM.serialize(a); var bSerialized = Jsonix.DOM.serialize(b); if (aSerialized !== bSerialized) { if (doReport) { report('Nodes differ.'); report('A=' + aSerialized); report('B=' + bSerialized); } return false; } else { return true; } } // If a is not an object by this point, we can't handle it. if (atype !== 'object') { return false; } // Check for different array lengths before comparing contents. if (Jsonix.Util.Type.isArray(a) && (a.length !== b.length)) { if (doReport) { report('Lengths differ.'); report('A.length=' + a.length); report('B.length=' + b.length); } return false; } // Nothing else worked, deep compare the contents. var aKeys = _keys(a); var bKeys = _keys(b); // Different object sizes? if (aKeys.length !== bKeys.length) { if (doReport) { report('Different number of properties [' + aKeys.length + '], [' + bKeys.length + '].'); } for (var andex = 0; andex < aKeys.length; andex++) { if (doReport) { report('A [' + aKeys[andex] + ']=' + a[aKeys[andex]]); } } for (var bndex = 0; bndex < bKeys.length; bndex++) { if (doReport) { report('B [' + bKeys[bndex] + ']=' + b[bKeys[bndex]]); } } return false; } // Recursive comparison of contents. for (var kndex = 0; kndex < aKeys.length; kndex++) { var key = aKeys[kndex]; if (!(key in b) || !Jsonix.Util.Type.isEqual(a[key], b[key], report)) { if (doReport) { report('One of the properties differ.'); report('Key: [' + key + '].'); report('Left: [' + a[key] + '].'); report('Right: [' + b[key] + '].'); } return false; } } return true; }, cloneObject: function (source, target) { target = target || {}; for (var p in source) { if (source.hasOwnProperty(p)) { target[p] = source[p]; } } return target; }, defaultValue: function () { var args = arguments; if (args.length === 0) { return undefined; } else { var defaultValue = args[args.length - 1]; var typeOfDefaultValue = typeof defaultValue; for (var index = 0; index < args.length - 1; index++) { var candidateValue = args[index]; if (typeof candidateValue === typeOfDefaultValue) { return candidateValue; } } return defaultValue; } } }; Jsonix.Util.NumberUtils = { isInteger: function (value) { return Jsonix.Util.Type.isNumber(value) && ((value % 1) === 0); } }; Jsonix.Util.StringUtils = { trim: (!!String.prototype.trim) ? function (str) { Jsonix.Util.Ensure.ensureString(str); return str.trim(); } : function (str) { Jsonix.Util.Ensure.ensureString(str); return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); }, /* isEmpty : function(str) { var wcm = Jsonix.Util.StringUtils.whitespaceCharactersMap; for (var index = 0; index < str.length; index++) { if (!wcm[str[index]]) { return false; } } return true; }, */ isEmpty: function (str) { var length = str.length; if (!length) { return true; } for (var index = 0; index < length; index++) { var c = str[index]; if (c === ' ') { // skip } else if (c > '\u000D' && c < '\u0085') { return false; } else if (c < '\u00A0') { if (c < '\u0009') { return false; } else if (c > '\u0085') { return false; } } else if (c > '\u00A0') { if (c < '\u2028') { if (c < '\u180E') { if (c < '\u1680') { return false; } else if (c > '\u1680') { return false; } } else if (c > '\u180E') { if (c < '\u2000') { return false; } else if (c > '\u200A') { return false; } } } else if (c > '\u2029') { if (c < '\u205F') { if (c < '\u202F') { return false; } else if (c > '\u202F') { return false; } } else if (c > '\u205F') { if (c < '\u3000') { return false; } else if (c > '\u3000') { return false; } } } } } return true; }, isNotBlank: function (str) { return Jsonix.Util.Type.isString(str) && !Jsonix.Util.StringUtils.isEmpty(str); }, whitespaceCharacters: '\u0009\u000A\u000B\u000C\u000D \u0085\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000', whitespaceCharactersMap: { '\u0009': true, '\u000A': true, '\u000B': true, '\u000C': true, '\u000D': true, ' ': true, '\u0085': true, '\u00A0': true, '\u1680': true, '\u180E': true, '\u2000': true, '\u2001': true, '\u2002': true, '\u2003': true, '\u2004': true, '\u2005': true, '\u2006': true, '\u2007': true, '\u2008': true, '\u2009': true, '\u200A': true, '\u2028': true, '\u2029': true, '\u202F': true, '\u205F': true, '\u3000': true }, splitBySeparatorChars: function (str, separatorChars) { Jsonix.Util.Ensure.ensureString(str); Jsonix.Util.Ensure.ensureString(separatorChars); var len = str.length; if (len === 0) { return []; } if (separatorChars.length === 1) { return str.split(separatorChars); } else { var list = []; var sizePlus1 = 1; var i = 0; var start = 0; var match = false; var lastMatch = false; var max = -1; var preserveAllTokens = false; // standard case while (i < len) { if (separatorChars.indexOf(str.charAt(i)) >= 0) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } list.push(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } if (match || (preserveAllTokens && lastMatch)) { list.push(str.substring(start, i)); } return list; } } }; Jsonix.Util.Ensure = { ensureBoolean: function (value) { if (!Jsonix.Util.Type.isBoolean(value)) { throw new Error('Argument [' + value + '] must be a boolean.'); } }, ensureString: function (value) { if (!Jsonix.Util.Type.isString(value)) { throw new Error('Argument [' + value + '] must be a string.'); } }, ensureNumber: function (value) { if (!Jsonix.Util.Type.isNumber(value)) { throw new Error('Argument [' + value + '] must be a number.'); } }, ensureNumberOrNaN: function (value) { if (!Jsonix.Util.Type.isNumberOrNaN(value)) { throw new Error('Argument [' + value + '] must be a number or NaN.'); } }, ensureInteger: function (value) { if (!Jsonix.Util.Type.isNumber(value)) { throw new Error('Argument [' + value + '] must be an integer, but it is not a number.'); } else if (!Jsonix.Util.NumberUtils.isInteger(value)) { throw new Error('Argument [' + value + '] must be an integer.'); } }, ensureDate: function (value) { if (!(value instanceof Date)) { throw new Error('Argument [' + value + '] must be a date.'); } }, ensureObject: function (value) { if (!Jsonix.Util.Type.isObject(value)) { throw new Error('Argument [' + value + '] must be an object.'); } }, ensureArray: function (value) { if (!Jsonix.Util.Type.isArray(value)) { throw new Error('Argument [' + value + '] must be an array.'); } }, ensureFunction: function (value) { if (!Jsonix.Util.Type.isFunction(value)) { throw new Error('Argument [' + value + '] must be a function.'); } }, ensureExists: function (value) { if (!Jsonix.Util.Type.exists(value)) { throw new Error('Argument [' + value + '] does not exist.'); } } }; Jsonix.XML.QName = Jsonix.Class({ key: null, namespaceURI: null, localPart: null, prefix: null, string: null, initialize: function (one, two, three) { var namespaceURI; var localPart; var prefix; var key; var string; if (!Jsonix.Util.Type.exists(two)) { namespaceURI = ''; localPart = one; prefix = ''; } else if (!Jsonix.Util.Type.exists(three)) { namespaceURI = Jsonix.Util.Type.exists(one) ? one : ''; localPart = two; var colonPosition = two.indexOf(':'); if (colonPosition > 0 && colonPosition < two.length) { prefix = two.substring(0, colonPosition); localPart = two.substring(colonPosition + 1); } else { prefix = ''; localPart = two; } } else { namespaceURI = Jsonix.Util.Type.exists(one) ? one : ''; localPart = two; prefix = Jsonix.Util.Type.exists(three) ? three : ''; } this.namespaceURI = namespaceURI; this.localPart = localPart; this.prefix = prefix; this.key = (namespaceURI !== '' ? ('{' + namespaceURI + '}') : '') + localPart; this.string = (namespaceURI !== '' ? ('{' + namespaceURI + '}') : '') + (prefix !== '' ? (prefix + ':') : '') + localPart; }, toString: function () { return this.string; }, // foo:bar toCanonicalString: function (namespaceContext) { var canonicalPrefix = namespaceContext ? namespaceContext.getPrefix(this.namespaceURI, this.prefix) : this.prefix; return this.prefix + (this.prefix === '' ? '' : ':') + this.localPart; }, clone: function () { return new Jsonix.XML.QName(this.namespaceURI, this.localPart, this.prefix); }, equals: function (that) { if (!that) { return false; } else { return (this.namespaceURI == that.namespaceURI) && (this.localPart == that.localPart); } }, CLASS_NAME: "Jsonix.XML.QName" }); Jsonix.XML.QName.fromString = function (qNameAsString, namespaceContext, defaultNamespaceURI) { var leftBracket = qNameAsString.indexOf('{'); var rightBracket = qNameAsString.lastIndexOf('}'); var namespaceURI; var prefixedName; if ((leftBracket === 0) && (rightBracket > 0) && (rightBracket < qNameAsString.length)) { namespaceURI = qNameAsString.substring(1, rightBracket); prefixedName = qNameAsString.substring(rightBracket + 1); } else { namespaceURI = null; prefixedName = qNameAsString; } var colonPosition = prefixedName.indexOf(':'); var prefix; var localPart; if (colonPosition > 0 && colonPosition < prefixedName.length) { prefix = prefixedName.substring(0, colonPosition); localPart = prefixedName.substring(colonPosition + 1); } else { prefix = ''; localPart = prefixedName; } // If namespace URI was not set and we have a namespace context, try to find the namespace URI via this context if (namespaceURI === null) { if (prefix === '' && Jsonix.Util.Type.isString(defaultNamespaceURI)) { namespaceURI = defaultNamespaceURI; } else if (namespaceContext) { namespaceURI = namespaceContext.getNamespaceURI(prefix); } } // If we don't have a namespace URI, assume '' by default // TODO document the assumption if (!Jsonix.Util.Type.isString(namespaceURI)) { namespaceURI = defaultNamespaceURI || ''; } return new Jsonix.XML.QName(namespaceURI, localPart, prefix); }; Jsonix.XML.QName.fromObject = function (object) { Jsonix.Util.Ensure.ensureObject(object); if (object instanceof Jsonix.XML.QName || (Jsonix.Util.Type.isString(object.CLASS_NAME) && object.CLASS_NAME === 'Jsonix.XML.QName')) { return object; } var localPart = object.localPart || object.lp || null; Jsonix.Util.Ensure.ensureString(localPart); var namespaceURI = object.namespaceURI || object.ns || ''; var prefix = object.prefix || object.p || ''; return new Jsonix.XML.QName(namespaceURI, localPart, prefix); }; Jsonix.XML.QName.fromObjectOrString = function (value, namespaceContext, defaultNamespaceURI) { if (Jsonix.Util.Type.isString(value)) { return Jsonix.XML.QName.fromString(value, namespaceContext, defaultNamespaceURI); } else { return Jsonix.XML.QName.fromObject(value); } }; Jsonix.XML.QName.key = function (namespaceURI, localPart) { Jsonix.Util.Ensure.ensureString(localPart); if (namespaceURI) { var colonPosition = localPart.indexOf(':'); var localName; if (colonPosition > 0 && colonPosition < localPart.length) { localName = localPart.substring(colonPosition + 1); } else { localName = localPart; } return '{' + namespaceURI + '}' + localName; } else { return localPart; } }; Jsonix.XML.Calendar = Jsonix.Class({ year: NaN, month: NaN, day: NaN, hour: NaN, minute: NaN, second: NaN, fractionalSecond: NaN, timezone: NaN, date: null, initialize: function (data) { Jsonix.Util.Ensure.ensureObject(data); // Year if (Jsonix.Util.Type.exists(data.year)) { Jsonix.Util.Ensure.ensureInteger(data.year); Jsonix.XML.Calendar.validateYear(data.year); this.year = data.year; } else { this.year = NaN; } // Month if (Jsonix.Util.Type.exists(data.month)) { Jsonix.Util.Ensure.ensureInteger(data.month); Jsonix.XML.Calendar.validateMonth(data.month); this.month = data.month; } else { this.month = NaN; } // Day if (Jsonix.Util.Type.exists(data.day)) { Jsonix.Util.Ensure.ensureInteger(data.day); if (Jsonix.Util.NumberUtils.isInteger(data.year) && Jsonix.Util.NumberUtils.isInteger(data.month)) { Jsonix.XML.Calendar.validateYearMonthDay(data.year, data.month, data.day); } else if (Jsonix.Util.NumberUtils.isInteger(data.month)) { Jsonix.XML.Calendar.validateMonthDay(data.month, data.day); } else { Jsonix.XML.Calendar.validateDay(data.day); } this.day = data.day; } else { this.day = NaN; } // Hour if (Jsonix.Util.Type.exists(data.hour)) { Jsonix.Util.Ensure.ensureInteger(data.hour); Jsonix.XML.Calendar.validateHour(data.hour); this.hour = data.hour; } else { this.hour = NaN; } // Minute if (Jsonix.Util.Type.exists(data.minute)) { Jsonix.Util.Ensure.ensureInteger(data.minute); Jsonix.XML.Calendar.validateMinute(data.minute); this.minute = data.minute; } else { this.minute = NaN; } // Second if (Jsonix.Util.Type.exists(data.second)) { Jsonix.Util.Ensure.ensureInteger(data.second); Jsonix.XML.Calendar.validateSecond(data.second); this.second = data.second; } else { this.second = NaN; } // Fractional second if (Jsonix.Util.Type.exists(data.fractionalSecond)) { Jsonix.Util.Ensure.ensureNumber(data.fractionalSecond); Jsonix.XML.Calendar.validateFractionalSecond(data.fractionalSecond); this.fractionalSecond = data.fractionalSecond; } else { this.fractionalSecond = NaN; } // Timezone if (Jsonix.Util.Type.exists(data.timezone)) { if (Jsonix.Util.Type.isNaN(data.timezone)) { this.timezone = NaN; } else { Jsonix.Util.Ensure.ensureInteger(data.timezone); Jsonix.XML.Calendar.validateTimezone(data.timezone); this.timezone = data.timezone; } } else { this.timezone = NaN; } var initialDate = new Date(0); initialDate.setUTCFullYear(this.year || 1970); initialDate.setUTCMonth(this.month - 1 || 0); initialDate.setUTCDate(this.day || 1); initialDate.setUTCHours(this.hour || 0); initialDate.setUTCMinutes(this.minute || 0); initialDate.setUTCSeconds(this.second || 0); initialDate.setUTCMilliseconds((this.fractionalSecond || 0) * 1000); var timezoneOffset = -60000 * (this.timezone || 0); this.date = new Date(initialDate.getTime() + timezoneOffset); }, CLASS_NAME: "Jsonix.XML.Calendar" }); Jsonix.XML.Calendar.MIN_TIMEZONE = -14 * 60; Jsonix.XML.Calendar.MAX_TIMEZONE = 14 * 60; Jsonix.XML.Calendar.DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; Jsonix.XML.Calendar.fromObject = function (object) { Jsonix.Util.Ensure.ensureObject(object); if (Jsonix.Util.Type.isString(object.CLASS_NAME) && object.CLASS_NAME === 'Jsonix.XML.Calendar') { return object; } return new Jsonix.XML.Calendar(object); }; Jsonix.XML.Calendar.validateYear = function (year) { if (year === 0) { throw new Error('Invalid year [' + year + ']. Year must not be [0].'); } }; Jsonix.XML.Calendar.validateMonth = function (month) { if (month < 1 || month > 12) { throw new Error('Invalid month [' + month + ']. Month must be in range [1, 12].'); } }; Jsonix.XML.Calendar.validateDay = function (day) { if (day < 1 || day > 31) { throw new Error('Invalid day [' + day + ']. Day must be in range [1, 31].'); } }; Jsonix.XML.Calendar.validateMonthDay = function (month, day) { Jsonix.XML.Calendar.validateMonth(month); var maxDaysInMonth = Jsonix.XML.Calendar.DAYS_IN_MONTH[month - 1]; if (day < 1 || day > Jsonix.XML.Calendar.DAYS_IN_MONTH[month - 1]) { throw new Error('Invalid day [' + day + ']. Day must be in range [1, ' + maxDaysInMonth + '].'); } }; Jsonix.XML.Calendar.validateYearMonthDay = function (year, month, day) { // #93 TODO proper validation of 28/29 02 Jsonix.XML.Calendar.validateYear(year); Jsonix.XML.Calendar.validateMonthDay(month, day); }; Jsonix.XML.Calendar.validateHour = function (hour) { if (hour < 0 || hour > 23) { throw new Error('Invalid hour [' + hour + ']. Hour must be in range [0, 23].'); } }; Jsonix.XML.Calendar.validateMinute = function (minute) { if (minute < 0 || minute > 59) { throw new Error('Invalid minute [' + minute + ']. Minute must be in range [0, 59].'); } }; Jsonix.XML.Calendar.validateSecond = function (second) { if (second < 0 || second > 59) { throw new Error('Invalid second [' + second + ']. Second must be in range [0, 59].'); } }; Jsonix.XML.Calendar.validateFractionalSecond = function (fractionalSecond) { if (fractionalSecond < 0 || fractionalSecond > 59) { throw new Error('Invalid fractional second [' + fractionalSecond + ']. Fractional second must be in range [0, 1).'); } }; Jsonix.XML.Calendar.validateTimezone = function (timezone) { if (timezone < Jsonix.XML.Calendar.MIN_TIMEZONE || timezone > Jsonix.XML.Calendar.MAX_TIMEZONE) { throw new Error('Invalid timezone [' + timezone + ']. Timezone must not be in range [' + Jsonix.XML.Calendar.MIN_TIMEZONE + ', ' + Jsonix.XML.Calendar.MAX_TIMEZONE + '].'); } }; Jsonix.XML.Input = Jsonix.Class({ root: null, node: null, attributes: null, eventType: null, pns: null, initialize: function (node) { Jsonix.Util.Ensure.ensureExists(node); this.root = node; var rootPnsItem = { '': '' }; rootPnsItem[Jsonix.XML.XMLNS_P] = Jsonix.XML.XMLNS_NS; this.pns = [rootPnsItem]; }, hasNext: function () { // No current node, we've not started yet if (this.node === null) { return true; } else if (this.node === this.root) { var nodeType = this.node.nodeType; // Root node is document, last event type is END_DOCUMENT if (nodeType === 9 && this.eventType === 8) { return false; } // Root node is element, last event type is END_ELEMENT else if (nodeType === 1 && this.eventType === 2) { return false; } else { return true; } } else { return true; } }, next: function () { if (this.eventType === null) { return this.enter(this.root); } // START_DOCUMENT if (this.eventType === 7) { var documentElement = this.node.documentElement; if (documentElement) { return this.enter(documentElement); } else { return this.leave(this.node); } } else if (this.eventType === 1) { var firstChild = this.node.firstChild; if (firstChild) { return this.enter(firstChild); } else { return this.leave(this.node); } } else if (this.eventType === 2) { var nextSibling = this.node.nextSibling; if (nextSibling) { return this.enter(nextSibling); } else { return this.leave(this.node); } } else { return this.leave(this.node); } }, enter: function (node) { var nodeType = node.nodeType; this.node = node; this.attributes = null; // Document node if (nodeType === 1) { // START_ELEMENT this.eventType = 1; this.pushNS(node); return this.eventType; } else if (nodeType === 2) { // ATTRIBUTE this.eventType = 10; return this.eventType; } else if (nodeType === 3) { var nodeValue = node.nodeValue; if (Jsonix.Util.StringUtils.isEmpty(nodeValue)) { // SPACE this.eventType = 6; } else { // CHARACTERS this.eventType = 4; } return this.eventType; } else if (nodeType === 4) { // CDATA this.eventType = 12; return this.eventType; } else if (nodeType === 5) { // ENTITY_REFERENCE_NODE = 5 // ENTITY_REFERENCE this.eventType = 9; return this.eventType; } else if (nodeType === 6) { // ENTITY_DECLARATION this.eventType = 15; return this.eventType; } else if (nodeType === 7) { // PROCESSING_INSTRUCTION this.eventType = 3; return this.eventType; } else if (nodeType === 8) { // COMMENT this.eventType = 5; return this.eventType; } else if (nodeType === 9) { // START_DOCUMENT this.eventType = 7; return this.eventType; } else if (nodeType === 10) { // DTD this.eventType = 12; return this.eventType; } else if (nodeType === 12) { // NOTATION_DECLARATION this.eventType = 14; return this.eventType; } else { // DOCUMENT_FRAGMENT_NODE = 11 throw new Error("Node type [" + nodeType + '] is not supported.'); } }, leave: function (node) { if (node.nodeType === 9) { if (this.eventType == 8) { throw new Error("Invalid state."); } else { this.node = node; this.attributes = null; // END_ELEMENT this.eventType = 8; return this.eventType; } } else if (node.nodeType === 1) { if (this.eventType == 2) { var nextSibling = node.nextSibling; if (nextSibling) { return this.enter(nextSibling); } } else { this.node = node; this.attributes = null; // END_ELEMENT this.eventType = 2; this.popNS(); return this.eventType; } } var nextSibling1 = node.nextSibling; if (nextSibling1) { return this.enter(nextSibling1); } else { var parentNode = node.parentNode; this.node = parentNode; this.attributes = null; if (parentNode.nodeType === 9) { this.eventType = 8; } else { this.eventType = 2; } return this.eventType; } }, getName: function () { var node = this.node; if (Jsonix.Util.Type.isString(node.nodeName)) { if (Jsonix.Util.Type.isString(node.namespaceURI)) { return new Jsonix.XML.QName(node.namespaceURI, node.nodeName); } else { return new Jsonix.XML.QName(node.nodeName); } } else { return null; } }, getNameKey: function () { var node = this.node; if (Jsonix.Util.Type.isString(node.nodeName)) { return Jsonix.XML.QName.key(node.namespaceURI, node.nodeName); } else { return null; } }, getText: function () { return this.node.nodeValue; }, nextTag: function () { var et = this.next(); // TODO isWhiteSpace while (et === 7 || et === 4 || et === 12 || et === 6 || et === 3 || et === 5) { et = this.next(); } if (et !== 1 && et !== 2) { // TODO location throw new Error('Expected start or end tag.'); } return et; }, skipElement: function () { if (this.eventType !== Jsonix.XML.Input.START_ELEMENT) { throw new Error("Parser must be on START_ELEMENT to skip element."); } var numberOfOpenTags = 1; var et; do { et = this.nextTag(); numberOfOpenTags += (et === Jsonix.XML.Input.START_ELEMENT) ? 1 : -1; } while (numberOfOpenTags > 0); return et; }, getElementText: function () { if (this.eventType != 1) { throw new Error("Parser must be on START_ELEMENT to read next text."); } var et = this.next(); var content = ''; while (et !== 2) { if (et === 4 || et === 12 || et === 6 || et === 9) { content = content + this.getText(); } else if (et === 3 || et === 5) { // Skip PI or comment } else if (et === 8) { // End document throw new Error("Unexpected end of document when reading element text content."); } else if (et === 1) { // End element // TODO location throw new Error("Element text content may not contain START_ELEMENT."); } else { // TODO location throw new Error("Unexpected event type [" + et + "]."); } et = this.next(); } return content; }, retrieveElement: function () { var element; if (this.eventType === 1) { element = this.node; } else if (this.eventType === 10) { element = this.node.parentNode; } else { throw new Error("Element can only be retrieved for START_ELEMENT or ATTRIBUTE nodes."); } return element; }, retrieveAttributes: function () { var attributes; if (this.attributes) { attributes = this.attributes; } else if (this.eventType === 1) { attributes = this.node.attributes; this.attributes = attributes; } else if (this.eventType === 10) { attributes = this.node.parentNode.attributes; this.attributes = attributes; } else { throw new Error("Attributes can only be retrieved for START_ELEMENT or ATTRIBUTE nodes."); } return attributes; }, getAttributeCount: function () { var attributes = this.retrieveAttributes(); return attributes.length; }, getAttributeName: function (index) { var attributes = this.retrieveAttributes(); if (index < 0 || index >= attributes.length) { throw new Error("Invalid attribute index [" + index + "]."); } var attribute = attributes[index]; if (Jsonix.Util.Type.isString(attribute.namespaceURI)) { return new Jsonix.XML.QName(attribute.namespaceURI, attribute.nodeName); } else { return new Jsonix.XML.QName(attribute.nodeName); } }, getAttributeNameKey: function (index) { var attributes = this.retrieveAttributes(); if (index < 0 || index >= attributes.length) { throw new Error("Invalid attribute index [" + index + "]."); } var attribute = attributes[index]; return Jsonix.XML.QName.key(attribute.namespaceURI, attribute.nodeName); }, getAttributeValue: function (index) { var attributes = this.retrieveAttributes(); if (index < 0 || index >= attributes.length) { throw new Error("Invalid attribute index [" + index + "]."); } var attribute = attributes[index]; return attribute.value; }, getAttributeValueNS: null, getAttributeValueNSViaElement: function (namespaceURI, localPart) { var element = this.retrieveElement(); return element.getAttributeNS(namespaceURI, localPart); }, getAttributeValueNSViaAttribute: function (namespaceURI, localPart) { var attributeNode = this.getAttributeNodeNS(namespaceURI, localPart); if (Jsonix.Util.Type.exists(attributeNode)) { return attributeNode.nodeValue; } else { return null; } }, getAttributeNodeNS: null, getAttributeNodeNSViaElement: function (namespaceURI, localPart) { var element = this.retrieveElement(); return element.getAttributeNodeNS(namespaceURI, localPart); }, getAttributeNodeNSViaAttributes: function (namespaceURI, localPart) { var attributeNode = null; var attributes = this.retrieveAttributes(); var potentialNode, fullName; for (var i = 0, len = attributes.length; i < len; ++i) { potentialNode = attributes[i]; if (potentialNode.namespaceURI === namespaceURI) { fullName = (potentialNode.prefix) ? (potentialNode.prefix + ':' + localPart) : localPart; if (fullName === potentialNode.nodeName) { attributeNode = potentialNode; break; } } } return attributeNode; }, getElement: function () { if (this.eventType === 1 || this.eventType === 2) { // Go to the END_ELEMENT this.eventType = 2; return this.node; } else { throw new Error("Parser must be on START_ELEMENT or END_ELEMENT to return current element."); } }, pushNS: function (node) { var pindex = this.pns.length - 1; var parentPnsItem = this.pns[pindex]; var pnsItem = Jsonix.Util.Type.isObject(parentPnsItem) ? pindex : parentPnsItem; this.pns.push(pnsItem); pindex++; var reference = true; if (node.attributes) { var attributes = node.attributes; var alength = attributes.length; if (alength > 0) { // If given node has attributes for (var aindex = 0; aindex < alength; aindex++) { var attribute = attributes[aindex]; var attributeName = attribute.nodeName; var p = null; var ns = null; var isNS = false; if (attributeName === 'xmlns') { p = ''; ns = attribute.value; isNS = true; } else if (attributeName.substring(0, 6) === 'xmlns:') { p = attributeName.substring(6); ns = attribute.value; isNS = true; } // Attribute is a namespace declaration if (isNS) { if (reference) { pnsItem = Jsonix.Util.Type.cloneObject(this.pns[pnsItem], {}); this.pns[pindex] = pnsItem; reference = false; } pnsItem[p] = ns; } } } } }, popNS: function () { this.pns.pop(); }, getNamespaceURI: function (p) { var pindex = this.pns.length - 1; var pnsItem = this.pns[pindex]; pnsItem = Jsonix.Util.Type.isObject(pnsItem) ? pnsItem : this.pns[pnsItem]; return pnsItem[p]; }, CLASS_NAME: "Jsonix.XML.Input" }); Jsonix.XML.Input.prototype.getAttributeValueNS = (Jsonix.DOM.isDomImplementationAvailable()) ? Jsonix.XML.Input.prototype.getAttributeValueNSViaElement : Jsonix.XML.Input.prototype.getAttributeValueNSViaAttribute; Jsonix.XML.Input.prototype.getAttributeNodeNS = (Jsonix.DOM.isDomImplementationAvailable()) ? Jsonix.XML.Input.prototype.getAttributeNodeNSViaElement : Jsonix.XML.Input.prototype.getAttributeNodeNSViaAttributes; Jsonix.XML.Input.START_ELEMENT = 1; Jsonix.XML.Input.END_ELEMENT = 2; Jsonix.XML.Input.PROCESSING_INSTRUCTION = 3; Jsonix.XML.Input.CHARACTERS = 4; Jsonix.XML.Input.COMMENT = 5; Jsonix.XML.Input.SPACE = 6; Jsonix.XML.Input.START_DOCUMENT = 7; Jsonix.XML.Input.END_DOCUMENT = 8; Jsonix.XML.Input.ENTITY_REFERENCE = 9; Jsonix.XML.Input.ATTRIBUTE = 10; Jsonix.XML.Input.DTD = 11; Jsonix.XML.Input.CDATA = 12; Jsonix.XML.Input.NAMESPACE = 13; Jsonix.XML.Input.NOTATION_DECLARATION = 14; Jsonix.XML.Input.ENTITY_DECLARATION = 15; Jsonix.XML.Output = Jsonix.Class({ document: null, documentElement: null, node: null, nodes: null, nsp: null, pns: null, namespacePrefixIndex: 0, xmldom: null, initialize: function (options) { // REWORK if (typeof ActiveXObject !== 'undefined') { this.xmldom = new ActiveXObject("Microsoft.XMLDOM"); } else { this.xmldom = null; } this.nodes = []; var rootNspItem = { '': '' }; rootNspItem[Jsonix.XML.XMLNS_NS] = Jsonix.XML.XMLNS_P; if (Jsonix.Util.Type.isObject(options)) { if (Jsonix.Util.Type.isObject(options.namespacePrefixes)) { Jsonix.Util.Type.cloneObject(options.namespacePrefixes, rootNspItem); } } this.nsp = [rootNspItem]; var rootPnsItem = { '': '' }; rootPnsItem[Jsonix.XML.XMLNS_P] = Jsonix.XML.XMLNS_NS; this.pns = [rootPnsItem]; }, destroy: function () { this.xmldom = null; }, writeStartDocument: function () { // TODO Check var doc = Jsonix.DOM.createDocument(); this.document = doc; return this.push(doc); }, writeEndDocument: function () { return this.pop(); }, writeStartElement: function (name) { Jsonix.Util.Ensure.ensureObject(name); var localPart = name.localPart || name.lp || null; Jsonix.Util.Ensure.ensureString(localPart); var ns = name.namespaceURI || name.ns || null; var namespaceURI = Jsonix.Util.Type.isString(ns) ? ns : ''; var p = name.prefix || name.p; var prefix = this.getPrefix(namespaceURI, p); var qualifiedName = (!prefix ? localPart : prefix + ':' + localPart); var element; if (Jsonix.Util.Type.isFunction(this.document.createElementNS)) { element = this.document.createElementNS(namespaceURI, qualifiedName); } else if (this.xmldom) { element = this.xmldom.createNode(1, qualifiedName, namespaceURI); } else { throw new Error("Could not create an element node."); } this.peek().appendChild(element); this.push(element); this.declareNamespace(namespaceURI, prefix); if (this.documentElement === null) { this.documentElement = element; this.declareNamespaces(); } return element; }, writeEndElement: function () { return this.pop(); }, writeCharacters: function (text) { var node; if (Jsonix.Util.Type.isFunction(this.document.createTextNode)) { node = this.document.createTextNode(text); } else if (this.xmldom) { node = this.xmldom.createTextNode(text); } else { throw new Error("Could not create a text node."); } this.peek().appendChild(node); return node; }, writeCdata: function (text) { var parts = text.split(']]>'); for (var index = 0; index < parts.length; index++) { if (index + 1 < parts.length) { parts[index] = parts[index] + ']]'; parts[index + 1] = '>' + parts[index + 1]; } } var node; for (var jndex = 0; jndex < parts.length; jndex++) { node = this.writeCdataWithoutCdend(parts[jndex]); } return node; }, writeCdataWithoutCdend: function (text) { var node; if (Jsonix.Util.Type.isFunction(this.document.createCDATASection)) { node = this.document.createCDATASection(text); } else if (this.xmldom) { node = this.xmldom.createCDATASection(text); } else { throw new Error("Could not create a CDATA section node."); } this.peek().appendChild(node); return node; }, writeAttribute: function (name, value) { Jsonix.Util.Ensure.ensureString(value); Jsonix.Util.Ensure.ensureObject(name); var localPart = name.localPart || name.lp || null; Jsonix.Util.Ensure.ensureString(localPart); var ns = name.namespaceURI || name.ns || null; var namespaceURI = Jsonix.Util.Type.isString(ns) ? ns : ''; var p = name.prefix || name.p || null; var prefix = this.getPrefix(namespaceURI, p); var qualifiedName = (!prefix ? localPart : prefix + ':' + localPart); var node = this.peek(); if (namespaceURI === '') { node.setAttribute(qualifiedName, value); } else { if (node.setAttributeNS) { node.setAttributeNS(namespaceURI, qualifiedName, value); } else { if (this.xmldom) { var attribute = this.document.createNode(2, qualifiedName, namespaceURI); attribute.nodeValue = value; node.setAttributeNode(attribute);