123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- # Windows dialog .RC file parser, by Adam Walker.
-
- # This module was adapted from the spambayes project, and is Copyright
- # 2003/2004 The Python Software Foundation and is covered by the Python
- # Software Foundation license.
- """
- This is a parser for Windows .rc files, which are text files which define
- dialogs and other Windows UI resources.
- """
- __author__ = "Adam Walker"
- __version__ = "0.11"
-
- import os
- import pprint
- import shlex
- import stat
- import sys
-
- import commctrl
- import win32con
-
- _controlMap = {
- "DEFPUSHBUTTON": 0x80,
- "PUSHBUTTON": 0x80,
- "Button": 0x80,
- "GROUPBOX": 0x80,
- "Static": 0x82,
- "CTEXT": 0x82,
- "RTEXT": 0x82,
- "LTEXT": 0x82,
- "LISTBOX": 0x83,
- "SCROLLBAR": 0x84,
- "COMBOBOX": 0x85,
- "EDITTEXT": 0x81,
- "ICON": 0x82,
- "RICHEDIT": "RichEdit20A",
- }
-
- # These are "default styles" for certain controls - ie, Visual Studio assumes
- # the styles will be applied, and emits a "NOT {STYLE_NAME}" if it is to be
- # disabled. These defaults have been determined by experimentation, so may
- # not be completely accurate (most notably, some styles and/or control-types
- # may be missing.
- _addDefaults = {
- "EDITTEXT": win32con.WS_BORDER | win32con.WS_TABSTOP,
- "GROUPBOX": win32con.BS_GROUPBOX,
- "LTEXT": win32con.SS_LEFT,
- "DEFPUSHBUTTON": win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP,
- "PUSHBUTTON": win32con.WS_TABSTOP,
- "CTEXT": win32con.SS_CENTER,
- "RTEXT": win32con.SS_RIGHT,
- "ICON": win32con.SS_ICON,
- "LISTBOX": win32con.LBS_NOTIFY,
- }
-
- defaultControlStyle = win32con.WS_CHILD | win32con.WS_VISIBLE
- defaultControlStyleEx = 0
-
-
- class DialogDef:
- name = ""
- id = 0
- style = 0
- styleEx = None
- caption = ""
- font = "MS Sans Serif"
- fontSize = 8
- x = 0
- y = 0
- w = 0
- h = 0
- template = None
-
- def __init__(self, n, i):
- self.name = n
- self.id = i
- self.styles = []
- self.stylesEx = []
- self.controls = []
- # print "dialog def for ",self.name, self.id
-
- def createDialogTemplate(self):
- t = None
- self.template = [
- [
- self.caption,
- (self.x, self.y, self.w, self.h),
- self.style,
- self.styleEx,
- (self.fontSize, self.font),
- ]
- ]
- # Add the controls
- for control in self.controls:
- self.template.append(control.createDialogTemplate())
- return self.template
-
-
- class ControlDef:
- id = ""
- controlType = ""
- subType = ""
- idNum = 0
- style = defaultControlStyle
- styleEx = defaultControlStyleEx
- label = ""
- x = 0
- y = 0
- w = 0
- h = 0
-
- def __init__(self):
- self.styles = []
- self.stylesEx = []
-
- def toString(self):
- s = (
- "<Control id:"
- + self.id
- + " controlType:"
- + self.controlType
- + " subType:"
- + self.subType
- + " idNum:"
- + str(self.idNum)
- + " style:"
- + str(self.style)
- + " styles:"
- + str(self.styles)
- + " label:"
- + self.label
- + " x:"
- + str(self.x)
- + " y:"
- + str(self.y)
- + " w:"
- + str(self.w)
- + " h:"
- + str(self.h)
- + ">"
- )
- return s
-
- def createDialogTemplate(self):
- ct = self.controlType
- if "CONTROL" == ct:
- ct = self.subType
- if ct in _controlMap:
- ct = _controlMap[ct]
- t = [
- ct,
- self.label,
- self.idNum,
- (self.x, self.y, self.w, self.h),
- self.style,
- self.styleEx,
- ]
- # print t
- return t
-
-
- class StringDef:
- def __init__(self, id, idNum, value):
- self.id = id
- self.idNum = idNum
- self.value = value
-
- def __repr__(self):
- return "StringDef(%r, %r, %r)" % (self.id, self.idNum, self.value)
-
-
- class RCParser:
- next_id = 1001
- dialogs = {}
- _dialogs = {}
- debugEnabled = False
- token = ""
-
- def __init__(self):
- self.ungot = False
- self.ids = {"IDC_STATIC": -1}
- self.names = {-1: "IDC_STATIC"}
- self.bitmaps = {}
- self.stringTable = {}
- self.icons = {}
-
- def debug(self, *args):
- if self.debugEnabled:
- print(args)
-
- def getToken(self):
- if self.ungot:
- self.ungot = False
- self.debug("getToken returns (ungot):", self.token)
- return self.token
- self.token = self.lex.get_token()
- self.debug("getToken returns:", self.token)
- if self.token == "":
- self.token = None
- return self.token
-
- def ungetToken(self):
- self.ungot = True
-
- def getCheckToken(self, expected):
- tok = self.getToken()
- assert tok == expected, "Expected token '%s', but got token '%s'!" % (
- expected,
- tok,
- )
- return tok
-
- def getCommaToken(self):
- return self.getCheckToken(",")
-
- # Return the *current* token as a number, only consuming a token
- # if it is the negative-sign.
- def currentNumberToken(self):
- mult = 1
- if self.token == "-":
- mult = -1
- self.getToken()
- return int(self.token) * mult
-
- # Return the *current* token as a string literal (ie, self.token will be a
- # quote. consumes all tokens until the end of the string
- def currentQuotedString(self):
- # Handle quoted strings - pity shlex doesn't handle it.
- assert self.token.startswith('"'), self.token
- bits = [self.token]
- while 1:
- tok = self.getToken()
- if not tok.startswith('"'):
- self.ungetToken()
- break
- bits.append(tok)
- sval = "".join(bits)[1:-1] # Remove end quotes.
- # Fixup quotes in the body, and all (some?) quoted characters back
- # to their raw value.
- for i, o in ('""', '"'), ("\\r", "\r"), ("\\n", "\n"), ("\\t", "\t"):
- sval = sval.replace(i, o)
- return sval
-
- def load(self, rcstream):
- """
- RCParser.loadDialogs(rcFileName) -> None
- Load the dialog information into the parser. Dialog Definations can then be accessed
- using the "dialogs" dictionary member (name->DialogDef). The "ids" member contains the dictionary of id->name.
- The "names" member contains the dictionary of name->id
- """
- self.open(rcstream)
- self.getToken()
- while self.token != None:
- self.parse()
- self.getToken()
-
- def open(self, rcstream):
- self.lex = shlex.shlex(rcstream)
- self.lex.commenters = "//#"
-
- def parseH(self, file):
- lex = shlex.shlex(file)
- lex.commenters = "//"
- token = " "
- while token is not None:
- token = lex.get_token()
- if token == "" or token is None:
- token = None
- else:
- if token == "define":
- n = lex.get_token()
- i = int(lex.get_token())
- self.ids[n] = i
- if i in self.names:
- # Dupe ID really isn't a problem - most consumers
- # want to go from name->id, and this is OK.
- # It means you can't go from id->name though.
- pass
- # ignore AppStudio special ones
- # if not n.startswith("_APS_"):
- # print "Duplicate id",i,"for",n,"is", self.names[i]
- else:
- self.names[i] = n
- if self.next_id <= i:
- self.next_id = i + 1
-
- def parse(self):
- noid_parsers = {
- "STRINGTABLE": self.parse_stringtable,
- }
-
- id_parsers = {
- "DIALOG": self.parse_dialog,
- "DIALOGEX": self.parse_dialog,
- # "TEXTINCLUDE": self.parse_textinclude,
- "BITMAP": self.parse_bitmap,
- "ICON": self.parse_icon,
- }
- deep = 0
- base_token = self.token
- rp = noid_parsers.get(base_token)
- if rp is not None:
- rp()
- else:
- # Not something we parse that isn't prefixed by an ID
- # See if it is an ID prefixed item - if it is, our token
- # is the resource ID.
- resource_id = self.token
- self.getToken()
- if self.token is None:
- return
-
- if "BEGIN" == self.token:
- # A 'BEGIN' for a structure we don't understand - skip to the
- # matching 'END'
- deep = 1
- while deep != 0 and self.token is not None:
- self.getToken()
- self.debug("Zooming over", self.token)
- if "BEGIN" == self.token:
- deep += 1
- elif "END" == self.token:
- deep -= 1
- else:
- rp = id_parsers.get(self.token)
- if rp is not None:
- self.debug("Dispatching '%s'" % (self.token,))
- rp(resource_id)
- else:
- # We don't know what the resource type is, but we
- # have already consumed the next, which can cause problems,
- # so push it back.
- self.debug("Skipping top-level '%s'" % base_token)
- self.ungetToken()
-
- def addId(self, id_name):
- if id_name in self.ids:
- id = self.ids[id_name]
- else:
- # IDOK, IDCANCEL etc are special - if a real resource has this value
- for n in ["IDOK", "IDCANCEL", "IDYES", "IDNO", "IDABORT"]:
- if id_name == n:
- v = getattr(win32con, n)
- self.ids[n] = v
- self.names[v] = n
- return v
- id = self.next_id
- self.next_id += 1
- self.ids[id_name] = id
- self.names[id] = id_name
- return id
-
- def lang(self):
- while (
- self.token[0:4] == "LANG"
- or self.token[0:7] == "SUBLANG"
- or self.token == ","
- ):
- self.getToken()
-
- def parse_textinclude(self, res_id):
- while self.getToken() != "BEGIN":
- pass
- while 1:
- if self.token == "END":
- break
- s = self.getToken()
-
- def parse_stringtable(self):
- while self.getToken() != "BEGIN":
- pass
- while 1:
- self.getToken()
- if self.token == "END":
- break
- sid = self.token
- self.getToken()
- sd = StringDef(sid, self.addId(sid), self.currentQuotedString())
- self.stringTable[sid] = sd
-
- def parse_bitmap(self, name):
- return self.parse_bitmap_or_icon(name, self.bitmaps)
-
- def parse_icon(self, name):
- return self.parse_bitmap_or_icon(name, self.icons)
-
- def parse_bitmap_or_icon(self, name, dic):
- self.getToken()
- while not self.token.startswith('"'):
- self.getToken()
- bmf = self.token[1:-1] # quotes
- dic[name] = bmf
-
- def parse_dialog(self, name):
- dlg = DialogDef(name, self.addId(name))
- assert len(dlg.controls) == 0
- self._dialogs[name] = dlg
- extras = []
- self.getToken()
- while not self.token.isdigit():
- self.debug("extra", self.token)
- extras.append(self.token)
- self.getToken()
- dlg.x = int(self.token)
- self.getCommaToken()
- self.getToken() # number
- dlg.y = int(self.token)
- self.getCommaToken()
- self.getToken() # number
- dlg.w = int(self.token)
- self.getCommaToken()
- self.getToken() # number
- dlg.h = int(self.token)
- self.getToken()
- while not (self.token == None or self.token == "" or self.token == "END"):
- if self.token == "STYLE":
- self.dialogStyle(dlg)
- elif self.token == "EXSTYLE":
- self.dialogExStyle(dlg)
- elif self.token == "CAPTION":
- self.dialogCaption(dlg)
- elif self.token == "FONT":
- self.dialogFont(dlg)
- elif self.token == "BEGIN":
- self.controls(dlg)
- else:
- break
- self.dialogs[name] = dlg.createDialogTemplate()
-
- def dialogStyle(self, dlg):
- dlg.style, dlg.styles = self.styles([], win32con.DS_SETFONT)
-
- def dialogExStyle(self, dlg):
- self.getToken()
- dlg.styleEx, dlg.stylesEx = self.styles([], 0)
-
- def styles(self, defaults, defaultStyle):
- list = defaults
- style = defaultStyle
-
- if "STYLE" == self.token:
- self.getToken()
- i = 0
- Not = False
- while (
- (i % 2 == 1 and ("|" == self.token or "NOT" == self.token)) or (i % 2 == 0)
- ) and not self.token == None:
- Not = False
- if "NOT" == self.token:
- Not = True
- self.getToken()
- i += 1
- if self.token != "|":
- if self.token in win32con.__dict__:
- value = getattr(win32con, self.token)
- else:
- if self.token in commctrl.__dict__:
- value = getattr(commctrl, self.token)
- else:
- value = 0
- if Not:
- list.append("NOT " + self.token)
- self.debug("styles add Not", self.token, value)
- style &= ~value
- else:
- list.append(self.token)
- self.debug("styles add", self.token, value)
- style |= value
- self.getToken()
- self.debug("style is ", style)
-
- return style, list
-
- def dialogCaption(self, dlg):
- if "CAPTION" == self.token:
- self.getToken()
- self.token = self.token[1:-1]
- self.debug("Caption is:", self.token)
- dlg.caption = self.token
- self.getToken()
-
- def dialogFont(self, dlg):
- if "FONT" == self.token:
- self.getToken()
- dlg.fontSize = int(self.token)
- self.getCommaToken()
- self.getToken() # Font name
- dlg.font = self.token[1:-1] # it's quoted
- self.getToken()
- while "BEGIN" != self.token:
- self.getToken()
-
- def controls(self, dlg):
- if self.token == "BEGIN":
- self.getToken()
- # All controls look vaguely like:
- # TYPE [text, ] Control_id, l, t, r, b [, style]
- # .rc parser documents all control types as:
- # CHECKBOX, COMBOBOX, CONTROL, CTEXT, DEFPUSHBUTTON, EDITTEXT, GROUPBOX,
- # ICON, LISTBOX, LTEXT, PUSHBUTTON, RADIOBUTTON, RTEXT, SCROLLBAR
- without_text = ["EDITTEXT", "COMBOBOX", "LISTBOX", "SCROLLBAR"]
- while self.token != "END":
- control = ControlDef()
- control.controlType = self.token
- self.getToken()
- if control.controlType not in without_text:
- if self.token[0:1] == '"':
- control.label = self.currentQuotedString()
- # Some funny controls, like icons and picture controls use
- # the "window text" as extra resource ID (ie, the ID of the
- # icon itself). This may be either a literal, or an ID string.
- elif self.token == "-" or self.token.isdigit():
- control.label = str(self.currentNumberToken())
- else:
- # An ID - use the numeric equiv.
- control.label = str(self.addId(self.token))
- self.getCommaToken()
- self.getToken()
- # Control IDs may be "names" or literal ints
- if self.token == "-" or self.token.isdigit():
- control.id = self.currentNumberToken()
- control.idNum = control.id
- else:
- # name of an ID
- control.id = self.token
- control.idNum = self.addId(control.id)
- self.getCommaToken()
-
- if control.controlType == "CONTROL":
- self.getToken()
- control.subType = self.token[1:-1]
- thisDefaultStyle = defaultControlStyle | _addDefaults.get(
- control.subType, 0
- )
- # Styles
- self.getCommaToken()
- self.getToken()
- control.style, control.styles = self.styles([], thisDefaultStyle)
- else:
- thisDefaultStyle = defaultControlStyle | _addDefaults.get(
- control.controlType, 0
- )
- # incase no style is specified.
- control.style = thisDefaultStyle
- # Rect
- control.x = int(self.getToken())
- self.getCommaToken()
- control.y = int(self.getToken())
- self.getCommaToken()
- control.w = int(self.getToken())
- self.getCommaToken()
- self.getToken()
- control.h = int(self.token)
- self.getToken()
- if self.token == ",":
- self.getToken()
- control.style, control.styles = self.styles([], thisDefaultStyle)
- if self.token == ",":
- self.getToken()
- control.styleEx, control.stylesEx = self.styles(
- [], defaultControlStyleEx
- )
- # print control.toString()
- dlg.controls.append(control)
-
-
- def ParseStreams(rc_file, h_file):
- rcp = RCParser()
- if h_file:
- rcp.parseH(h_file)
- try:
- rcp.load(rc_file)
- except:
- lex = getattr(rcp, "lex", None)
- if lex:
- print("ERROR parsing dialogs at line", lex.lineno)
- print("Next 10 tokens are:")
- for i in range(10):
- print(lex.get_token(), end=" ")
- print()
- raise
- return rcp
-
-
- def Parse(rc_name, h_name=None):
- if h_name:
- h_file = open(h_name, "r")
- else:
- # See if same basename as the .rc
- h_name = rc_name[:-2] + "h"
- try:
- h_file = open(h_name, "r")
- except IOError:
- # See if MSVC default of 'resource.h' in the same dir.
- h_name = os.path.join(os.path.dirname(rc_name), "resource.h")
- try:
- h_file = open(h_name, "r")
- except IOError:
- # .h files are optional anyway
- h_file = None
- rc_file = open(rc_name, "r")
- try:
- return ParseStreams(rc_file, h_file)
- finally:
- if h_file is not None:
- h_file.close()
- rc_file.close()
- return rcp
-
-
- def GenerateFrozenResource(rc_name, output_name, h_name=None):
- """Converts an .rc windows resource source file into a python source file
- with the same basic public interface as the rest of this module.
- Particularly useful for py2exe or other 'freeze' type solutions,
- where a frozen .py file can be used inplace of a real .rc file.
- """
- rcp = Parse(rc_name, h_name)
- in_stat = os.stat(rc_name)
-
- out = open(output_name, "wt")
- out.write("#%s\n" % output_name)
- out.write("#This is a generated file. Please edit %s instead.\n" % rc_name)
- out.write("__version__=%r\n" % __version__)
- out.write(
- "_rc_size_=%d\n_rc_mtime_=%d\n"
- % (in_stat[stat.ST_SIZE], in_stat[stat.ST_MTIME])
- )
-
- out.write("class StringDef:\n")
- out.write("\tdef __init__(self, id, idNum, value):\n")
- out.write("\t\tself.id = id\n")
- out.write("\t\tself.idNum = idNum\n")
- out.write("\t\tself.value = value\n")
- out.write("\tdef __repr__(self):\n")
- out.write(
- '\t\treturn "StringDef(%r, %r, %r)" % (self.id, self.idNum, self.value)\n'
- )
-
- out.write("class FakeParser:\n")
-
- for name in "dialogs", "ids", "names", "bitmaps", "icons", "stringTable":
- out.write("\t%s = \\\n" % (name,))
- pprint.pprint(getattr(rcp, name), out)
- out.write("\n")
-
- out.write("def Parse(s):\n")
- out.write("\treturn FakeParser()\n")
- out.close()
-
-
- if __name__ == "__main__":
- if len(sys.argv) <= 1:
- print(__doc__)
- print()
- print("See test_win32rcparser.py, and the win32rcparser directory (both")
- print("in the test suite) for an example of this module's usage.")
- else:
- import pprint
-
- filename = sys.argv[1]
- if "-v" in sys.argv:
- RCParser.debugEnabled = 1
- print("Dumping all resources in '%s'" % filename)
- resources = Parse(filename)
- for id, ddef in resources.dialogs.items():
- print("Dialog %s (%d controls)" % (id, len(ddef)))
- pprint.pprint(ddef)
- print()
- for id, sdef in resources.stringTable.items():
- print("String %s=%r" % (id, sdef.value))
- print()
- for id, sdef in resources.bitmaps.items():
- print("Bitmap %s=%r" % (id, sdef))
- print()
- for id, sdef in resources.icons.items():
- print("Icon %s=%r" % (id, sdef))
- print()
|