| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 #!/usr/bin/env python |  | 
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |  | 
| 3 # Use of this source code is governed by a BSD-style license that can be |  | 
| 4 # found in the LICENSE file. |  | 
| 5 |  | 
| 6 '''Support for gathering resources from RC files. |  | 
| 7 ''' |  | 
| 8 |  | 
| 9 |  | 
| 10 import re |  | 
| 11 |  | 
| 12 from grit import exception |  | 
| 13 from grit import lazy_re |  | 
| 14 from grit import tclib |  | 
| 15 |  | 
| 16 from grit.gather import regexp |  | 
| 17 |  | 
| 18 |  | 
| 19 # Find portions that need unescaping in resource strings.  We need to be |  | 
| 20 # careful that a \\n is matched _first_ as a \\ rather than matching as |  | 
| 21 # a \ followed by a \n. |  | 
| 22 # TODO(joi) Handle ampersands if we decide to change them into <ph> |  | 
| 23 # TODO(joi) May need to handle other control characters than \n |  | 
| 24 _NEED_UNESCAPE = lazy_re.compile(r'""|\\\\|\\n|\\t') |  | 
| 25 |  | 
| 26 # Find portions that need escaping to encode string as a resource string. |  | 
| 27 _NEED_ESCAPE = lazy_re.compile(r'"|\n|\t|\\|\ \;') |  | 
| 28 |  | 
| 29 # How to escape certain characters |  | 
| 30 _ESCAPE_CHARS = { |  | 
| 31   '"' : '""', |  | 
| 32   '\n' : '\\n', |  | 
| 33   '\t' : '\\t', |  | 
| 34   '\\' : '\\\\', |  | 
| 35   ' ' : ' ' |  | 
| 36 } |  | 
| 37 |  | 
| 38 # How to unescape certain strings |  | 
| 39 _UNESCAPE_CHARS = dict([[value, key] for key, value in _ESCAPE_CHARS.items()]) |  | 
| 40 |  | 
| 41 |  | 
| 42 |  | 
| 43 class Section(regexp.RegexpGatherer): |  | 
| 44   '''A section from a resource file.''' |  | 
| 45 |  | 
| 46   @staticmethod |  | 
| 47   def Escape(text): |  | 
| 48     '''Returns a version of 'text' with characters escaped that need to be |  | 
| 49     for inclusion in a resource section.''' |  | 
| 50     def Replace(match): |  | 
| 51       return _ESCAPE_CHARS[match.group()] |  | 
| 52     return _NEED_ESCAPE.sub(Replace, text) |  | 
| 53 |  | 
| 54   @staticmethod |  | 
| 55   def UnEscape(text): |  | 
| 56     '''Returns a version of 'text' with escaped characters unescaped.''' |  | 
| 57     def Replace(match): |  | 
| 58       return _UNESCAPE_CHARS[match.group()] |  | 
| 59     return _NEED_UNESCAPE.sub(Replace, text) |  | 
| 60 |  | 
| 61   def _RegExpParse(self, rexp, text_to_parse): |  | 
| 62     '''Overrides _RegExpParse to add shortcut group handling.  Otherwise |  | 
| 63     the same. |  | 
| 64     ''' |  | 
| 65     super(Section, self)._RegExpParse(rexp, text_to_parse) |  | 
| 66 |  | 
| 67     if not self.is_skeleton and len(self.GetTextualIds()) > 0: |  | 
| 68       group_name = self.GetTextualIds()[0] |  | 
| 69       for c in self.GetCliques(): |  | 
| 70         c.AddToShortcutGroup(group_name) |  | 
| 71 |  | 
| 72   def ReadSection(self): |  | 
| 73     rc_text = self._LoadInputFile() |  | 
| 74 |  | 
| 75     out = '' |  | 
| 76     begin_count = 0 |  | 
| 77     assert self.extkey |  | 
| 78     first_line_re = re.compile(r'\s*' + self.extkey + r'\b') |  | 
| 79     for line in rc_text.splitlines(True): |  | 
| 80       if out or first_line_re.match(line): |  | 
| 81         out += line |  | 
| 82 |  | 
| 83       # we stop once we reach the END for the outermost block. |  | 
| 84       begin_count_was = begin_count |  | 
| 85       if len(out) > 0 and line.strip() == 'BEGIN': |  | 
| 86         begin_count += 1 |  | 
| 87       elif len(out) > 0 and line.strip() == 'END': |  | 
| 88         begin_count -= 1 |  | 
| 89       if begin_count_was == 1 and begin_count == 0: |  | 
| 90         break |  | 
| 91 |  | 
| 92     if len(out) == 0: |  | 
| 93       raise exception.SectionNotFound('%s in file %s' % (self.extkey, self.rc_fi
     le)) |  | 
