1b8e80941Smrg# encoding=utf-8 2b8e80941Smrg# 3b8e80941Smrg# Usage: 4b8e80941Smrg# gen_xmlpool.py /path/to/t_option.h localedir lang lang lang ... 5b8e80941Smrg# 6b8e80941Smrg# For each given language, this script expects to find a .mo file at 7b8e80941Smrg# `{localedir}/{language}/LC_MESSAGES/options.mo`. 8b8e80941Smrg# 9b8e80941Smrg 10b8e80941Smrgfrom __future__ import print_function, unicode_literals 11b8e80941Smrgimport argparse 12b8e80941Smrgimport gettext 13b8e80941Smrgimport io 14b8e80941Smrgimport os 15b8e80941Smrgimport re 16b8e80941Smrgimport sys 17b8e80941Smrg 18b8e80941Smrgif sys.version_info < (3, 0): 19b8e80941Smrg gettext_method = 'ugettext' 20b8e80941Smrgelse: 21b8e80941Smrg gettext_method = 'gettext' 22b8e80941Smrg 23b8e80941Smrg# Escape special characters in C strings 24b8e80941Smrgdef escapeCString(s): 25b8e80941Smrg escapeSeqs = {'\a' : '\\a', '\b' : '\\b', '\f' : '\\f', '\n' : '\\n', 26b8e80941Smrg '\r' : '\\r', '\t' : '\\t', '\v' : '\\v', '\\' : '\\\\'} 27b8e80941Smrg # " -> '' is a hack. Quotes (") aren't possible in XML attributes. 28b8e80941Smrg # Better use Unicode characters for typographic quotes in option 29b8e80941Smrg # descriptions and translations. 30b8e80941Smrg last_quote = '”' 31b8e80941Smrg i = 0 32b8e80941Smrg r = '' 33b8e80941Smrg for c in s: 34b8e80941Smrg # Special case: escape double quote with “ or ”, depending 35b8e80941Smrg # on whether it's an open or close quote. This is needed because plain 36b8e80941Smrg # double quotes are not possible in XML attributes. 37b8e80941Smrg if c == '"': 38b8e80941Smrg if last_quote == '”': 39b8e80941Smrg q = '“' 40b8e80941Smrg else: 41b8e80941Smrg q = '”' 42b8e80941Smrg last_quote = q 43b8e80941Smrg r = r + q 44b8e80941Smrg elif c in escapeSeqs: 45b8e80941Smrg r = r + escapeSeqs[c] 46b8e80941Smrg else: 47b8e80941Smrg r = r + c 48b8e80941Smrg return r 49b8e80941Smrg 50b8e80941Smrg# Expand escape sequences in C strings (needed for gettext lookup) 51b8e80941Smrgdef expandCString(s): 52b8e80941Smrg escapeSeqs = {'a' : '\a', 'b' : '\b', 'f' : '\f', 'n' : '\n', 53b8e80941Smrg 'r' : '\r', 't' : '\t', 'v' : '\v', 54b8e80941Smrg '"' : '"', '\\' : '\\'} 55b8e80941Smrg escape = False 56b8e80941Smrg hexa = False 57b8e80941Smrg octa = False 58b8e80941Smrg num = 0 59b8e80941Smrg digits = 0 60b8e80941Smrg r = u'' 61b8e80941Smrg for c in s: 62b8e80941Smrg if not escape: 63b8e80941Smrg if c == '\\': 64b8e80941Smrg escape = True 65b8e80941Smrg else: 66b8e80941Smrg r = r + c 67b8e80941Smrg elif hexa: 68b8e80941Smrg if (c >= '0' and c <= '9') or \ 69b8e80941Smrg (c >= 'a' and c <= 'f') or \ 70b8e80941Smrg (c >= 'A' and c <= 'F'): 71b8e80941Smrg num = num * 16 + int(c, 16) 72b8e80941Smrg digits = digits + 1 73b8e80941Smrg else: 74b8e80941Smrg digits = 2 75b8e80941Smrg if digits >= 2: 76b8e80941Smrg hexa = False 77b8e80941Smrg escape = False 78b8e80941Smrg r = r + chr(num) 79b8e80941Smrg elif octa: 80b8e80941Smrg if c >= '0' and c <= '7': 81b8e80941Smrg num = num * 8 + int(c, 8) 82b8e80941Smrg digits = digits + 1 83b8e80941Smrg else: 84b8e80941Smrg digits = 3 85b8e80941Smrg if digits >= 3: 86b8e80941Smrg octa = False 87b8e80941Smrg escape = False 88b8e80941Smrg r = r + chr(num) 89b8e80941Smrg else: 90b8e80941Smrg if c in escapeSeqs: 91b8e80941Smrg r = r + escapeSeqs[c] 92b8e80941Smrg escape = False 93b8e80941Smrg elif c >= '0' and c <= '7': 94b8e80941Smrg octa = True 95b8e80941Smrg num = int(c, 8) 96b8e80941Smrg if num <= 3: 97b8e80941Smrg digits = 1 98b8e80941Smrg else: 99b8e80941Smrg digits = 2 100b8e80941Smrg elif c == 'x' or c == 'X': 101b8e80941Smrg hexa = True 102b8e80941Smrg num = 0 103b8e80941Smrg digits = 0 104b8e80941Smrg else: 105b8e80941Smrg r = r + c 106b8e80941Smrg escape = False 107b8e80941Smrg return r 108b8e80941Smrg 109b8e80941Smrg# Expand matches. The first match is always a DESC or DESC_BEGIN match. 110b8e80941Smrg# Subsequent matches are ENUM matches. 111b8e80941Smrg# 112b8e80941Smrg# DESC, DESC_BEGIN format: \1 \2=<lang> \3 \4=gettext(" \5=<text> \6=") \7 113b8e80941Smrg# ENUM format: \1 \2=gettext(" \3=<text> \4=") \5 114b8e80941Smrgdef expandMatches(matches, translations, outfile, end=None): 115b8e80941Smrg assert len(matches) > 0 116b8e80941Smrg nTranslations = len(translations) 117b8e80941Smrg i = 0 118b8e80941Smrg # Expand the description+enums for all translations 119b8e80941Smrg for lang,trans in translations: 120b8e80941Smrg i = i + 1 121b8e80941Smrg # Make sure that all but the last line of a simple description 122b8e80941Smrg # are extended with a backslash. 123b8e80941Smrg suffix = '' 124b8e80941Smrg if len(matches) == 1 and i < len(translations) and \ 125b8e80941Smrg not matches[0].expand(r'\7').endswith('\\'): 126b8e80941Smrg suffix = ' \\' 127b8e80941Smrg text = escapeCString(getattr(trans, gettext_method)(expandCString( 128b8e80941Smrg matches[0].expand (r'\5')))) 129b8e80941Smrg text = (matches[0].expand(r'\1' + lang + r'\3"' + text + r'"\7') + suffix) 130b8e80941Smrg 131b8e80941Smrg outfile.write(text + '\n') 132b8e80941Smrg 133b8e80941Smrg # Expand any subsequent enum lines 134b8e80941Smrg for match in matches[1:]: 135b8e80941Smrg text = escapeCString(getattr(trans, gettext_method)(expandCString( 136b8e80941Smrg match.expand(r'\3')))) 137b8e80941Smrg text = match.expand(r'\1"' + text + r'"\5') 138b8e80941Smrg 139b8e80941Smrg outfile.write(text + '\n') 140b8e80941Smrg 141b8e80941Smrg # Expand description end 142b8e80941Smrg if end: 143b8e80941Smrg outfile.write(end) 144b8e80941Smrg 145b8e80941Smrg# Regular expressions: 146b8e80941SmrgreLibintl_h = re.compile(r'#\s*include\s*<libintl.h>') 147b8e80941SmrgreDESC = re.compile(r'(\s*DRI_CONF_DESC\s*\(\s*)([a-z]+)(\s*,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$') 148b8e80941SmrgreDESC_BEGIN = re.compile(r'(\s*DRI_CONF_DESC_BEGIN\s*\(\s*)([a-z]+)(\s*,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$') 149b8e80941SmrgreENUM = re.compile(r'(\s*DRI_CONF_ENUM\s*\([^,]+,\s*)(gettext\s*\(\s*")(.*)("\s*\))(\s*\)[ \t]*\\?)$') 150b8e80941SmrgreDESC_END = re.compile(r'\s*DRI_CONF_DESC_END') 151b8e80941Smrg 152b8e80941Smrg 153b8e80941Smrgdef main(): 154b8e80941Smrg parser = argparse.ArgumentParser() 155b8e80941Smrg parser.add_argument('--template', required=True) 156b8e80941Smrg parser.add_argument('--output', required=True) 157b8e80941Smrg parser.add_argument('--localedir', required=True) 158b8e80941Smrg parser.add_argument('--languages', nargs='*', default=[]) 159b8e80941Smrg args = parser.parse_args() 160b8e80941Smrg 161b8e80941Smrg # Compile a list of translation classes to all supported languages. 162b8e80941Smrg # The first translation is always a NullTranslations. 163b8e80941Smrg translations = [("en", gettext.NullTranslations())] 164b8e80941Smrg for lang in args.languages: 165b8e80941Smrg try: 166b8e80941Smrg filename = os.path.join(args.localedir, '{}.gmo'.format(lang)) 167b8e80941Smrg with io.open(filename, 'rb') as f: 168b8e80941Smrg trans = gettext.GNUTranslations(f) 169b8e80941Smrg except (IOError, OSError): 170b8e80941Smrg print("Warning: language '%s' not found." % lang, file=sys.stderr) 171b8e80941Smrg continue 172b8e80941Smrg translations.append((lang, trans)) 173b8e80941Smrg 174b8e80941Smrg with io.open(args.output, mode='wt', encoding='utf-8') as output: 175b8e80941Smrg output.write("/* This is file is generated automatically. Don't edit! */\n") 176b8e80941Smrg 177b8e80941Smrg # Process the options template and generate options.h with all 178b8e80941Smrg # translations. 179b8e80941Smrg with io.open(args.template, mode="rt", encoding='utf-8') as template: 180b8e80941Smrg descMatches = [] 181b8e80941Smrg for line in template: 182b8e80941Smrg if descMatches: 183b8e80941Smrg matchENUM = reENUM.match(line) 184b8e80941Smrg matchDESC_END = reDESC_END.match(line) 185b8e80941Smrg if matchENUM: 186b8e80941Smrg descMatches.append(matchENUM) 187b8e80941Smrg elif matchDESC_END: 188b8e80941Smrg expandMatches(descMatches, translations, output, line) 189b8e80941Smrg descMatches = [] 190b8e80941Smrg else: 191b8e80941Smrg print("Warning: unexpected line inside description dropped:\n", 192b8e80941Smrg line, file=sys.stderr) 193b8e80941Smrg continue 194b8e80941Smrg if reLibintl_h.search(line): 195b8e80941Smrg # Ignore (comment out) #include <libintl.h> 196b8e80941Smrg output.write("/* %s * commented out by gen_xmlpool.py */\n" % line) 197b8e80941Smrg continue 198b8e80941Smrg matchDESC = reDESC.match(line) 199b8e80941Smrg matchDESC_BEGIN = reDESC_BEGIN.match(line) 200b8e80941Smrg if matchDESC: 201b8e80941Smrg assert not descMatches 202b8e80941Smrg expandMatches([matchDESC], translations, output) 203b8e80941Smrg elif matchDESC_BEGIN: 204b8e80941Smrg assert not descMatches 205b8e80941Smrg descMatches = [matchDESC_BEGIN] 206b8e80941Smrg else: 207b8e80941Smrg 208b8e80941Smrg output.write(line) 209b8e80941Smrg 210b8e80941Smrg if descMatches: 211b8e80941Smrg print("Warning: unterminated description at end of file.", file=sys.stderr) 212b8e80941Smrg expandMatches(descMatches, translations, output) 213b8e80941Smrg 214b8e80941Smrg 215b8e80941Smrgif __name__ == '__main__': 216b8e80941Smrg main() 217