dfmparse.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """
  2. This module can parse a Delphi Form (dfm) file.
  3. The main is used in experimenting (to find which files fail
  4. to parse, and where), but isn't useful for anything else.
  5. """
  6. __version__ = "1.0"
  7. __author__ = "Daniel 'Dang' Griffith <pythondev - dang at lazytwinacres . net>"
  8. from pyparsing import Literal, CaselessLiteral, Word, delimitedList \
  9. , Optional, Combine, Group, alphas, nums, alphanums, Forward \
  10. , oneOf, OneOrMore, ZeroOrMore, CharsNotIn
  11. # This converts DFM character constants into Python string (unicode) values.
  12. def to_chr(x):
  13. """chr(x) if 0 < x < 128 ; unicode(x) if x > 127."""
  14. return 0 < x < 128 and chr(x) or eval("u'\\u%d'" % x )
  15. #################
  16. # BEGIN GRAMMAR
  17. #################
  18. COLON = Literal(":").suppress()
  19. CONCAT = Literal("+").suppress()
  20. EQUALS = Literal("=").suppress()
  21. LANGLE = Literal("<").suppress()
  22. LBRACE = Literal("[").suppress()
  23. LPAREN = Literal("(").suppress()
  24. PERIOD = Literal(".").suppress()
  25. RANGLE = Literal(">").suppress()
  26. RBRACE = Literal("]").suppress()
  27. RPAREN = Literal(")").suppress()
  28. CATEGORIES = CaselessLiteral("categories").suppress()
  29. END = CaselessLiteral("end").suppress()
  30. FONT = CaselessLiteral("font").suppress()
  31. HINT = CaselessLiteral("hint").suppress()
  32. ITEM = CaselessLiteral("item").suppress()
  33. OBJECT = CaselessLiteral("object").suppress()
  34. attribute_value_pair = Forward() # this is recursed in item_list_entry
  35. simple_identifier = Word(alphas, alphanums + "_")
  36. identifier = Combine( simple_identifier + ZeroOrMore( Literal(".") + simple_identifier ))
  37. object_name = identifier
  38. object_type = identifier
  39. # Integer and floating point values are converted to Python longs and floats, respectively.
  40. int_value = Combine(Optional("-") + Word(nums)).setParseAction(lambda s,l,t: [ int(t[0]) ] )
  41. float_value = Combine(Optional("-") + Optional(Word(nums)) + "." + Word(nums)).setParseAction(lambda s,l,t: [ float(t[0]) ] )
  42. number_value = float_value | int_value
  43. # Base16 constants are left in string form, including the surrounding braces.
  44. base16_value = Combine(Literal("{") + OneOrMore(Word("0123456789ABCDEFabcdef")) + Literal("}"), adjacent=False)
  45. # This is the first part of a hack to convert the various delphi partial sglQuotedStrings
  46. # into a single sglQuotedString equivalent. The gist of it is to combine
  47. # all sglQuotedStrings (with their surrounding quotes removed (suppressed))
  48. # with sequences of #xyz character constants, with "strings" concatenated
  49. # with a '+' sign.
  50. unquoted_sglQuotedString = Combine( Literal("'").suppress() + ZeroOrMore( CharsNotIn("'\n\r") ) + Literal("'").suppress() )
  51. # The parse action on this production converts repetitions of constants into a single string.
  52. pound_char = Combine(
  53. OneOrMore((Literal("#").suppress()+Word(nums)
  54. ).setParseAction( lambda s, l, t: to_chr(int(t[0]) ))))
  55. # This is the second part of the hack. It combines the various "unquoted"
  56. # partial strings into a single one. Then, the parse action puts
  57. # a single matched pair of quotes around it.
  58. delphi_string = Combine(
  59. OneOrMore(CONCAT | pound_char | unquoted_sglQuotedString)
  60. , adjacent=False
  61. ).setParseAction(lambda s, l, t: "'%s'" % t[0])
  62. string_value = delphi_string | base16_value
  63. list_value = LBRACE + Optional(Group(delimitedList(identifier | number_value | string_value))) + RBRACE
  64. paren_list_value = LPAREN + ZeroOrMore(identifier | number_value | string_value) + RPAREN
  65. item_list_entry = ITEM + ZeroOrMore(attribute_value_pair) + END
  66. item_list = LANGLE + ZeroOrMore(item_list_entry) + RANGLE
  67. generic_value = identifier
  68. value = item_list | number_value | string_value | list_value | paren_list_value | generic_value
  69. category_attribute = CATEGORIES + PERIOD + oneOf("strings itemsvisibles visibles", True)
  70. event_attribute = oneOf("onactivate onclosequery onclose oncreate ondeactivate onhide onshow", True)
  71. font_attribute = FONT + PERIOD + oneOf("charset color height name style", True)
  72. hint_attribute = HINT
  73. layout_attribute = oneOf("left top width height", True)
  74. generic_attribute = identifier
  75. attribute = (category_attribute | event_attribute | font_attribute | hint_attribute | layout_attribute | generic_attribute)
  76. category_attribute_value_pair = category_attribute + EQUALS + paren_list_value
  77. event_attribute_value_pair = event_attribute + EQUALS + value
  78. font_attribute_value_pair = font_attribute + EQUALS + value
  79. hint_attribute_value_pair = hint_attribute + EQUALS + value
  80. layout_attribute_value_pair = layout_attribute + EQUALS + value
  81. generic_attribute_value_pair = attribute + EQUALS + value
  82. attribute_value_pair << Group(
  83. category_attribute_value_pair
  84. | event_attribute_value_pair
  85. | font_attribute_value_pair
  86. | hint_attribute_value_pair
  87. | layout_attribute_value_pair
  88. | generic_attribute_value_pair
  89. )
  90. object_declaration = Group((OBJECT + object_name + COLON + object_type))
  91. object_attributes = Group(ZeroOrMore(attribute_value_pair))
  92. nested_object = Forward()
  93. object_definition = object_declaration + object_attributes + ZeroOrMore(nested_object) + END
  94. nested_object << Group(object_definition)
  95. #################
  96. # END GRAMMAR
  97. #################
  98. def printer(s, loc, tok):
  99. print(tok, end=' ')
  100. return tok
  101. def get_filename_list(tf):
  102. import sys, glob
  103. if tf == None:
  104. if len(sys.argv) > 1:
  105. tf = sys.argv[1:]
  106. else:
  107. tf = glob.glob("*.dfm")
  108. elif type(tf) == str:
  109. tf = [tf]
  110. testfiles = []
  111. for arg in tf:
  112. testfiles.extend(glob.glob(arg))
  113. return testfiles
  114. def main(testfiles=None, action=printer):
  115. """testfiles can be None, in which case the command line arguments are used as filenames.
  116. testfiles can be a string, in which case that file is parsed.
  117. testfiles can be a list.
  118. In all cases, the filenames will be globbed.
  119. If more than one file is parsed successfully, a dictionary of ParseResults is returned.
  120. Otherwise, a simple ParseResults is returned.
  121. """
  122. testfiles = get_filename_list(testfiles)
  123. print(testfiles)
  124. if action:
  125. for i in (simple_identifier, value, item_list):
  126. i.setParseAction(action)
  127. success = 0
  128. failures = []
  129. retval = {}
  130. for f in testfiles:
  131. try:
  132. retval[f] = object_definition.parseFile(f)
  133. success += 1
  134. except Exception:
  135. failures.append(f)
  136. if failures:
  137. print('\nfailed while processing %s' % ', '.join(failures))
  138. print('\nsucceeded on %d of %d files' %(success, len(testfiles)))
  139. if len(retval) == 1 and len(testfiles) == 1:
  140. # if only one file is parsed, return the parseResults directly
  141. return retval[list(retval.keys())[0]]
  142. # else, return a dictionary of parseResults
  143. return retval
  144. if __name__ == "__main__":
  145. main()