Initial revision
This commit is contained in:
426
intern/python/modules/vrml/parser.py
Normal file
426
intern/python/modules/vrml/parser.py
Normal file
@@ -0,0 +1,426 @@
|
||||
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'
|
||||
<ts> := ( [ \011-\015,]+ / ('#',-'\012'*,'\n')+ )*
|
||||
'''
|
||||
|
||||
|
||||
PARSERTABLE = generator.buildParser( PARSERDECLARATION )
|
||||
HEADERPARSER = PARSERTABLE.parserbyname( "header" )
|
||||
ROOTITEMPARSER = PARSERTABLE.parserbyname( "rootItem" )
|
||||
|
Reference in New Issue
Block a user