"""
This module can parse a Delphi Form (dfm) file.
The main is used in experimenting (to find which files fail
to parse, and where), but isn't useful for anything else.
"""
__version__ = "1.0"
__author__ = "Daniel 'Dang' Griffith <pythondev - dang at lazytwinacres . net>"
from pyparsing import Literal,CaselessLiteral,Word,delimitedList\
, Optional, Combine, Group, alphas, nums, alphanums, Forward \
, oneOf, sglQuotedString, OneOrMore, ZeroOrMore, CharsNotIn
# This converts DFM character constants into Python string (unicode) values.
def to_chr(x):
"""chr(x) if 0 < x < 128 ; unicode(x) if x > 127."""
return 0 < x < 128 and chr(x) or eval("u'\\u%d'" % x )
#################
# BEGIN GRAMMAR
#################
COLON = Literal(":").suppress()
CONCAT = Literal("+").suppress()
EQUALS = Literal("=").suppress()
LANGLE = Literal("<").suppress()
LBRACE = Literal("[").suppress()
LPAREN = Literal("(").suppress()
PERIOD = Literal(".").suppress()
RANGLE = Literal(">").suppress()
RBRACE = Literal("]").suppress()
RPAREN = Literal(")").suppress()
CATEGORIES = CaselessLiteral("categories").suppress()
END = CaselessLiteral("end").suppress()
FONT = CaselessLiteral("font").suppress()
HINT = CaselessLiteral("hint").suppress()
ITEM = CaselessLiteral("item").suppress()
OBJECT = CaselessLiteral("object").suppress()
attribute_value_pair = Forward() # this is recursed in item_list_entry
simple_identifier = Word(alphas, alphanums + "_")
identifier = Combine( simple_identifier + ZeroOrMore( Literal(".") + simple_identifier ))
object_name = identifier
object_type = identifier
# Integer and floating point values are converted to Python longs and floats, respectively.
int_value = Combine(Optional("-") + Word(nums)).setParseAction(lambda s,l,t: [ long(t[0]) ] )
float_value = Combine(Optional("-") + Optional(Word(nums)) + "." + Word(nums)).setParseAction(lambda s,l,t: [ float(t[0]) ] )
number_value = float_value | int_value
# Base16 constants are left in string form, including the surrounding braces.
base16_value = Combine(Literal("{") + OneOrMore(Word("0123456789ABCDEFabcdef")) + Literal("}"), adjacent=False)
# This is the first part of a hack to convert the various delphi partial sglQuotedStrings
# into a single sglQuotedString equivalent. The gist of it is to combine
# all sglQuotedStrings (with their surrounding quotes removed (suppressed))
# with sequences of #xyz character constants, with "strings" concatenated
# with a '+' sign.
unquoted_sglQuotedString = Combine( Literal("'").suppress() + ZeroOrMore( CharsNotIn("'\n\r") ) + Literal("'").suppress() )
# The parse action on this production converts repetitions of constants into a single string.
pound_char = Combine(
OneOrMore((Literal("#").suppress()+Word(nums)
).setParseAction( lambda s, l, t: to_chr(int(t[0]) ))))
# This is the second part of the hack. It combines the various "unquoted"
# partial strings into a single one. Then, the parse action puts
# a single matched pair of quotes around it.
delphi_string = Combine(
OneOrMore(CONCAT | pound_char | unquoted_sglQuotedString)
, adjacent=False
).setParseAction(lambda s, l, t: "'%s'" % t[0])
string_value = delphi_string | base16_value
list_value = LBRACE + Optional(Group(delimitedList(identifier | number_value | string_value))) + RBRACE
paren_list_value = LPAREN + ZeroOrMore(identifier | number_value | string_value) + RPAREN
item_list_entry = ITEM + ZeroOrMore(attribute_value_pair) + END
item_list = LANGLE + ZeroOrMore(item_list_entry) + RANGLE
generic_value = identifier
value = item_list | number_value | string_value | list_value | paren_list_value | generic_value
category_attribute = CATEGORIES + PERIOD + oneOf("strings itemsvisibles visibles", True)
event_attribute = oneOf("onactivate onclosequery onclose oncreate ondeactivate onhide onshow", True)
font_attribute = FONT + PERIOD + oneOf("charset color height name style", True)
hint_attribute = HINT
layout_attribute = oneOf("left top width height", True)
generic_attribute = identifier
attribute = (category_attribute | event_attribute | font_attribute | hint_attribute | layout_attribute | generic_attribute)
category_attribute_value_pair = category_attribute + EQUALS + paren_list_value
event_attribute_value_pair = event_attribute + EQUALS + value
font_attribute_value_pair = font_attribute + EQUALS + value
hint_attribute_value_pair = hint_attribute + EQUALS + value
layout_attribute_value_pair = layout_attribute + EQUALS + value
generic_attribute_value_pair = attribute + EQUALS + value
attribute_value_pair << Group(
category_attribute_value_pair
| event_attribute_value_pair
| font_attribute_value_pair
| hint_attribute_value_pair
| layout_attribute_value_pair
| generic_attribute_value_pair
)
object_declaration = Group((OBJECT + object_name + COLON + object_type))
object_attributes = Group(ZeroOrMore(attribute_value_pair))
nested_object = Forward()
object_definition = object_declaration + object_attributes + ZeroOrMore(nested_object) + END
nested_object << Group(object_definition)
#################
# END GRAMMAR
#################
def printer(s, loc, tok):
print tok,
return tok
def get_filename_list(tf):
import sys, glob
if tf == None:
tf = sys.argv[1:]
elif type(tf) == str:
tf = [tf]
testfiles = []
for arg in tf:
for i in glob.glob(arg):
testfiles.append(i)
return testfiles
def main(testfiles=None, action=printer):
"""testfiles can be None, in which case the command line arguments are used as filenames.
testfiles can be a string, in which case that file is parsed.
testfiles can be a list.
In all cases, the filenames will be globbed.
If more than one file is parsed successfully, a dictionary of ParseResults is returned.
Otherwise, a simple ParseResults is returned.
"""
testfiles = get_filename_list(testfiles)
if action:
for i in (simple_identifier, value, item_list):
i.setParseAction(action)
success = 0
failures = []
retval = {}
for f in testfiles:
try:
retval[f] = object_definition.parseFile(f)
success += 1
except:
failures.append(f)
if failures:
print '\nfailed while processing %s' % ', '.join(failures)
print '\nsucceeded on %d of %d files' %(success, len(testfiles))
if len(retval) == 1 and len(testfiles) == 1:
# if only one file is parsed, return the parseResults directly
return retval[retval.keys()[0]]
# else, return a dictionary of parseResults
return retval
if __name__ == "__main__":
main()
|