| 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 """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, fallback in self.GetConfigurations(): | |
| 327 self.SetOutputLanguage(lang or self.GetSourceLanguage()) | |
| 328 self.SetOutputContext(ctx) | |
| 329 self.SetFallbackToDefaultLayout(fallback) | |
| 330 | |
| 331 for node in self.ActiveDescendants(): | |
| 332 if isinstance(node, (io.FileNode, include.IncludeNode, misc.PartNode, | |
| 333 structure.StructureNode, variant.SkeletonNode)): | |
| 334 input_path = node.GetInputPath() | |
| 335 if input_path is not None: | |
| 336 input_files.add(self.ToRealPath(input_path)) | |
| 337 | |
| 338 # If it's a flattened node, grab inlined resources too. | |
| 339 if ((node.name == 'structure' or node.name == 'include') | |
| 340 and node.attrs['flattenhtml'] == 'true'): | |
| 341 if node.name == 'structure': | |
| 342 node.RunPreSubstitutionGatherer() | |
| 343 input_files.update(node.GetHtmlResourceFilenames()) | |
| 344 | |
| 345 self.SetOutputLanguage(old_output_language) | |
| 346 return sorted(input_files) | |
| 347 | |
| 348 def GetFirstIdsFile(self): | |
| 349 """Returns a usable path to the first_ids file, if set, otherwise | |
| 350 returns None. | |
| 351 | |
| 352 The first_ids_file attribute is by default relative to the | |
| 353 base_dir of the .grd file, but may be prefixed by GRIT_DIR/, | |
| 354 which makes it relative to the directory of grit.py | |
| 355 (e.g. GRIT_DIR/../gritsettings/resource_ids). | |
| 356 """ | |
| 357 if not self.attrs['first_ids_file']: | |
| 358 return None | |
| 359 | |
| 360 path = self.attrs['first_ids_file'] | |
| 361 GRIT_DIR_PREFIX = 'GRIT_DIR' | |
| 362 if (path.startswith(GRIT_DIR_PREFIX) | |
| 363 and path[len(GRIT_DIR_PREFIX)] in ['/', '\\']): | |
| 364 return util.PathFromRoot(path[len(GRIT_DIR_PREFIX) + 1:]) | |
| 365 else: | |
| 366 return self.ToRealPath(path) | |
| 367 | |
| 368 def GetOutputFiles(self): | |
| 369 """Returns the list of <output> nodes that are descendants of this node's | |
| 370 <outputs> child and are not enclosed by unsatisfied <if> conditionals. | |
| 371 """ | |
| 372 for child in self.children: | |
| 373 if child.name == 'outputs': | |
| 374 return [node for node in child.ActiveDescendants() | |
| 375 if node.name == 'output'] | |
| 376 raise exception.MissingElement() | |
| 377 | |
| 378 def GetConfigurations(self): | |
| 379 """Returns the distinct (language, context, fallback_to_default_layout) | |
| 380 triples from the output nodes. | |
| 381 """ | |
| 382 return set((n.GetLanguage(), n.GetContext(), n.GetFallbackToDefaultLayout())
for n in self.GetOutputFiles()) | |
| 383 | |
| 384 def GetSubstitutionMessages(self): | |
| 385 """Returns the list of <message sub_variable="true"> nodes.""" | |
| 386 return [n for n in self.ActiveDescendants() | |
| 387 if isinstance(n, message.MessageNode) | |
| 388 and n.attrs['sub_variable'] == 'true'] | |
| 389 | |
| 390 def SetOutputLanguage(self, output_language): | |
| 391 """Set the output language. Prepares substitutions. | |
| 392 | |
| 393 The substitutions are reset every time the language is changed. | |
| 394 They include messages designated as variables, and language codes for html | |
| 395 and rc files. | |
| 396 | |
| 397 Args: | |
| 398 output_language: a two-letter language code (eg: 'en', 'ar'...) or '' | |
| 399 """ | |
| 400 if not output_language: | |
| 401 # We do not specify the output language for .grh files, | |
| 402 # so we get an empty string as the default. | |
| 403 # The value should match grit.clique.MessageClique.source_language. | |
| 404 output_language = self.GetSourceLanguage() | |
| 405 if output_language != self.output_language: | |
| 406 self.output_language = output_language | |
| 407 self.substituter = None # force recalculate | |
| 408 | |
| 409 def SetOutputContext(self, output_context): | |
| 410 self.output_context = output_context | |
| 411 self.substituter = None # force recalculate | |
| 412 | |
| 413 def SetFallbackToDefaultLayout(self, fallback_to_default_layout): | |
| 414 self.fallback_to_default_layout = fallback_to_default_layout | |
| 415 self.substituter = None # force recalculate | |
| 416 | |
| 417 def SetDefines(self, defines): | |
| 418 self.defines = defines | |
| 419 self.substituter = None # force recalculate | |
| 420 | |
| 421 def SetTargetPlatform(self, target_platform): | |
| 422 self.target_platform = target_platform | |
| 423 | |
| 424 def GetSubstituter(self): | |
| 425 if self.substituter is None: | |
| 426 self.substituter = util.Substituter() | |
| 427 self.substituter.AddMessages(self.GetSubstitutionMessages(), | |
| 428 self.output_language) | |
| 429 if self.output_language in _RTL_LANGS: | |
| 430 direction = 'dir="RTL"' | |
| 431 else: | |
| 432 direction = 'dir="LTR"' | |
| 433 self.substituter.AddSubstitutions({ | |
| 434 'GRITLANGCODE': self.output_language, | |
| 435 'GRITDIR': direction, | |
| 436 }) | |
| 437 from grit.format import rc # avoid circular dep | |
| 438 rc.RcSubstitutions(self.substituter, self.output_language) | |
| 439 return self.substituter | |
| 440 | |
| 441 def AssignFirstIds(self, filename_or_stream, defines): | |
| 442 """Assign first ids to each grouping node based on values from the | |
| 443 first_ids file (if specified on the <grit> node). | |
| 444 """ | |
| 445 # If the input is a stream, then we're probably in a unit test and | |
| 446 # should skip this step. | |
| 447 if type(filename_or_stream) not in (str, unicode): | |
| 448 return | |
| 449 | |
| 450 # Nothing to do if the first_ids_filename attribute isn't set. | |
| 451 first_ids_filename = self.GetFirstIdsFile() | |
| 452 if not first_ids_filename: | |
| 453 return | |
| 454 | |
| 455 src_root_dir, first_ids = _ReadFirstIdsFromFile(first_ids_filename, | |
| 456 defines) | |
| 457 from grit.node import empty | |
| 458 for node in self.Preorder(): | |
| 459 if isinstance(node, empty.GroupingNode): | |
| 460 abs_filename = os.path.abspath(filename_or_stream) | |
| 461 if abs_filename[:len(src_root_dir)] != src_root_dir: | |
| 462 filename = os.path.basename(filename_or_stream) | |
| 463 else: | |
| 464 filename = abs_filename[len(src_root_dir) + 1:] | |
| 465 filename = filename.replace('\\', '/') | |
| 466 | |
| 467 if node.attrs['first_id'] != '': | |
| 468 raise Exception( | |
| 469 "Don't set the first_id attribute when using the first_ids_file " | |
| 470 "attribute on the <grit> node, update %s instead." % | |
| 471 first_ids_filename) | |
| 472 | |
| 473 try: | |
| 474 id_list = first_ids[filename][node.name] | |
| 475 except KeyError, e: | |
| 476 print '-' * 78 | |
| 477 print 'Resource id not set for %s (%s)!' % (filename, node.name) | |
| 478 print ('Please update %s to include an entry for %s. See the ' | |
| 479 'comments in resource_ids for information on why you need to ' | |
| 480 'update that file.' % (first_ids_filename, filename)) | |
| 481 print '-' * 78 | |
| 482 raise e | |
| 483 | |
| 484 try: | |
| 485 node.attrs['first_id'] = str(id_list.pop(0)) | |
| 486 except IndexError, e: | |
| 487 raise Exception('Please update %s and add a first id for %s (%s).' | |
| 488 % (first_ids_filename, filename, node.name)) | |
| 489 | |
| 490 def RunGatherers(self, debug=False): | |
| 491 '''Call RunPreSubstitutionGatherer() on every node of the tree, then apply | |
| 492 substitutions, then call RunPostSubstitutionGatherer() on every node. | |
| 493 | |
| 494 The substitutions step requires that the output language has been set. | |
| 495 Locally, get the Substitution messages and add them to the substituter. | |
| 496 Also add substitutions for language codes in the Rc. | |
| 497 | |
| 498 Args: | |
| 499 debug: will print information while running gatherers. | |
| 500 ''' | |
| 501 for node in self.ActiveDescendants(): | |
| 502 if hasattr(node, 'RunPreSubstitutionGatherer'): | |
| 503 with node: | |
| 504 node.RunPreSubstitutionGatherer(debug=debug) | |
| 505 | |
| 506 assert self.output_language | |
| 507 self.SubstituteMessages(self.GetSubstituter()) | |
| 508 | |
| 509 for node in self.ActiveDescendants(): | |
| 510 if hasattr(node, 'RunPostSubstitutionGatherer'): | |
| 511 with node: | |
| 512 node.RunPostSubstitutionGatherer(debug=debug) | |
| 513 | |
| 514 | |
| 515 class IdentifierNode(base.Node): | |
| 516 """A node for specifying identifiers that should appear in the resource | |
| 517 header file, and be unique amongst all other resource identifiers, but don't | |
| 518 have any other attributes or reference any resources. | |
| 519 """ | |
| 520 | |
| 521 def MandatoryAttributes(self): | |
| 522 return ['name'] | |
| 523 | |
| 524 def DefaultAttributes(self): | |
| 525 return { 'comment' : '', 'id' : '', 'systemid': 'false' } | |
| 526 | |
| 527 def GetId(self): | |
| 528 """Returns the id of this identifier if it has one, None otherwise | |
| 529 """ | |
| 530 if 'id' in self.attrs: | |
| 531 return self.attrs['id'] | |
| 532 return None | |
| 533 | |
| 534 def EndParsing(self): | |
| 535 """Handles system identifiers.""" | |
| 536 super(IdentifierNode, self).EndParsing() | |
| 537 if self.attrs['systemid'] == 'true': | |
| 538 util.SetupSystemIdentifiers((self.attrs['name'],)) | |
| 539 | |
| 540 @staticmethod | |
| 541 def Construct(parent, name, id, comment, systemid='false'): | |
| 542 """Creates a new node which is a child of 'parent', with attributes set | |
| 543 by parameters of the same name. | |
| 544 """ | |
| 545 node = IdentifierNode() | |
| 546 node.StartParsing('identifier', parent) | |
| 547 node.HandleAttribute('name', name) | |
| 548 node.HandleAttribute('id', id) | |
| 549 node.HandleAttribute('comment', comment) | |
| 550 node.HandleAttribute('systemid', systemid) | |
| 551 node.EndParsing() | |
| 552 return node | |
| OLD | NEW |