| 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 |