| 94 |  | 
| 95     self.text_ = out.strip() |  | 
| 96 |  | 
| 97 |  | 
| 98 class Dialog(Section): |  | 
| 99   '''A resource section that contains a dialog resource.''' |  | 
| 100 |  | 
| 101   # A typical dialog resource section looks like this: |  | 
| 102   # |  | 
| 103   # IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75 |  | 
| 104   # STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU |  | 
| 105   # CAPTION "About" |  | 
| 106   # FONT 8, "System", 0, 0, 0x0 |  | 
| 107   # BEGIN |  | 
| 108   #     ICON            IDI_KLONK,IDC_MYICON,14,9,20,20 |  | 
| 109   #     LTEXT           "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8, |  | 
| 110   #                     SS_NOPREFIX |  | 
| 111   #     LTEXT           "Copyright (C) 2005",IDC_STATIC,49,20,119,8 |  | 
| 112   #     DEFPUSHBUTTON   "OK",IDOK,195,6,30,11,WS_GROUP |  | 
| 113   #     CONTROL         "Jack ""Black"" Daniels",IDC_RADIO1,"Button", |  | 
| 114   #                     BS_AUTORADIOBUTTON,46,51,84,10 |  | 
| 115   # END |  | 
| 116 |  | 
| 117   # We are using a sorted set of keys, and we assume that the |  | 
| 118   # group name used for descriptions (type) will come after the "text" |  | 
| 119   # group in alphabetical order. We also assume that there cannot be |  | 
| 120   # more than one description per regular expression match. |  | 
| 121   # If that's not the case some descriptions will be clobbered. |  | 
| 122   dialog_re_ = lazy_re.compile(''' |  | 
| 123     # The dialog's ID in the first line |  | 
| 124     (?P<id1>[A-Z0-9_]+)\s+DIALOG(EX)? |  | 
| 125     | |  | 
| 126     # The caption of the dialog |  | 
| 127     (?P<type1>CAPTION)\s+"(?P<text1>.*?([^"]|""))"\s |  | 
| 128     | |  | 
| 129     # Lines for controls that have text and an ID |  | 
| 130     \s+(?P<type2>[A-Z]+)\s+"(?P<text2>.*?([^"]|"")?)"\s*,\s*(?P<id2>[A-Z0-9_]+)\
     s*, |  | 
| 131     | |  | 
| 132     # Lines for controls that have text only |  | 
| 133     \s+(?P<type3>[A-Z]+)\s+"(?P<text3>.*?([^"]|"")?)"\s*, |  | 
| 134     | |  | 
| 135     # Lines for controls that reference other resources |  | 
| 136     \s+[A-Z]+\s+[A-Z0-9_]+\s*,\s*(?P<id3>[A-Z0-9_]*[A-Z][A-Z0-9_]*) |  | 
| 137     | |  | 
| 138     # This matches "NOT SOME_STYLE" so that it gets consumed and doesn't get |  | 
| 139     # matched by the next option (controls that have only an ID and then just |  | 
| 140     # numbers) |  | 
| 141     \s+NOT\s+[A-Z][A-Z0-9_]+ |  | 
| 142     | |  | 
| 143     # Lines for controls that have only an ID and then just numbers |  | 
| 144     \s+[A-Z]+\s+(?P<id4>[A-Z0-9_]*[A-Z][A-Z0-9_]*)\s*, |  | 
| 145     ''', re.MULTILINE | re.VERBOSE) |  | 
| 146 |  | 
| 147   def Parse(self): |  | 
| 148     '''Knows how to parse dialog resource sections.''' |  | 
| 149     self.ReadSection() |  | 
| 150     self._RegExpParse(self.dialog_re_, self.text_) |  | 
| 151 |  | 
| 152 |  | 
| 153 class Menu(Section): |  | 
| 154   '''A resource section that contains a menu resource.''' |  | 
| 155 |  | 
| 156   # A typical menu resource section looks something like this: |  | 
| 157   # |  | 
| 158   # IDC_KLONK MENU |  | 
| 159   # BEGIN |  | 
| 160   #     POPUP "&File" |  | 
| 161   #     BEGIN |  | 
| 162   #         MENUITEM "E&xit",                       IDM_EXIT |  | 
| 163   #         MENUITEM "This be ""Klonk"" me like",   ID_FILE_THISBE |  | 
| 164   #         POPUP "gonk" |  | 
| 165   #         BEGIN |  | 
| 166   #             MENUITEM "Klonk && is ""good""",           ID_GONK_KLONKIS |  | 
| 167   #         END |  | 
| 168   #     END |  | 
| 169   #     POPUP "&Help" |  | 
| 170   #     BEGIN |  | 
| 171   #         MENUITEM "&About ...",                  IDM_ABOUT |  | 
| 172   #     END |  | 
| 173   # END |  | 
| 174 |  | 
| 175   # Description used for the messages generated for menus, to explain to |  | 
| 176   # the translators how to handle them. |  | 
| 177   MENU_MESSAGE_DESCRIPTION = ( |  | 
| 178     'This message represents a menu. Each of the items appears in sequence ' |  | 
| 179     '(some possibly within sub-menus) in the menu. The XX01XX placeholders ' |  | 
| 180     'serve to separate items. Each item contains an & (ampersand) character ' |  | 
| 181     'in front of the keystroke that should be used as a shortcut for that item ' |  | 
| 182     'in the menu. Please make sure that no two items in the same menu share ' |  | 
| 183     'the same shortcut.' |  | 
| 184   ) |  | 
| 185 |  | 
| 186   # A dandy regexp to suck all the IDs and translateables out of a menu |  | 
| 187   # resource |  | 
| 188   menu_re_ = lazy_re.compile(''' |  | 
| 189     # Match the MENU ID on the first line |  | 
| 190     ^(?P<id1>[A-Z0-9_]+)\s+MENU |  | 
| 191     | |  | 
| 192     # Match the translateable caption for a popup menu |  | 
| 193     POPUP\s+"(?P<text1>.*?([^"]|""))"\s |  | 
| 194     | |  | 
| 195     # Match the caption & ID of a MENUITEM |  | 
| 196     MENUITEM\s+"(?P<text2>.*?([^"]|""))"\s*,\s*(?P<id2>[A-Z0-9_]+) |  | 
| 197     ''', re.MULTILINE | re.VERBOSE) |  | 
| 198 |  | 
| 199   def Parse(self): |  | 
| 200     '''Knows how to parse menu resource sections.  Because it is important that |  | 
| 201     menu shortcuts are unique within the menu, we return each menu as a single |  | 
| 202     message with placeholders to break up the different menu items, rather than |  | 
| 203     return a single message per menu item.  we also add an automatic description |  | 
| 204     with instructions for the translators.''' |  | 
| 205     self.ReadSection() |  | 
| 206     self.single_message_ = tclib.Message(description=self.MENU_MESSAGE_DESCRIPTI
     ON) |  | 
