| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| '''Fast and efficient parser for XTB files. |
| ''' |
| |
| |
| import sys |
| import xml.sax |
| import xml.sax.handler |
| |
| import grit.node.base |
| |
| |
| class XtbContentHandler(xml.sax.handler.ContentHandler): |
| '''A content handler that calls a given callback function for each |
| translation in the XTB file. |
| ''' |
| |
| def __init__(self, callback, defs=None, debug=False, target_platform=None): |
| self.callback = callback |
| self.debug = debug |
| # 0 if we are not currently parsing a translation, otherwise the message |
| # ID of that translation. |
| self.current_id = 0 |
| # Empty if we are not currently parsing a translation, otherwise the |
| # parts we have for that translation - a list of tuples |
| # (is_placeholder, text) |
| self.current_structure = [] |
| # Set to the language ID when we see the <translationbundle> node. |
| self.language = '' |
| # Keep track of the if block we're inside. We can't nest ifs. |
| self.if_expr = None |
| # Root defines to be used with if expr. |
| if defs: |
| self.defines = defs |
| else: |
| self.defines = {} |
| # Target platform for build. |
| if target_platform: |
| self.target_platform = target_platform |
| else: |
| self.target_platform = sys.platform |
| |
| def startElement(self, name, attrs): |
| if name == 'translation': |
| assert self.current_id == 0 and len(self.current_structure) == 0, ( |
| "Didn't expect a <translation> element here.") |
| self.current_id = attrs.getValue('id') |
| elif name == 'ph': |
| assert self.current_id != 0, "Didn't expect a <ph> element here." |
| self.current_structure.append((True, attrs.getValue('name'))) |
| elif name == 'translationbundle': |
| self.language = attrs.getValue('lang') |
| elif name in ('if', 'then', 'else'): |
| assert self.if_expr is None, "Can't nest <if> or use <else> in xtb files" |
| self.if_expr = attrs.getValue('expr') |
| |
| def endElement(self, name): |
| if name == 'translation': |
| assert self.current_id != 0 |
| |
| defs = self.defines |
| def pp_ifdef(define): |
| return define in defs |
| def pp_if(define): |
| return define in defs and defs[define] |
| |
| # If we're in an if block, only call the callback (add the translation) |
| # if the expression is True. |
| should_run_callback = True |
| if self.if_expr: |
| should_run_callback = grit.node.base.Node.EvaluateExpression( |
| self.if_expr, self.defines, self.target_platform) |
| if should_run_callback: |
| self.callback(self.current_id, self.current_structure) |
| |
| self.current_id = 0 |
| self.current_structure = [] |
| elif name == 'if': |
| assert self.if_expr is not None |
| self.if_expr = None |
| |
| def characters(self, content): |
| if self.current_id != 0: |
| # We are inside a <translation> node so just add the characters to our |
| # structure. |
| # |
| # This naive way of handling characters is OK because in the XTB format, |
| # <ph> nodes are always empty (always <ph name="XXX"/>) and whitespace |
| # inside the <translation> node should be preserved. |
| self.current_structure.append((False, content)) |
| |
| |
| class XtbErrorHandler(xml.sax.handler.ErrorHandler): |
| def error(self, exception): |
| pass |
| |
| def fatalError(self, exception): |
| raise exception |
| |
| def warning(self, exception): |
| pass |
| |
| |
| def Parse(xtb_file, callback_function, defs=None, debug=False, |
| target_platform=None): |
| '''Parse xtb_file, making a call to callback_function for every translation |
| in the XTB file. |
| |
| The callback function must have the signature as described below. The 'parts' |
| parameter is a list of tuples (is_placeholder, text). The 'text' part is |
| either the raw text (if is_placeholder is False) or the name of the placeholder |
| (if is_placeholder is True). |
| |
| Args: |
| xtb_file: open('fr.xtb') |
| callback_function: def Callback(msg_id, parts): pass |
| defs: None, or a dictionary of preprocessor definitions. |
| debug: Default False. Set True for verbose debug output. |
| target_platform: None, or a sys.platform-like identifier of the build |
| target platform. |
| |
| Return: |
| The language of the XTB, e.g. 'fr' |
| ''' |
| # Start by advancing the file pointer past the DOCTYPE thing, as the TC |
| # uses a path to the DTD that only works in Unix. |
| # TODO(joi) Remove this ugly hack by getting the TC gang to change the |
| # XTB files somehow? |
| front_of_file = xtb_file.read(1024) |
| xtb_file.seek(front_of_file.find('<translationbundle')) |
| |
| handler = XtbContentHandler(callback=callback_function, defs=defs, |
| debug=debug, target_platform=target_platform) |
| xml.sax.parse(xtb_file, handler) |
| assert handler.language != '' |
| return handler.language |