Search This Blog

Wednesday, 14 July 2010

String List Parser

One of our clients cms is saving properties still as name/value pairs (propert=value) and doesn't really accept XML as an Input.
That makes saving complex property data difficult and XML is in this case for a single property file to big.


I thought a string property definition in AS3 style could be handy. For example a list/array:
[12123,1.123,'entry1']
or an object
{love:'hate',ying:'yang'}

A parser for that is easy for simple objects. Much more difficult is when the data is encapsulated like:
[12123,1.123,{love:[ {a1:'string,with,comma and also \" double quote \" ',a2:'SimpleString'} ,3]},{b2:345,b3:1.04}]

A big problem for me was the delimiter "'" that i wanted to keep the same for a list types (object/Array).

The first version of the parser is done. Validation needs to be included and maybe speed can be optimized by ordering things differently .?.

Code below.
Looks like a lot but creation of object of a complex property doesn't take longer than 3 ms on my machine. I wonder how I can make it shorter.




package com.fourddigital.parser
{
   
   
    public class StringListParser
    {
        public function StringListParser()
        {}
           
        public function parse(value:*):*{
               
            return getNext( cleanWhitespace( value.toString() ) )[0];
        }       
   
       
        protected function getNext(value:String,startIndex:int=0,objectMode:Boolean=false):Array{
            if(startIndex >= value.length -1)
                return null
           
            var i :int = startIndex;
           
            if(objectMode ){ // starting point is assumed to be a property name because we are parsing an object
                exp = /[^:]+/g;
                exp.lastIndex = i;
                name = exp.exec( value )[0];
                if(!name)
                    throw new Error('Object malformed at char '+ i);
                i = exp.lastIndex+1;
            }
           
            var result:Array = new Array(3);//to be filled wit o = object, i = lastIndex, name=property name if object Mode
           
           
            var o : *;
            var name:String;
           
            var isString:String;
            var isObject:Boolean;
            var isArray:Boolean;
            var overflow:int=0;// just in case :)
           
            var exp:RegExp = /[\{\[\}\],\'"]/g; //search for command character
            exp.lastIndex = i;
            var command:* = exp.exec( value );
            while(command && command.index == i && overflow < 1000)   
            {
                switch(command[0].toString()){
                    case '"':
                    case "'":
                        isString = command[0].toString();
                        command = null;
                        break;
                    case "{":
                        isObject=true;
                        command = null;
                    break;
                    case "[":
                        isArray=true;
                        command = null;
                    break;
                    case '}': //closures
                    case ',':
                    case '}':
                        command = exp.exec( value );
                    break;
                }
                i++;
            }

           
            if(!isObject && !isArray){ //is primitive
           
                exp = isString ? new RegExp("(?                exp.lastIndex = i;
                exp.exec( value );               
                o = getPrimitive( value.substring(i,isString ? exp.lastIndex -1 : exp.lastIndex));
                i = exp.lastIndex;
            }
           
            else{
                var comp:* = isObject ? {} : new Array();
                var child:Array = getNext(value,i,isObject );
                overflow=0;
                while(child && overflow<1000){
                    overflow++;
                    i= child[1];
                    if(isObject)
                        comp[child[2]] = child[0];
                    else
                        (comp as Array).push(child[0]);
                    if(value.charAt(i).search(/[\}\]]/) == 0){
                        i++;
                        break;
                    }
                    child = getNext(value,i+1,isObject);
                }
                o=comp;
            }
           
            result[0] = o;
            result[1] = i;
            result[2] = name;
            return result ;
        }
       
   
        protected function getPrimitive(value:String):*{
            if(value.length > 1){ //number check
                var exp:RegExp = /^\d*\.\d+/;
               
                if(exp.test(value))
                    return Number(value);
            }
           
            if( int(value) ) return int(value);
            else if( value == 'true' || value == 'false') return value == 'true'
            return value;
        }

       
        protected function cleanWhitespace( value:String ):*{
            return value.replace(/\s+(?=\{|\||\[)|(?<=\}|\||\])\s+/gsm,'');
        }
       
    }
}

No comments:

Post a Comment