1#!/usr/bin/env python 2# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Produces localized strings.xml files for Android. 7 8In cases where an "android" type output file is requested in a grd, the classes 9in android_xml will process the messages and translations to produce a valid 10strings.xml that is properly localized with the specified language. 11 12For example if the following output tag were to be included in a grd file 13 <outputs> 14 ... 15 <output filename="values-es/strings.xml" type="android" lang="es" /> 16 ... 17 </outputs> 18 19for a grd file with the following messages: 20 21 <message name="IDS_HELLO" desc="Simple greeting">Hello</message> 22 <message name="IDS_WORLD" desc="The world">world</message> 23 24and there existed an appropriate xtb file containing the Spanish translations, 25then the output would be: 26 27 <?xml version="1.0" encoding="utf-8"?> 28 <resources xmlns:android="http://schemas.android.com/apk/res/android"> 29 <string name="hello">"Hola"</string> 30 <string name="world">"mundo"</string> 31 </resources> 32 33which would be written to values-es/strings.xml and usable by the Android 34resource framework. 35 36Advanced usage 37-------------- 38 39To process only certain messages in a grd file, tag each desired message by 40adding "android_java" to formatter_data. Then set the environmental variable 41ANDROID_JAVA_TAGGED_ONLY to "true" when building the grd file. For example: 42 43 <message name="IDS_HELLO" formatter_data="android_java">Hello</message> 44 45To specify the product attribute to be added to a <string> element, add 46"android_java_product" to formatter_data. "android_java_name" can be used to 47override the name in the <string> element. For example, 48 49 <message name="IDS_FOO_NOSDCARD" formatter_data="android_java_product=nosdcard 50 android_java_name=foo">no card</message> 51 <message name="IDS_FOO_DEFAULT" formatter_data="android_java_product=default 52 android_java_name=foo">has card</message> 53 54would generate 55 56 <string name="foo" product="nosdcard">"no card"</string> 57 <string name="foo" product="default">"has card"</string> 58""" 59 60import os 61import types 62import xml.sax.saxutils 63 64from grit import lazy_re 65from grit.node import message 66 67 68# When this environmental variable has value "true", only tagged messages will 69# be outputted. 70_TAGGED_ONLY_ENV_VAR = 'ANDROID_JAVA_TAGGED_ONLY' 71_TAGGED_ONLY_DEFAULT = False 72 73# In tagged-only mode, only messages with this tag will be ouputted. 74_EMIT_TAG = 'android_java' 75 76# This tag controls the product attribute of the generated <string> element. 77_PRODUCT_TAG = 'android_java_product' 78 79# This tag controls the name attribute of the generated <string> element. 80_NAME_TAG = 'android_java_name' 81 82# The Android resource name and optional product information are placed 83# in the grd string name because grd doesn't know about Android product 84# information. 85# TODO(newt): Don't allow product information in mangled names, since it can now 86# be specified using "android_java_product" in formatter_data. 87_NAME_PATTERN = lazy_re.compile( 88 'IDS_(?P<name>[A-Z0-9_]+)(_product_(?P<product>[a-z]+))?\Z') 89 90 91# In most cases we only need a name attribute and string value. 92_SIMPLE_TEMPLATE = u'<string name="%s">%s</string>\n' 93 94 95# In a few cases a product attribute is needed. 96_PRODUCT_TEMPLATE = u'<string name="%s" product="%s">%s</string>\n' 97 98 99def Format(root, lang='en', output_dir='.'): 100 yield ('<?xml version="1.0" encoding="utf-8"?>\n' 101 '<resources ' 102 'xmlns:android="http://schemas.android.com/apk/res/android">\n') 103 104 tagged_only = _TAGGED_ONLY_DEFAULT 105 if _TAGGED_ONLY_ENV_VAR in os.environ: 106 tagged_only = os.environ[_TAGGED_ONLY_ENV_VAR].lower() 107 if tagged_only == 'true': 108 tagged_only = True 109 elif tagged_only == 'false': 110 tagged_only = False 111 else: 112 raise Exception('env variable ANDROID_JAVA_TAGGED_ONLY must have value ' 113 'true or false. Invalid value: %s' % tagged_only) 114 115 for item in root.ActiveDescendants(): 116 with item: 117 if ShouldOutputNode(item, tagged_only): 118 yield _FormatMessage(item, lang) 119 120 yield '</resources>\n' 121 122 123def ShouldOutputNode(node, tagged_only): 124 """Returns true if node should be outputted. 125 126 Args: 127 node: a Node from the grd dom 128 tagged_only: true, if only tagged messages should be outputted 129 """ 130 return (isinstance(node, message.MessageNode) and 131 (not tagged_only or _EMIT_TAG in node.formatter_data)) 132 133 134def _FormatMessage(item, lang): 135 """Writes out a single string as a <resource/> element.""" 136 137 value = item.ws_at_start + item.Translate(lang) + item.ws_at_end 138 # Replace < > & with < > & to ensure we generate valid XML and 139 # replace ' " with \' \" to conform to Android's string formatting rules. 140 value = xml.sax.saxutils.escape(value, {"'": "\\'", '"': '\\"'}) 141 # Wrap the string in double quotes to preserve whitespace. 142 value = '"' + value + '"' 143 144 mangled_name = item.GetTextualIds()[0] 145 match = _NAME_PATTERN.match(mangled_name) 146 if not match: 147 raise Exception('Unexpected resource name: %s' % mangled_name) 148 name = match.group('name').lower() 149 product = match.group('product') 150 151 # Override product or name with values in formatter_data, if any. 152 product = item.formatter_data.get(_PRODUCT_TAG, product) 153 name = item.formatter_data.get(_NAME_TAG, name) 154 155 if product: 156 return _PRODUCT_TEMPLATE % (name, product, value) 157 else: 158 return _SIMPLE_TEMPLATE % (name, value) 159