PyXR

c:\python24\lib\site-packages\win32\lib \ win32rcparser.py



0001 # Windows dialog .RC file parser, by Adam Walker.
0002 
0003 # This module was adapted from the spambayes project, and is Copyright
0004 # 2003/2004 The Python Software Foundation and is covered by the Python
0005 # Software Foundation license.
0006 """
0007 This is a parser for Windows .rc files, which are text files which define
0008 dialogs and other Windows UI resources.
0009 """
0010 __author__="Adam Walker"
0011 __version__="0.11"
0012 
0013 import sys, os, shlex, stat
0014 import pprint
0015 import win32con
0016 import commctrl
0017 
0018 _controlMap = {"DEFPUSHBUTTON":0x80,
0019                "PUSHBUTTON":0x80,
0020                "Button":0x80,
0021                "GROUPBOX":0x80,
0022                "Static":0x82,
0023                "CTEXT":0x82,
0024                "RTEXT":0x82,
0025                "LTEXT":0x82,
0026                "LISTBOX":0x83,
0027                "SCROLLBAR":0x84,
0028                "COMBOBOX":0x85,
0029                "EDITTEXT":0x81,
0030                "ICON":0x82,
0031                "RICHEDIT":"RichEdit20A"
0032                }
0033 
0034 _addDefaults = {"EDITTEXT":win32con.WS_BORDER | win32con.WS_TABSTOP,
0035                 "GROUPBOX":win32con.BS_GROUPBOX,
0036                 "LTEXT":win32con.SS_LEFT,
0037                 "DEFPUSHBUTTON":win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP,
0038                 "PUSHBUTTON": win32con.WS_TABSTOP,
0039                 "CTEXT":win32con.SS_CENTER,
0040                 "RTEXT":win32con.SS_RIGHT,
0041                 "ICON":win32con.SS_ICON}
0042 
0043 defaultControlStyle = win32con.WS_CHILD | win32con.WS_VISIBLE
0044 defaultControlStyleEx = 0
0045 
0046 class DialogDef:
0047     name = ""
0048     id = 0
0049     style = 0
0050     styleEx = None
0051     caption = ""
0052     font = "MS Sans Serif"
0053     fontSize = 8
0054     x = 0
0055     y = 0
0056     w = 0
0057     h = 0
0058     template = None
0059     def __init__(self, n, i):
0060         self.name = n
0061         self.id = i
0062         self.styles = []
0063         self.stylesEx = []
0064         self.controls = []
0065         #print "dialog def for ",self.name, self.id
0066     def createDialogTemplate(self):
0067         t = None
0068         self.template = [[self.caption,
0069                           (self.x,self.y,self.w,self.h),
0070                           self.style, self.styleEx,
0071                           (self.fontSize, self.font)]
0072         ]
0073         # Add the controls
0074         for control in self.controls:
0075             self.template.append(control.createDialogTemplate())
0076         return self.template
0077 
0078 class ControlDef:
0079     id = ""
0080     controlType = ""
0081     subType = ""
0082     idNum = 0
0083     style = defaultControlStyle
0084     styleEx = defaultControlStyleEx
0085     label = ""
0086     x = 0
0087     y = 0
0088     w = 0
0089     h = 0
0090     def __init__(self):
0091         self.styles = []
0092         self.stylesEx = []
0093     def toString(self):
0094         s = "<Control id:"+self.id+" controlType:"+self.controlType+" subType:"+self.subType\
0095             +" idNum:"+str(self.idNum)+" style:"+str(self.style)+" styles:"+str(self.styles)+" label:"+self.label\
0096             +" x:"+str(self.x)+" y:"+str(self.y)+" w:"+str(self.w)+" h:"+str(self.h)+">"
0097         return s
0098     def createDialogTemplate(self):
0099         ct = self.controlType
0100         if "CONTROL"==ct:
0101             ct = self.subType
0102         if ct in _controlMap:
0103             ct = _controlMap[ct]
0104         t = [ct, self.label, self.idNum, (self.x, self.y, self.w, self.h), self.style, self.styleEx]
0105         #print t
0106         return t
0107 
0108 class StringDef:
0109     def __init__(self, id, idNum, value):
0110         self.id = id
0111         self.idNum = idNum
0112         self.value = value
0113 
0114 class RCParser:
0115     next_id = 1001
0116     dialogs = {}
0117     _dialogs = {}
0118     debugEnabled = False
0119     token = ""
0120 
0121     def __init__(self):
0122         self.ungot = False
0123         self.ids = {"IDC_STATIC": -1}
0124         self.names = {-1:"IDC_STATIC"}
0125         self.bitmaps = {}
0126         self.stringTable = {}
0127         self.icons = {}
0128 
0129     def debug(self, *args):
0130         if self.debugEnabled:
0131             print args
0132 
0133     def getToken(self):
0134         if self.ungot:
0135             self.ungot = False
0136             self.debug("getToken returns (ungot):", self.token)
0137             return self.token
0138         self.token = self.lex.get_token()
0139         self.debug("getToken returns:", self.token)
0140         if self.token=="":
0141             self.token = None
0142         return self.token
0143 
0144     def ungetToken(self):
0145         self.ungot = True
0146 
0147     def getCheckToken(self, expected):
0148         tok = self.getToken()
0149         assert tok == expected, "Expected token '%s', but got token '%s'!" % (expected, tok)
0150         return tok
0151 
0152     def getCommaToken(self):
0153         return self.getCheckToken(",")
0154 
0155     # Return the *current* token as a number, only consuming a token
0156     # if it is the negative-sign.
0157     def currentNumberToken(self):
0158         mult = 1
0159         if self.token=='-':
0160             mult = -1
0161             self.getToken()
0162         return int(self.token) * mult
0163 
0164     # Return the *current* token as a string literal (ie, self.token will be a
0165     # quote.  consumes all tokens until the end of the string
0166     def currentQuotedString(self):
0167         # Handle quoted strings - pity shlex doesn't handle it.
0168         assert self.token.startswith('"'), self.token
0169         bits = [self.token]
0170         while 1:
0171             tok = self.getToken()
0172             if not tok.startswith('"'):
0173                 self.ungetToken()
0174                 break
0175             bits.append(tok)
0176         sval = "".join(bits)[1:-1] # Remove end quotes.            
0177         # Fixup quotes in the body, and all (some?) quoted characters back
0178         # to their raw value.
0179         for i, o in ('""', '"'), ("\\r", "\r"), ("\\n", "\n"), ("\\t", "\t"):
0180             sval = sval.replace(i, o)
0181         return sval
0182         
0183     def load(self, rcstream):
0184         """
0185         RCParser.loadDialogs(rcFileName) -> None
0186         Load the dialog information into the parser. Dialog Definations can then be accessed
0187         using the "dialogs" dictionary member (name->DialogDef). The "ids" member contains the dictionary of id->name.
0188         The "names" member contains the dictionary of name->id
0189         """
0190         self.open(rcstream)
0191         self.getToken()
0192         while self.token!=None:
0193             self.parse()
0194             self.getToken()
0195 
0196     def open(self, rcstream):
0197         self.lex = shlex.shlex(rcstream)
0198         self.lex.commenters = "//#"
0199 
0200     def parseH(self, file):
0201         lex = shlex.shlex(file)
0202         lex.commenters = "//"
0203         token = " "
0204         while token is not None:
0205             token = lex.get_token()
0206             if token == "" or token is None:
0207                 token = None
0208             else:
0209                 if token=='define':
0210                     n = lex.get_token()
0211                     i = int(lex.get_token())
0212                     self.ids[n] = i
0213                     if self.names.has_key(i):
0214                         # Dupe ID really isn't a problem - most consumers
0215                         # want to go from name->id, and this is OK.
0216                         # It means you can't go from id->name though.
0217                         pass
0218                         # ignore AppStudio special ones
0219                         #if not n.startswith("_APS_"):
0220                         #    print "Duplicate id",i,"for",n,"is", self.names[i]
0221                     else:
0222                         self.names[i] = n
0223                     if self.next_id<=i:
0224                         self.next_id = i+1
0225 
0226     def parse(self):
0227         noid_parsers = {
0228             "STRINGTABLE":      self.parse_stringtable,
0229         }
0230 
0231         id_parsers = {
0232             "DIALOG" :          self.parse_dialog,
0233             "DIALOGEX":         self.parse_dialog,
0234 #            "TEXTINCLUDE":      self.parse_textinclude,
0235             "BITMAP":           self.parse_bitmap,
0236             "ICON":             self.parse_icon,
0237         }
0238         deep = 0
0239         base_token = self.token
0240         rp = noid_parsers.get(base_token)
0241         if rp is not None:
0242             rp()
0243         else:
0244             # Not something we parse that isn't prefixed by an ID
0245             # See if it is an ID prefixed item - if it is, our token
0246             # is the resource ID.
0247             resource_id = self.token
0248             self.getToken()
0249             if self.token is None:
0250                 return
0251 
0252             if "BEGIN" == self.token:
0253                 # A 'BEGIN' for a structure we don't understand - skip to the
0254                 # matching 'END'
0255                 deep = 1
0256                 while deep!=0 and self.token is not None:
0257                     self.getToken()
0258                     self.debug("Zooming over", self.token)
0259                     if "BEGIN" == self.token:
0260                         deep += 1
0261                     elif "END" == self.token:
0262                         deep -= 1
0263             else:
0264                 rp = id_parsers.get(self.token)
0265                 if rp is not None:
0266                     self.debug("Dispatching '%s'" % (self.token,))
0267                     rp(resource_id)
0268                 else:
0269                     # We don't know what the resource type is, but we
0270                     # have already consumed the next, which can cause problems,
0271                     # so push it back.
0272                     self.debug("Skipping top-level '%s'" % base_token)
0273                     self.ungetToken()
0274 
0275     def addId(self, id_name):
0276         if id_name in self.ids:
0277             id = self.ids[id_name]
0278         else:
0279             # IDOK, IDCANCEL etc are special - if a real resource has this value
0280             for n in ["IDOK","IDCANCEL","IDYES","IDNO", "IDABORT"]:
0281                 if id_name == n:
0282                     v = getattr(win32con, n)
0283                     self.ids[n] = v
0284                     self.names[v] = n
0285                     return v
0286             id = self.next_id
0287             self.next_id += 1
0288             self.ids[id_name] = id
0289             self.names[id] = id_name
0290         return id
0291 
0292     def lang(self):
0293         while self.token[0:4]=="LANG" or self.token[0:7]=="SUBLANG" or self.token==',':
0294             self.getToken();
0295 
0296     def parse_textinclude(self, res_id):
0297         while self.getToken() != "BEGIN":
0298             pass
0299         while 1:
0300             if self.token == "END":
0301                 break
0302             s = self.getToken()
0303 
0304     def parse_stringtable(self):
0305         while self.getToken() != "BEGIN":
0306             pass
0307         while 1:
0308             self.getToken()
0309             if self.token == "END":
0310                 break
0311             sid = self.token
0312             self.getToken()
0313             sd = StringDef(sid, self.addId(sid), self.currentQuotedString())
0314             self.stringTable[sid] = sd
0315 
0316     def parse_bitmap(self, name):
0317         return self.parse_bitmap_or_icon(name, self.bitmaps)
0318 
0319     def parse_icon(self, name):
0320         return self.parse_bitmap_or_icon(name, self.icons)
0321 
0322     def parse_bitmap_or_icon(self, name, dic):
0323         self.getToken()
0324         while not self.token.startswith('"'):
0325             self.getToken()
0326         bmf = self.token[1:-1] # quotes
0327         dic[name] = bmf
0328 
0329     def parse_dialog(self, name):
0330         dlg = DialogDef(name,self.addId(name))
0331         assert len(dlg.controls)==0
0332         self._dialogs[name] = dlg
0333         extras = []
0334         self.getToken()
0335         while not self.token.isdigit():
0336             self.debug("extra", self.token)
0337             extras.append(self.token)
0338             self.getToken()
0339         dlg.x = int(self.token)
0340         self.getCommaToken()
0341         self.getToken() # number
0342         dlg.y = int(self.token)
0343         self.getCommaToken()
0344         self.getToken() # number
0345         dlg.w = int(self.token)
0346         self.getCommaToken()
0347         self.getToken() # number
0348         dlg.h = int(self.token)
0349         self.getToken()
0350         while not (self.token==None or self.token=="" or self.token=="END"):
0351             if self.token=="STYLE":
0352                 self.dialogStyle(dlg)
0353             elif self.token=="EXSTYLE":
0354                 self.dialogExStyle(dlg)
0355             elif self.token=="CAPTION":
0356                 self.dialogCaption(dlg)
0357             elif self.token=="FONT":
0358                 self.dialogFont(dlg)
0359             elif self.token=="BEGIN":
0360                 self.controls(dlg)
0361             else:
0362                 break
0363         self.dialogs[name] = dlg.createDialogTemplate()
0364 
0365     def dialogStyle(self, dlg):
0366         dlg.style, dlg.styles = self.styles( [], win32con.DS_SETFONT)
0367     def dialogExStyle(self, dlg):
0368         self.getToken()
0369         dlg.styleEx, dlg.stylesEx = self.styles( [], 0)
0370 
0371     def styles(self, defaults, defaultStyle):
0372         list = defaults
0373         style = defaultStyle
0374 
0375         if "STYLE"==self.token:
0376             self.getToken()
0377         i = 0
0378         Not = False
0379         while ((i%2==1 and ("|"==self.token or "NOT"==self.token)) or (i%2==0)) and not self.token==None:
0380             Not = False;
0381             if "NOT"==self.token:
0382                 Not = True
0383                 self.getToken()
0384             i += 1
0385             if self.token!="|":
0386                 if self.token in win32con.__dict__:
0387                     value = getattr(win32con,self.token)
0388                 else:
0389                     if self.token in commctrl.__dict__:
0390                         value = getattr(commctrl,self.token)
0391                     else:
0392                         value = 0
0393                 if Not:
0394                     list.append("NOT "+self.token)
0395                     self.debug("styles add Not",self.token, value)
0396                     style &= ~value
0397                 else:
0398                     list.append(self.token)
0399                     self.debug("styles add", self.token, value)
0400                     style |= value
0401             self.getToken()
0402         self.debug("style is ",style)
0403 
0404         return style, list
0405 
0406     def dialogCaption(self, dlg):
0407         if "CAPTION"==self.token:
0408             self.getToken()
0409         self.token = self.token[1:-1]
0410         self.debug("Caption is:",self.token)
0411         dlg.caption = self.token
0412         self.getToken()
0413     def dialogFont(self, dlg):
0414         if "FONT"==self.token:
0415             self.getToken()
0416         dlg.fontSize = int(self.token)
0417         self.getCommaToken()
0418         self.getToken() # Font name
0419         dlg.font = self.token[1:-1] # it's quoted
0420         self.getToken()
0421         while "BEGIN"!=self.token:
0422             self.getToken()
0423     def controls(self, dlg):
0424         if self.token=="BEGIN": self.getToken()
0425         # All controls look vaguely like:
0426         # TYPE [text, ] Control_id, l, t, r, b [, style]
0427         # .rc parser documents all control types as:
0428         # CHECKBOX, COMBOBOX, CONTROL, CTEXT, DEFPUSHBUTTON, EDITTEXT, GROUPBOX,
0429         # ICON, LISTBOX, LTEXT, PUSHBUTTON, RADIOBUTTON, RTEXT, SCROLLBAR
0430         without_text = ["EDITTEXT", "COMBOBOX", "LISTBOX", "SCROLLBAR"]
0431         while self.token!="END":
0432             control = ControlDef()
0433             control.controlType = self.token;
0434             self.getToken()
0435             if control.controlType not in without_text:
0436                 if self.token[0:1]=='"':
0437                     control.label = self.currentQuotedString()
0438                 # Some funny controls, like icons and picture controls use
0439                 # the "window text" as extra resource ID (ie, the ID of the
0440                 # icon itself).  This may be either a literal, or an ID string.
0441                 elif self.token=="-" or self.token.isdigit():
0442                     control.label = str(self.currentNumberToken())
0443                 else:
0444                     # An ID - use the numeric equiv.
0445                     control.label = str(self.addId(self.token))
0446                 self.getCommaToken()
0447                 self.getToken()
0448             # Control IDs may be "names" or literal ints
0449             if self.token=="-" or self.token.isdigit():
0450                 control.id = self.currentNumberToken()
0451                 control.idNum = control.id
0452             else:
0453                 # name of an ID
0454                 control.id = self.token
0455                 control.idNum = self.addId(control.id)
0456             self.getCommaToken()
0457 
0458             if control.controlType == "CONTROL":
0459                 self.getToken()
0460                 control.subType = self.token[1:-1]
0461                 thisDefaultStyle = defaultControlStyle | \
0462                                    _addDefaults.get(control.subType, 0)
0463                 # Styles
0464                 self.getCommaToken()
0465                 self.getToken()
0466                 control.style, control.styles = self.styles([], thisDefaultStyle)
0467             else:
0468                 thisDefaultStyle = defaultControlStyle | \
0469                                    _addDefaults.get(control.controlType, 0)
0470                 # incase no style is specified.
0471                 control.style = thisDefaultStyle
0472             # Rect
0473             control.x = int(self.getToken())
0474             self.getCommaToken()
0475             control.y = int(self.getToken())
0476             self.getCommaToken()
0477             control.w = int(self.getToken())
0478             self.getCommaToken()
0479             self.getToken()
0480             control.h = int(self.token)
0481             self.getToken()
0482             if self.token==",":
0483                 self.getToken()
0484                 control.style, control.styles = self.styles([], thisDefaultStyle)
0485             if self.token==",":
0486                 self.getToken()
0487                 control.styleEx, control.stylesEx = self.styles([], defaultControlStyleEx)
0488             #print control.toString()
0489             dlg.controls.append(control)
0490 
0491 def ParseStreams(rc_file, h_file):
0492     rcp = RCParser()
0493     rcp.parseH(h_file)
0494     try:
0495         rcp.load(rc_file)
0496     except:
0497         lex = getattr(rcp, "lex", None)
0498         if lex:
0499             print "ERROR parsing dialogs at line", lex.lineno
0500             print "Next 10 tokens are:"
0501             for i in range(10):
0502                 print lex.get_token(),
0503             print
0504         raise
0505     return rcp
0506     
0507 def Parse(rc_name, h_name = None):
0508     if h_name:
0509         h_file = open(h_name, "rU")
0510     else:
0511         # See if same basename as the .rc
0512         h_name = rc_name[:-2]+"h"
0513         try:
0514             h_file = open(h_name, "rU")
0515         except IOError:
0516             # See if MSVC default of 'resource.h' in the same dir.
0517             h_name = os.path.join(os.path.dirname(rc_name), "resource.h")
0518             try:
0519                 h_file = open(h_name, "rU")
0520             except IOError:
0521                 # .h files are optional anyway
0522                 h_file = None
0523     rc_file = open(rc_name, "rU")
0524     try:
0525         return ParseStreams(rc_file, h_file)
0526     finally:
0527         if h_file is not None:
0528             h_file.close()
0529         rc_file.close()
0530     return rcp
0531 
0532 def GenerateFrozenResource(rc_name, output_name, h_name = None):
0533     """Converts an .rc windows resource source file into a python source file
0534        with the same basic public interface as the rest of this module.
0535        Particularly useful for py2exe or other 'freeze' type solutions,
0536        where a frozen .py file can be used inplace of a real .rc file.
0537     """
0538     rcp = Parse(rc_name, h_name)
0539     in_stat = os.stat(rc_name)
0540 
0541     out = open(output_name, "wt")
0542     out.write("#%s\n" % output_name)
0543     out.write("#This is a generated file. Please edit %s instead.\n" % rc_name)
0544     out.write("__version__=%r\n" % __version__)
0545     out.write("_rc_size_=%d\n_rc_mtime_=%d\n" % (in_stat[stat.ST_SIZE], in_stat[stat.ST_MTIME]))
0546     out.write("class FakeParser:\n")
0547 
0548     for name in "dialogs", "ids", "names", "bitmaps", "icons", "stringTable":
0549         out.write("\t%s = \\\n" % (name,))
0550         pprint.pprint(getattr(rcp, name), out)
0551         out.write("\n")
0552 
0553     out.write("def Parse(s):\n")
0554     out.write("\treturn FakeParser()\n")
0555     out.close()
0556 
0557 if __name__=='__main__':
0558     if len(sys.argv) <= 1:
0559         print __doc__
0560         print
0561         print "See test_win32rcparser.py, and the win32rcparser directory (both"
0562         print "in the test suite) for an example of this module's usage."
0563     else:
0564         import pprint
0565         filename = sys.argv[1]
0566         if "-v" in sys.argv:
0567             RCParser.debugEnabled = 1
0568         print "Dumping all resources in '%s'" % filename
0569         resources = Parse(filename)
0570         for id, ddef in resources.dialogs.items():
0571             print "Dialog %s (%d controls)" % (id, len(ddef))
0572             pprint.pprint(ddef)
0573             print
0574         for id, sdef in resources.stringTable.items():
0575             print "String %s=%r" % (id, sdef.value)
0576             print
0577         for id, sdef in resources.bitmaps.items():
0578             print "Bitmap %s=%r" % (id, sdef)
0579             print
0580         for id, sdef in resources.icons.items():
0581             print "Icon %s=%r" % (id, sdef)
0582             print
0583 

Generated by PyXR 0.9.4
SourceForge.net Logo