OLD | NEW |
| (Empty) |
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 '''Fast and efficient parser for XTB files. | |
7 ''' | |
8 | |
9 | |
10 import sys | |
11 import xml.sax | |
12 import xml.sax.handler | |
13 | |
14 import grit.node.base | |
15 | |
16 | |
17 class XtbContentHandler(xml.sax.handler.ContentHandler): | |
18 '''A content handler that calls a given callback function for each | |
19 translation in the XTB file. | |
20 ''' | |
21 | |
22 def __init__(self, callback, defs=None, debug=False, target_platform=None): | |
23 self.callback = callback | |
24 self.debug = debug | |
25 # 0 if we are not currently parsing a translation, otherwise the message | |
26 # ID of that translation. | |
27 self.current_id = 0 | |
28 # Empty if we are not currently parsing a translation, otherwise the | |
29 # parts we have for that translation - a list of tuples | |
30 # (is_placeholder, text) | |
31 self.current_structure = [] | |
32 # Set to the language ID when we see the <translationbundle> node. | |
33 self.language = '' | |
34 # Keep track of the if block we're inside. We can't nest ifs. | |
35 self.if_expr = None | |
36 # Root defines to be used with if expr. | |
37 if defs: | |
38 self.defines = defs | |
39 else: | |
40 self.defines = {} | |
41 # Target platform for build. | |
42 if target_platform: | |
43 self.target_platform = target_platform | |
44 else: | |
45 self.target_platform = sys.platform | |
46 | |
47 def startElement(self, name, attrs): | |
48 if name == 'translation': | |
49 assert self.current_id == 0 and len(self.current_structure) == 0, ( | |
50 "Didn't expect a <translation> element here.") | |
51 self.current_id = attrs.getValue('id') | |
52 elif name == 'ph': | |
53 assert self.current_id != 0, "Didn't expect a <ph> element here." | |
54 self.current_structure.append((True, attrs.getValue('name'))) | |
55 elif name == 'translationbundle': | |
56 self.language = attrs.getValue('lang') | |
57 elif name in ('if', 'then', 'else'): | |
58 assert self.if_expr is None, "Can't nest <if> or use <else> in xtb files" | |
59 self.if_expr = attrs.getValue('expr') | |
60 | |
61 def endElement(self, name): | |
62 if name == 'translation': | |
63 assert self.current_id != 0 | |
64 | |
65 defs = self.defines | |
66 def pp_ifdef(define): | |
67 return define in defs | |
68 def pp_if(define): | |
69 return define in defs and defs[define] | |
70 | |
71 # If we're in an if block, only call the callback (add the translation) | |
72 # if the expression is True. | |
73 should_run_callback = True | |
74 if self.if_expr: | |
75 should_run_callback = grit.node.base.Node.EvaluateExpression( | |
76 self.if_expr, self.defines, self.target_platform) | |
77 if should_run_callback: | |
78 self.callback(self.current_id, self.current_structure) | |
79 | |
80 self.current_id = 0 | |
81 self.current_structure = [] | |
82 elif name == 'if': | |
83 assert self.if_expr is not None | |
84 self.if_expr = None | |
85 | |
86 def characters(self, content): | |
87 if self.current_id != 0: | |
88 # We are inside a <translation> node so just add the characters to our | |
89 # structure. | |
90 # | |
91 # This naive way of handling characters is OK because in the XTB format, | |
92 # <ph> nodes are always empty (always <ph name="XXX"/>) and whitespace | |
93 # inside the <translation> node should be preserved. | |
94 self.current_structure.append((False, content)) | |
95 | |
96 | |
97 class XtbErrorHandler(xml.sax.handler.ErrorHandler): | |
98 def error(self, exception): | |
99 pass | |
100 | |
101 def fatalError(self, exception): | |
102 raise exception | |
103 | |
104 def warning(self, exception): | |
105 pass | |
106 | |
107 | |
108 def Parse(xtb_file, callback_function, defs=None, debug=False, | |
109 target_platform=None): | |
110 '''Parse xtb_file, making a call to callback_function for every translation | |
111 in the XTB file. | |
112 | |
113 The callback function must have the signature as described below. The 'parts' | |
114 parameter is a list of tuples (is_placeholder, text). The 'text' part is | |
115 either the raw text (if is_placeholder is False) or the name of the placeholde
r | |
116 (if is_placeholder is True). | |
117 | |
118 Args: | |
119 xtb_file: open('fr.xtb') | |
120 callback_function: def Callback(msg_id, parts): pass | |
121 defs: None, or a dictionary of preprocessor definitions. | |
122 debug: Default False. Set True for verbose debug output. | |
123 target_platform: None, or a sys.platform-like identifier of the build | |
124 target platform. | |
125 | |
126 Return: | |
127 The language of the XTB, e.g. 'fr' | |
128 ''' | |
129 # Start by advancing the file pointer past the DOCTYPE thing, as the TC | |
130 # uses a path to the DTD that only works in Unix. | |
131 # TODO(joi) Remove this ugly hack by getting the TC gang to change the | |
132 # XTB files somehow? | |
133 front_of_file = xtb_file.read(1024) | |
134 xtb_file.seek(front_of_file.find('<translationbundle')) | |
135 | |
136 handler = XtbContentHandler(callback=callback_function, defs=defs, | |
137 debug=debug, target_platform=target_platform) | |
138 xml.sax.parse(xtb_file, handler) | |
139 assert handler.language != '' | |
140 return handler.language | |
141 | |
OLD | NEW |