from TextTools import TextTools from simpleparse import generator import scenegraph as proto import strop as string IMPORT_PARSE_TIME = 0.4 PROGRESS_DEPTH = 5 class UnfinishedError(Exception): pass class Parser: def __init__( self, data ): self.data = data self.position = 0 self.result = proto.sceneGraph() self.finalised = None self.sceneGraphStack = [self.result] self.prototypeStack = [] self.nodeStack = [] self.fieldTypeStack = [] self.readHeader() self.depth = 0 self.progresscount = 0 def _lines( self, index=None ): if index is None: index = self.position return TextTools.countlines (self.data[:index]) def parse( self, progressCallback=None ): datalength = float( len( self.data )) while self.readNext(): if progressCallback: if not progressCallback(IMPORT_PARSE_TIME * self.position/datalength ): raise UnfinishedError( "Did not complete parsing, cancelled by user. Stopped at line %s" %(self._lines()) ) if self.position < len( self.data ): raise UnfinishedError( '''Unable to complete parsing of file, stopped at line %s:\n%s...'''%(self._lines(), self.data[self.position:self.position+120]) ) return self.result def readHeader( self ): '''Read the file header''' success, tags, next = TextTools.tag( self.data, HEADERPARSER, self.position ) if success: self.datalength = len( self.data ) #print "header ok" return success else: try: self.decompress() success, tags, next = TextTools.tag( self.data, HEADERPARSER, self.position ) self.datalength = len( self.data ) return success except: raise ValueError( "Could not find VRML97 header in file!" ) def readNext( self): '''Read the next root-level construct''' success, tags, next = TextTools.tag( self.data, ROOTITEMPARSER, self.position ) ## print 'readnext', success if self.position >= self.datalength: print 'reached file end' return None if success: # print ' successful parse' self.position = next map (self.rootItem_Item, tags ) return success else: return None def rootItem (self, (type, start, stop, (item,))): ''' Process a single root item ''' self.rootItem_Item( item ) def rootItem_Item( self, item ): result = self._dispatch(item) if result is not None: ## print "non-null result" ## print id( self.sceneGraphStack[-1] ), id(self.result ) self.sceneGraphStack[-1].children.append( result ) def _getString (self, (tag, start, stop, sublist)): ''' Return the raw string for a given interval in the data ''' return self.data [start: stop] def _dispatch (self, (tag, left, right, sublist)): ''' Dispatch to the appropriate processing function based on tag value ''' ## print "dispatch", tag self.depth += 1 if self.depth < PROGRESS_DEPTH: self.progresscount += 1 try: meth = getattr (self, tag) except AttributeError: raise AttributeError("Unknown parse tag '%s' found! Check the parser definition!" % (tag)) ret = meth( (tag, left, right, sublist) ) self.depth -= 1 return ret def Proto(self, (tag, start, stop, sublist)): ''' Create a new prototype in the current sceneGraph ''' # first entry is always ID ID = self._getString ( sublist [0]) print "PROTO",ID newNode = proto.Prototype (ID) ## print "\t",newNode setattr ( self.sceneGraphStack [-1].protoTypes, ID, newNode) self.prototypeStack.append( newNode ) # process the rest of the entries with the given stack map ( self._dispatch, sublist [1:] ) self.prototypeStack.pop( ) def fieldDecl(self,(tag, left, right, (exposure, datatype, name, field))): ''' Create a new field declaration for the current prototype''' # get the definition in recognizable format exposure = self._getString (exposure) == "exposedField" datatype = self._getString (datatype) name = self._getString (name) # get the vrml value for the field self.fieldTypeStack.append( datatype ) field = self._dispatch (field) self.fieldTypeStack.pop( ) self.prototypeStack[-1].addField ((name, datatype, exposure), field) def eventDecl(self,(tag, left, right, (direction, datatype, name))): # get the definition in recognizable format direction = self._getString (direction) == "eventOut" datatype = self._getString (datatype) name = self._getString (name) # get the vrml value for the field self.prototypeStack[-1].addEvent((name, datatype, direction)) def decompress( self ): pass def ExternProto( self, (tag, start, stop, sublist)): ''' Create a new external prototype from a tag list''' # first entry is always ID ID = self._getString ( sublist [0]) newNode = proto.Prototype (ID) setattr ( self.sceneGraphStack [-1].protoTypes, ID, newNode) self.prototypeStack.append( newNode ) # process the rest of the entries with the given stack map ( self._dispatch, sublist [1:] ) self.prototypeStack.pop( ) def ExtProtoURL( self, (tag, start, stop, sublist)): ''' add the url to the external prototype ''' ## print sublist values = self.MFString( sublist ) self.prototypeStack[-1].url = values return values def extFieldDecl(self, (tag, start, stop, (exposure, datatype, name))): ''' An external field declaration, no default value ''' # get the definition in recognizable format exposure = self._getString (exposure) == "exposedField" datatype = self._getString (datatype) name = self._getString (name) # get the vrml value for the field self.prototypeStack[-1].addField ((name, datatype, exposure)) def ROUTE(self, (tag, start, stop, names )): ''' Create a new route object, add the current sceneGraph ''' names = map(self._getString, names) self.sceneGraphStack [-1].addRoute( names ) def Node (self, (tag, start, stop, sublist)): ''' Create new node, returning the value to the caller''' ## print 'node' if sublist[0][0] == 'name': name = self._getString ( sublist [0]) ID = self._getString ( sublist [1]) rest = sublist [2:] else: name = "" ID = self._getString ( sublist [0]) rest = sublist [1:] try: prototype = getattr ( self.sceneGraphStack [-1].protoTypes, ID) except AttributeError: #raise NameError ('''Prototype %s used without declaration! %s:%s'''%(ID, start, stop) ) print ('''### Prototype %s used without declaration! %s:%s'''%(ID, start, stop) ) return None newNode = prototype(name) if name: self.sceneGraphStack [-1].regDefName( name, newNode ) self.nodeStack.append (newNode) map (self._dispatch, rest) self.nodeStack.pop () ## print 'node finished' return newNode def Attr(self, (tag, start, stop, (name, value))): ''' An attribute of a node or script ''' name = self._getString ( name ) self.fieldTypeStack.append( self.nodeStack[-1].PROTO.getField( name ).type ) value = self._dispatch( value ) self.fieldTypeStack.pop() if hasattr( self.nodeStack[-1], "__setattr__" ): self.nodeStack[-1].__setattr__( name, value, raw=1 ) else: # use slower coercing versions... setattr( self.nodeStack[-1], name, value ) def Script( self, (tag, start, stop, sublist)): ''' A script node (can be a root node)''' # what's the DEF name... if sublist and sublist[0][0] == 'name': name = self._getString ( sublist [0]) rest = sublist [1:] else: name = "" rest = sublist # build the script node... newNode = proto.Script( name ) # register with sceneGraph if name: self.sceneGraphStack [-1].regDefName( name, newNode ) self.nodeStack.append (newNode) map( self._dispatch, rest ) self.nodeStack.pop () return newNode def ScriptEventDecl( self,(tag, left, right, sublist)): # get the definition in recognizable format direction, datatype, name = sublist[:3] # must have at least these... direction = self._getString (direction) == "eventOut" datatype = self._getString (datatype) name = self._getString (name) # get the vrml value for the field self.nodeStack[-1].PROTO.addEvent((name, datatype, direction)) if sublist[3:]: # will this work??? setattr( self.nodeStack[-1], name, self._dispatch( sublist[3] ) ) def ScriptFieldDecl(self,(tag, left, right, (exposure, datatype, name, field))): ''' Create a new field declaration for the current prototype''' # get the definition in recognizable format exposure = self._getString (exposure) == "exposedField" datatype = self._getString (datatype) name = self._getString (name) # get the vrml value for the field self.fieldTypeStack.append( datatype ) field = self._dispatch (field) self.fieldTypeStack.pop( ) self.nodeStack[-1].PROTO.addField ((name, datatype, exposure)) setattr( self.nodeStack[-1], name, field ) def SFNull(self, tup): ''' Create a reference to the SFNull node ''' ## print 'hi' return proto.NULL def USE( self, (tag, start, stop, (nametuple,) )): ''' Create a reference to an already defined node''' name = self._getString (nametuple) if self.depth < PROGRESS_DEPTH: self.progresscount += 1 try: node = self.sceneGraphStack [-1].defNames [name] return node except KeyError: raise NameError ('''USE without DEF for node %s %s:%s'''%(name, start, stop)) def IS(self, (tag, start, stop, (nametuple,))): ''' Create a field reference ''' name = self._getString (nametuple) if not self.prototypeStack [-1].getField (name): raise Exception (''' Attempt to create IS mapping of non-existent field %s %s:%s'''%(name, start, stop)) return proto.IS(name) def Field( self, (tag, start, stop, sublist)): ''' A field value (of any type) ''' if sublist and sublist[0][0] in ('USE','Script','Node','SFNull'): if self.fieldTypeStack[-1] == 'SFNode': return self._dispatch( sublist[0] ) else: return map( self._dispatch, sublist ) elif self.fieldTypeStack[-1] == 'MFNode': return [] else: # is a simple data type... function = getattr( self, self.fieldTypeStack[-1] ) try: return function( sublist ) except ValueError: traceback.print_exc() print sublist raise def SFBool( self, (tup,) ): '''Boolean, in Python tradition is either 0 or 1''' return self._getString(tup) == 'TRUE' def SFFloat( self, (x,) ): return string.atof( self._getString(x) ) SFTime = SFFloat def SFInt32( self, (x,) ): return string.atoi( self._getString(x), 0 ) # allow for non-decimal numbers def SFVec3f( self, (x,y,z) ): return map( string.atof, map(self._getString, (x,y,z)) ) def SFVec2f( self, (x,y) ): return map( string.atof, map(self._getString, (x,y)) ) def SFColor( self, (r,g,b) ): return map( string.atof, map(self._getString, (r,g,b)) ) def SFRotation( self, (x,y,z,a) ): return map( string.atof, map(self._getString, (x,y,z,a)) ) def MFInt32( self, tuples ): result = [] # localisation atoi = string.atoi append = result.append data = self.data for tag, start, stop, children in tuples: append( atoi( data[start:stop], 0) ) return result SFImage = MFInt32 def MFFloat( self, tuples ): result = [] # localisation atof = string.atof append = result.append data = self.data for tag, start, stop, children in tuples: append( atof( data[start:stop]) ) return result MFTime = MFFloat def MFVec3f( self, tuples, length=3, typename='MFVec3f'): result = [] # localisation atof = string.atof data = self.data while tuples: newobj = [] for tag, start, stop, children in tuples[:length]: newobj.append( atof(data[start:stop] )) if len(newobj) != length: raise ValueError( '''Incorrect number of elements in %s field at line %s'''%(typename, self._lines(stop)) ) result.append( newobj ) del tuples[:length] return result def MFVec2f( self, tuples): return self.MFVec3f( tuples, length=2, typename='MFVec2f') def MFRotation( self, tuples ): return self.MFVec3f( tuples, length=4, typename='MFRotation') def MFColor( self, tuples ): return self.MFVec3f( tuples, length=3, typename='MFColor') def MFString( self, tuples ): bigresult = [] for (tag, start, stop, sublist) in tuples: result = [] for element in sublist: if element[0] == 'CHARNODBLQUOTE': result.append( self.data[element[1]:element[2]] ) elif element[0] == 'ESCAPEDCHAR': result.append( self.data[element[1]+1:element[2]] ) elif element[0] == 'SIMPLEBACKSLASH': result.append( '\\' ) bigresult.append( string.join( result, "") ) return bigresult ## result = [] ## for tuple in tuples: ## result.append( self.SFString( tuple) ) ## return result def SFString( self, tuples ): '''Return the (escaped) string as a simple Python string''' if tuples: (tag, start, stop, sublist) = tuples[0] if len( tuples ) > 1: print '''Warning: SFString field has more than one string value''', self.data[tuples[0][1]:tuples[-1][2]] result = [] for element in sublist: if element[0] == 'CHARNODBLQUOTE': result.append( self.data[element[1]:element[2]] ) elif element[0] == 'ESCAPEDCHAR': result.append( self.data[element[1]+1:element[2]] ) elif element[0] == 'SIMPLEBACKSLASH': result.append( '\\' ) return string.join( result, "") else: raise ValueError( "NULL SFString parsed???!!!" ) def vrmlScene( self, (tag, start, stop, sublist)): '''A (prototype's) vrml sceneGraph''' newNode = proto.sceneGraph (root=self.sceneGraphStack [-1]) self.sceneGraphStack.append (newNode) #print 'setting proto sceneGraph', `newNode` self.prototypeStack[-1].sceneGraph = newNode results = filter (None, map (self._dispatch, sublist)) if results: # items which are not auto-magically inserted into their parent for result in results: newNode.children.append( result) self.sceneGraphStack.pop() PARSERDECLARATION = r'''header := -[\n]* rootItem := ts,(Proto/ExternProto/ROUTE/('USE',ts,USE,ts)/Script/Node),ts vrmlScene := rootItem* Proto := 'PROTO',ts,nodegi,ts,'[',ts,(fieldDecl/eventDecl)*,']', ts, '{', ts, vrmlScene,ts, '}', ts fieldDecl := fieldExposure,ts,dataType,ts,name,ts,Field,ts fieldExposure := 'field'/'exposedField' dataType := 'SFBool'/'SFString'/'SFFloat'/'SFTime'/'SFVec3f'/'SFVec2f'/'SFRotation'/'SFInt32'/'SFImage'/'SFColor'/'SFNode'/'MFBool'/'MFString'/'MFFloat'/'MFTime'/'MFVec3f'/'MFVec2f'/'MFRotation'/'MFInt32'/'MFColor'/'MFNode' eventDecl := eventDirection, ts, dataType, ts, name, ts eventDirection := 'eventIn'/'eventOut' ExternProto := 'EXTERNPROTO',ts,nodegi,ts,'[',ts,(extFieldDecl/eventDecl)*,']', ts, ExtProtoURL extFieldDecl := fieldExposure,ts,dataType,ts,name,ts ExtProtoURL := '['?,(ts,SFString)*, ts, ']'?, ts # just an MFString by another name :) ROUTE := 'ROUTE',ts, name,'.',name, ts, 'TO', ts, name,'.',name, ts Node := ('DEF',ts,name,ts)?,nodegi,ts,'{',ts,(Proto/ExternProto/ROUTE/Attr)*,ts,'}', ts Script := ('DEF',ts,name,ts)?,'Script',ts,'{',ts,(ScriptFieldDecl/ScriptEventDecl/Proto/ExternProto/ROUTE/Attr)*,ts,'}', ts ScriptEventDecl := eventDirection, ts, dataType, ts, name, ts, ('IS', ts, IS,ts)? ScriptFieldDecl := fieldExposure,ts,dataType,ts,name,ts,(('IS', ts,IS,ts)/Field),ts SFNull := 'NULL', ts # should really have an optimised way of declaring a different reporting name for the same production... USE := name IS := name nodegi := name Attr := name, ts, (('IS', ts,IS,ts)/Field), ts Field := ( '[',ts,((SFNumber/SFBool/SFString/('USE',ts,USE,ts)/Script/Node),ts)*, ']', ts )/((SFNumber/SFBool/SFNull/SFString/('USE',ts,USE,ts)/Script/Node),ts)+ name := -[][0-9{}\000-\020"'#,.\\ ], -[][{}\000-\020"'#,.\\ ]* SFNumber := [-+]*, ( ('0',[xX],[0-9]+) / ([0-9.]+,([eE],[-+0-9.]+)?)) SFBool := 'TRUE'/'FALSE' SFString := '"',(CHARNODBLQUOTE/ESCAPEDCHAR/SIMPLEBACKSLASH)*,'"' CHARNODBLQUOTE := -[\134"]+ SIMPLEBACKSLASH := '\134' ESCAPEDCHAR := '\\"'/'\134\134' := ( [ \011-\015,]+ / ('#',-'\012'*,'\n')+ )* ''' PARSERTABLE = generator.buildParser( PARSERDECLARATION ) HEADERPARSER = PARSERTABLE.parserbyname( "header" ) ROOTITEMPARSER = PARSERTABLE.parserbyname( "rootItem" )