# # Usage: # gen_xmlpool.py /path/to/t_option.h localedir lang lang lang ... # # For each given language, this script expects to find a .mo file at # `{localedir}/{language}/LC_MESSAGES/options.mo`. # from __future__ import print_function import sys import gettext import re # Path to t_options.h template_header_path = sys.argv[1] localedir = sys.argv[2] # List of supported languages languages = sys.argv[3:] # Escape special characters in C strings def escapeCString (s): escapeSeqs = {'\a' : '\\a', '\b' : '\\b', '\f' : '\\f', '\n' : '\\n', '\r' : '\\r', '\t' : '\\t', '\v' : '\\v', '\\' : '\\\\'} # " -> '' is a hack. Quotes (") aren't possible in XML attributes. # Better use Unicode characters for typographic quotes in option # descriptions and translations. i = 0 r = '' while i < len(s): # Special case: escape double quote with \u201c or \u201d, depending # on whether it's an open or close quote. This is needed because plain # double quotes are not possible in XML attributes. if s[i] == '"': if i == len(s)-1 or s[i+1].isspace(): # close quote q = u'\u201c' else: # open quote q = u'\u201d' r = r + q elif escapeSeqs.has_key(s[i]): r = r + escapeSeqs[s[i]] else: r = r + s[i] i = i + 1 return r # Expand escape sequences in C strings (needed for gettext lookup) def expandCString (s): escapeSeqs = {'a' : '\a', 'b' : '\b', 'f' : '\f', 'n' : '\n', 'r' : '\r', 't' : '\t', 'v' : '\v', '"' : '"', '\\' : '\\'} i = 0 escape = False hexa = False octa = False num = 0 digits = 0 r = '' while i < len(s): if not escape: if s[i] == '\\': escape = True else: r = r + s[i] elif hexa: if (s[i] >= '0' and s[i] <= '9') or \ (s[i] >= 'a' and s[i] <= 'f') or \ (s[i] >= 'A' and s[i] <= 'F'): num = num * 16 + int(s[i],16) digits = digits + 1 else: digits = 2 if digits >= 2: hexa = False escape = False r = r + chr(num) elif octa: if s[i] >= '0' and s[i] <= '7': num = num * 8 + int(s[i],8) digits = digits + 1 else: digits = 3 if digits >= 3: octa = False escape = False r = r + chr(num) else: if escapeSeqs.has_key(s[i]): r = r + escapeSeqs[s[i]] escape = False elif s[i] >= '0' and s[i] <= '7': octa = True num = int(s[i],8) if num <= 3: digits = 1 else: digits = 2 elif s[i] == 'x' or s[i] == 'X': hexa = True num = 0 digits = 0 else: r = r + s[i] escape = False i = i + 1 return r # Expand matches. The first match is always a DESC or DESC_BEGIN match. # Subsequent matches are ENUM matches. # # DESC, DESC_BEGIN format: \1 \2= \3 \4=gettext(" \5= \6=") \7 # ENUM format: \1 \2=gettext(" \3= \4=") \5 def expandMatches (matches, translations, end=None): assert len(matches) > 0 nTranslations = len(translations) i = 0 # Expand the description+enums for all translations for lang,trans in translations: i = i + 1 # Make sure that all but the last line of a simple description # are extended with a backslash. suffix = '' if len(matches) == 1 and i < len(translations) and \ not matches[0].expand (r'\7').endswith('\\'): suffix = ' \\' # Expand the description line. Need to use ugettext in order to allow # non-ascii unicode chars in the original English descriptions. text = escapeCString (trans.ugettext (unicode (expandCString ( matches[0].expand (r'\5')), "utf-8"))).encode("utf-8") print(matches[0].expand (r'\1' + lang + r'\3"' + text + r'"\7') + suffix) # Expand any subsequent enum lines for match in matches[1:]: text = escapeCString (trans.ugettext (unicode (expandCString ( match.expand (r'\3')), "utf-8"))).encode("utf-8") print(match.expand (r'\1"' + text + r'"\5')) # Expand description end if end: print(end, end='') # Compile a list of translation classes to all supported languages. # The first translation is always a NullTranslations. translations = [("en", gettext.NullTranslations())] for lang in languages: try: trans = gettext.translation ("options", localedir, [lang]) except IOError: sys.stderr.write ("Warning: language '%s' not found.\n" % lang) continue translations.append ((lang, trans)) # Regular expressions: reLibintl_h = re.compile (r'#\s*include\s*') reDESC = re.compile (r'(\s*DRI_CONF_DESC\s*\(\s*)([a-z]+)(\s*,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$') reDESC_BEGIN = re.compile (r'(\s*DRI_CONF_DESC_BEGIN\s*\(\s*)([a-z]+)(\s*,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$') reENUM = re.compile (r'(\s*DRI_CONF_ENUM\s*\([^,]+,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$') reDESC_END = re.compile (r'\s*DRI_CONF_DESC_END') # Print a header print("/***********************************************************************\n" \ " *** THIS FILE IS GENERATED AUTOMATICALLY. DON'T EDIT! ***\n" \ " ***********************************************************************/") # Process the options template and generate options.h with all # translations. template = file (template_header_path, "r") descMatches = [] for line in template: if len(descMatches) > 0: matchENUM = reENUM .match (line) matchDESC_END = reDESC_END.match (line) if matchENUM: descMatches.append (matchENUM) elif matchDESC_END: expandMatches (descMatches, translations, line) descMatches = [] else: sys.stderr.write ( "Warning: unexpected line inside description dropped:\n%s\n" \ % line) continue if reLibintl_h.search (line): # Ignore (comment out) #include print("/* %s * commented out by gen_xmlpool.py */" % line) continue matchDESC = reDESC .match (line) matchDESC_BEGIN = reDESC_BEGIN.match (line) if matchDESC: assert len(descMatches) == 0 expandMatches ([matchDESC], translations) elif matchDESC_BEGIN: assert len(descMatches) == 0 descMatches = [matchDESC_BEGIN] else: print(line, end='') if len(descMatches) > 0: sys.stderr.write ("Warning: unterminated description at end of file.\n") expandMatches (descMatches, translations)