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'''Support for "strings.xml" format used by Muppet plug-ins in Google Desktop.'''
7
8import StringIO
9import xml.sax
10import xml.sax.handler
11import xml.sax.saxutils
12
13from grit import lazy_re
14from grit import tclib
15from grit import util
16from grit.gather import regexp
17
18
19# Placeholders can be defined in strings.xml files by putting the name of the
20# placeholder between [![ and ]!] e.g. <MSG>Hello [![USER]!] how are you<MSG>
21PLACEHOLDER_RE = lazy_re.compile('(\[!\[|\]!\])')
22
23
24class MuppetStringsContentHandler(xml.sax.handler.ContentHandler):
25  '''A very dumb parser for splitting the strings.xml file into translateable
26  and nontranslateable chunks.'''
27
28  def __init__(self, parent):
29    self.curr_elem = ''
30    self.curr_text = ''
31    self.parent = parent
32    self.description = ''
33    self.meaning = ''
34    self.translateable = True
35
36  def startElement(self, name, attrs):
37    if (name != 'strings'):
38      self.curr_elem = name
39
40      attr_names = attrs.getQNames()
41      if 'desc' in attr_names:
42        self.description = attrs.getValueByQName('desc')
43      if 'meaning' in attr_names:
44        self.meaning = attrs.getValueByQName('meaning')
45      if 'translateable' in attr_names:
46        value = attrs.getValueByQName('translateable')
47        if value.lower() not in ['true', 'yes']:
48          self.translateable = False
49
50      att_text = []
51      for attr_name in attr_names:
52        att_text.append(' ')
53        att_text.append(attr_name)
54        att_text.append('=')
55        att_text.append(
56          xml.sax.saxutils.quoteattr(attrs.getValueByQName(attr_name)))
57
58      self.parent._AddNontranslateableChunk("<%s%s>" %
59                                            (name, ''.join(att_text)))
60
61  def characters(self, content):
62    if self.curr_elem != '':
63      self.curr_text += content
64
65  def endElement(self, name):
66    if name != 'strings':
67      self.parent.AddMessage(self.curr_text, self.description,
68                             self.meaning, self.translateable)
69      self.parent._AddNontranslateableChunk("</%s>\n" % name)
70      self.curr_elem = ''
71      self.curr_text = ''
72      self.description = ''
73      self.meaning = ''
74      self.translateable = True
75
76  def ignorableWhitespace(self, whitespace):
77    pass
78
79class MuppetStrings(regexp.RegexpGatherer):
80  '''Supports the strings.xml format used by Muppet gadgets.'''
81
82  def AddMessage(self, msgtext, description, meaning, translateable):
83    if msgtext == '':
84      return
85
86    msg = tclib.Message(description=description, meaning=meaning)
87
88    unescaped_text = self.UnEscape(msgtext)
89    parts = PLACEHOLDER_RE.split(unescaped_text)
90    in_placeholder = False
91    for part in parts:
92      if part == '':
93        continue
94      elif part == '[![':
95        in_placeholder = True
96      elif part == ']!]':
97        in_placeholder = False
98      else:
99        if in_placeholder:
100          msg.AppendPlaceholder(tclib.Placeholder(part, '[![%s]!]' % part,
101                                                  '(placeholder)'))
102        else:
103          msg.AppendText(part)
104
105    self.skeleton_.append(
106      self.uberclique.MakeClique(msg, translateable=translateable))
107
108    # if statement needed because this is supposed to be idempotent (so never
109    # set back to false)
110    if translateable:
111      self.translatable_chunk_ = True
112
113  # Although we use the RegexpGatherer base class, we do not use the
114  # _RegExpParse method of that class to implement Parse().  Instead, we
115  # parse using a SAX parser.
116  def Parse(self):
117    if self.have_parsed_:
118      return
119    self.have_parsed_ = True
120
121    text = self._LoadInputFile().encode(self.encoding)
122    if util.IsExtraVerbose():
123      print text
124    self.text_ = text.strip()
125
126    self._AddNontranslateableChunk(u'<strings>\n')
127    stream = StringIO.StringIO(self.text_)
128    handler = MuppetStringsContentHandler(self)
129    xml.sax.parse(stream, handler)
130    self._AddNontranslateableChunk(u'</strings>\n')
131
132  def Escape(self, text):
133    return util.EncodeCdata(text)
134