Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / grit / grit / node / misc.py
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 """Miscellaneous node types.
7 """
8
9 import os.path
10 import re
11 import sys
12
13 from grit import constants
14 from grit import exception
15 from grit import util
16 import grit.format.rc_header
17 from grit.node import base
18 from grit.node import io
19 from grit.node import message
20
21
22 # RTL languages
23 # TODO(jennyz): remove this fixed set of RTL language array
24 # now that generic expand_variable code exists.
25 _RTL_LANGS = (
26     'ar',  # Arabic
27     'fa',  # Farsi
28     'iw',  # Hebrew
29     'ks',  # Kashmiri
30     'ku',  # Kurdish
31     'ps',  # Pashto
32     'ur',  # Urdu
33     'yi',  # Yiddish
34 )
35
36
37 def _ReadFirstIdsFromFile(filename, defines):
38   """Read the starting resource id values from |filename|.  We also
39   expand variables of the form <(FOO) based on defines passed in on
40   the command line.
41
42   Returns a tuple, the absolute path of SRCDIR followed by the
43   first_ids dictionary.
44   """
45   first_ids_dict = eval(util.ReadFile(filename, util.RAW_TEXT))
46   src_root_dir = os.path.abspath(os.path.join(os.path.dirname(filename),
47                                               first_ids_dict['SRCDIR']))
48
49   def ReplaceVariable(matchobj):
50     for key, value in defines.iteritems():
51       if matchobj.group(1) == key:
52         return value
53     return ''
54
55   renames = []
56   for grd_filename in first_ids_dict:
57     new_grd_filename = re.sub(r'<\(([A-Za-z_]+)\)', ReplaceVariable,
58                               grd_filename)
59     if new_grd_filename != grd_filename:
60       abs_grd_filename = os.path.abspath(new_grd_filename)
61       if abs_grd_filename[:len(src_root_dir)] != src_root_dir:
62         new_grd_filename = os.path.basename(abs_grd_filename)
63       else:
64         new_grd_filename = abs_grd_filename[len(src_root_dir) + 1:]
65         new_grd_filename = new_grd_filename.replace('\\', '/')
66       renames.append((grd_filename, new_grd_filename))
67
68   for grd_filename, new_grd_filename in renames:
69     first_ids_dict[new_grd_filename] = first_ids_dict[grd_filename]
70     del(first_ids_dict[grd_filename])
71
72   return (src_root_dir, first_ids_dict)
73
74
75 class SplicingNode(base.Node):
76   """A node whose children should be considered to be at the same level as
77   its siblings for most purposes. This includes <if> and <part> nodes.
78   """
79
80   def _IsValidChild(self, child):
81     assert self.parent, '<%s> node should never be root.' % self.name
82     if isinstance(child, SplicingNode):
83       return True  # avoid O(n^2) behavior
84     return self.parent._IsValidChild(child)
85
86
87 class IfNode(SplicingNode):
88   """A node for conditional inclusion of resources.
89   """
90
91   def MandatoryAttributes(self):
92     return ['expr']
93
94   def _IsValidChild(self, child):
95     return (isinstance(child, (ThenNode, ElseNode)) or
96             super(IfNode, self)._IsValidChild(child))
97
98   def EndParsing(self):
99     children = self.children
100     self.if_then_else = False
101     if any(isinstance(node, (ThenNode, ElseNode)) for node in children):
102       if (len(children) != 2 or not isinstance(children[0], ThenNode) or
103                                 not isinstance(children[1], ElseNode)):
104         raise exception.UnexpectedChild(
105             '<if> element must be <if><then>...</then><else>...</else></if>')
106       self.if_then_else = True
107
108   def ActiveChildren(self):
109     cond = self.EvaluateCondition(self.attrs['expr'])
110     if self.if_then_else:
111       return self.children[0 if cond else 1].ActiveChildren()
112     else:
113       # Equivalent to having all children inside <then> with an empty <else>
114       return super(IfNode, self).ActiveChildren() if cond else []
115
116
117 class ThenNode(SplicingNode):
118   """A <then> node. Can only appear directly inside an <if> node."""
119   pass
120
121
122 class ElseNode(SplicingNode):
123   """An <else> node. Can only appear directly inside an <if> node."""
124   pass
125
126
127 class PartNode(SplicingNode):
128   """A node for inclusion of sub-grd (*.grp) files.
129   """
130
131   def __init__(self):
132     super(PartNode, self).__init__()
133     self.started_inclusion = False
134
135   def MandatoryAttributes(self):
136     return ['file']
137
138   def _IsValidChild(self, child):
139     return self.started_inclusion and super(PartNode, self)._IsValidChild(child)
140
141
142 class ReleaseNode(base.Node):
143   """The <release> element."""
144
145   def _IsValidChild(self, child):
146     from grit.node import empty
147     return isinstance(child, (empty.IncludesNode, empty.MessagesNode,
148                               empty.StructuresNode, empty.IdentifiersNode))
149
150   def _IsValidAttribute(self, name, value):
151     return (
152       (name == 'seq' and int(value) <= self.GetRoot().GetCurrentRelease()) or
153       name == 'allow_pseudo'
154     )
155
156   def MandatoryAttributes(self):
157     return ['seq']
158
159   def DefaultAttributes(self):
160     return { 'allow_pseudo' : 'true' }
161
162   def GetReleaseNumber():
163     """Returns the sequence number of this release."""
164     return self.attribs['seq']
165
166 class GritNode(base.Node):
167   """The <grit> root element."""
168
169   def __init__(self):
170     super(GritNode, self).__init__()
171     self.output_language = ''
172     self.defines = {}
173     self.substituter = None
174     self.target_platform = sys.platform
175
176   def _IsValidChild(self, child):
177     from grit.node import empty
178     return isinstance(child, (ReleaseNode, empty.TranslationsNode,
179                               empty.OutputsNode))
180
181   def _IsValidAttribute(self, name, value):
182     if name not in ['base_dir', 'first_ids_file', 'source_lang_id',
183                     'latest_public_release', 'current_release',
184                     'enc_check', 'tc_project', 'grit_version',
185                     'output_all_resource_defines', 'rc_header_format']:
186       return False
187     if name in ['latest_public_release', 'current_release'] and value.strip(
188       '0123456789') != '':
189       return False
190     return True
191
192   def MandatoryAttributes(self):
193     return ['latest_public_release', 'current_release']
194
195   def DefaultAttributes(self):
196     return {
197       'base_dir' : '.',
198       'first_ids_file': '',
199       'grit_version': 1,
200       'source_lang_id' : 'en',
201       'enc_check' : constants.ENCODING_CHECK,
202       'tc_project' : 'NEED_TO_SET_tc_project_ATTRIBUTE',
203       'output_all_resource_defines': 'true',
204       'rc_header_format': None
205     }
206
207   def EndParsing(self):
208     super(GritNode, self).EndParsing()
209     if (int(self.attrs['latest_public_release'])
210         > int(self.attrs['current_release'])):
211       raise exception.Parsing('latest_public_release cannot have a greater '
212                               'value than current_release')
213
214     self.ValidateUniqueIds()
215
216     # Add the encoding check if it's not present (should ensure that it's always
217     # present in all .grd files generated by GRIT). If it's present, assert if
218     # it's not correct.
219     if 'enc_check' not in self.attrs or self.attrs['enc_check'] == '':
220       self.attrs['enc_check'] = constants.ENCODING_CHECK
221     else:
222       assert self.attrs['enc_check'] == constants.ENCODING_CHECK, (
223         'Are you sure your .grd file is in the correct encoding (UTF-8)?')
224
225   def ValidateUniqueIds(self):
226     """Validate that 'name' attribute is unique in all nodes in this tree
227     except for nodes that are children of <if> nodes.
228     """
229     unique_names = {}
230     duplicate_names = []
231     # To avoid false positives from mutually exclusive <if> clauses, check
232     # against whatever the output condition happens to be right now.
233     # TODO(benrg): do something better.
234     for node in self.ActiveDescendants():
235       if node.attrs.get('generateid', 'true') == 'false':
236         continue  # Duplication not relevant in that case
237
238       for node_id in node.GetTextualIds():
239         if util.SYSTEM_IDENTIFIERS.match(node_id):
240           continue  # predefined IDs are sometimes used more than once
241
242         if node_id in unique_names and node_id not in duplicate_names:
243           duplicate_names.append(node_id)
244         unique_names[node_id] = 1
245
246     if len(duplicate_names):
247       raise exception.DuplicateKey(', '.join(duplicate_names))
248
249
250   def GetCurrentRelease(self):
251     """Returns the current release number."""
252     return int(self.attrs['current_release'])
253
254   def GetLatestPublicRelease(self):
255     """Returns the latest public release number."""
256     return int(self.attrs['latest_public_release'])
257
258   def GetSourceLanguage(self):
259     """Returns the language code of the source language."""
260     return self.attrs['source_lang_id']
261
262   def GetTcProject(self):
263     """Returns the name of this project in the TranslationConsole, or
264     'NEED_TO_SET_tc_project_ATTRIBUTE' if it is not defined."""
265     return self.attrs['tc_project']
266
267   def SetOwnDir(self, dir):
268     """Informs the 'grit' element of the directory the file it is in resides.
269     This allows it to calculate relative paths from the input file, which is
270     what we desire (rather than from the current path).
271
272     Args:
273       dir: r'c:\bla'
274
275     Return:
276       None
277     """
278     assert dir
279     self.base_dir = os.path.normpath(os.path.join(dir, self.attrs['base_dir']))
280
281   def GetBaseDir(self):
282     """Returns the base directory, relative to the working directory.  To get
283     the base directory as set in the .grd file, use GetOriginalBaseDir()
284     """
285     if hasattr(self, 'base_dir'):
286       return self.base_dir
287     else:
288       return self.GetOriginalBaseDir()
289
290   def GetOriginalBaseDir(self):
291     """Returns the base directory, as set in the .grd file.
292     """
293     return self.attrs['base_dir']
294
295   def SetShouldOutputAllResourceDefines(self, value):
296     """Overrides the value of output_all_resource_defines found in the grd file.
297     """
298     self.attrs['output_all_resource_defines'] = 'true' if value else 'false'
299
300   def ShouldOutputAllResourceDefines(self):
301     """Returns true if all resource defines should be output, false if
302     defines for resources not emitted to resource files should be
303     skipped.
304     """
305     return self.attrs['output_all_resource_defines'] == 'true'
306
307   def GetRcHeaderFormat(self):
308     return self.attrs['rc_header_format']
309
310   def AssignRcHeaderFormat(self, rc_header_format):
311     self.attrs['rc_header_format'] = rc_header_format
312
313   def GetInputFiles(self):
314     """Returns the list of files that are read to produce the output."""
315
316     # Importing this here avoids a circular dependency in the imports.
317     # pylint: disable-msg=C6204
318     from grit.node import include
319     from grit.node import misc
320     from grit.node import structure
321     from grit.node import variant
322
323     # Check if the input is required for any output configuration.
324     input_files = set()
325     old_output_language = self.output_language
326     for lang, ctx in self.GetConfigurations():
327       self.SetOutputLanguage(lang or self.GetSourceLanguage())
328       self.SetOutputContext(ctx)
329       for node in self.ActiveDescendants():
330         if isinstance(node, (io.FileNode, include.IncludeNode, misc.PartNode,
331                              structure.StructureNode, variant.SkeletonNode)):
332           input_files.add(node.GetInputPath())
333     self.SetOutputLanguage(old_output_language)
334     return sorted(map(self.ToRealPath, input_files))
335
336   def GetFirstIdsFile(self):
337     """Returns a usable path to the first_ids file, if set, otherwise
338     returns None.
339
340     The first_ids_file attribute is by default relative to the
341     base_dir of the .grd file, but may be prefixed by GRIT_DIR/,
342     which makes it relative to the directory of grit.py
343     (e.g. GRIT_DIR/../gritsettings/resource_ids).
344     """
345     if not self.attrs['first_ids_file']:
346       return None
347
348     path = self.attrs['first_ids_file']
349     GRIT_DIR_PREFIX = 'GRIT_DIR'
350     if (path.startswith(GRIT_DIR_PREFIX)
351         and path[len(GRIT_DIR_PREFIX)] in ['/', '\\']):
352       return util.PathFromRoot(path[len(GRIT_DIR_PREFIX) + 1:])
353     else:
354       return self.ToRealPath(path)
355
356   def GetOutputFiles(self):
357     """Returns the list of <output> nodes that are descendants of this node's
358     <outputs> child and are not enclosed by unsatisfied <if> conditionals.
359     """
360     for child in self.children:
361       if child.name == 'outputs':
362         return [node for node in child.ActiveDescendants()
363                      if node.name == 'output']
364     raise exception.MissingElement()
365
366   def GetConfigurations(self):
367     """Returns the distinct (language, context) pairs from the output nodes.
368     """
369     return set((n.GetLanguage(), n.GetContext()) for n in self.GetOutputFiles())
370
371   def GetSubstitutionMessages(self):
372     """Returns the list of <message sub_variable="true"> nodes."""
373     return [n for n in self.ActiveDescendants()
374             if isinstance(n, message.MessageNode)
375                 and n.attrs['sub_variable'] == 'true']
376
377   def SetOutputLanguage(self, output_language):
378     """Set the output language. Prepares substitutions.
379
380     The substitutions are reset every time the language is changed.
381     They include messages designated as variables, and language codes for html
382     and rc files.
383
384     Args:
385       output_language: a two-letter language code (eg: 'en', 'ar'...) or ''
386     """
387     if not output_language:
388       # We do not specify the output language for .grh files,
389       # so we get an empty string as the default.
390       # The value should match grit.clique.MessageClique.source_language.
391       output_language = self.GetSourceLanguage()
392     if output_language != self.output_language:
393       self.output_language = output_language
394       self.substituter = None  # force recalculate
395
396   def SetOutputContext(self, output_context):
397     self.output_context = output_context
398     self.substituter = None  # force recalculate
399
400   def SetDefines(self, defines):
401     self.defines = defines
402     self.substituter = None  # force recalculate
403
404   def SetTargetPlatform(self, target_platform):
405     self.target_platform = target_platform
406
407   def GetSubstituter(self):
408     if self.substituter is None:
409       self.substituter = util.Substituter()
410       self.substituter.AddMessages(self.GetSubstitutionMessages(),
411                                    self.output_language)
412       if self.output_language in _RTL_LANGS:
413         direction = 'dir="RTL"'
414       else:
415         direction = 'dir="LTR"'
416       self.substituter.AddSubstitutions({
417           'GRITLANGCODE': self.output_language,
418           'GRITDIR': direction,
419       })
420       from grit.format import rc  # avoid circular dep
421       rc.RcSubstitutions(self.substituter, self.output_language)
422     return self.substituter
423
424   def AssignFirstIds(self, filename_or_stream, defines):
425     """Assign first ids to each grouping node based on values from the
426     first_ids file (if specified on the <grit> node).
427     """
428     # If the input is a stream, then we're probably in a unit test and
429     # should skip this step.
430     if type(filename_or_stream) not in (str, unicode):
431       return
432
433     # Nothing to do if the first_ids_filename attribute isn't set.
434     first_ids_filename = self.GetFirstIdsFile()
435     if not first_ids_filename:
436       return
437
438     src_root_dir, first_ids = _ReadFirstIdsFromFile(first_ids_filename,
439                                                     defines)
440     from grit.node import empty
441     for node in self.Preorder():
442       if isinstance(node, empty.GroupingNode):
443         abs_filename = os.path.abspath(filename_or_stream)
444         if abs_filename[:len(src_root_dir)] != src_root_dir:
445           filename = os.path.basename(filename_or_stream)
446         else:
447           filename = abs_filename[len(src_root_dir) + 1:]
448           filename = filename.replace('\\', '/')
449
450         if node.attrs['first_id'] != '':
451           raise Exception(
452               "Don't set the first_id attribute when using the first_ids_file "
453               "attribute on the <grit> node, update %s instead." %
454               first_ids_filename)
455
456         try:
457           id_list = first_ids[filename][node.name]
458         except KeyError, e:
459           print '-' * 78
460           print 'Resource id not set for %s (%s)!' % (filename, node.name)
461           print ('Please update %s to include an entry for %s.  See the '
462                  'comments in resource_ids for information on why you need to '
463                  'update that file.' % (first_ids_filename, filename))
464           print '-' * 78
465           raise e
466
467         try:
468           node.attrs['first_id'] = str(id_list.pop(0))
469         except IndexError, e:
470           raise Exception('Please update %s and add a first id for %s (%s).'
471                           % (first_ids_filename, filename, node.name))
472
473   def RunGatherers(self, debug=False):
474     '''Call RunPreSubstitutionGatherer() on every node of the tree, then apply
475     substitutions, then call RunPostSubstitutionGatherer() on every node.
476
477     The substitutions step requires that the output language has been set.
478     Locally, get the Substitution messages and add them to the substituter.
479     Also add substitutions for language codes in the Rc.
480
481     Args:
482       debug: will print information while running gatherers.
483     '''
484     for node in self.ActiveDescendants():
485       if hasattr(node, 'RunPreSubstitutionGatherer'):
486         with node:
487           node.RunPreSubstitutionGatherer(debug=debug)
488
489     assert self.output_language
490     self.SubstituteMessages(self.GetSubstituter())
491
492     for node in self.ActiveDescendants():
493       if hasattr(node, 'RunPostSubstitutionGatherer'):
494         with node:
495           node.RunPostSubstitutionGatherer(debug=debug)
496
497
498 class IdentifierNode(base.Node):
499   """A node for specifying identifiers that should appear in the resource
500   header file, and be unique amongst all other resource identifiers, but don't
501   have any other attributes or reference any resources.
502   """
503
504   def MandatoryAttributes(self):
505     return ['name']
506
507   def DefaultAttributes(self):
508     return { 'comment' : '', 'id' : '', 'systemid': 'false' }
509
510   def GetId(self):
511     """Returns the id of this identifier if it has one, None otherwise
512     """
513     if 'id' in self.attrs:
514       return self.attrs['id']
515     return None
516
517   def EndParsing(self):
518     """Handles system identifiers."""
519     super(IdentifierNode, self).EndParsing()
520     if self.attrs['systemid'] == 'true':
521       util.SetupSystemIdentifiers((self.attrs['name'],))
522
523   @staticmethod
524   def Construct(parent, name, id, comment, systemid='false'):
525     """Creates a new node which is a child of 'parent', with attributes set
526     by parameters of the same name.
527     """
528     node = IdentifierNode()
529     node.StartParsing('identifier', parent)
530     node.HandleAttribute('name', name)
531     node.HandleAttribute('id', id)
532     node.HandleAttribute('comment', comment)
533     node.HandleAttribute('systemid', systemid)
534     node.EndParsing()
535     return node