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