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