fix git support for v1.5.3 (or higher) by setting "--work-tree"
[translate_toolkit.git] / storage / rc.py
blob41b20dc3a17a4427d39cc44005752771f693b524
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright 2004-2006, 2008 Zuza Software Foundation
5 #
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.
27 """
29 from translate.storage import base
30 import re
31 import sys
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
40 return pystring
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)
47 return 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)
54 self.name = ""
55 self._value = ""
56 self.comments = []
57 self.source = source
58 self.match = None
60 def setsource(self, source):
61 """Sets the source AND the target to be equal"""
62 self._value = source or ""
64 def getsource(self):
65 return self._value
67 source = property(getsource, setsource)
69 def settarget(self, target):
70 """Note: this also sets the .source attribute!"""
71 self.source = target
73 def gettarget(self):
74 return self.source
75 target = property(gettarget, settarget)
77 def __str__(self):
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"))
82 return source
84 def getoutput(self):
85 """convert the element back into formatted lines for a .rc file"""
86 if self.isblank():
87 return "".join(self.comments + ["\n"])
88 else:
89 return "".join(self.comments + ["%s=%s\n" % (self.name, self.value)])
91 def getlocations(self):
92 return [self.name]
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):
101 self.comments = []
103 def isblank(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"""
109 UnitClass = rcunit
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()
116 inputfile.close()
117 self.parse(rcsrc)
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]|
130 \s*[\n] # Whitespace
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
150 (?:\s*,?\s*)?
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']:
163 continue
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"):
169 #print "language"
170 if not languagesection:
171 languagesection = True
172 else:
173 print >> sys.stderr, "Can only process one language section"
174 self.blocks = self.blocks[:blocknum]
175 break
176 if block.startswith("/*"): # Comments
177 #print "comment"
178 pass
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']:
186 continue
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)
193 elif name == "-1":
194 newunit.name = "%s.%s.%s.%s" % (dialogtype, dialogname, type, value.replace(" ", "_"))
195 else:
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']:
204 continue
205 type = match.groupdict()['type']
206 value = match.groupdict()['value']
207 name = match.groupdict()['name']
208 newunit = rcunit(escape_to_python(value))
209 if type == "POPUP":
210 newunit.name = "MENU.%s.%s" % (menuname, type)
211 elif name == "-1":
212 newunit.name = "MENU.%s.%s.%s" % (menuname, type, value.replace(" ", "_"))
213 else:
214 newunit.name = "MENU.%s.%s.%s" % (menuname, type, name)
215 newunit.match = match
216 self.addunit(newunit)
218 def __str__(self):
219 """convert the units back to lines"""
220 return "".join(self.blocks)