195640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org#!/usr/bin/env python
295640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org# Copyright (c) 2012 The Chromium Authors. All rights reserved.
301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# Use of this source code is governed by a BSD-style license that can be
401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# found in the LICENSE file.
501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''Support for "strings.xml" format used by Muppet plug-ins in Google Desktop.'''
701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport StringIO
901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport xml.sax
1001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport xml.sax.handler
1101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport xml.sax.saxutils
1201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1301fadb72b6e94e6511eaffd1874a8cc095f098a7joi@chromium.orgfrom grit import lazy_re
1401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import tclib
15ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.orgfrom grit import util
16b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.orgfrom grit.gather import regexp
17b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org
1801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# Placeholders can be defined in strings.xml files by putting the name of the
2001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# placeholder between [![ and ]!] e.g. <MSG>Hello [![USER]!] how are you<MSG>
2101fadb72b6e94e6511eaffd1874a8cc095f098a7joi@chromium.orgPLACEHOLDER_RE = lazy_re.compile('(\[!\[|\]!\])')
2201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass MuppetStringsContentHandler(xml.sax.handler.ContentHandler):
2501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''A very dumb parser for splitting the strings.xml file into translateable
2601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  and nontranslateable chunks.'''
2701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def __init__(self, parent):
2901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.curr_elem = ''
3001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.curr_text = ''
3101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.parent = parent
3201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.description = ''
3301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.meaning = ''
3401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.translateable = True
3501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
3601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def startElement(self, name, attrs):
3701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if (name != 'strings'):
3801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.curr_elem = name
3901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
4001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      attr_names = attrs.getQNames()
4101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if 'desc' in attr_names:
4201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        self.description = attrs.getValueByQName('desc')
4301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if 'meaning' in attr_names:
4401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        self.meaning = attrs.getValueByQName('meaning')
4501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if 'translateable' in attr_names:
4601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        value = attrs.getValueByQName('translateable')
4701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if value.lower() not in ['true', 'yes']:
4801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          self.translateable = False
4901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
5001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      att_text = []
5101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      for attr_name in attr_names:
5201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        att_text.append(' ')
5301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        att_text.append(attr_name)
5401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        att_text.append('=')
5501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        att_text.append(
5601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          xml.sax.saxutils.quoteattr(attrs.getValueByQName(attr_name)))
5701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
5801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.parent._AddNontranslateableChunk("<%s%s>" %
5901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                                            (name, ''.join(att_text)))
6001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
6101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def characters(self, content):
6201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if self.curr_elem != '':
6301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.curr_text += content
6401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
6501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def endElement(self, name):
6601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if name != 'strings':
6701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.parent.AddMessage(self.curr_text, self.description,
6801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                             self.meaning, self.translateable)
6901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.parent._AddNontranslateableChunk("</%s>\n" % name)
7001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.curr_elem = ''
7101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.curr_text = ''
7201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.description = ''
7301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.meaning = ''
7401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.translateable = True
7501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
7601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def ignorableWhitespace(self, whitespace):
7701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    pass
7801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
7901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass MuppetStrings(regexp.RegexpGatherer):
8001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''Supports the strings.xml format used by Muppet gadgets.'''
8101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
8201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def AddMessage(self, msgtext, description, meaning, translateable):
8301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if msgtext == '':
8401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      return
8501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
8601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    msg = tclib.Message(description=description, meaning=meaning)
8701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
8801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    unescaped_text = self.UnEscape(msgtext)
8901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    parts = PLACEHOLDER_RE.split(unescaped_text)
9001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    in_placeholder = False
9101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for part in parts:
9201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if part == '':
9301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        continue
9401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      elif part == '[![':
9501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        in_placeholder = True
9601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      elif part == ']!]':
9701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        in_placeholder = False
9801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
9901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if in_placeholder:
10001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          msg.AppendPlaceholder(tclib.Placeholder(part, '[![%s]!]' % part,
10101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                                                  '(placeholder)'))
10201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        else:
10301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          msg.AppendText(part)
10401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.skeleton_.append(
10601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.uberclique.MakeClique(msg, translateable=translateable))
10701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # if statement needed because this is supposed to be idempotent (so never
10901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # set back to false)
11001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if translateable:
11101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.translatable_chunk_ = True
11201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
11301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  # Although we use the RegexpGatherer base class, we do not use the
11401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  # _RegExpParse method of that class to implement Parse().  Instead, we
11501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  # parse using a SAX parser.
11601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def Parse(self):
117ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    if self.have_parsed_:
11801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      return
11901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.have_parsed_ = True
120ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org
121b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org    text = self._LoadInputFile().encode(self.encoding)
122ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    if util.IsExtraVerbose():
123ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      print text
124ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    self.text_ = text.strip()
125ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org
12601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self._AddNontranslateableChunk(u'<strings>\n')
12701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    stream = StringIO.StringIO(self.text_)
12801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    handler = MuppetStringsContentHandler(self)
12901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    xml.sax.parse(stream, handler)
13001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self._AddNontranslateableChunk(u'</strings>\n')
13101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
13201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def Escape(self, text):
13301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return util.EncodeCdata(text)
134