1# Copyright (c) 2013 by Ladislav Lhotka, CZ.NIC <lhotka@nic.cz> 2# Martin Bjorklund <mbj@tail-f.com> 3# 4# Translator of YANG to the hybrid DSDL schema (see RFC 6110). 5# 6# Permission to use, copy, modify, and/or distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18"""Translator from YANG to hybrid DSDL schema. 19 20It is designed as a plugin for the pyang program and defines several 21new command-line options: 22 23--dsdl-no-documentation 24 No output of DTD compatibility documentation annotations 25 26--dsdl-no-dublin-core 27 No output of Dublin Core annotations 28 29--dsdl-record-defs 30 Record all top-level defs, even if they are not used 31 32Three classes are defined in this module: 33 34* `DSDLPlugin`: pyang plugin interface class 35 36* `HybridDSDLSchema`: provides instance that performs the mapping 37 of input YANG modules to the hybrid DSDL schema. 38 39* `Patch`: utility class representing a patch to the YANG tree 40 where augment and refine statements are recorded. 41""" 42 43__docformat__ = "reStructuredText" 44 45import sys 46import optparse 47import time 48 49 50from pyang import plugin, error, xpath, util, statements, types 51 52from .schemanode import SchemaNode 53 54def pyang_plugin_init(): 55 plugin.register_plugin(DSDLPlugin()) 56 57class DSDLPlugin(plugin.PyangPlugin): 58 def add_output_format(self, fmts): 59 self.multiple_modules = True 60 fmts['dsdl'] = self 61 def add_opts(self, optparser): 62 optlist = [ 63 optparse.make_option("--dsdl-no-documentation", 64 dest="dsdl_no_documentation", 65 action="store_true", 66 default=False, 67 help="No output of DTD compatibility" 68 " documentation annotations"), 69 optparse.make_option("--dsdl-no-dublin-core", 70 dest="dsdl_no_dublin_core", 71 action="store_true", 72 default=False, 73 help="No output of Dublin Core" 74 " metadata annotations"), 75 optparse.make_option("--dsdl-record-defs", 76 dest="dsdl_record_defs", 77 action="store_true", 78 default=False, 79 help="Record all top-level defs" 80 " (even if not used)"), 81 optparse.make_option("--dsdl-lax-yang-version", 82 dest="dsdl_lax_yang_version", 83 action="store_true", 84 default=False, 85 help="Try to translate modules with " 86 "unsupported YANG versions (use at own risk)"), 87 ] 88 g = optparser.add_option_group("Hybrid DSDL schema " 89 "output specific options") 90 g.add_options(optlist) 91 92 def emit(self, ctx, modules, fd): 93 if 'submodule' in [ m.keyword for m in modules ]: 94 raise error.EmitError("Cannot translate submodules") 95 emit_dsdl(ctx, modules, fd) 96 97def emit_dsdl(ctx, modules, fd): 98 for (epos, etag, eargs) in ctx.errors: 99 if error.is_error(error.err_level(etag)): 100 raise error.EmitError("DSDL translation needs a valid module") 101 schema = HybridDSDLSchema().from_modules(modules, 102 ctx.opts.dsdl_no_dublin_core, 103 ctx.opts.dsdl_no_documentation, 104 ctx.opts.dsdl_record_defs, 105 ctx.opts.dsdl_lax_yang_version, 106 debug=0) 107 fd.write(schema.serialize()) 108 109class Patch(object): 110 111 """Instances of this class represent a patch to the YANG tree. 112 113 A Patch is filled with substatements of 'refine' and/or 'augment' 114 that are to be applied to a single node. 115 116 Instance variables: 117 118 * `self.path`: list specifying the relative path to the node where 119 the patch is to be applied 120 121 * `self.plist`: list of statements to apply 122 """ 123 124 def __init__(self, path, refaug): 125 """Initialize the instance with `refaug` statement. 126 127 `refaug` must be either 'refine' or 'augment'. 128 """ 129 self.path = path 130 self.plist = [refaug] 131 132 def pop(self): 133 """Pop and return the first element of `self.path`.""" 134 return self.path.pop(0) 135 136 def combine(self, patch): 137 """Add `patch.plist` to `self.plist`.""" 138 exclusive = set(["config", "default", "mandatory", "presence", 139 "min-elements", "max-elements"]) 140 kws = set([s.keyword for s in self.plist]) & exclusive 141 add = [n for n in patch.plist if n.keyword not in kws] 142 self.plist.extend(add) 143 144class HybridDSDLSchema(object): 145 146 """Instance of this class maps YANG to the hybrid DSDL schema. 147 148 Typically, only a single instance is created. 149 150 Instance variables: 151 152 * `self.all_defs`: dictionary of all named pattern 153 definitions. The keys are mangled names of the definitions. 154 155 * `self.data`: root of the data tree. 156 157 * `self.debug`: debugging information level (0 = no debugging). 158 159 * `self.gg_level`: level of immersion in global groupings. 160 161 * `self.global_defs`: dictionary of global (aka chameleon) named 162 pattern definitions. The keys are mangled names of the 163 definitions. 164 165 * `self.identities`: dictionary of identity names as keys and the 166 corresponding name pattern definitions as values. 167 168 * `self.identity_deps: each item has an identity (statement) as 169 the key and a list of identities derived from the key identity 170 as the value. 171 172 * `self.local_defs`: dictionary of local named pattern 173 definitions. The keys are mangled names of the definitions. 174 175 * `self.local_grammar`: the inner <grammar> element containing the 176 mapping of a single YANG module. 177 178 * `self.module`: the module being processed. 179 180 * `self.module_prefixes`: maps module names to (disambiguated) 181 prefixes. 182 183 * `self.namespaces`: maps used namespace URIs to (disambiguated) 184 prefixes. 185 186 * `self.notifications`: root of the subtree containing 187 notifications. 188 189 * `self.prefix_stack`: stack of active module prefixes. A new 190 prefix is pushed on the stack for an augment from an external 191 module. 192 193 * `self.rpcs`: root of the subtree containing RPC signatures. 194 195 * `self.stmt_handler`: dictionary of methods that are dispatched 196 for handling individual YANG statements. Its keys are YANG 197 statement keywords. 198 199 * `self.top_grammar`: the outer (root) <grammar> element. 200 201 * `self.type_handler`: dictionary of methods that are dispatched 202 for handling individual YANG types. Its keys are the names of 203 YANG built-in types. 204 205 * `self.tree`: outer <start> pattern. 206 207 """ 208 209 YANG_version = 1.1 210 """Checked against the yang-version statement, if present.""" 211 212 dc_uri = "http://purl.org/dc/terms" 213 """Dublin Core URI""" 214 a_uri = "http://relaxng.org/ns/compatibility/annotations/1.0" 215 """DTD compatibility annotations URI""" 216 217 datatype_map = { 218 "int8": "byte", 219 "int16": "short", 220 "int32": "int", 221 "int64": "long", 222 "uint8": "unsignedByte", 223 "uint16": "unsignedShort", 224 "uint32": "unsignedInt", 225 "uint64": "unsignedLong", 226 "decimal64": "decimal", 227 "binary": "base64Binary", 228 "string": "string", 229 } 230 """Mapping of simple datatypes from YANG to W3C datatype library""" 231 232 data_nodes = ("leaf", "container", "leaf-list", "list", 233 "anydata", "anyxml", "rpc", "notification") 234 """Keywords of YANG data nodes.""" 235 236 schema_nodes = data_nodes + ("choice", "case") 237 """Keywords of YANG schema nodes.""" 238 239 def __init__(self): 240 """Initialize the dispatch dictionaries.""" 241 self.stmt_handler = { 242 "action": self.noop, 243 "anyxml": self.anyxml_stmt, 244 "anydata": self.anyxml_stmt, 245 "argument": self.noop, 246 "augment": self.noop, 247 "base": self.noop, 248 "belongs-to": self.noop, 249 "bit": self.noop, 250 "case": self.case_stmt, 251 "choice": self.choice_stmt, 252 "config": self.nma_attribute, 253 "contact": self.noop, 254 "container": self.container_stmt, 255 "default": self.noop, 256 "deviation": self.noop, 257 "deviate": self.noop, 258 "description": self.description_stmt, 259 "enum" : self.enum_stmt, 260 "error-app-tag": self.noop, 261 "error-message": self.noop, 262 "extension": self.noop, 263 "feature": self.noop, 264 "fraction-digits": self.noop, 265 "identity": self.noop, 266 "if-feature": self.noop, 267 "import" : self.noop, 268 "identity": self.noop, 269 "include" : self.include_stmt, 270 "input": self.noop, 271 "grouping" : self.noop, 272 "key": self.noop, 273 "leaf": self.leaf_stmt, 274 "leaf-list": self.leaf_list_stmt, 275 "length": self.noop, 276 "list": self.list_stmt, 277 "mandatory": self.noop, 278 "min-elements": self.noop, 279 "max-elements": self.noop, 280 "modifier": self.noop, 281 "module": self.noop, 282 "must": self.must_stmt, 283 "namespace": self.noop, 284 "notification": self.notification_stmt, 285 "ordered-by": self.nma_attribute, 286 "organization": self.noop, 287 "output": self.noop, 288 "path": self.noop, 289 "pattern": self.noop, 290 "position": self.noop, 291 "prefix": self.noop, 292 "presence": self.noop, 293 "range": self.noop, 294 "reference": self.reference_stmt, 295 "refine": self.noop, 296 "require-instance": self.noop, 297 "revision": self.noop, 298 "revision-date": self.noop, 299 "rpc": self.rpc_stmt, 300 "min-elements": self.noop, 301 "status": self.nma_attribute, 302 "submodule": self.noop, 303 "type": self.type_stmt, 304 "typedef" : self.noop, 305 "unique" : self.unique_stmt, 306 "units" : self.nma_attribute, 307 "uses" : self.uses_stmt, 308 "value": self.noop, 309 "when" : self.when_stmt, 310 "yang-version": self.yang_version_stmt, 311 "yin-element": self.noop, 312 } 313 self.ext_handler = { 314 "ietf-yang-metadata": { 315 "annotation": self.noop 316 } 317 } 318 self.type_handler = { 319 "boolean": self.boolean_type, 320 "binary": self.binary_type, 321 "bits": self.bits_type, 322 "decimal64": self.numeric_type, 323 "enumeration": self.choice_type, 324 "empty": self.noop, 325 "identityref": self.identityref_type, 326 "instance-identifier": self.instance_identifier_type, 327 "int8": self.numeric_type, 328 "int16": self.numeric_type, 329 "int32": self.numeric_type, 330 "int64": self.numeric_type, 331 "leafref": self.leafref_type, 332 "string" : self.string_type, 333 "uint8": self.numeric_type, 334 "uint16": self.numeric_type, 335 "uint32": self.numeric_type, 336 "uint64": self.numeric_type, 337 "union": self.choice_type, 338 } 339 340 def serialize(self): 341 """Return the string representation of the receiver.""" 342 res = '<?xml version="1.0" encoding="UTF-8"?>' 343 for ns in self.namespaces: 344 self.top_grammar.attr["xmlns:" + self.namespaces[ns]] = ns 345 res += self.top_grammar.start_tag() 346 for ch in self.top_grammar.children: 347 res += ch.serialize() 348 res += self.tree.serialize() 349 for d in self.global_defs: 350 res += self.global_defs[d].serialize() 351 for i in self.identities: 352 res += self.identities[i].serialize() 353 return res + self.top_grammar.end_tag() 354 355 def from_modules(self, modules, no_dc=False, no_a=False, 356 record_defs=False, lax_yang_version=False, debug=0): 357 """Return the instance representing mapped input modules.""" 358 self.namespaces = { 359 "urn:ietf:params:xml:ns:netmod:dsdl-annotations:1" : "nma", 360 } 361 if not no_dc: self.namespaces[self.dc_uri] = "dc" 362 if not no_a: self.namespaces[self.a_uri] = "a" 363 self.global_defs = {} 364 self.all_defs = {} 365 self.identity_deps = {} 366 self.identities = {} 367 self.debug = debug 368 self.module_prefixes = {} 369 gpset = {} 370 self.gg_level = 0 371 metadata = [] 372 self.has_meta = False 373 for module in modules[0].i_ctx.modules.values(): 374 yver = module.search_one("yang-version") 375 if yver and float(yver.arg) > 1.0 and not lax_yang_version: 376 raise error.EmitError( 377 "DSDL plugin supports only YANG version 1.") 378 if module.keyword == "module": 379 for idn in module.i_identities.values(): 380 self.register_identity(idn) 381 for module in modules: 382 self.add_namespace(module) 383 self.module = module 384 annots = module.search(("ietf-yang-metadata", "annotation")) 385 for ann in annots: 386 aname = (self.module_prefixes[ann.main_module().arg] + ":" + 387 ann.arg) 388 optel = SchemaNode("optional") 389 atel = SchemaNode("attribute", optel).set_attr("name", aname) 390 self.handle_substmts(ann, atel) 391 metadata.append(optel) 392 if metadata: 393 self.has_meta = True 394 metel = SchemaNode.define("__yang_metadata__") 395 self.global_defs["__yang_metadata__"] = metel 396 for mattr in metadata: 397 metel.subnode(mattr) 398 for module in modules: 399 self.module = module 400 self.prefix_stack = [self.module_prefixes[module.arg]] 401 for aug in module.search("augment"): 402 self.add_patch(gpset, aug) 403 for sub in [ module.i_ctx.get_module(inc.arg) 404 for inc in module.search("include") ]: 405 for aug in sub.search("augment"): 406 self.add_patch(gpset, aug) 407 self.setup_top() 408 for module in modules: 409 self.module = module 410 self.local_defs = {} 411 if record_defs: self.preload_defs() 412 self.prefix_stack = [self.module_prefixes[module.arg]] 413 self.create_roots(module) 414 self.lookup_expand(module, list(gpset.keys())) 415 self.handle_substmts(module, self.data, gpset) 416 for d in list(self.local_defs.values()): 417 self.local_grammar.subnode(d) 418 self.tree.subnode(self.local_grammar) 419 self.all_defs.update(self.local_defs) 420 self.all_defs.update(self.global_defs) 421 self.dc_element(self.top_grammar, "date", time.strftime("%Y-%m-%d")) 422 return self 423 424 def setup_top(self): 425 """Create top-level elements of the hybrid schema.""" 426 self.top_grammar = SchemaNode("grammar") 427 self.top_grammar.attr = { 428 "xmlns": "http://relaxng.org/ns/structure/1.0", 429 "datatypeLibrary": "http://www.w3.org/2001/XMLSchema-datatypes"} 430 self.tree = SchemaNode("start") 431 432 def create_roots(self, yam): 433 """Create the top-level structure for module `yam`.""" 434 self.local_grammar = SchemaNode("grammar") 435 self.local_grammar.attr = { 436 "ns": yam.search_one("namespace").arg, 437 "nma:module": self.module.arg} 438 src_text = "YANG module '%s'" % yam.arg 439 revs = yam.search("revision") 440 if len(revs) > 0: 441 src_text += " revision %s" % self.current_revision(revs) 442 self.dc_element(self.local_grammar, "source", src_text) 443 start = SchemaNode("start", self.local_grammar) 444 self.data = SchemaNode("nma:data", start, interleave=True) 445 self.data.occur = 2 446 self.rpcs = SchemaNode("nma:rpcs", start, interleave=False) 447 self.notifications = SchemaNode("nma:notifications", start, 448 interleave=False) 449 450 def yang_to_xpath(self, xpe): 451 """Transform YANG's `xpath` to a form suitable for Schematron. 452 453 1. Prefixes are added to unprefixed local names. Inside global 454 groupings, the prefix is represented as the variable 455 '$pref' which is substituted via Schematron abstract 456 patterns. 457 2. '$root' is prepended to every absolute location path. 458 """ 459 if self.gg_level: 460 pref = "$pref:" 461 else: 462 pref = self.prefix_stack[-1] + ":" 463 toks = xpath.tokens(xpe) 464 prev = None 465 res = "" 466 for tok in toks: 467 if tok[0] == "/" and prev not in (".", "..", ")", "]", "name", 468 "wildcard", "prefix-test"): 469 res += "$root" 470 elif tok[0] == "name" and ":" not in tok[1]: 471 res += pref 472 res += tok[1] 473 if tok[0] != "whitespace": prev = tok[0] 474 return res 475 476 def add_namespace(self, module): 477 """Add item uri:prefix for `module` to `self.namespaces`. 478 479 The prefix to be actually used for `uri` is returned. If the 480 namespace is already present, the old prefix is used. Prefix 481 clashes are resolved by disambiguating `prefix`. 482 """ 483 uri = module.search_one("namespace").arg 484 prefix = module.search_one("prefix").arg 485 if uri in self.namespaces: return self.namespaces[uri] 486 end = 1 487 new = prefix 488 while new in list(self.namespaces.values()): 489 new = "%s%x" % (prefix,end) 490 end += 1 491 self.namespaces[uri] = new 492 self.module_prefixes[module.arg] = new 493 for inc in module.search("include"): 494 self.module_prefixes[inc.arg] = new 495 return new 496 497 def register_identity(self, id_stmt): 498 """Register `id_stmt` with its base identity, if any. 499 """ 500 bst = id_stmt.search_one("base") 501 if bst: 502 bder = self.identity_deps.setdefault(bst.i_identity, []) 503 bder.append(id_stmt) 504 505 def add_derived_identity(self, id_stmt): 506 """Add pattern def for `id_stmt` and all derived identities. 507 508 The corresponding "ref" pattern is returned. 509 """ 510 p = self.add_namespace(id_stmt.main_module()) 511 if id_stmt not in self.identities: # add named pattern def 512 self.identities[id_stmt] = SchemaNode.define("__%s_%s" % 513 (p, id_stmt.arg)) 514 parent = self.identities[id_stmt] 515 if id_stmt in self.identity_deps: 516 parent = SchemaNode.choice(parent, occur=2) 517 for i in self.identity_deps[id_stmt]: 518 parent.subnode(self.add_derived_identity(i)) 519 idval = SchemaNode("value", parent, p+":"+id_stmt.arg) 520 idval.attr["type"] = "QName" 521 res = SchemaNode("ref") 522 res.attr["name"] = self.identities[id_stmt].attr["name"] 523 return res 524 525 def preload_defs(self): 526 """Preload all top-level definitions.""" 527 for d in (self.module.search("grouping") + 528 self.module.search("typedef")): 529 uname, dic = self.unique_def_name(d) 530 self.install_def(uname, d, dic) 531 532 def add_prefix(self, name, stmt): 533 """Return `name` prepended with correct prefix. 534 535 If the name is already prefixed, the prefix may be translated 536 to the value obtained from `self.module_prefixes`. Unmodified 537 `name` is returned if we are inside a global grouping. 538 """ 539 if self.gg_level: return name 540 pref, colon, local = name.partition(":") 541 if colon: 542 return (self.module_prefixes[stmt.i_module.i_prefixes[pref][0]] 543 + ":" + local) 544 else: 545 return self.prefix_stack[-1] + ":" + pref 546 547 def qname(self, stmt): 548 """Return (prefixed) node name of `stmt`. 549 550 The result is prefixed with the local prefix unless we are 551 inside a global grouping. 552 """ 553 if self.gg_level: return stmt.arg 554 return self.prefix_stack[-1] + ":" + stmt.arg 555 556 def dc_element(self, parent, name, text): 557 """Add DC element `name` containing `text` to `parent`.""" 558 if self.dc_uri in self.namespaces: 559 dcel = SchemaNode(self.namespaces[self.dc_uri] + ":" + name, 560 text=text) 561 parent.children.insert(0,dcel) 562 563 def get_default(self, stmt, refd): 564 """Return default value for `stmt` node. 565 566 `refd` is a dictionary of applicable refinements that is 567 constructed in the `process_patches` method. 568 """ 569 if refd["default"]: 570 return refd["default"] 571 defst = stmt.search_one("default") 572 if defst: 573 return defst.arg 574 return None 575 576 def unique_def_name(self, stmt, inrpc=False): 577 """Mangle the name of `stmt` (typedef or grouping). 578 579 Return the mangled name and dictionary where the definition is 580 to be installed. The `inrpc` flag indicates when we are inside 581 an RPC, in which case the name gets the "__rpc" suffix. 582 """ 583 module = stmt.main_module() 584 name = "" 585 while True: 586 pref = stmt.arg if stmt.arg else stmt.keyword 587 name = "__" + pref + name 588 if stmt.keyword == "grouping": name = "_" + name 589 if stmt.parent.parent is None: break 590 stmt = stmt.parent 591 defs = (self.global_defs 592 if stmt.keyword in ("grouping", "typedef") 593 else self.local_defs) 594 if inrpc: name += "__rpc" 595 return (module.arg + name, defs) 596 597 def add_patch(self, pset, augref): 598 """Add patch corresponding to `augref` to `pset`. 599 600 `augref` must be either 'augment' or 'refine' statement. 601 """ 602 try: 603 path = [ self.add_prefix(c, augref) 604 for c in augref.arg.split("/") if c ] 605 except KeyError: 606 # augment of a module that's not among input modules 607 return 608 car = path[0] 609 patch = Patch(path[1:], augref) 610 if car in pset: 611 sel = [ x for x in pset[car] if patch.path == x.path ] 612 if sel: 613 sel[0].combine(patch) 614 else: 615 pset[car].append(patch) 616 else: 617 pset[car] = [patch] 618 619 def apply_augments(self, auglist, p_elem, pset): 620 """Handle substatements of augments from `auglist`. 621 622 The augments are applied in the context of `p_elem`. `pset` 623 is a patch set containing patches that may be applicable to 624 descendants. 625 """ 626 for a in auglist: 627 par = a.parent 628 if a.search_one("when") is None: 629 wel = p_elem 630 else: 631 if p_elem.interleave: 632 kw = "interleave" 633 else: 634 kw = "group" 635 wel = SchemaNode(kw, p_elem, interleave=p_elem.interleave) 636 wel.occur = p_elem.occur 637 if par.keyword == "uses": 638 self.handle_substmts(a, wel, pset) 639 continue 640 if par.keyword == "submodule": 641 mnam = par.i_including_modulename 642 else: 643 mnam = par.arg 644 if self.prefix_stack[-1] == self.module_prefixes[mnam]: 645 self.handle_substmts(a, wel, pset) 646 else: 647 self.prefix_stack.append(self.module_prefixes[mnam]) 648 self.handle_substmts(a, wel, pset) 649 self.prefix_stack.pop() 650 651 def current_revision(self, r_stmts): 652 """Pick the most recent revision date. 653 654 `r_stmts` is a list of 'revision' statements. 655 """ 656 cur = max([[int(p) for p in r.arg.split("-")] for r in r_stmts]) 657 return "%4d-%02d-%02d" % tuple(cur) 658 659 def insert_doc(self, p_elem, docstring): 660 """Add <a:documentation> with `docstring` to `p_elem`.""" 661 dtag = self.namespaces[self.a_uri] + ":documentation" 662 elem = SchemaNode(dtag, text=docstring) 663 p_elem.annots.append(elem) 664 665 def install_def(self, name, dstmt, def_map, interleave=False): 666 """Install definition `name` into the appropriate dictionary. 667 668 `dstmt` is the definition statement ('typedef' or 'grouping') 669 that is to be mapped to a RELAX NG named pattern '<define 670 name="`name`">'. `def_map` must be either `self.local_defs` or 671 `self.global_defs`. `interleave` determines the interleave 672 status inside the definition. 673 """ 674 delem = SchemaNode.define(name, interleave=interleave) 675 delem.attr["name"] = name 676 def_map[name] = delem 677 if def_map is self.global_defs: self.gg_level += 1 678 self.handle_substmts(dstmt, delem) 679 if def_map is self.global_defs: self.gg_level -= 1 680 681 def rng_annotation(self, stmt, p_elem): 682 """Append YIN representation of extension statement `stmt`.""" 683 ext = stmt.i_extension 684 prf, extkw = stmt.raw_keyword 685 (modname,rev)=stmt.i_module.i_prefixes[prf] 686 prefix = self.add_namespace( 687 statements.modulename_to_module(self.module,modname,rev)) 688 eel = SchemaNode(prefix + ":" + extkw, p_elem) 689 argst = ext.search_one("argument") 690 if argst: 691 if argst.search_one("yin-element", "true"): 692 SchemaNode(prefix + ":" + argst.arg, eel, stmt.arg) 693 else: 694 eel.attr[argst.arg] = stmt.arg 695 self.handle_substmts(stmt, eel) 696 697 def propagate_occur(self, node, value): 698 """Propagate occurence `value` to `node` and its ancestors. 699 700 Occurence values are defined and explained in the SchemaNode 701 class. 702 """ 703 while node.occur < value: 704 node.occur = value 705 if node.name == "define": 706 break 707 node = node.parent 708 709 def process_patches(self, pset, stmt, elem, altname=None): 710 """Process patches for data node `name` from `pset`. 711 712 `stmt` provides the context in YANG and `elem` is the parent 713 element in the output schema. Refinements adding documentation 714 and changing the config status are immediately applied. 715 716 The returned tuple consists of: 717 - a dictionary of refinements, in which keys are the keywords 718 of the refinement statements and values are the new values 719 of refined parameters. 720 - a list of 'augment' statements that are to be applied 721 directly under `elem`. 722 - a new patch set containing patches applicable to 723 substatements of `stmt`. 724 """ 725 if altname: 726 name = altname 727 else: 728 name = stmt.arg 729 new_pset = {} 730 augments = [] 731 refine_dict = dict.fromkeys(("presence", "default", "mandatory", 732 "min-elements", "max-elements")) 733 for p in pset.pop(self.add_prefix(name, stmt), []): 734 if p.path: 735 head = p.pop() 736 if head in new_pset: 737 new_pset[head].append(p) 738 else: 739 new_pset[head] = [p] 740 else: 741 for refaug in p.plist: 742 if refaug.keyword == "augment": 743 augments.append(refaug) 744 else: 745 for s in refaug.substmts: 746 if s.keyword == "description": 747 self.description_stmt(s, elem, None) 748 elif s.keyword == "reference": 749 self.reference_stmt(s, elem, None) 750 elif s.keyword == "must": 751 self.must_stmt(s, elem, None) 752 elif s.keyword == "config": 753 self.nma_attribute(s, elem) 754 elif refine_dict.get(s.keyword, False) is None: 755 refine_dict[s.keyword] = s.arg 756 return (refine_dict, augments, new_pset) 757 758 def get_minmax(self, stmt, refine_dict): 759 """Return pair of (min,max)-elements values for `stmt`. 760 761 `stmt` must be a 'list' or 'leaf-list'. Applicable refinements 762 from `refine_dict` are also taken into account. 763 """ 764 minel = refine_dict["min-elements"] 765 maxel = refine_dict["max-elements"] 766 if minel is None: 767 minst = stmt.search_one("min-elements") 768 if minst: 769 minel = minst.arg 770 else: 771 minel = "0" 772 if maxel is None: 773 maxst = stmt.search_one("max-elements") 774 if maxst: 775 maxel = maxst.arg 776 if maxel == "unbounded": maxel = None 777 return (minel, maxel) 778 779 def lookup_expand(self, stmt, names): 780 """Find schema nodes under `stmt`, also in used groupings. 781 782 `names` is a list with qualified names of the schema nodes to 783 look up. All 'uses'/'grouping' pairs between `stmt` and found 784 schema nodes are marked for expansion. 785 """ 786 if not names: return [] 787 todo = [stmt] 788 while todo: 789 pst = todo.pop() 790 for sub in pst.substmts: 791 if sub.keyword in self.schema_nodes: 792 qname = self.qname(sub) 793 if qname in names: 794 names.remove(qname) 795 par = sub.parent 796 while hasattr(par,"d_ref"): # par must be grouping 797 par.d_ref.d_expand = True 798 par = par.d_ref.parent 799 if not names: return [] # all found 800 elif sub.keyword == "uses": 801 g = sub.i_grouping 802 g.d_ref = sub 803 todo.append(g) 804 return names 805 806 def type_with_ranges(self, tchain, p_elem, rangekw, gen_data): 807 """Handle types with 'range' or 'length' restrictions. 808 809 `tchain` is the chain of type definitions from which the 810 ranges may need to be extracted. `rangekw` is the statement 811 keyword determining the range type (either 'range' or 812 'length'). `gen_data` is a function that generates the 813 output schema node (a RELAX NG <data> pattern). 814 """ 815 ranges = self.get_ranges(tchain, rangekw) 816 if not ranges: return p_elem.subnode(gen_data()) 817 if len(ranges) > 1: 818 p_elem = SchemaNode.choice(p_elem) 819 p_elem.occur = 2 820 for r in ranges: 821 d_elem = gen_data() 822 for p in self.range_params(r, rangekw): 823 d_elem.subnode(p) 824 p_elem.subnode(d_elem) 825 826 def get_ranges(self, tchain, kw): 827 """Return list of ranges defined in `tchain`. 828 829 `kw` is the statement keyword determining the type of the 830 range, i.e. 'range' or 'length'. `tchain` is the chain of type 831 definitions from which the resulting range is obtained. 832 833 The returned value is a list of tuples containing the segments 834 of the resulting range. 835 """ 836 (lo, hi) = ("min", "max") 837 ran = None 838 for t in tchain: 839 rstmt = t.search_one(kw) 840 if rstmt is None: continue 841 parts = [ p.strip() for p in rstmt.arg.split("|") ] 842 ran = [ [ i.strip() for i in p.split("..") ] for p in parts ] 843 if ran[0][0] != 'min': lo = ran[0][0] 844 if ran[-1][-1] != 'max': hi = ran[-1][-1] 845 if ran is None: return None 846 if len(ran) == 1: 847 return [(lo, hi)] 848 else: 849 return [(lo, ran[0][-1])] + ran[1:-1] + [(ran[-1][0], hi)] 850 851 def range_params(self, ran, kw): 852 """Return list of <param>s corresponding to range `ran`. 853 854 `kw` is the statement keyword determining the type of the 855 range, i.e. 'range' or 'length'. `ran` is the internal 856 representation of a range as constructed by the `get_ranges` 857 method. 858 """ 859 if kw == "length": 860 if ran[0][0] != "m" and (len(ran) == 1 or ran[0] == ran[1]): 861 elem = SchemaNode("param").set_attr("name","length") 862 elem.text = ran[0] 863 return [elem] 864 min_ = SchemaNode("param").set_attr("name","minLength") 865 max_ = SchemaNode("param").set_attr("name","maxLength") 866 else: 867 if len(ran) == 1: ran *= 2 # duplicating the value 868 min_ = SchemaNode("param").set_attr("name","minInclusive") 869 max_ = SchemaNode("param").set_attr("name","maxInclusive") 870 res = [] 871 if ran[0][0] != "m": 872 elem = min_ 873 elem.text = ran[0] 874 res.append(elem) 875 if ran[1][0] != "m": 876 elem = max_ 877 elem.text = ran[1] 878 res.append(elem) 879 return res 880 881 def handle_stmt(self, stmt, p_elem, pset={}): 882 """ 883 Run handler method for statement `stmt`. 884 885 `p_elem` is the parent node in the output schema. `pset` is 886 the current "patch set" - a dictionary with keys being QNames 887 of schema nodes at the current level of hierarchy for which 888 (or descendants thereof) any pending patches exist. The values 889 are instances of the Patch class. 890 891 All handler methods are defined below and must have the same 892 arguments as this method. They should create the output schema 893 fragment corresponding to `stmt`, apply all patches from 894 `pset` belonging to `stmt`, insert the fragment under `p_elem` 895 and perform all side effects as necessary. 896 """ 897 if self.debug > 0: 898 sys.stderr.write("Handling '%s %s'\n" % 899 (util.keyword_to_str(stmt.raw_keyword), stmt.arg)) 900 try: 901 method = self.stmt_handler[stmt.keyword] 902 except KeyError: 903 if isinstance(stmt.keyword, tuple): 904 try: 905 method = self.ext_handler[stmt.keyword[0]][stmt.keyword[1]] 906 except KeyError: 907 method = self.rng_annotation 908 method(stmt, p_elem) 909 return 910 else: 911 raise error.EmitError( 912 "Unknown keyword %s - this should not happen.\n" 913 % stmt.keyword) 914 method(stmt, p_elem, pset) 915 916 def handle_substmts(self, stmt, p_elem, pset={}): 917 """Handle all substatements of `stmt`.""" 918 for sub in stmt.substmts: 919 self.handle_stmt(sub, p_elem, pset) 920 921 # Handlers for YANG statements 922 923 def noop(self, stmt, p_elem, pset=''): 924 """`stmt` is not handled in the regular way.""" 925 pass 926 927 def anyxml_stmt(self, stmt, p_elem, pset): 928 elem = SchemaNode.element(self.qname(stmt), p_elem) 929 if self.has_meta: 930 elem.annot( 931 SchemaNode("ref").set_attr("name", "__yang_metadata__")) 932 SchemaNode("parentRef", elem).set_attr("name", "__anyxml__") 933 refd = self.process_patches(pset, stmt, elem)[0] 934 if p_elem.name == "choice": 935 elem.occur = 3 936 elif refd["mandatory"] or stmt.search_one("mandatory", "true"): 937 elem.occur = 2 938 self.propagate_occur(p_elem, 2) 939 self.handle_substmts(stmt, elem) 940 941 def nma_attribute(self, stmt, p_elem, pset=None): 942 """Map `stmt` to a NETMOD-specific attribute. 943 944 The name of the attribute is the same as the 'keyword' of 945 `stmt`. 946 """ 947 att = "nma:" + stmt.keyword 948 if att not in p_elem.attr: 949 p_elem.attr[att] = stmt.arg 950 951 def case_stmt(self, stmt, p_elem, pset): 952 celem = SchemaNode.case(p_elem) 953 if p_elem.default_case != stmt.arg: 954 celem.occur = 3 955 refd, augs, new_pset = self.process_patches(pset, stmt, celem) 956 left = self.lookup_expand(stmt, list(new_pset.keys())) 957 for a in augs: 958 left = self.lookup_expand(a, left) 959 self.handle_substmts(stmt, celem, new_pset) 960 self.apply_augments(augs, celem, new_pset) 961 962 def choice_stmt(self, stmt, p_elem, pset): 963 chelem = SchemaNode.choice(p_elem) 964 chelem.attr["nma:name"] = stmt.arg 965 refd, augs, new_pset = self.process_patches(pset, stmt, chelem) 966 left = self.lookup_expand(stmt, list(new_pset.keys())) 967 for a in augs: 968 left = self.lookup_expand(a, left) 969 if refd["mandatory"] or stmt.search_one("mandatory", "true"): 970 chelem.attr["nma:mandatory"] = "true" 971 self.propagate_occur(chelem, 2) 972 else: 973 defv = self.get_default(stmt, refd) 974 if defv is not None: 975 chelem.default_case = defv 976 else: 977 chelem.occur = 3 978 self.handle_substmts(stmt, chelem, new_pset) 979 self.apply_augments(augs, chelem, new_pset) 980 981 def container_stmt(self, stmt, p_elem, pset): 982 celem = SchemaNode.element(self.qname(stmt), p_elem) 983 if self.has_meta: 984 celem.annot( 985 SchemaNode("ref").set_attr("name", "__yang_metadata__")) 986 refd, augs, new_pset = self.process_patches(pset, stmt, celem) 987 left = self.lookup_expand(stmt, list(new_pset.keys())) 988 for a in augs: 989 left = self.lookup_expand(a, left) 990 if (p_elem.name == "choice" and p_elem.default_case != stmt.arg 991 or p_elem.name == "case" and 992 p_elem.parent.default_case != stmt.parent.arg and 993 len(stmt.parent.i_children) < 2 or 994 refd["presence"] or stmt.search_one("presence")): 995 celem.occur = 3 996 self.handle_substmts(stmt, celem, new_pset) 997 self.apply_augments(augs, celem, new_pset) 998 999 def description_stmt(self, stmt, p_elem, pset): 1000 # ignore imported and top-level descriptions + desc. of enum 1001 if (self.a_uri in self.namespaces and 1002 stmt.i_module == self.module != stmt.parent and 1003 stmt.parent.keyword != "enum"): 1004 self.insert_doc(p_elem, stmt.arg) 1005 1006 def enum_stmt(self, stmt, p_elem, pset): 1007 elem = SchemaNode("value", p_elem, stmt.arg) 1008 for sub in stmt.search("status"): 1009 self.handle_stmt(sub, elem) 1010 1011 def include_stmt(self, stmt, p_elem, pset): 1012 if stmt.parent.keyword == "module": 1013 subm = self.module.i_ctx.get_module(stmt.arg) 1014 self.handle_substmts(subm, p_elem, pset) 1015 1016 def leaf_stmt(self, stmt, p_elem, pset): 1017 qname = self.qname(stmt) 1018 elem = SchemaNode.element(qname) 1019 if self.has_meta: 1020 elem.annot( 1021 SchemaNode("ref").set_attr("name", "__yang_metadata__")) 1022 if p_elem.name == "_list_" and qname in p_elem.keys: 1023 p_elem.keymap[qname] = elem 1024 elem.occur = 2 1025 else: 1026 p_elem.subnode(elem) 1027 refd = self.process_patches(pset, stmt, elem)[0] 1028 if (p_elem.name == "choice" and p_elem.default_case != stmt.arg or 1029 p_elem.name == "case" and 1030 p_elem.parent.default_case != stmt.parent.arg and 1031 len(stmt.parent.i_children) < 2): 1032 elem.occur = 3 1033 elif refd["mandatory"] or stmt.search_one("mandatory", "true"): 1034 self.propagate_occur(elem, 2) 1035 if elem.occur == 0: 1036 defv = self.get_default(stmt, refd) 1037 if defv is not None: 1038 elem.default = defv 1039 self.propagate_occur(elem, 1) 1040 self.handle_substmts(stmt, elem) 1041 1042 def leaf_list_stmt(self, stmt, p_elem, pset): 1043 lelem = SchemaNode.leaf_list(self.qname(stmt), p_elem) 1044 lelem.attr["nma:leaf-list"] = "true" 1045 if self.has_meta: 1046 lelem.annot( 1047 SchemaNode("ref").set_attr("name", "__yang_metadata__")) 1048 refd = self.process_patches(pset, stmt, lelem)[0] 1049 lelem.minEl, lelem.maxEl = self.get_minmax(stmt, refd) 1050 if int(lelem.minEl) > 0: self.propagate_occur(p_elem, 2) 1051 self.handle_substmts(stmt, lelem) 1052 1053 def list_stmt(self, stmt, p_elem, pset): 1054 lelem = SchemaNode.list(self.qname(stmt), p_elem) 1055 if self.has_meta: 1056 lelem.annot( 1057 SchemaNode("ref").set_attr("name", "__yang_metadata__")) 1058 keyst = stmt.search_one("key") 1059 if keyst: lelem.keys = [self.add_prefix(k, stmt) 1060 for k in keyst.arg.split()] 1061 refd, augs, new_pset = self.process_patches(pset, stmt, lelem) 1062 left = self.lookup_expand(stmt, list(new_pset.keys()) + lelem.keys) 1063 for a in augs: 1064 left = self.lookup_expand(a, left) 1065 lelem.minEl, lelem.maxEl = self.get_minmax(stmt, refd) 1066 if int(lelem.minEl) > 0: self.propagate_occur(p_elem, 2) 1067 self.handle_substmts(stmt, lelem, new_pset) 1068 self.apply_augments(augs, lelem, new_pset) 1069 1070 def must_stmt(self, stmt, p_elem, pset): 1071 mel = SchemaNode("nma:must") 1072 p_elem.annot(mel) 1073 mel.attr["assert"] = self.yang_to_xpath(stmt.arg) 1074 em = stmt.search_one("error-message") 1075 if em: 1076 SchemaNode("nma:error-message", mel, em.arg) 1077 eat = stmt.search_one("error-app-tag") 1078 if eat: 1079 SchemaNode("nma:error-app-tag", mel, eat.arg) 1080 1081 def notification_stmt(self, stmt, p_elem, pset): 1082 notel = SchemaNode("nma:notification", self.notifications) 1083 notel.occur = 2 1084 elem = SchemaNode.element(self.qname(stmt), notel, 1085 interleave=True, occur=2) 1086 augs, new_pset = self.process_patches(pset, stmt, elem)[1:] 1087 self.handle_substmts(stmt, elem, new_pset) 1088 self.apply_augments(augs, elem, new_pset) 1089 1090 def reference_stmt(self, stmt, p_elem, pset): 1091 # ignore imported and top-level descriptions + desc. of enum 1092 if (self.a_uri in self.namespaces and 1093 stmt.i_module == self.module != stmt.parent and 1094 stmt.parent.keyword != "enum"): 1095 self.insert_doc(p_elem, "See: " + stmt.arg) 1096 1097 def rpc_stmt(self, stmt, p_elem, pset): 1098 rpcel = SchemaNode("nma:rpc", self.rpcs) 1099 r_pset = self.process_patches(pset, stmt, rpcel)[2] 1100 inpel = SchemaNode("nma:input", rpcel) 1101 elem = SchemaNode.element(self.qname(stmt), inpel, occur=2) 1102 augs, pset = self.process_patches(r_pset,stmt,elem,"input")[1:] 1103 inst = stmt.search_one("input") 1104 if inst: 1105 self.handle_substmts(inst, elem, pset) 1106 else: 1107 SchemaNode("empty", elem) 1108 self.apply_augments(augs, elem, pset) 1109 augs, pset = self.process_patches(r_pset,stmt,None,"output")[1:] 1110 oust = stmt.search_one("output") 1111 if oust or augs: 1112 outel = SchemaNode("nma:output", rpcel) 1113 outel.occur = 2 1114 if oust: self.handle_substmts(oust, outel, pset) 1115 self.apply_augments(augs, outel, pset) 1116 self.handle_substmts(stmt, rpcel, r_pset) 1117 1118 def type_stmt(self, stmt, p_elem, pset): 1119 """Handle ``type`` statement. 1120 1121 Built-in types are handled by one of the specific type 1122 callback methods defined below. 1123 """ 1124 typedef = stmt.i_typedef 1125 if typedef and not stmt.i_is_derived: # just ref 1126 uname, dic = self.unique_def_name(typedef) 1127 if uname not in dic: 1128 self.install_def(uname, typedef, dic) 1129 SchemaNode("ref", p_elem).set_attr("name", uname) 1130 defst = typedef.search_one("default") 1131 if defst: 1132 dic[uname].default = defst.arg 1133 occur = 1 1134 else: 1135 occur = dic[uname].occur 1136 if occur > 0: self.propagate_occur(p_elem, occur) 1137 return 1138 chain = [stmt] 1139 tdefault = None 1140 while typedef: 1141 type_ = typedef.search_one("type") 1142 chain.insert(0, type_) 1143 if tdefault is None: 1144 tdef = typedef.search_one("default") 1145 if tdef: 1146 tdefault = tdef.arg 1147 typedef = type_.i_typedef 1148 if tdefault and p_elem.occur == 0: 1149 p_elem.default = tdefault 1150 self.propagate_occur(p_elem, 1) 1151 self.type_handler[chain[0].arg](chain, p_elem) 1152 1153 def unique_stmt(self, stmt, p_elem, pset): 1154 def addpref(nid): 1155 xpath_nodes = [] 1156 child = stmt.parent 1157 for node in nid.split("/"): 1158 ns, node_name = self.add_prefix(node, stmt).split(":") 1159 child = statements.search_child(child.substmts, 1160 child.i_module.i_modulename, 1161 node_name) 1162 if child is None or child.keyword not in ["choice", "case"]: 1163 xpath_nodes.append(ns + ":" + node_name) 1164 return "/".join(xpath_nodes) 1165 uel = SchemaNode("nma:unique") 1166 p_elem.annot(uel) 1167 uel.attr["tag"] = " ".join( 1168 [addpref(nid) for nid in stmt.arg.split()]) 1169 1170 def uses_stmt(self, stmt, p_elem, pset): 1171 expand = False 1172 grp = stmt.i_grouping 1173 for sub in stmt.substmts: 1174 if sub.keyword in ("refine", "augment"): 1175 expand = True 1176 self.add_patch(pset, sub) 1177 if expand: 1178 self.lookup_expand(grp, list(pset.keys())) 1179 elif len(self.prefix_stack) <= 1 and not(hasattr(stmt,"d_expand")): 1180 uname, dic = self.unique_def_name(stmt.i_grouping, 1181 not(p_elem.interleave)) 1182 if uname not in dic: 1183 self.install_def(uname, stmt.i_grouping, dic, 1184 p_elem.interleave) 1185 elem = SchemaNode("ref", p_elem).set_attr("name", uname) 1186 occur = dic[uname].occur 1187 if occur > 0: self.propagate_occur(p_elem, occur) 1188 self.handle_substmts(stmt, elem) 1189 return 1190 self.handle_substmts(grp, p_elem, pset) 1191 1192 def when_stmt(self, stmt, p_elem, pset=None): 1193 p_elem.attr["nma:when"] = self.yang_to_xpath(stmt.arg) 1194 1195 def yang_version_stmt(self, stmt, p_elem, pset): 1196 if float(stmt.arg) > self.YANG_version: 1197 raise error.EmitError("Unsupported YANG version: %s" % stmt.arg) 1198 1199 # Handlers for YANG types 1200 1201 def binary_type(self, tchain, p_elem): 1202 def gen_data(): 1203 return SchemaNode("data").set_attr("type", "base64Binary") 1204 self.type_with_ranges(tchain, p_elem, "length", gen_data) 1205 1206 def bits_type(self, tchain, p_elem): 1207 elem = SchemaNode("list", p_elem) 1208 zom = SchemaNode("zeroOrMore", elem) 1209 choi = SchemaNode.choice(zom, occur=2) 1210 for bit in tchain[0].search("bit"): 1211 SchemaNode("value", choi, bit.arg) 1212 1213 def boolean_type(self, tchain, p_elem): 1214 elem = SchemaNode.choice(p_elem, occur=2) 1215 SchemaNode("value", elem, "true") 1216 SchemaNode("value", elem, "false") 1217 1218 def choice_type(self, tchain, p_elem): 1219 """Handle ``enumeration`` and ``union`` types.""" 1220 elem = SchemaNode.choice(p_elem, occur=2) 1221 self.handle_substmts(tchain[0], elem) 1222 1223 def empty_type(self, tchain, p_elem): 1224 SchemaNode("empty", p_elem) 1225 1226 def identityref_type(self, tchain, p_elem): 1227 bid = tchain[0].search_one("base").i_identity 1228 if bid not in self.identity_deps: 1229 sys.stderr.write("%s: warning: identityref has empty value space\n" 1230 % tchain[0].pos) 1231 p_elem.subnode(SchemaNode("notAllowed")) 1232 p_elem.occur = 0 1233 return 1234 der = self.identity_deps[bid] 1235 if len(der) > 1: 1236 p_elem = SchemaNode.choice(p_elem, occur=2) 1237 for i in der: 1238 p_elem.subnode(self.add_derived_identity(i)) 1239 1240 def instance_identifier_type(self, tchain, p_elem): 1241 SchemaNode("parentRef", p_elem).attr["name"] = "__instance-identifier__" 1242 ii = SchemaNode("nma:instance-identifier") 1243 p_elem.annot(ii) 1244 rinst = tchain[0].search_one("require-instance") 1245 if rinst: ii.attr["require-instance"] = rinst.arg 1246 1247 def leafref_type(self, tchain, p_elem): 1248 typ = tchain[0] 1249 occur = p_elem.occur 1250 pathstr = typ.parent.i_leafref.i_expanded_path 1251 p_elem.attr["nma:leafref"] = self.yang_to_xpath(pathstr) 1252 while type(typ.i_type_spec) == types.PathTypeSpec: 1253 typ = typ.i_type_spec.i_target_node.search_one("type") 1254 self.handle_stmt(typ, p_elem) 1255 if occur == 0: p_elem.occur = 0 1256 1257 def mapped_type(self, tchain, p_elem): 1258 """Handle types that are simply mapped to RELAX NG.""" 1259 SchemaNode("data", p_elem).set_attr("type", 1260 self.datatype_map[tchain[0].arg]) 1261 1262 def numeric_type(self, tchain, p_elem): 1263 """Handle numeric types.""" 1264 typ = tchain[0].arg 1265 def gen_data(): 1266 elem = SchemaNode("data").set_attr("type", self.datatype_map[typ]) 1267 if typ == "decimal64": 1268 fd = tchain[0].search_one("fraction-digits").arg 1269 SchemaNode("param",elem,"19").set_attr("name","totalDigits") 1270 SchemaNode("param",elem,fd).set_attr("name","fractionDigits") 1271 return elem 1272 self.type_with_ranges(tchain, p_elem, "range", gen_data) 1273 1274 def string_type(self, tchain, p_elem): 1275 pels = [] 1276 for t in tchain: 1277 for pst in t.search("pattern"): 1278 pels.append(SchemaNode("param", 1279 text=pst.arg).set_attr("name","pattern")) 1280 def get_data(): 1281 elem = SchemaNode("data").set_attr("type", "string") 1282 for p in pels: elem.subnode(p) 1283 return elem 1284 self.type_with_ranges(tchain, p_elem, "length", get_data) 1285