function LEJSONFormat() { }
LEJSONFormat.prototype.format = function(o) { return this.formatObject(o); }
LEJSONFormat.prototype.parse = function(str) {
var index = new LEIndex();
return this.parseObject(str,index);
}
LEJSONFormat.prototype.JSON_VALUE_CLASS_HARDNULL = 'lejsHN';
LEJSONFormat.prototype.JSON_VALUE_CLASS_TIMESTAMP = 'lejsT';
LEJSONFormat.prototype.JSON_VALUE_CLASS_TEMPORARYGLOBALID = 'lejsTGID';
LEJSONFormat.prototype.JSON_VALUE_CLASS_KEYGLOBALID = 'lejsKGID';
LEJSONFormat.prototype.JSON_VALUE_CLASS_BIGDECIMAL = 'lejsBD';
LEJSONFormat.prototype.JSON_VALUE_CLASS_DATA = 'lejsDATA';
LEJSONFormat.prototype.JSON_VALUE_CLASS_MAP = 'lejsMap';
LEJSONFormat.prototype.JSON_VALUE_CLASS_ENTERPRISEOBJECT = 'lewosEO';
LEJSONFormat.prototype.JSON_KEY_CLASS = '__jsonclass__';
LEJSONFormat.prototype.JSON_HEXCHARS = new Array(
'0','1','2','3',
'4','5','6','7',
'8','9','A','B',
'C','D','E','F' );
LEJSONFormat.prototype.JSON_NUMBERREGEX = /(-)?([1-9][0-9]*|[0])(\\.[0-9]+)?([Ee][+-]?[0-9]+)?/;
LEJSONFormat.prototype.JSON_INTEGERREGEX = /(-)?([1-9][0-9]*|[0])/;
LEJSONFormat.prototype.JSON_WHITESPACEREGEX = /[ \n\r]/;
LEJSONFormat.prototype.typeOf = function(value) {
var s = typeof value;
if (s === 'object') {
if (value) {
if(typeof value.length === 'number') {
if(typeof value.concat === 'function') {
if(typeof value.splice === 'function')
s = 'array';
}
}
} else {
s = 'null';
}
}
return s;
}
LEJSONFormat.prototype.snippetAroundIndex = function(str,index,size) {
if(index.offset < str.length) {
if(0!=index.offset) {
var start = index.offset - size;
var end = index.offset + size;
if(start < 0) start = 0;
if(end > str.length) end = str.length;
return str.substring(start,end);
}
else
return '>>at start<<';
}
else
return '>>at end<<';
}
LEJSONFormat.prototype.formatChar = function(ch) {
return '\\u' +
this.JSON_HEXCHARS[(ch >> 12) & 0x0F] +
this.JSON_HEXCHARS[(ch >> 8) & 0x0F] +
this.JSON_HEXCHARS[(ch >> 4) & 0x0F] +
this.JSON_HEXCHARS[ch & 0x0F];
}
LEJSONFormat.prototype.formatString = function(str) {
var isSimpleString = true;
if(str.length > 24)
isSimpleString = false;
else
isSimpleString = str.match(/^([A-Za-z0-9]*)$/);
if(!isSimpleString)
{
str = str.replace(/[\n]/g,'\\n');
str = str.replace(/[\r]/g,'\\r');
str = str.replace(/[\t]/g,'\\t');
str = str.replace(/[\f]/g,'\\f');
str = str.replace(/[\b]/g,'\\b');
str = str.replace(/[\"]/g,'\\\"');
str = str.replace(/[\\]/g,'\\\\');
str = str.replace(/[\/]/g,'\\/');
for(i=0;i<str.length;i++) {
var ch = str.charCodeAt(i);
if((ch < 0x20) || (ch > 0x7F))
str.replace(ch,this.formatChar(ch));
}
}
return [ '\"',str,'\"'].join('');
}
LEJSONFormat.prototype.formatBoolean = function(b) {
if(b) return "true";
return "false";
}
LEJSONFormat.prototype.formatNumber = function(num) {
return num.toString();
}
LEJSONFormat.prototype.formatDate = function(date) {
function f(n) {
return n < 10 ? '0' + n : n;
}
var millisOff = date.getTimezoneOffset() * 60 * 1000;
var millisGMT = date.getTime() + millisOff;
var dateGMT = new Date(millisGMT);
return "{\"__jsonclass__\":\"lejsT\",\"iso8601\":\""+
dateGMT.getFullYear() + "-" +
f(dateGMT.getMonth() + 1) + "-" +
f(dateGMT.getDate()) + "T" +
f(dateGMT.getHours()) + ":" +
f(dateGMT.getMinutes()) + ":" +
f(dateGMT.getSeconds()) + ".000Z\"}";
};
LEJSONFormat.prototype.formatGlobalID = function(gid) {
var str = "{\"__jsonclass__\":";
if(null!=gid.token)
str = str + "\"lejsTGID\",\"token\":\""+gid.token+"\"}";
else
str = str + "\"lejsKGID\",\"entityName\":\""+gid.entityName+"\",\"primaryKeys\":"+formatObject(gid.primaryKeys)+"}";
return str;
};
LEJSONFormat.prototype.formatHardNull = function() { return "{\"__jsonclass__\":\"lejsHN\"}"; }
LEJSONFormat.prototype.formatNull = function() { return "null"; }
LEJSONFormat.prototype.formatArray = function(list) {
if(0==list.length)
return "[]";
var listLen = list.length;
var resultAtoms = new Array(listLen);
var i2;
for(i2=0;i2<listLen;i2++) {
i = list[i2];
if(typeof i != 'function')
resultAtoms[i2] = this.formatObject(i);
}
return "[" + resultAtoms.join(",") + "]";
}
LEJSONFormat.prototype.formatObject = function(o) {
if(null==o) return this.formatNull();
var ot = this.typeOf(o);
if(ot === 'number') return this.formatNumber(o);
if(ot === 'string') return this.formatString(o);
if(ot === 'boolean') return this.formatBoolean(o);
if(ot === 'array') return this.formatArray(o);
if(ot === 'object') {
if(o instanceof Date) return this.formatDate(o);
if(o instanceof LEEOGlobalIDProxy) return this.formatGlobalID(o);
var resultAtoms = [];
resultAtoms.push('{');
for(propName in o) {
if(o.hasOwnProperty(propName)) {
if(resultAtoms.length > 2)
resultAtoms.push(',');
resultAtoms.push(this.formatString(propName));
resultAtoms.push(':');
resultAtoms.push(this.formatObject(o[propName]));
}
}
resultAtoms.push('}');
return resultAtoms.join('');
}
throw new Error('unable to format the item \"'+o.toString()+'\"');
}
/*
This method will skip the index over the whitespace that
it is currently looking at.  If it gets to the end then
so be it.
*/
LEJSONFormat.prototype.skipWhitespace = function(str, index) {
while(index.offset < str.length) {
var ch = str.charAt(index.offset);
if((ch!=' ') && (ch!='\n') && (ch!='\r') && (ch!='\t'))
return;
index.offset++;
}
}
LEJSONFormat.prototype.replaceEscapedSequences = function(stra) {
while(true)
{
var last = stra.pop();
var i = last.indexOf('\\',0);
if(-1==i) {
stra.push(last);
return stra;
}
else {
if(0!=i)
stra.push(last.substring(0,i));
if(i==last.length-1)
throw new Error("the escape sequence was found at the end of the string.");
switch(last.charAt(i+1)) {
case '\\': stra.push('\\'); i+=2; break;
case '/': stra.push('/'); i+=2; break;
case '\"': stra.push('\"'); i+=2; break;
case 'n': stra.push('\n'); i+=2; break;
case 'r': stra.push('\r'); i+=2; break;
case 'f': stra.push('\f'); i+=2; break;
case 'b': stra.push('\b'); i+=2; break;
case 't': stra.push('\t'); i+=2; break;
case 'u': {
if(i+6 > last.length)
throw Error('unexpected end of string unescaping unicode character');
var ucStr = last.substr(i+2,4);
var uc = parseInt(ucStr,16);
stra.push(String.fromCharCode(uc));
i+=6;
}
break;
default:
throw new Error('unknown escape sequence with character \''+last.charAt(i+1)+'\'.');
}
if(i<last.length)
stra.push(last.substr(i));
else
stra.push('');
}
}
}
LEJSONFormat.prototype.parseString = function(str, index) {
var startCh = str.charAt(index.offset);
if('\"' != startCh)
throw new Error('the JSON string should start with a \", but instead started with '+startCh);
index.offset++;
var resultStart = index.offset;
var result = null;
while(null==result)
{
var quoteOffset = str.indexOf('\"',index.offset);
if(-1==quoteOffset)
throw new Error('missing quotes at the end of the JSON string');
if('\\' != str.charAt(quoteOffset-1))
result = str.substr(resultStart,quoteOffset-resultStart);
index.offset = quoteOffset+1;
}
var stra = [ result ];
this.replaceEscapedSequences(stra);
result = stra.join('');
return result;
}
LEJSONFormat.prototype.parseNumber = function(str, index) {
var numEnd;
numEnd = index.offset;
while((numEnd < str.length) && (-1!=".0123456789eE-".indexOf(str.charAt(numEnd))))
numEnd++;
var possibleNumStr = str.substring(index.offset,numEnd);
var matchResult;
if(null==(matchResult=possibleNumStr.match(this.JSON_NUMBERREGEX)))
throw new Error('unable to identify a number at location '+index.offset+' in the string');
var numStr = matchResult[0];
index.offset = numEnd;
var matchResultInt = numStr.match(this.JSON_INTEGERREGEX);
var numStrInt = matchResultInt[0];
var result;
if(numStrInt.length == numStr.length)
result = parseInt(numStrInt);
else
result = parseFloat(numStr);
if(isNaN(result))
throw new Error('the string '+numStr+' does not appear to be a number.');
return result;
}
/*
This method will parse a javascript array into an array object
and will return the results.
*/
LEJSONFormat.prototype.parseArray = function(str, index) {
if('[' != str.charAt(index.offset))
throw new Error("unable to parse the array because the first character was not [");
index.offset++;
var stopParseArray = false;
var result = [];
while(!stopParseArray) {
this.skipWhitespace(str,index);
if(index.offset >= str.length)
throw new Error("unexpected end of string when parsing an array.");
var nextCh = str.charAt(index.offset);
if(']'==nextCh) {
stopParseArray = true;
index.offset++;
}
else {
if(0!=result.length) {
if(','!=nextCh)
throw new Error("expected array separator, but found \""+nextCh+"\" when parsing an item from an array.");
index.offset++;
this.skipWhitespace(str,index);
}
result.push(this.parseObject(str,index));
}
}
return result;
}
LEJSONFormat.prototype.parseBoolean = function(str, index) {
if(index.offset==str.indexOf('true',index.offset))
{
index.offset += 4;
return true;
}
if(index.offset==str.indexOf('false',index.offset))
{
index.offset += 5;
return false;
}
throw new Error("unknown boolean value.");
}
LEJSONFormat.prototype.parseNull = function(str, index) {
if(index.offset==str.indexOf('null',index.offset))
{
index.offset += 4;
return null;
}
throw new Error("unknown null value.");
}
/*
This will parse an object into itself.  This could be a basic type
or could be a complex type; the 'object' in JavaScript.
*/
LEJSONFormat.prototype.parseObject = function(str, index) {
this.skipWhitespace(str,index);
var ch = str.charAt(index.offset);
if('{' != ch)
{
if('\"'==ch) return this.parseString(str,index);
if('['==ch) return this.parseArray(str,index);
if('t'==ch) return this.parseBoolean(str,index);
if('f'==ch) return this.parseBoolean(str,index);
if('n'==ch) return this.parseNull(str,index);
return this.parseNumber(str,index)
}
index.offset++;
var stopParseObject = false;
var result = new Object();
var firstKeyValuePair = true;
while(!stopParseObject) {
this.skipWhitespace(str,index);
if(index.offset >= str.length)
throw new Error("unexpected end of string when parsing an object looking for the key.");
if('}'==str.charAt(index.offset)) {
stopParseObject = true;
index.offset++;
}
else {
if(!firstKeyValuePair) {
if(','!=str.charAt(index.offset))
throw new Error("missing member separator when parsing an object at "+index.offset+" : "+this.snippetAroundIndex(str,index,10));
index.offset++;
this.skipWhitespace(str,index);
}
var key = this.parseString(str,index);
this.skipWhitespace(str,index);
if(index.offset >= str.length)
throw new Error("unexpected end of string when parsing an object looking for member separator");
if(':' != str.charAt(index.offset))
throw new Error("missing key and value separator when parsing an object");
index.offset++;
this.skipWhitespace(str,index);
if(index.offset >= str.length)
throw new Error("unexpected end of string when parsing an object looking for value");
result[key] = this.parseObject(str,index);
firstKeyValuePair = false;
}
}
return this.convertObject(result);
}
/*
The object that is supplied may actually be a specially
serialised object that is designed to actually be
deserialised as another sort of object.  In this case,
this method will convert the object into the new class
of object.
*/
LEJSONFormat.prototype.convertObject = function(obj) {
var jC = obj[this.JSON_KEY_CLASS];
if(null!=jC)
{
if(jC == this.JSON_VALUE_CLASS_HARDNULL)
return new LEHardNull();
if(jC == this.JSON_VALUE_CLASS_TIMESTAMP)
return this.convertObjectToDate(obj);
if(jC == this.JSON_VALUE_CLASS_TEMPORARYGLOBALID)
return this.convertObjectToTemporaryGlobalID(obj);
if(jC == this.JSON_VALUE_CLASS_KEYGLOBALID)
return this.convertObjectToKeyGlobalID(obj);
if(jC == this.JSON_VALUE_CLASS_ENTERPRISEOBJECT)
return this.convertObjectToEnterpriseObject(obj);
throw new Error('the class for '+jC+' is unknown.');
}
return obj;
}
/*
This method will convert an object into a Date object by taking the
GMT time and converting it into the Date object.
*/
LEJSONFormat.prototype.convertObjectToDate = function(obj) {
var iso8601 = obj.iso8601;
if((null==iso8601)||(24!=iso8601.length))
throw new Error('badly formatted \'lejsT\' jsonclass');
var millisGMT = Date.UTC(
parseInt(iso8601.substr(0,4)), // year
parseInt(iso8601.substr(5,7))-1, // month
parseInt(iso8601.substr(8,10)), // day
parseInt(iso8601.substr(11,13)), // hour
parseInt(iso8601.substr(14,16)), // minute
parseInt(iso8601.substr(17,19))); // second
return new Date(millisGMT);
}
LEJSONFormat.prototype.convertObjectToTemporaryGlobalID = function(obj) {
var gid = new LEEOGlobalIDProxy();
gid.token = obj.token;
if(null==gid.token)
throw new Error("missing token for temporary GID");
return gid;
}
LEJSONFormat.prototype.convertObjectToKeyGlobalID = function(obj) {
var gid = new LEEOGlobalIDProxy();
gid.entityName = obj.entityName;
gid.primaryKeys = obj.primaryKeys;
if((null==gid.entityName)||(0==gid.entityName.length))
throw new Error("missing entity name for key GID");
if((null==gid.primaryKeys)||(0==gid.primaryKeys.length))
throw new Error("missing primary keys for key GID");
return gid;
}
LEJSONFormat.prototype.convertObjectToEnterpriseObject = function(obj) {
var eo = new LEEOEnterpriseObjectProxy();
eo.globalID = obj.globalID;
eo.entityName = obj.entityName;
eo.properties = obj.properties;
if(null==eo.globalID)
throw new Error("an EO must have a global ID");
if(null==eo.entityName)
throw new Error("an EO must have an entity name");
if(null==eo.properties)
throw new Error("an EO must have properties");
return eo;
}
