1  //!-- UTF8
     2  /*
     3  Oregano Multiuser Server - Version 1.2.0 - January 4th, 2005
     4   
     5  	Web:  www.oregano-server.org
     6  	Mail: info@oregano-server.org
     7   
     8  	Copyright 2003 - 2004 - 2004 Jens Halm / Cologne, Germany
     9   
    10   This library is free software; you can redistribute it and/or
    11   modify it under the terms of the GNU Lesser General Public
    12   License as published by the Free Software Foundation; either
    13   version 2.1 of the License, or (at your option) any later version.
    14   
    15   This library is distributed in the hope that it will be useful,
    16   but WITHOUT ANY WARRANTY; without even the implied warranty of
    17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    18   Lesser General Public License for more details.
    19   
    20   You should have received a copy of the GNU Lesser General Public
    21   License along with this library; if not, write to the Free Software
    22   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    23   */
    24  
    25  /*
    26  -------------------------------------------
    27  	Classe Message
    28  
    29  	@description :
    30  	Gestion du parsing des pièces jointes.
    31  	Associé avec Message.
    32  
    33  	@author Jens Halm copyright http://www.spicefactory.org/
    34  	@author erixtekila copyleft http://www.v-i-a.net  
    35  -------------------------------------------
    36  	version history :
    37  	1.2.0 : 18/01/05
    38  			- Portage en actionscript 2 pour le
    39  			compile time type checking
    40  			- Remplecement de substring(string,b,c) par  string.substring(b,c)
    41  -------------------------------------------
    42  */
    43  
    44  import org.omus.data.TableDefinition;
    45  import org.omus.data.Table;
    46  import org.omus.data.TableBackup;
    47  import org.omus.data.UpdateSequence;
    48  import org.omus.data.ClearTable;
    49  import org.omus.data.RemoveRow;
    50  import org.omus.data.AddRow;
    51  import org.omus.data.UpdateRow;
    52  
    53  import org.omus.util._Class;
    54  
    55  /**
    56   *	Analyse et décodage des pièces jointes associées à Message.
    57   *
    58   *	@see org.omus.msg.Message
    59   *
    60   *	@author Jens Halm copyright http://www.spicefactory.org/
    61   *	@author erixtekila copyleft http://www.v-i-a.net 
    62   *	@version 1.2.0
    63   */
    64  class org.omus.data.DataParser
    65  {
    66  	//--------------------
    67  	// PROPRIETES
    68  	//--------------------
    69  	/**
    70  	 *	Données brutes.
    71  	 */
    72  	public var data:String;
    73  	
    74  	/**
    75  	 *	Index de départ de l'analyse des données brutes.
    76  	 */
    77  	public var idx:Number;
    78  	
    79  	/**
    80  	 *	Code de l'erreur de parsing.
    81  	 */
    82  	public var error:String;
    83  	
    84  	/**
    85  	 *	Portion encodée des données.
    86  	 *	[code] TODO
    87  	 *	- 1 start of structured field type
    88  	 *	- 2 update part (structured)
    89  	 *	- 3 string
    90  	 *	- 4 primitive
    91  	 *	- 5 end of primitive or string field
    92  	 *	- 6 end of structured field
    93  	 */
    94  	private var categories:Object;
    95  	
    96  	/**
    97  	 *	Longueur des données brutes.
    98  	 */
    99  	private var len:Number;
   100  	
   101  	//--------------------
   102  	// CONSTRUCTEUR
   103  	//--------------------
   104  	/**
   105  	 *	Conteneur des données analysées d'un pièce jointe.
   106  	 *
   107  	 *	@param str		Données brutes
   108  	 *	@param index	TODO
   109  	 */
   110  	public function DataParser(str:String, index:Number)
   111  	{		
   112  		categories = {u:1,t:1,o:1,a:1,p:2,s:3,b:4,c:4,i:4,l:4,f:4,d:4,x:5,y:5,z:6};
   113  		
   114  		reset(str, index);
   115  		// 1 start of structured field type
   116  		// 2 update part (structured)
   117  		// 3 string
   118  		// 4 primitive
   119  		// 5 end of primitive or string field
   120  		// 6 end of structured field
   121  		
   122  		// trace(this+ " installé.");
   123  	}
   124  	
   125  	//--------------------
   126  	// METHODES PUBLIQUES
   127  	//--------------------
   128  	/**
   129  	 *	Utilisé dans un contexte littéral
   130  	 *	@return	Une chaine définissant l'objet
   131  	 */
   132  	public function toString():String
   133  	{
   134  		return "[Object DataParser]";
   135  	}
   136  	
   137  	/**
   138  	 *	Réinitialisation.
   139  	 *
   140  	 *	@param str		Les données brutes.
   141  	 *	@param index	?
   142  	 */
   143  	public function reset (str:String, index:Number):Void
   144  	{
   145  		data = str;
   146  		len = str.length;
   147  		idx = index;
   148  		error = null;
   149  	}
   150  
   151  	/*
   152  	 *	Parsing.
   153  	 *	Renvoie la valeur du type analysé.
   154  	 *	Positionne le curseur sur le motif suivant.
   155  	 *
   156  	 *	@param includeBackup	?.
   157  	 *	@return		Une valeur, null si echec.
   158  	 */	
   159  	public function nextValue (includeBackup:Boolean):Object
   160  	{
   161  		var type = nextType();
   162  		var cat = categories[type];
   163  		var bd = (cat == 1) ? "z" : (cat == 3) ? "y" : (cat == 4) ? "x" : "0";
   164  		if (bd == "0") 
   165  		{
   166  			if (error == null) error = "illegal field type: " + type;
   167  			return null;
   168  		}
   169  		
   170  		// structured data
   171  		if (type == "o" || type == "a") 
   172  		{
   173  			var startIdx = idx - 1;
   174  			var val:Object;
   175  			if (type == "o") val = nextObject(false);
   176  			if (type == "a") val = nextArray(false);
   177  			if (includeBackup) return getSplittedValue(val, startIdx);
   178  			else return val;
   179  		}
   180  		if (type == "t") return nextTable(false, includeBackup);
   181  		if (type == "u") return nextUpdateSequence(false);
   182  		
   183  		var b = nextBoundary(bd);
   184  		var i = idx + 1;
   185  		var l = b - idx - 2;
   186  		idx = b;
   187  		// string
   188  		if (type == "s")
   189  		{
   190  			return unescape(data.substr(i-1, l));
   191  		}
   192  		// boolean
   193  		if (type == "b") 
   194  		{
   195  			if (l != 1) 
   196  			{
   197  				if (error == null) error = "illegal format for boolean field";
   198  				return null;
   199  			}
   200  			var c = data.substr(i-1, 1);
   201  			return (c == "1");
   202  		}
   203  		// date
   204  		if (type == "d") 
   205  		{
   206  			var tm = parseInt(data.substr(i-1, l));
   207  			if (isNaN(tm)) {
   208  				if (error == null) error = "error parsing number: " + data.substr(i-1, l);
   209  				return null;
   210  			}
   211  			var dt = new Date();
   212  			dt.setTime(tm);
   213  			if (includeBackup) return {value:dt, backup:tm};
   214  			else return dt;
   215  		}
   216  		// check for remaining types
   217  		if (categories[type] != 4) {
   218  			if (error == null) error = "illegal field type: " + type;
   219  			return null;
   220  		}
   221  		// number
   222  		var s = data.substr(i-1, l);
   223  		var n = (type == "f") ? parseFloat(s) : parseInt(s) ;
   224  		if (isNaN(n)) {
   225  			if (error == null) error = "error parsing number: " + s;
   226  			return null;
   227  		}
   228  		return n;
   229  	}
   230  
   231  	
   232  	/**
   233  	 *	Parsing.
   234  	 *	Renvoie un objet et ses valeurs une fois le tout parsé.
   235  	 *	
   236  	 *	@param check		true si valide, false sinon.
   237  	 *	@return				L'objet parsé ou null en cas d'échec.
   238  	 */
   239  	public function nextObject (check:Boolean):Object
   240  	{
   241  		if (check)
   242  		{
   243  			if (nextType() != "o") 
   244  			{
   245  				if (error == null) error = "next value not an object";
   246  				return null;
   247  			}
   248  		}
   249  		
   250  		// create object
   251  		var _class = nextString();
   252  		if (error != null) return null;
   253  		var obj:Object;
   254  		
   255  		if (_class.length != 0 && _class != "PropertySet") 
   256  		{
   257  			// TODO : Accès Singleton
   258  			var clazz = _Class.getInstance();
   259  			var constr = clazz.getConstructor(_class);
   260  			if (typeof(constr) != "function") 
   261  			{
   262  				// Logs internes
   263  				_global.oregano.iLog.error("clj-015","class name = " + _class);
   264  				obj = new Object();
   265  			} else
   266  			{
   267  				obj = new Function(constr) ();
   268  			}
   269  		} else 
   270  		{
   271  			obj = new Object();
   272  		}
   273  		// read properties
   274  		var cnt = nextInt();
   275  		var name:String;
   276  		var value:Object;
   277  		for (var i = 0; i < cnt; i++)
   278  		{
   279  			name = nextString ();
   280  			value = nextValue (_class == "PropertySet");
   281  			obj[name] = value;
   282  			if (error != null) return null;
   283  		}
   284  		nextDelimiter("z");
   285  		if (check) finish();
   286  		if (error != null) return null;
   287  		return obj;
   288  	}
   289  	
   290  	/*
   291  	 *	Parsing.
   292  	 *	Renvoie une liste et ses valeurs une fois le tout parsé.
   293  	 *	
   294  	 *	@param check		true si valide, false sinon.
   295  	 *	@return				La liste parsée ou null en cas d'échec.
   296  	 */
   297  	public function nextArray (check:Boolean):Object
   298  	{
   299  		if (check) 
   300  		{
   301  			if (nextType() != "a") 
   302  			{
   303  				if (error == null) error = "next value not an array";
   304  				return null;
   305  			}
   306  		}
   307  		var arr = new Array();
   308  		var cnt = nextInt();
   309  		for (var i = 0; i < cnt; i++) {
   310  			var elem = nextValue(false);
   311  			arr.push(elem);
   312  		}
   313  		nextDelimiter("z");
   314  		if (check) finish();
   315  		return arr;
   316  	}
   317  	
   318  	//--------------------
   319  	// METHODES PRIVEES
   320  	//--------------------
   321  	/**
   322  	 *	Parsing.
   323  	 *	Positionne l'index d'analyse sur le prochain marqueur.
   324  	 *
   325  	 *	@param char		Un caractère de délimitation.
   326  	 */
   327  	private function nextDelimiter (char:String):Void
   328  	{
   329  		var delim = nextType();
   330  		if (delim == null)
   331  			if (error == null) error = "unexpected or illegal delimiter: " + data.substr(idx, 2);
   332  		if (delim != char)
   333  			if (error == null) error = "unexpected or illegal delimiter: " + data.substr(idx-2, 2);
   334  	}
   335  	
   336  	/**
   337  	 *	Parsing.
   338  	 *	Renvoie l'index du type suivant dans l'analyse dans les données brutes.
   339  	 *	Positionne le curseur sur le motif suivant.
   340  	 *
   341  	 *	@return		Un caractère de délimitation ou null en cas d'échec.
   342  	 */	
   343  	private function nextType ():String
   344  	{
   345  		if (idx + 2 > len)
   346  		{
   347  			if (error == null) error = "unexpected end of string";
   348  			return null;
   349  		}
   350  		if (data.substr(idx, 1) != "#") 
   351  		{
   352  			if (error == null) error = "illegal type definition" + data.substr(idx, 2);
   353  			return null;
   354  		}
   355  		idx += 2;
   356  		return data.substr(idx-1, 1);
   357  	}
   358  	
   359  	/**
   360  	 *	Parsing.
   361  	 *	Renvoie l'index du nombre suivant dans l'analyse dans les données brutes.
   362  	 *	Positionne le curseur sur le motif suivant.
   363  	 *
   364  	 *	@return		Un caractère de délimitation ou null en cas d'échec.
   365  	 */	
   366  	private function nextInt ():Number
   367  	{
   368  		var b = nextBoundary("x");
   369  		var val = parseInt(data.substr(idx, b - idx - 2));
   370  		if (isNaN(val))
   371  		{
   372  			if (error == null) error = "error parsing number: " + data.substr(idx, b - idx -2);
   373  			return null;
   374  		}
   375  		idx = b;
   376  		return val;
   377  	}
   378  	
   379  	/**
   380  	 *	Parsing.
   381  	 *	Renvoie l'index de la chaine suivante dans l'analyse dans les données brutes.
   382  	 *	Positionne le curseur sur le motif suivant.
   383  	 *
   384  	 *	@return		Un caractère de délimitation.
   385  	 */	
   386  	private function nextString ():String
   387  	{
   388  		var b = nextBoundary("y");
   389  		var val = unescape(data.substr(idx, b - idx-2));
   390  		idx = b;
   391  		return val;
   392  	}
   393  	
   394  	/*
   395  	 *	Parsing.
   396  	 *	Renvoie l'index du prochain marqueur dans l'analyse dans les données brutes.
   397  	 *
   398  	 *	@param b	Marqueur : x = boolean, number, date, y = string, z = object.
   399  	 *	@return		L'index du prochain marqueur, null si echec.
   400  	 */	
   401  	private function nextBoundary (b:String):Number
   402  	{
   403  		if (b != "x" && b != "y" && b != "z")
   404  		{
   405  			if (error == null) error = "illegal boundary: " + b;
   406  			return null;
   407  		}
   408  		var l = len;
   409  		var d = data;
   410  		
   411  		var lvl = 1;
   412  		var esc = false;
   413  		var cat = categories;
   414  		var i:Number;
   415  		
   416  		// Parsing
   417  		for (i = idx + 1; i <= l; i++) {
   418  			var c = d.substr(i-1, 1);
   419  			if (esc) {
   420  				if (b == "x") {
   421  					// boolean, numbers, date:
   422  					if (c == "x") return i;
   423  					if (error == null) error = "illegal escape sequence(1): #" + c;
   424  					return null;
   425  				}
   426  				if (b == "y") {
   427  					// Strings:
   428  					if (c == "y") return i;
   429  					if (c != "#" && c != "e") {
   430  						if (error == null) error = "illegal escape sequence(2): #" + c;
   431  						return null;
   432  					}
   433  				}
   434  				if (b == "z") {
   435  					// structured data:
   436  					if (c == "z") {
   437  						lvl--;
   438  					} else {
   439  						var g = cat[c];
   440  						if (g == 1 || g == 2) {
   441  							lvl++;
   442  						} else {
   443  							if (c != "#" && g == undefined) {
   444  								if (error == null) error = "illegal escape sequence(3): #" + c;
   445  								return null;
   446  							}
   447  						}
   448  					}
   449  					
   450  					if (lvl == 0) return i;
   451  				}
   452  				esc = false;
   453  				
   454  			} else {
   455  				if (c == "#") esc = true;
   456  			}
   457  		}
   458  		if (error == null) error = "unexpected end of data";
   459  		return null;
   460  	}
   461  	
   462  	/*
   463  	 *	Parsing.
   464  	 *	Renvoie la valeur d'un élément d'un type.
   465  	 *
   466  	 *	@param val			L'objet?
   467  	 *	@param startIdx		Index numérique.
   468  	 *	@return				Une valeur, null si echec.
   469  	 */	
   470  	private function getSplittedValue (val:Object, startIdx:Number):Object
   471  	{
   472  		 var obj = new Object();
   473  		 obj.value = val;
   474  		 var len = idx - startIdx + 1;
   475  		 obj.backup = data.substr(startIdx-1, len);
   476  		 return obj;
   477  	 }
   478  	
   479  	/**
   480  	 *	Parsing.
   481  	 *
   482  	 *	@param orig		Chaine non échapée.
   483  	 *	@return			La chaine échappée null en cas d'échec.
   484  	 */
   485  	private function unescape (orig:String):String
   486  	{
   487  		if (orig.substr(0, 2) != "#e") return orig;
   488  		var len = orig.length;
   489  		var esc = false;
   490  		var s = "";
   491  		for (var i = 3; i <= len; i++) 
   492  		{
   493  			var c = orig.substr(i-1, 1);
   494  			if (c == "#")
   495  			{
   496  				if (esc) 
   497  				{
   498  					esc = false;
   499  					s += "#";
   500  				} else 
   501  				{
   502  					esc = true;
   503  				}
   504  			} else 
   505  			{
   506  				if (esc)
   507  				{
   508  					if (error == null) error = "illegal escape sequence4: #" + c;
   509  					return null;
   510  				}
   511  				s += c;
   512  			}
   513  		}
   514  		return s;
   515  	}
   516  	
   517  	
   518  	/*
   519  	 *	Parsing.
   520  	 *	Renvoie une Table et ses valeurs une fois le tout parsé.
   521  	 *	
   522  	 *	@param check			true si valide, false sinon.
   523  	 *	@param includeBackup	true si backup de la Table.
   524  	 *	@return					La Table, 
   525  	 *							un objet contenant la Table et sa TableBackup si includeBackup est true
   526  	 *							ou null en cas d'échec.
   527  	 *	@see Table
   528  	 *	@see TableDefinition
   529  	 *	@see TableBackup
   530  	 */
   531  	private function nextTable (check:Boolean, includeBackup:Boolean):Object
   532  	{
   533  		if (check) 
   534  		{
   535  			if (nextType() != "t") 
   536  			{
   537  				if (error == null) error = "next value not a org.omus.Table object";
   538  				return null;
   539  			}
   540  		}
   541  		var rowCnt = nextInt();
   542  		var nextRowID = nextInt();
   543  		var cols = new Array();
   544  		
   545  		// build TableDefinition object
   546  		var fieldCnt = nextInt();
   547  		var def = new TableDefinition();
   548  		var type:String;
   549  		var name:String;
   550  		for (var i = 0; i < fieldCnt; i++) 
   551  		{
   552  			// TODO : Accès Singleton
   553  			var clazz = _Class.getInstance();
   554  			type = clazz.charToCellType(nextType());
   555  			name = nextString();
   556  			def.addColumn(name,type);
   557  			cols.push(name);
   558  		}
   559  		if (error != null) return null;
   560  		def.lock();
   561  		
   562  		// build table object
   563  		var table = new Table(def);
   564  		table.nextRowID = nextRowID;
   565  		var backup:TableBackup;
   566  		if (includeBackup) backup = new TableBackup();
   567  		for (var j = 0; j < rowCnt; j++)
   568  		{
   569  			// parse row
   570  			nextDelimiter("r");
   571  			var rowID = nextInt();
   572  			var row = new Object();
   573  			row.__rowID = rowID;
   574  			var startIdx = idx + 1;
   575  			for (var k = 0; k < fieldCnt; k++) 
   576  			{
   577  				// parse cell
   578  				var cell = this.nextValue(false);
   579  				row[cols[k]] = cell;
   580  			}
   581  			// Enumeration
   582  			_global.ASSetPropFlags(row, ["__rowID"], 7);
   583  			table.addExistingRow(rowID, row);
   584  			
   585  			if (includeBackup) 
   586  			{
   587  				var len = idx - startIdx + 1;
   588  				var arr = "#a" + fieldCnt + "#x" + data.substr(startIdx-1, len) + "#z";
   589  				backup.addRow(rowID, arr);
   590  			}
   591  			if (error != null) return null;
   592  		}
   593  		
   594  		nextDelimiter("z");
   595  		if (check) finish();
   596  		if (error != null) return null;
   597  		
   598  		if (includeBackup)
   599  		{
   600  			table.enableRecording(true);
   601  			return { value: table, backup: backup };
   602  		} else 
   603  		{
   604  			return table;
   605  		}
   606  	}
   607  	
   608  	/**
   609  	 *	Parsing.
   610  	 *	Renvoie une UpdateSequence une fois le tout parsé.
   611  	 *	TODO : ?
   612  	 *
   613  	 *	@param check		true si valide, false sinon.
   614  	 *	@return				Un object contenant une Update et sa TableBackup	
   615  	 *	@see UpdateSequence
   616  	 *	@see TableBackup
   617  	 */
   618  	private function nextUpdateSequence (check:Boolean):Object
   619  	{
   620  		if (check) 
   621  		{
   622  			if (nextType() != "u")
   623  			{
   624  				if (error == null) error = "next value not a org.omus.UpdateSequence object";
   625  				return null;
   626  			}
   627  		}
   628  		
   629  		var cnt = nextInt();
   630  		var rowID:Number;
   631  		// TODO : suppression var row:;
   632  		var us = new UpdateSequence();
   633  		var aor = us.addOrRemove;
   634  		var upd = us.updates;
   635  		var backup = new TableBackup();
   636  		backup.clear = false;
   637  		
   638  		for (var i = 0; i < cnt; i++)
   639  		{
   640  			nextDelimiter("p");
   641  			var type = nextInt();
   642  			var up = null;
   643  			if (type == org.omus.data.UpdateSequence.CLEAR_TABLE) 
   644  			{
   645  				aor.push(new ClearTable());
   646  				backup.clear = true;
   647  			} else if (type == org.omus.data.UpdateSequence.REMOVE_ROW) 
   648  			{
   649  				rowID = nextInt();
   650  				aor.push(new RemoveRow(rowID));
   651  				backup.addRemoveRow(rowID);
   652  			} else if (type == org.omus.data.UpdateSequence.ADD_ROW || type == org.omus.data.UpdateSequence.UPDATE_ROW)
   653  			{
   654  				rowID = nextInt();
   655  				var startIdx = idx + 1;
   656  				nextDelimiter("a");
   657  				var arr = nextArray(false);
   658  				if (type == org.omus.data.UpdateSequence.ADD_ROW) aor.push(new AddRow(rowID,arr));
   659  				if (type == org.omus.data.UpdateSequence.UPDATE_ROW) upd["r" + rowID] = new UpdateRow(rowID,arr);
   660  				var len = this.idx - startIdx + 1;
   661  				backup.addRow(rowID, data.substr(startIdx-1, len));
   662  			} else if (error == null)
   663  			{
   664  				error = "error unmarshalling: illegal identifier for UpdatePart: " + type;
   665  			}
   666  			nextDelimiter("z");
   667  			if (error != null) return null;
   668  		}
   669  		nextDelimiter("z");
   670  		if (check) finish();
   671  		if (error != null) return null;
   672  		return { value: us, backup: backup };
   673  	}
   674  	
   675  	 /**
   676  	  *	Termine le parsing.
   677  	  */
   678  	private function finish ():Void
   679  	{
   680  		 if (idx != len) error = "marshalled data not correctly terminated";
   681  	 }
   682  	
   683  	//--------------------
   684  	// METHODES STATIQUES
   685  	//--------------------
   686  	/**
   687  	 *	Utilisé dans un contexte littéral
   688  	 *	@return	Une chaine définissant l'objet
   689  	 */
   690  	public static function toLog():String
   691  	{
   692  		return "[Object DataParser]";
   693  	}
   694  	
   695  	/**
   696  	 *	Transforme les données d'une Table en un tableau associatif.
   697  	 *	Pratique pour la lecture des données de la base.
   698  	 *
   699  	 *	@param arr		La liste indexée.
   700  	 *	@param colNames	Liste de tous les noms de colonne.
   701  	 *	@return			Un tableau associatif des valeurs.
   702  	 *	@see TableDefinition
   703  	 */
   704  	public static function arrayToRow (arr:Object, colNames:Array):Object
   705  	{
   706  		if (arr.length != colNames.length)
   707  		{
   708  			// Logs internes
   709  			_global.oregano.iLog.error("clj-014","column names = " + colNames.toString());
   710  			return null;
   711  		}
   712  		
   713  		var row = new Object();
   714  		var l = colNames.length;
   715  		for (var i = 0; i < l; i++) 
   716  		{
   717  			row[colNames[i]] = arr[i];
   718  		}
   719  		return row;
   720  	}
   721  }
   722