Home | History | Annotate | Download | only in format
      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 
      8 In cases where an "android" type output file is requested in a grd, the classes
      9 in android_xml will process the messages and translations to produce a valid
     10 strings.xml that is properly localized with the specified language.
     11 
     12 For 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 
     19 for 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 
     24 and there existed an appropriate xtb file containing the Spanish translations,
     25 then 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 
     33 which would be written to values-es/strings.xml and usable by the Android
     34 resource framework.
     35 
     36 Advanced usage
     37 --------------
     38 
     39 To process only certain messages in a grd file, tag each desired message by
     40 adding "android_java" to formatter_data. Then set the environmental variable
     41 ANDROID_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 
     45 To 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
     47 override 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 
     54 would generate
     55 
     56   <string name="foo" product="nosdcard">"no card"</string>
     57   <string name="foo" product="default">"has card"</string>
     58 """
     59 
     60 import os
     61 import types
     62 import xml.sax.saxutils
     63 
     64 from grit import lazy_re
     65 from 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 
     99 def 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 
    123 def 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 
    134 def _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