#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2004-2006,2008-2009 Zuza Software Foundation # # This file is part of the Translate Toolkit. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . """Classes that hold units of .rc files (rcunit) or entire files (rcfile) used in translating Windows Resources. @note: This implementation is based mostly on observing WINE .rc files, these should mimic other non-WINE .rc files. """ from translate.storage import base import re def escape_to_python(string): """escape a given .rc string into a valid Python string""" pystring = re.sub('"\s*\\\\\n\s*"', "", string) # xxx"\n"xxx line continuation pystring = re.sub("\\\\\\\n", "", pystring) # backslash newline line continuation pystring = re.sub("\\\\n", "\n", pystring) # Convert escaped newline to a real newline pystring = re.sub("\\\\t", "\t", pystring) # Convert escape tab to a real tab pystring = re.sub("\\\\\\\\", "\\\\", pystring) # Convert escape backslash to a real escaped backslash return pystring def escape_to_rc(string): """Escape a given Python string into a valid .rc string.""" rcstring = re.sub("\\\\", "\\\\\\\\", string) rcstring = re.sub("\t", "\\\\t", rcstring) rcstring = re.sub("\n", "\\\\n", rcstring) return rcstring class rcunit(base.TranslationUnit): """A unit of an rc file""" def __init__(self, source=""): """Construct a blank rcunit.""" super(rcunit, self).__init__(source) self.name = "" self._value = "" self.comments = [] self.source = source self.match = None def setsource(self, source): """Sets the source AND the target to be equal""" self._value = source or "" def getsource(self): return self._value source = property(getsource, setsource) def settarget(self, target): """Note: this also sets the .source attribute!""" self.source = target def gettarget(self): return self.source target = property(gettarget, settarget) def __str__(self): """Convert to a string. Double check that unicode is handled somehow here.""" source = self.getoutput() if isinstance(source, unicode): return source.encode(getattr(self, "encoding", "UTF-8")) return source def getoutput(self): """Convert the element back into formatted lines for a .rc file.""" if self.isblank(): return "".join(self.comments + ["\n"]) else: return "".join(self.comments + ["%s=%s\n" % (self.name, self.value)]) def getlocations(self): return [self.name] def addnote(self, note, origin=None): self.comments.append(note) def getnotes(self, origin=None): return '\n'.join(self.comments) def removenotes(self): self.comments = [] def isblank(self): """Returns whether this is a blank element, containing only comments.""" return not (self.name or self.value) class rcfile(base.TranslationStore): """This class represents a .rc file, made up of rcunits.""" UnitClass = rcunit def __init__(self, inputfile=None, lang=None, sublang=None): """Construct an rcfile, optionally reading in from inputfile.""" super(rcfile, self).__init__(unitclass = self.UnitClass) self.filename = getattr(inputfile, 'name', '') self.lang = lang self.sublang = sublang if inputfile is not None: rcsrc = inputfile.read() inputfile.close() self.parse(rcsrc) def parse(self, rcsrc): """Read the source of a .rc file in and include them as units.""" BLOCKS_RE = re.compile(""" (?: LANGUAGE\s+[^\n]*| # Language details /\*.*?\*/[^\n]*| # Comments (?:[0-9A-Z_]+\s+(?:MENU|DIALOG|DIALOGEX)|STRINGTABLE)\s # Translatable section .*? (?: BEGIN(?:\s*?POPUP.*?BEGIN.*?END\s*?)+?END|BEGIN.*?END| # FIXME Need a much better approach to nesting menus {(?:\s*?POPUP.*?{.*?}\s*?)+?}|{.*?})+[\n]| \s*[\n] # Whitespace ) """, re.DOTALL + re.VERBOSE) STRINGTABLE_RE = re.compile(""" (?P[0-9A-Za-z_]+?),?\s* L?"(?P.*?)"\s*[\n] """, re.DOTALL + re.VERBOSE) DIALOG_RE = re.compile(""" (?PAUTOCHECKBOX|AUTORADIOBUTTON|CAPTION|Caption|CHECKBOX|CTEXT|CONTROL|DEFPUSHBUTTON| GROUPBOX|LTEXT|PUSHBUTTON|RADIOBUTTON|RTEXT) # Translatable types \s+ L? # Unkown prefix see ./dlls/shlwapi/shlwapi_En.rc "(?P.*?)" # String value (?:\s*,\s*|[\n]) # FIXME ./dlls/mshtml/En.rc ID_DWL_DIALOG.LTEXT.ID_DWL_STATUS (?P.*?|)\s*(?:/[*].*?[*]/|), """, re.DOTALL + re.VERBOSE) MENU_RE = re.compile(""" (?PPOPUP|MENUITEM) \s+ "(?P.*?)" # String value (?:\s*,?\s*)? (?P[^\s]+).*?[\n] """, re.DOTALL + re.VERBOSE) processsection = False self.blocks = BLOCKS_RE.findall(rcsrc) for blocknum, block in enumerate(self.blocks): #print block.split("\n")[0] processblock = None if block.startswith("LANGUAGE"): if self.lang == None or self.sublang == None or re.match("LANGUAGE\s+%s,\s*%s\s*$" % (self.lang, self.sublang), block) is not None: processsection = True else: processsection = False else: if re.match(".+LANGUAGE\s+[0-9A-Za-z_]+,\s*[0-9A-Za-z_]+\s*[\n]", block, re.DOTALL) is not None: if re.match(".+LANGUAGE\s+%s,\s*%s\s*[\n]" % (self.lang, self.sublang), block, re.DOTALL) is not None: processblock = True else: processblock = False if not (processblock == True or (processsection == True and processblock != False)): continue if block.startswith("STRINGTABLE"): #print "stringtable:\n %s------\n" % block for match in STRINGTABLE_RE.finditer(block): if not match.groupdict()['value']: continue newunit = rcunit(escape_to_python(match.groupdict()['value'])) newunit.name = "STRINGTABLE." + match.groupdict()['name'] newunit.match = match self.addunit(newunit) if block.startswith("/*"): # Comments #print "comment" pass if re.match("[0-9A-Z_]+\s+DIALOG", block) is not None: dialog = re.match("(?P[0-9A-Z_]+)\s+(?PDIALOGEX|DIALOG)", block).groupdict() dialogname = dialog["dialogname"] dialogtype = dialog["dialogtype"] #print "dialog: %s" % dialogname for match in DIALOG_RE.finditer(block): if not match.groupdict()['value']: continue type = match.groupdict()['type'] value = match.groupdict()['value'] name = match.groupdict()['name'] newunit = rcunit(escape_to_python(value)) if type == "CAPTION" or type == "Caption": newunit.name = "%s.%s.%s" % (dialogtype, dialogname, type) elif name == "-1": newunit.name = "%s.%s.%s.%s" % (dialogtype, dialogname, type, value.replace(" ", "_")) else: newunit.name = "%s.%s.%s.%s" % (dialogtype, dialogname, type, name) newunit.match = match self.addunit(newunit) if re.match("[0-9A-Z_]+\s+MENU", block) is not None: menuname = re.match("(?P[0-9A-Z_]+)\s+MENU", block).groupdict()["menuname"] #print "menu: %s" % menuname for match in MENU_RE.finditer(block): if not match.groupdict()['value']: continue type = match.groupdict()['type'] value = match.groupdict()['value'] name = match.groupdict()['name'] newunit = rcunit(escape_to_python(value)) if type == "POPUP": newunit.name = "MENU.%s.%s" % (menuname, type) elif name == "-1": newunit.name = "MENU.%s.%s.%s" % (menuname, type, value.replace(" ", "_")) else: newunit.name = "MENU.%s.%s.%s" % (menuname, type, name) newunit.match = match self.addunit(newunit) def __str__(self): """convert the units back to lines""" return "".join(self.blocks)