// URL Serialization/Restoration implementation for JavaX // Require: prototype.js // Require: types.js // Require: aserializer.js // Bugs: Grep TODOs, NOTEs, FIXMEs in the code // Known issues: // undefined & null are serialized in the same way (asymmetricity) //#include "/js/aserializer.js" // ------------------------------------------------------------ // Helper class for serialization handling // ------------------------------------------------------------ var URLContextEntry = Class.create({ initialize: function(name, status) { this.name_ = name; this.status_ = status; }, Name: function() { return this.name_; }, SetName: function(name) { this.name_ = name; }, Status: function() { return this.status_; }, SetStatus: function(status) { this.status_ = status; }, Print: function() { return "N:"+this.name_+",S:"+this.status_+"; "; } }); function URLRestore(sBuffer) { var oRestorer = new AURLRestorer(sBuffer); return oRestorer.Restore(); } function URLRestoreV(sBuffer) { var sVBuffer = "Value:"+sBuffer+"&"; return URLRestore(sVBuffer); } function URLRestoreVS(sBuffer) { var sVSBuffer = "Value:Structure:"+sBuffer+"&&"; return URLRestore(sVSBuffer); } function URLSerialize(oObj) { var oSerializer = new AURLSerializer(); oSerializer.Serialize(oObj); return oSerializer.Buffer(); } function URLSerializeV(oObj) { var sV = URLSerialize(oObj); // Cut 'Value:' and '&' return sV.substr(6, sV.length-7); } function URLSerializeVS(oObj) { var sVS = URLSerialize(oObj); // Cut 'Value:Structure:' and '&&' return sVS.substr(16, sVS.length-16-2); } String.prototype.Value = function() { return this; } // ------------------------------------------------------------ // Restorer class // ------------------------------------------------------------ var AURLRestorer = Class.create({ initialize: function(buffer) { this.buffer_ = (buffer)?buffer:""; this.pos_ = 0; this.context_ = new Array(); this.BEGIN = 0; this.BEFOREKEY = 1; this.AFTERKEY = 2; this.END = 3; this.BEGIN_ANONYM = 4; this.END_ANONYM = 5; }, sh: function(len) { this.pos_ += len; }, setEnd: function() { var oBack = null; if (this.context_.length != 0) oBack = this.context_.last(); if (oBack && oBack.Name() == "Value" && oBack.Status() == this.BEGIN) oBack.SetStatus(this.END); else if(oBack && oBack.Name() == "Value" && oBack.Status() == this.BEGIN_ANONYM) oBack.SetStatus(this.END_ANONYM); }, invalidContext: function(sTrace, typeName) { alert(sTrace+": Invalid restoring context for the type '"+typeName+ "'.\nBuffer is '"+this.buffer_+"'.\n Passed part: '"+this.buffer_.substr(0, this.pos_)); }, BeginType: function(typeName) { var oBack = null; if (this.context_.length != 0) oBack = this.context_.last(); if (oBack) { if (typeName == "Value") { if (oBack.Name() == "Structure" && oBack.Status() == this.AFTERKEY) { oBack.SetStatus(this.BEFOREKEY); this.context_.push(new URLContextEntry(typeName, this.BEGIN_ANONYM)); return; } else if (oBack.Name() == "Array") { this.context_.push(new URLContextEntry(typeName, this.BEGIN_ANONYM)); return; } } } var len = typeName.length; if (len < 1 || this.buffer_.substr(this.pos_, len) != typeName) { this.invalidContext("BeginType", typeName); return; } this.sh(len+1); this.context_.push(new URLContextEntry(typeName, this.BEGIN)); if (typeName == "Structure") { this.context_.last().SetStatus(this.BEFOREKEY); } }, EndType: function(typeName) { if (this.context_.length == 0) { this.invalidContext("EndType:begin", typeName); return; } var oBack = this.context_.last(); if (typeName == "Value") { if( oBack.Status() == this.END ) { /*if( this.buffer_.charAt(this.pos_)=='&' )*/ this.sh(1); this.context_.pop(); return; } else if( oBack.Status() == this.END_ANONYM ) { this.context_.pop(); return; } } if (this.pos_ < this.buffer_.length && this.buffer_.charAt(this.pos_) != '&') { this.invalidContext("EndType:end", typeName); return; } this.sh(1); this.context_.pop(); this.setEnd(); }, GetType: function() { this.bNull = false; if (this.pos_ >= this.buffer_.length) return ""; if (this.buffer_.charAt(this.pos_) == '&') return ""; var oBack = null; if (this.context_.length != 0) oBack = this.context_.last(); if (oBack) { if (oBack.Name() == "Structure" && oBack.Status() == this.BEFOREKEY) { return "string"; } else if (oBack.Name() == "Structure" && oBack.Status() == this.AFTERKEY) { return "Value"; } else if (oBack.Name() == "Array") { return "Value"; } else if (oBack.Name() == "Value" && (oBack.Status()==this.END || oBack.Status()==this.END_ANONYM )) { return ""; } } var nP = this.buffer_.indexOf(':', this.pos_); if (nP < 0) return ""; var typeName = this.buffer_.substr(this.pos_, nP-this.pos_); if (typeName == "i") return "int"; if (typeName == "l") return "int64_t"; if (typeName == "s") return "string"; if (typeName == "b") return "base64"; if (typeName == "null") { this.bNull = true; return ""; } if (typeName == "d") return "double"; return typeName; }, restoreSimpleType: function(T) { var TC = T + ":"; if (this.buffer_.substr(this.pos_, TC.length) != TC) { this.invalidContext("restoreSimpleType:type", typeName); return null; } var nE = this.buffer_.indexOf('&', this.pos_); if (nE < 0) { this.invalidContext("restoreSimpleType:end", typeName); return null; } var nB = this.pos_+TC.length; var sVal = this.buffer_.substr(nB,nE-nB); var oVal = null; if (T == "i") oVal = new Int(sVal); else if (T == "d") oVal=new Double(sVal); // NaN values are handled inside else if (T == "l") oVal = new Int64(sVal); else if (T == "null") { // default value } else if (T == "b") { oVal=new Blob(sVal); } else { throw new Error("Unknown simple type! "); } this.sh(nE-nB+TC.length+1); this.setEnd(); return oVal; }, RestoreInt: function() { return this.restoreSimpleType("i"); }, RestoreInt64: function() { return this.restoreSimpleType("l"); }, RestoreDouble: function() { return this.restoreSimpleType("d"); }, RestoreBool: function() { this.BeginType("bool"); var v=this.RestoreInt().Value(); this.EndType("bool"); if (v==0) return false; else if (v==1) return true; else throw new Error("Invalid boolean value. "); }, RestoreBinary: function() { return this.restoreSimpleType("b"); }, RestoreNull: function() { return this.restoreSimpleType("null"); }, RestoreString: function() { var nE = -1; var nB = this.pos_; var oBack = null; if (this.context_.length != 0) oBack = this.context_.last(); if (oBack && oBack.Name() == "Structure" && oBack.Status() == this.BEFOREKEY) { nE = this.buffer_.indexOf('=', nB); oBack.SetStatus(this.AFTERKEY); } else { if (this.buffer_.substr(nB, 2) != "s:") { this.invalidContext("RestoreString:begin", "string"); return null; } nB += 2; // TODO: BAD: this changes stream state, however, restoration can fail after that this.sh(2); nE = this.buffer_.indexOf('&', nB); } if (nE < 0) { this.invalidContext("RestoreString:end", "string"); return null; } var sVal = this.urlUnescape(this.buffer_.substr(nB, nE-nB)); this.sh(nE-nB + 1); // string length + '=' or '&' this.setEnd(); return sVal; }, RestoreCompound: function() { var oObj = null; this.BeginType("Value"); var typeName = this.GetType(); if (typeName == "Structure") { oObj = new Object(); this.BeginType("Structure"); while (this.GetType() == "string") { var sKey = this.RestoreString(); //this.BeginType("Value"); oObj[sKey] = this.Restore(); //this.EndType("Value"); } this.EndType("Structure"); } else if (typeName == "Array") { oObj = new Array(); this.BeginType("Array"); while (this.GetType() == "Value") { //this.BeginType("Value"); oObj.push(this.Restore()); //this.EndType("Value"); } this.EndType("Array"); } else if (!typeName) { if (this.bNull) this.RestoreNull(); // for empty Values oObj = null; } else { oObj = this.Restore(); } this.EndType("Value"); return oObj; }, Restore: function() { return RestorerIF.restore(this); }, Buffer: function() { return this.buffer_; }, urlUnescape: function(sVal) { if (!sVal) return ""; return sVal.replace(/\\a/g, "&") .replace(/\\d/g, "#") .replace(/\\w/g, " ") .replace(/\\p/g, "%") .replace(/\\b/g, "`") .replace(/\\s/g, "\\"); } }); // ------------------------------------------------------------ // Serializer class // ------------------------------------------------------------ var AURLSerializer = Class.create({ initialize: function() { this.buffer_ = ""; this.context_ = Array(); this.BEGIN = 0; this.BEFOREKEY = 1; this.AFTERKEY = 2; this.END = 3; }, Buffer: function() { return this.buffer_; }, setEnd: function() { var oBack = null; if (this.context_.length != 0) oBack = this.context_.last(); if (oBack && oBack.Name() == "Value" && oBack.Status() == this.BEGIN) oBack.SetStatus(this.END); }, invalidContext: function(sTrace, typeName) { alert(sTrace+": Invalid restoring context for the type '"+typeName+ "'.\nBuffer is '"+this.buffer_+"'.\n Passed part: '"+this.buffer_.substr(0, this.pos_)); }, BeginType: function (typeName) { var oBack = null; if (this.context_.length != 0) oBack = this.context_.last(); if (oBack) { if (typeName == "Value") { if (oBack.Name() == "Structure" && oBack.Status() == this.AFTERKEY) { oBack.SetStatus(this.BEFOREKEY); // can be optimized... this.context_.push(new URLContextEntry(typeName, this.BEGIN)); return; } else if (oBack.Name() == "Array") { this.context_.push(new URLContextEntry(typeName, this.BEGIN)); return; } } } this.buffer_ += typeName; this.buffer_ += ":"; oBack = new URLContextEntry(typeName, this.BEGIN); this.context_.push(oBack); if (typeName == "Structure") { oBack.SetStatus(this.BEFOREKEY); } }, EndType: function (typeName) { if (!this.context_.length) { this.invalidContext("EndType", typeName); return; } var oBackStatus = this.context_.last().Status(); this.context_.pop(); var back = this.context_.last(); if (typeName != "Value" || oBackStatus != this.END || !back || (back.Name() != "Array" & back.Name() != "Structure")) { this.buffer_ += "&"; this.setEnd(); } }, PrintContext: function() { var c = ""; for (var i = 0; i < this.context_.length; i++) { c += this.context_[i].Print(); } LogL(c); }, SerializeObject: function(oObj) { var prevName = (this.context_.length ? this.context_.last().Name() : ""); var inCompound = (prevName == "Array" || prevName == "Structure"); if (!inCompound) this.BeginType("Value"); var nCount = 0; for (var sKey in oObj) { if (typeof(oObj[sKey]) == "function") continue; ++nCount; } if (nCount) { this.BeginType("Structure"); for (var sKey in oObj) { if (typeof(oObj[sKey]) == "function") continue; var oItem = oObj[sKey]; // put key this.Serialize(String(sKey)); // put value as Value if (typeof(oItem) != 'undefined' && oItem !== null) { // NULL-FIX this.Serialize(oItem, true); } else { this.buffer_ += 'null:&'; // NULL-FIX } this.context_.last().SetStatus( this.BEFOREKEY ); } this.EndType("Structure"); } else { this.buffer_ += 'null:&'; // NULL-FIX } if (!inCompound) this.EndType("Value"); }, SerializeArray: function(oObj) { var prevName = (this.context_.length ? this.context_.last().Name() : ""); var inCompound = (prevName == "Array" || prevName == "Structure"); if (!inCompound) this.BeginType("Value"); this.BeginType("Array"); for (var i = 0; i < oObj.length; i++) { var oItem = oObj[i]; // put value as Value if (typeof(oItem) != 'undefined' && oItem !== null) { // NULL-FIX this.Serialize(oItem, true); } else { this.buffer_ += 'null:&'; } } this.EndType("Array"); if (!inCompound) this.EndType("Value"); }, SerializeString: function(oObj) { var oBack = null; if (this.context_.length != 0) oBack = this.context_.last(); if (oBack && oBack.Name() == "Structure" && oBack.Status() == this.BEFOREKEY) { this.buffer_ += String(oObj); this.buffer_ += "="; oBack.SetStatus(this.AFTERKEY); } else { this.buffer_ += "s:"; this.buffer_ += String(oObj) .replace(/\\/g, "\\s") .replace(/&/g, "\\a") .replace(/#/g, "\\d") .replace(/ /g, "\\w") .replace(/%/g, "\\p") .replace(/`/g, "\\b"); this.buffer_ += "&"; } this.setEnd(); }, SerializeDouble: function(oObj) { this.buffer_ += "d:"; if (isNaN(oObj)) this.buffer_ += "NaN"; else this.buffer_ += String(oObj); this.buffer_ += "&"; this.setEnd(); }, SerializeBool: function(oObj) { this.BeginType("bool"); var iv=(oObj==true) ? 1 : 0; this.Serialize(iv); this.EndType("bool"); }, SerializeInt: function(oObj) { this.buffer_ += "i:"; this.buffer_ += String(oObj); this.buffer_ += "&"; this.setEnd(); }, SerializeInt64: function(oObj) { this.buffer_ += "l:"; this.buffer_ += oObj.toString(); this.buffer_ += "&"; this.setEnd(); }, SerializeBinary: function(oObj) { this.buffer_ += "b:"; this.buffer_ += Encoder.Base64Encode(Encoder.UTF8Encode(oObj)); this.buffer_ += "&"; this.setEnd(); }, // generic serialization interface Serialize: function(oObj, bValueElement) { SerializerIF.serialize(this, oObj, bValueElement); } });