| 207     self._RegExpParse(self.menu_re_, self.text_) |  | 
| 208 |  | 
| 209 |  | 
| 210 class Version(Section): |  | 
| 211   '''A resource section that contains a VERSIONINFO resource.''' |  | 
| 212 |  | 
| 213   # A typical version info resource can look like this: |  | 
| 214   # |  | 
| 215   # VS_VERSION_INFO VERSIONINFO |  | 
| 216   #  FILEVERSION 1,0,0,1 |  | 
| 217   #  PRODUCTVERSION 1,0,0,1 |  | 
| 218   #  FILEFLAGSMASK 0x3fL |  | 
| 219   # #ifdef _DEBUG |  | 
| 220   #  FILEFLAGS 0x1L |  | 
| 221   # #else |  | 
| 222   #  FILEFLAGS 0x0L |  | 
| 223   # #endif |  | 
| 224   #  FILEOS 0x4L |  | 
| 225   #  FILETYPE 0x2L |  | 
| 226   #  FILESUBTYPE 0x0L |  | 
| 227   # BEGIN |  | 
| 228   #     BLOCK "StringFileInfo" |  | 
| 229   #     BEGIN |  | 
| 230   #         BLOCK "040904e4" |  | 
| 231   #         BEGIN |  | 
| 232   #             VALUE "CompanyName", "TODO: <Company name>" |  | 
| 233   #             VALUE "FileDescription", "TODO: <File description>" |  | 
| 234   #             VALUE "FileVersion", "1.0.0.1" |  | 
| 235   #             VALUE "LegalCopyright", "TODO: (c) <Company name>.  All rights r
     eserved." |  | 
