Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(83)

Side by Side Diff: grit/gather/rc.py

Issue 7994004: Initial source commit to grit-i18n project. (Closed) Base URL: http://grit-i18n.googlecode.com/svn/trunk/
Patch Set: Created 9 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « grit/gather/policy_json_unittest.py ('k') | grit/gather/rc_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(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|\\|\&nbsp\;')
30
31 # How to escape certain characters
32 _ESCAPE_CHARS = {
33 '"' : '""',
34 '\n' : '\\n',
35 '\t' : '\\t',
36 '\\' : '\\\\',
37 '&nbsp;' : ' '
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
OLDNEW
« no previous file with comments | « grit/gather/policy_json_unittest.py ('k') | grit/gather/rc_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698