2 # -*- coding: utf-8 -*-
4 # Copyright 2004-2006, 2008 Zuza Software Foundation
6 # This file is part of translate.
8 # translate is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # translate is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with translate; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 """classes that hold units of .rc files (rcunit) or entire files
23 (rcfile) these files are used in translating Windows Resources
25 @note: This implementation is based mostly on observing WINE .rc files,
26 these should mimic other non-WINE .rc files.
29 from translate
.storage
import base
33 def escape_to_python(string
):
34 """escape a given .rc string into a valid Python string"""
35 pystring
= re
.sub('"\s*\\\\\n\s*"', "", string
) # xxx"\n"xxx line continuation
36 pystring
= re
.sub("\\\\\\\n", "", pystring
) # backslash newline line continuation
37 pystring
= re
.sub("\\\\n", "\n", pystring
) # Convert escaped newline to a real newline
38 pystring
= re
.sub("\\\\t", "\t", pystring
) # Convert escape tab to a real tab
39 pystring
= re
.sub("\\\\\\\\", "\\\\", pystring
) # Convert escape backslash to a real escaped backslash
42 def escape_to_rc(string
):
43 """escape a given Python string into a valid .rc string"""
44 rcstring
= re
.sub("\\\\", "\\\\\\\\", string
)
45 rcstring
= re
.sub("\t", "\\\\t", rcstring
)
46 rcstring
= re
.sub("\n", "\\\\n", rcstring
)
49 class rcunit(base
.TranslationUnit
):
50 """a unit of an rc file"""
51 def __init__(self
, source
=""):
52 """construct a blank rcunit"""
53 super(rcunit
, self
).__init
__(source
)
60 def setsource(self
, source
):
61 """Sets the source AND the target to be equal"""
62 self
._value
= source
or ""
67 source
= property(getsource
, setsource
)
69 def settarget(self
, target
):
70 """Note: this also sets the .source attribute!"""
75 target
= property(gettarget
, settarget
)
78 """convert to a string. double check that unicode is handled somehow here"""
79 source
= self
.getoutput()
80 if isinstance(source
, unicode):
81 return source
.encode(getattr(self
, "encoding", "UTF-8"))
85 """convert the element back into formatted lines for a .rc file"""
87 return "".join(self
.comments
+ ["\n"])
89 return "".join(self
.comments
+ ["%s=%s\n" % (self
.name
, self
.value
)])
91 def getlocations(self
):
94 def addnote(self
, note
, origin
=None):
95 self
.comments
.append(note
)
97 def getnotes(self
, origin
=None):
98 return '\n'.join(self
.comments
)
100 def removenotes(self
):
104 """returns whether this is a blank element, containing only comments..."""
105 return not (self
.name
or self
.value
)
107 class rcfile(base
.TranslationStore
):
108 """this class represents a .rc file, made up of rcunits"""
110 def __init__(self
, inputfile
=None):
111 """construct an rcfile, optionally reading in from inputfile"""
112 super(rcfile
, self
).__init
__(unitclass
= self
.UnitClass
)
113 self
.filename
= getattr(inputfile
, 'name', '')
114 if inputfile
is not None:
115 rcsrc
= inputfile
.read()
119 def parse(self
, rcsrc
):
120 """read the source of a .rc file in and include them as units"""
121 BLOCKS_RE
= re
.compile("""
123 LANGUAGE\s+[^\n]*| # Language details
124 /\*.*?\*/[^\n]*| # Comments
125 (?:[0-9A-Z_]+\s+(?:MENU|DIALOG|DIALOGEX)|STRINGTABLE)\s # Translatable section
128 BEGIN(?:\s*?POPUP.*?BEGIN.*?END\s*?)+?END|BEGIN.*?END| # FIXME Need a much better approach to nesting menus
129 {(?:\s*?POPUP.*?{.*?}\s*?)+?}|{.*?})+[\n]|
132 """, re
.DOTALL
+ re
.VERBOSE
)
133 STRINGTABLE_RE
= re
.compile("""
134 (?P<name>[0-9A-Za-z_]+?),?\s*
135 L?"(?P<value>.*?)"\s*[\n]
136 """, re
.DOTALL
+ re
.VERBOSE
)
137 DIALOG_RE
= re
.compile("""
138 (?P<type>AUTOCHECKBOX|AUTORADIOBUTTON|CAPTION|Caption|CHECKBOX|CTEXT|CONTROL|DEFPUSHBUTTON|
139 GROUPBOX|LTEXT|PUSHBUTTON|RADIOBUTTON|RTEXT) # Translatable types
141 L? # Unkown prefix see ./dlls/shlwapi/shlwapi_En.rc
142 "(?P<value>.*?)" # String value
143 (?:\s*,\s*|[\n]) # FIXME ./dlls/mshtml/En.rc ID_DWL_DIALOG.LTEXT.ID_DWL_STATUS
144 (?P<name>.*?|)\s*(?:/[*].*?[*]/|),
145 """, re
.DOTALL
+ re
.VERBOSE
)
146 MENU_RE
= re
.compile("""
147 (?P<type>POPUP|MENUITEM)
149 "(?P<value>.*?)" # String value
151 (?P<name>[^\s]+).*?[\n]
152 """, re
.DOTALL
+ re
.VERBOSE
)
153 languagesection
= False
155 self
.blocks
= BLOCKS_RE
.findall(rcsrc
)
157 for blocknum
, block
in enumerate(self
.blocks
):
158 #print block.split("\n")[0]
159 if block
.startswith("STRINGTABLE"):
160 #print "stringtable:\n %s------\n" % block
161 for match
in STRINGTABLE_RE
.finditer(block
):
162 if not match
.groupdict()['value']:
164 newunit
= rcunit(escape_to_python(match
.groupdict()['value']))
165 newunit
.name
= "STRINGTABLE." + match
.groupdict()['name']
166 newunit
.match
= match
167 self
.addunit(newunit
)
168 if block
.startswith("LANGUAGE"):
170 if not languagesection
:
171 languagesection
= True
173 print >> sys
.stderr
, "Can only process one language section"
174 self
.blocks
= self
.blocks
[:blocknum
]
176 if block
.startswith("/*"): # Comments
179 if re
.match("[0-9A-Z_]+\s+DIALOG", block
) is not None:
180 dialog
= re
.match("(?P<dialogname>[0-9A-Z_]+)\s+(?P<dialogtype>DIALOGEX|DIALOG)", block
).groupdict()
181 dialogname
= dialog
["dialogname"]
182 dialogtype
= dialog
["dialogtype"]
183 #print "dialog: %s" % dialogname
184 for match
in DIALOG_RE
.finditer(block
):
185 if not match
.groupdict()['value']:
187 type = match
.groupdict()['type']
188 value
= match
.groupdict()['value']
189 name
= match
.groupdict()['name']
190 newunit
= rcunit(escape_to_python(value
))
191 if type == "CAPTION" or type == "Caption":
192 newunit
.name
= "%s.%s.%s" % (dialogtype
, dialogname
, type)
194 newunit
.name
= "%s.%s.%s.%s" % (dialogtype
, dialogname
, type, value
.replace(" ", "_"))
196 newunit
.name
= "%s.%s.%s.%s" % (dialogtype
, dialogname
, type, name
)
197 newunit
.match
= match
198 self
.addunit(newunit
)
199 if re
.match("[0-9A-Z_]+\s+MENU", block
) is not None:
200 menuname
= re
.match("(?P<menuname>[0-9A-Z_]+)\s+MENU", block
).groupdict()["menuname"]
201 #print "menu: %s" % menuname
202 for match
in DIALOG_RE
.finditer(block
):
203 if not match
.groupdict()['value']:
205 type = match
.groupdict()['type']
206 value
= match
.groupdict()['value']
207 name
= match
.groupdict()['name']
208 newunit
= rcunit(escape_to_python(value
))
210 newunit
.name
= "MENU.%s.%s" % (menuname
, type)
212 newunit
.name
= "MENU.%s.%s.%s" % (menuname
, type, value
.replace(" ", "_"))
214 newunit
.name
= "MENU.%s.%s.%s" % (menuname
, type, name
)
215 newunit
.match
= match
216 self
.addunit(newunit
)
219 """convert the units back to lines"""
220 return "".join(self
.blocks
)