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 &lt; &gt; &amp; 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