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