| 236   #             VALUE "InternalName", "res_format_test.dll" |  | 
| 237   #             VALUE "OriginalFilename", "res_format_test.dll" |  | 
| 238   #             VALUE "ProductName", "TODO: <Product name>" |  | 
| 239   #             VALUE "ProductVersion", "1.0.0.1" |  | 
| 240   #         END |  | 
| 241   #     END |  | 
| 242   #     BLOCK "VarFileInfo" |  | 
| 243   #     BEGIN |  | 
| 244   #         VALUE "Translation", 0x409, 1252 |  | 
| 245   #     END |  | 
| 246   # END |  | 
| 247   # |  | 
| 248   # |  | 
| 249   # In addition to the above fields, VALUE fields named "Comments" and |  | 
| 250   # "LegalTrademarks" may also be translateable. |  | 
| 251 |  | 
| 252   version_re_ = lazy_re.compile(''' |  | 
| 253     # Match the ID on the first line |  | 
| 254     ^(?P<id1>[A-Z0-9_]+)\s+VERSIONINFO |  | 
| 255     | |  | 
| 256     # Match all potentially translateable VALUE sections |  | 
| 257     \s+VALUE\s+" |  | 
| 258     ( |  | 
| 259       CompanyName|FileDescription|LegalCopyright| |  | 
| 260       ProductName|Comments|LegalTrademarks |  | 
| 261     )",\s+"(?P<text1>.*?([^"]|""))"\s |  | 
| 262     ''', re.MULTILINE | re.VERBOSE) |  | 
| 263 |  | 
| 264   def Parse(self): |  | 
| 265     '''Knows how to parse VERSIONINFO resource sections.''' |  | 
| 266     self.ReadSection() |  | 
| 267     self._RegExpParse(self.version_re_, self.text_) |  | 
| 268 |  | 
| 269   # TODO(joi) May need to override the Translate() method to change the |  | 
| 270   # "Translation" VALUE block to indicate the correct language code. |  | 
| 271 |  | 
| 272 |  | 
| 273 class RCData(Section): |  | 
| 274   '''A resource section that contains some data .''' |  | 
| 275 |  | 
| 276   # A typical rcdataresource section looks like this: |  | 
| 277   # |  | 
| 278   # IDR_BLAH        RCDATA      { 1, 2, 3, 4 } |  | 
| 279 |  | 
| 280   dialog_re_ = lazy_re.compile(''' |  | 
| 281     ^(?P<id1>[A-Z0-9_]+)\s+RCDATA\s+(DISCARDABLE)?\s+\{.*?\} |  | 
| 282     ''', re.MULTILINE | re.VERBOSE | re.DOTALL) |  | 
| 283 |  | 
| 284   def Parse(self): |  | 
| 285     '''Implementation for resource types w/braces (not BEGIN/END) |  | 
| 286     ''' |  | 
| 287     rc_text = self._LoadInputFile() |  | 
| 288 |  | 
| 289     out = '' |  | 
| 290     begin_count = 0 |  | 
| 291     openbrace_count = 0 |  | 
| 292     assert self.extkey |  | 
| 293     first_line_re = re.compile(r'\s*' + self.extkey + r'\b') |  | 
| 294     for line in rc_text.splitlines(True): |  | 
| 295       if out or first_line_re.match(line): |  | 
| 296         out += line |  | 
| 297 |  | 
| 298       # We stop once the braces balance (could happen in one line). |  | 
| 299       begin_count_was = begin_count |  | 
| 300       if len(out) > 0: |  | 
| 301         openbrace_count += line.count('{') |  | 
| 302         begin_count += line.count('{') |  | 
| 303         begin_count -= line.count('}') |  | 
| 304       if ((begin_count_was == 1 and begin_count == 0) or |  | 
| 305          (openbrace_count > 0 and begin_count == 0)): |  | 
| 306         break |  | 
| 307 |  | 
| 308     if len(out) == 0: |  | 
| 309       raise exception.SectionNotFound('%s in file %s' % (self.extkey, self.rc_fi
     le)) |  | 
| 310 |  | 
| 311     self.text_ = out |  | 
| 312 |  | 
| 313     self._RegExpParse(self.dialog_re_, out) |  | 
| 314 |  | 
| 315 |  | 
| 316 class Accelerators(Section): |  | 
| 317   '''An ACCELERATORS table. |  | 
| 318   ''' |  | 
| 319 |  | 
| 320   # A typical ACCELERATORS section looks like this: |  | 
| 321   # |  | 
| 322   # IDR_ACCELERATOR1 ACCELERATORS |  | 
| 323   # BEGIN |  | 
| 324   #   "^C",           ID_ACCELERATOR32770,    ASCII,  NOINVERT |  | 
| 325   #   "^V",           ID_ACCELERATOR32771,    ASCII,  NOINVERT |  | 
| 326   #   VK_INSERT,      ID_ACCELERATOR32772,    VIRTKEY, CONTROL, NOINVERT |  | 
| 327   # END |  | 
| 328 |  | 
| 329   accelerators_re_ = lazy_re.compile(''' |  | 
| 330     # Match the ID on the first line |  | 
| 331     ^(?P<id1>[A-Z0-9_]+)\s+ACCELERATORS\s+ |  | 
| 332     | |  | 
| 333     # Match accelerators specified as VK_XXX |  | 
| 334     \s+VK_[A-Z0-9_]+,\s*(?P<id2>[A-Z0-9_]+)\s*, |  | 
| 335     | |  | 
| 336     # Match accelerators specified as e.g. "^C" |  | 
| 337     \s+"[^"]*",\s+(?P<id3>[A-Z0-9_]+)\s*, |  | 
| 338     ''', re.MULTILINE | re.VERBOSE) |  | 
| 339 |  | 
| 340   def Parse(self): |  | 
| 341     '''Knows how to parse ACCELERATORS resource sections.''' |  | 
| 342     self.ReadSection() |  | 
| 343     self._RegExpParse(self.accelerators_re_, self.text_) |  | 
| OLD | NEW | 
|---|