Use FileGenerator for java-api-generator
[mdsal.git] / binding / maven-sal-api-gen-plugin / src / main / java / org / opendaylight / mdsal / binding / yang / unified / doc / generator / GeneratorImpl.xtend
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.yang.unified.doc.generator
9
10 import java.io.BufferedWriter
11 import java.io.File
12 import java.io.IOException
13 import java.io.OutputStreamWriter
14 import java.nio.charset.StandardCharsets
15 import java.nio.file.Files
16 import java.util.ArrayList
17 import java.util.Collection
18 import java.util.IdentityHashMap
19 import java.util.HashMap
20 import java.util.HashSet
21 import java.util.LinkedHashMap
22 import java.util.List
23 import java.util.Map
24 import java.util.Optional
25 import java.util.Set
26 import org.gaul.modernizer_maven_annotations.SuppressModernizer
27 import org.opendaylight.yangtools.yang.common.QName
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates
30 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode
31 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget
32 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode
33 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode
34 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
35 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
36 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
37 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext
38 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraintAware
39 import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition
40 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition
41 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
42 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
43 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
44 import org.opendaylight.yangtools.yang.model.api.MandatoryAware
45 import org.opendaylight.yangtools.yang.model.api.Module
46 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
47 import org.opendaylight.yangtools.yang.model.api.SchemaNode
48 import org.opendaylight.yangtools.yang.model.api.SchemaPath
49 import org.opendaylight.yangtools.yang.model.api.TypeAware
50 import org.opendaylight.yangtools.yang.model.api.TypeDefinition
51 import org.opendaylight.yangtools.yang.model.api.UsesNode
52 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement
53 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier
54 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute
55 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint
56 import org.opendaylight.yangtools.yang.model.api.type.LengthRestrictedTypeDefinition
57 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint
58 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition
59 import org.slf4j.Logger
60 import org.slf4j.LoggerFactory
61 import org.sonatype.plexus.build.incremental.BuildContext
62
63 @SuppressModernizer
64 class GeneratorImpl {
65
66     static val Logger LOG = LoggerFactory.getLogger(GeneratorImpl)
67
68     val Map<String, String> imports = new HashMap
69     val Map<TypeDefinition<?>, SchemaPath> types = new IdentityHashMap
70     var Module currentModule
71     var EffectiveModelContext ctx
72     var File path
73
74     StringBuilder augmentChildNodesAsString
75
76     DataSchemaNode lastNodeInTargetPath = null
77
78     new(EffectiveModelContext context) {
79         this.ctx = context
80         fillTypes(SchemaPath.ROOT, context.moduleStatements.values)
81     }
82
83     private def void fillTypes(SchemaPath path, Collection<? extends EffectiveStatement<?, ?>> stmts) {
84         for (stmt : stmts) {
85             val arg = stmt.argument
86             if (arg instanceof QName) {
87                 val stmtPath = path.createChild(arg)
88                 if (stmt instanceof TypeDefinition) {
89                     types.putIfAbsent(stmt, stmtPath)
90                 } else if (stmt instanceof TypeAware) {
91                     val type = stmt.type
92                     val typePath = stmtPath.createChild(type.QName)
93                     types.putIfAbsent(type, typePath)
94                 }
95
96                 fillTypes(stmtPath, stmt.effectiveSubstatements)
97             }
98         }
99     }
100
101     def generate(BuildContext buildContext, File targetPath, Set<Module> modulesToGen) throws IOException {
102         path = targetPath
103         Files.createDirectories(path.getParentFile().toPath())
104         val it = new HashSet
105         for (module : modulesToGen) {
106             add(generateDocumentation(buildContext, module))
107         }
108         return it;
109     }
110
111     def generateDocumentation(BuildContext buildContext, Module module) {
112         val destination = new File(path, '''«module.name».html''')
113         module.imports.forEach[importModule | this.imports.put(importModule.prefix, importModule.moduleName)]
114         var OutputStreamWriter fw
115         var BufferedWriter bw
116         try {
117             fw = new OutputStreamWriter(buildContext.newFileOutputStream(destination), StandardCharsets.UTF_8)
118             bw = new BufferedWriter(fw)
119             currentModule = module
120             bw.append(generate(module, ctx))
121         } catch (IOException e) {
122             LOG.error("Failed to emit file {}", destination, e);
123         } finally {
124             if (bw !== null) {
125                 bw.close();
126             }
127             if (fw !== null) {
128                 fw.close();
129             }
130         }
131         return destination
132     }
133
134     def generate(Module module, EffectiveModelContext ctx) '''
135         <!DOCTYPE html>
136         <html lang="en">
137           <head>
138             <title>«module.name»</title>
139           </head>
140           <body>
141             «body(module, ctx)»
142           </body>
143         </html>
144     '''
145
146     def body(Module module, EffectiveModelContext ctx) '''
147         «header(module)»
148
149         «typeDefinitionsSummary(module)»
150         «identitiesSummary(module)»
151         «groupingsSummary(module)»
152         «augmentationsSummary(module, ctx)»
153         «objectsSummary(module)»
154         «notificationsSummary(module)»
155         «rpcsSummary(module)»
156         «extensionsSummary(module)»
157         «featuresSummary(module)»
158
159         «typeDefinitions(module)»
160
161         «identities(module)»
162
163         «groupings(module)»
164
165         «dataStore(module)»
166
167         «childNodes(module)»
168
169         «notifications(module)»
170
171         «augmentations(module, ctx)»
172
173         «rpcs(module)»
174
175         «extensions(module)»
176
177         «features(module)»
178
179     '''
180
181
182     private def typeDefinitionsSummary(Module module) {
183         val Collection<? extends TypeDefinition<?>> typedefs = module.typeDefinitions
184         if (typedefs.empty) {
185             return '';
186         }
187         return '''
188         <div>
189             <h3>Type Definitions Summary</h3>
190             <table>
191                 <tr>
192                     <th>Name</th>
193                     <th>Description</th>
194                 </tr>
195                 «FOR typedef : typedefs»
196                 <tr>
197                     <td>
198                     «anchorLink(typedef.QName.localName, strong(typedef.QName.localName))»
199                     </td>
200                     <td>
201                     «typedef.description»
202                     </td>
203                 </tr>
204                 «ENDFOR»
205             </table>
206         </div>
207         '''
208     }
209
210     def typeDefinitions(Module module) {
211         val Collection<? extends TypeDefinition<?>> typedefs = module.typeDefinitions
212         if (typedefs.empty) {
213             return '';
214         }
215         return '''
216             <h2>Type Definitions</h2>
217             <ul>
218             «FOR typedef : typedefs»
219                 <li>
220                     <h3 id="«typedef.QName.localName»">«typedef.QName.localName»</h3>
221                     <ul>
222                     «typedef.descAndRefLi»
223                     «typedef.restrictions»
224                     </ul>
225                 </li>
226             «ENDFOR»
227             </ul>
228         '''
229     }
230
231     private def identities(Module module) {
232         if (module.identities.empty) {
233             return '';
234         }
235         return '''
236             <h2>Identities</h2>
237             <ul>
238             «FOR identity : module.identities»
239                 <li>
240                     <h3 id="«identity.QName.localName»">«identity.QName.localName»</h3>
241                     <ul>
242                     «identity.descAndRefLi»
243                     «IF !identity.baseIdentities.isEmpty»
244                         «listItem("base", identity.baseIdentities.get(0).QName.localName)»
245                     «ENDIF»
246                     </ul>
247                 </li>
248             «ENDFOR»
249             </ul>
250         '''
251     }
252
253     private def identitiesSummary(Module module) {
254         if (module.identities.empty) {
255             return '';
256         }
257         return '''
258         <h3>Identities Summary</h3>
259         <table>
260             <tr>
261                 <th>Name</th>
262                 <th>Description</th>
263             </tr>
264             «FOR identity : module.identities»
265             <tr>
266                 <td>
267                 «anchorLink(identity.QName.localName, strong(identity.QName.localName))»
268                 </td>
269                 <td>
270                 «identity.description»
271                 </td>
272             </tr>
273             «ENDFOR»
274         </table>
275         '''
276     }
277
278     private def groupings(Module module) {
279         if (module.groupings.empty) {
280             return '';
281         }
282         return '''
283             <h2>Groupings</h2>
284             <ul>
285             «FOR grouping : module.groupings»
286                 <li>
287                     <h3 id="«grouping.QName.localName»">«grouping.QName.localName»</h3>
288                     <ul>
289                         «grouping.descAndRefLi»
290                         «FOR childNode : grouping.childNodes»
291                             «childNode.printSchemaNodeInfo»
292                         «ENDFOR»
293                     </ul>
294                 </li>
295             «ENDFOR»
296             </ul>
297         '''
298     }
299
300     private def groupingsSummary(Module module) {
301         if (module.groupings.empty) {
302             return '';
303         }
304         return '''
305         <h3>Groupings Summary</h3>
306         <table>
307             <tr>
308                 <th>Name</th>
309                 <th>Description</th>
310             </tr>
311             «FOR grouping : module.groupings»
312             <tr>
313                 <td>
314                 «anchorLink(grouping.QName.localName, strong(grouping.QName.localName))»
315                 </td>
316                 <td>
317                 «grouping.description»
318                 </td>
319             </tr>
320             «ENDFOR»
321         </table>
322         '''
323     }
324
325     def dataStore(Module module) {
326         if (module.childNodes.empty) {
327             return '';
328         }
329         return '''
330             <h2>Datastore Structure</h2>
331             «tree(module)»
332         '''
333     }
334
335     def augmentations(Module module, EffectiveModelContext context) {
336         if (module.augmentations.empty) {
337             return '';
338         }
339         return '''
340             <h2>Augmentations</h2>
341
342             <ul>
343             «FOR augment : module.augmentations»
344                 <li>
345                     <h3 id="«schemaPathToString(module, augment.targetPath, context, augment)»">
346                     Target [«typeAnchorLink(augment.targetPath.asSchemaPath, schemaPathToString(module, augment.targetPath, context, augment))»]</h3>
347                     «augment.description»
348                         Status: «strong(String.valueOf(augment.status))»
349                     «IF augment.reference !== null»
350                         Reference «augment.reference»
351                     «ENDIF»
352                     «IF augment.whenCondition !== null»
353                         When «augment.whenCondition.toString»
354                     «ENDIF»
355                     «FOR childNode : augment.childNodes»
356                         «childNode.printSchemaNodeInfo»
357                     «ENDFOR»
358
359                     <h3>Example</h3>
360                     «createAugmentChildNodesAsString(new ArrayList(augment.childNodes))»
361                     «printNodeChildren(parseTargetPath(augment.targetPath))»
362                 </li>
363             «ENDFOR»
364             </ul>
365         '''
366     }
367
368     private def createAugmentChildNodesAsString(List<DataSchemaNode> childNodes) {
369         augmentChildNodesAsString = new StringBuilder();
370         augmentChildNodesAsString.append(printNodeChildren(childNodes))
371         return ''
372     }
373
374     private def parseTargetPath(SchemaNodeIdentifier path) {
375         val nodes = new ArrayList<DataSchemaNode>();
376         for (QName pathElement : path.nodeIdentifiers) {
377             val module = ctx.findModule(pathElement.module)
378             if (module.isPresent) {
379                 var foundNode = module.get.dataChildByName(pathElement)
380                 if (foundNode === null) {
381                     val child = nodes.last
382                     if (child instanceof DataNodeContainer) {
383                         val dataContNode = child as DataNodeContainer
384                         foundNode = findNodeInChildNodes(pathElement, dataContNode.childNodes)
385                     }
386                 }
387                 if (foundNode !== null) {
388                     nodes.add(foundNode);
389                 }
390             }
391         }
392         if (!nodes.empty) {
393             lastNodeInTargetPath = nodes.get(nodes.size() - 1)
394         }
395
396         val targetPathNodes = new ArrayList<DataSchemaNode>();
397         targetPathNodes.add(lastNodeInTargetPath)
398
399         return targetPathNodes
400     }
401
402     private def DataSchemaNode findNodeInChildNodes(QName findingNode, Iterable<? extends DataSchemaNode> childNodes) {
403         for (child : childNodes) {
404             if (child.QName.equals(findingNode))
405                 return child;
406         }
407         // find recursively
408         for(child : childNodes) {
409             if (child instanceof ContainerSchemaNode) {
410                 val foundChild = findNodeInChildNodes(findingNode, child.childNodes)
411                 if (foundChild !== null)
412                     return foundChild;
413             } else if (child instanceof ListSchemaNode) {
414                 val foundChild = findNodeInChildNodes(findingNode, child.childNodes)
415                 if (foundChild !== null)
416                     return foundChild;
417             }
418         }
419     }
420
421     private def printNodeChildren(List<DataSchemaNode> childNodes) {
422         if (childNodes.empty) {
423             return ''
424         }
425
426         return
427         '''
428         <pre>
429         «printAugmentedNode(childNodes.get(0))»
430         </pre>
431         '''
432     }
433
434     private def CharSequence printAugmentedNode(DataSchemaNode child) {
435
436         if(child instanceof CaseSchemaNode)
437             return ''
438
439         return
440         '''
441         «IF child instanceof ContainerSchemaNode»
442             «printContainerNode(child)»
443         «ENDIF»
444         «IF child instanceof AnyxmlSchemaNode»
445             «printAnyXmlNode(child)»
446         «ENDIF»
447         «IF child instanceof LeafSchemaNode»
448             «printLeafNode(child)»
449         «ENDIF»
450         «IF child instanceof LeafListSchemaNode»
451             «printLeafListNode(child)»
452         «ENDIF»
453         «IF child instanceof ListSchemaNode»
454             «printListNode(child)»
455         «ENDIF»
456         «IF child instanceof ChoiceSchemaNode»
457             «printChoiceNode(child)»
458         «ENDIF»
459         '''
460     }
461
462     private def printChoiceNode(ChoiceSchemaNode child) {
463         val cases = new ArrayList(child.cases)
464         if (!cases.empty) {
465             val CaseSchemaNode aCase = cases.get(0)
466             for (caseChildNode : aCase.childNodes)
467                 printAugmentedNode(caseChildNode)
468         }
469     }
470
471     private def printListNode(ListSchemaNode listNode) {
472         return
473         '''
474             &lt;«listNode.QName.localName»«IF !listNode.QName.namespace.equals(currentModule.namespace)» xmlns="«listNode.QName.namespace»"«ENDIF»&gt;
475                 «FOR child : listNode.childNodes»
476                     «printAugmentedNode(child)»
477                 «ENDFOR»
478             &lt;/«listNode.QName.localName»&gt;
479         '''
480     }
481
482     private def printContainerNode(ContainerSchemaNode containerNode) {
483         return
484         '''
485             &lt;«containerNode.QName.localName»«IF !containerNode.QName.namespace.equals(currentModule.namespace)» xmlns="«containerNode.QName.namespace»"«ENDIF»&gt;
486                 «FOR child : containerNode.childNodes»
487                     «printAugmentedNode(child)»
488                 «ENDFOR»
489             &lt;/«containerNode.QName.localName»&gt;
490         '''
491     }
492
493     private def printLeafListNode(LeafListSchemaNode leafListNode) {
494         return
495         '''
496             &lt;«leafListNode.QName.localName»&gt;. . .&lt;/«leafListNode.QName.localName»&gt;
497             &lt;«leafListNode.QName.localName»&gt;. . .&lt;/«leafListNode.QName.localName»&gt;
498             &lt;«leafListNode.QName.localName»&gt;. . .&lt;/«leafListNode.QName.localName»&gt;
499         '''
500     }
501
502     private def printAnyXmlNode(AnyxmlSchemaNode anyXmlNode) {
503         return
504         '''
505             &lt;«anyXmlNode.QName.localName»&gt;. . .&lt;/«anyXmlNode.QName.localName»&gt;
506         '''
507     }
508
509     private def printLeafNode(LeafSchemaNode leafNode) {
510         return
511         '''
512             &lt;«leafNode.QName.localName»&gt;. . .&lt;/«leafNode.QName.localName»&gt;
513         '''
514     }
515
516     private def augmentationsSummary(Module module, EffectiveModelContext context) {
517         if (module.augmentations.empty) {
518             return '';
519         }
520         return '''
521         <h3>Augmentations Summary</h3>
522         <table>
523             <tr>
524                 <th>Target</th>
525                 <th>Description</th>
526             </tr>
527             «FOR augment : module.augmentations»
528             <tr>
529                 <td>
530                 «anchorLink(schemaPathToString(module, augment.targetPath, context, augment),
531                 strong(schemaPathToString(module, augment.targetPath, context, augment)))»
532                 </td>
533                 <td>
534                 «augment.description»
535                 </td>
536             </tr>
537             «ENDFOR»
538         </table>
539         '''
540     }
541
542     def notifications(Module module) {
543         val Collection<? extends NotificationDefinition> notificationdefs = module.notifications
544         if (notificationdefs.empty) {
545             return '';
546         }
547
548         return '''
549             <h2>Notifications</h2>
550             «FOR notificationdef : notificationdefs»
551
552                 <h3 id="«notificationdef.path.schemaPathToId»">«notificationdef.nodeName»</h3>
553                     «notificationdef.descAndRef»
554                     «FOR childNode : notificationdef.childNodes»
555                         «childNode.printSchemaNodeInfo»
556                     «ENDFOR»
557             «ENDFOR»
558         '''
559     }
560
561     private def notificationsSummary(Module module) {
562         if (module.notifications.empty) {
563             return '';
564         }
565         return '''
566         <h3>Notifications Summary</h3>
567         <table>
568             <tr>
569                 <th>Name</th>
570                 <th>Description</th>
571             </tr>
572             «FOR notification : module.notifications»
573             <tr>
574                 <td>
575                 «anchorLink(notification.path.schemaPathToId, strong(notification.QName.localName))»
576                 </td>
577                 <td>
578                 «notification.description»
579                 </td>
580             </tr>
581             «ENDFOR»
582         </table>
583         '''
584     }
585
586     def rpcs(Module module) {
587         if (module.rpcs.empty) {
588             return '';
589         }
590
591         return '''
592             <h2>RPC Definitions</h2>
593             «FOR rpc : module.rpcs»
594                 <h3 id="«rpc.QName.localName»">«rpc.nodeName»</h3>
595                     <ul>
596                         «rpc.descAndRefLi»
597                         «rpc.input.printSchemaNodeInfo»
598                         «rpc.output.printSchemaNodeInfo»
599                     </ul>
600             «ENDFOR»
601             </ul>
602         '''
603     }
604
605     private def rpcsSummary(Module module) {
606         if (module.rpcs.empty) {
607             return '';
608         }
609         return '''
610         <h3>RPCs Summary</h3>
611         <table>
612             <tr>
613                 <th>Name</th>
614                 <th>Description</th>
615             </tr>
616             «FOR rpc : module.rpcs»
617             <tr>
618                 <td>
619                 «anchorLink(rpc.QName.localName, strong(rpc.QName.localName))»
620                 </td>
621                 <td>
622                 «rpc.description»
623                 </td>
624             </tr>
625             «ENDFOR»
626         </table>
627         '''
628     }
629
630     def extensions(Module module) {
631         if (module.extensionSchemaNodes.empty) {
632             return '';
633         }
634         return '''
635             <h2>Extensions</h2>
636             «FOR ext : module.extensionSchemaNodes»
637                 <li>
638                     <h3 id="«ext.QName.localName»">«ext.nodeName»</h3>
639                 </li>
640                 «extensionInfo(ext)»
641             «ENDFOR»
642         '''
643     }
644
645     private def extensionsSummary(Module module) {
646         if (module.extensionSchemaNodes.empty) {
647             return '';
648         }
649         return '''
650         <h3>Extensions Summary</h3>
651         <table>
652             <tr>
653                 <th>Name</th>
654                 <th>Description</th>
655             </tr>
656             «FOR ext : module.extensionSchemaNodes»
657             <tr>
658                 <td>
659                 «anchorLink(ext.QName.localName, strong(ext.QName.localName))»
660                 </td>
661                 <td>
662                 «ext.description»
663                 </td>
664             </tr>
665             «ENDFOR»
666         </table>
667         '''
668     }
669
670     def features(Module module) {
671         if (module.features.empty) {
672             return '';
673         }
674         return '''
675             <h2>Features</h2>
676
677             <ul>
678             «FOR feature : module.features»
679                 <li>
680                     <h3 id="«feature.QName.localName»">«feature.QName.localName»</h3>
681                     <ul>
682                         «feature.descAndRefLi»
683                     </ul>
684                 </li>
685             «ENDFOR»
686             </ul>
687         '''
688     }
689
690     private def featuresSummary(Module module) {
691         if (module.features.empty) {
692             return '';
693         }
694         return '''
695         <h3>Features Summary</h3>
696         <table>
697             <tr>
698                 <th>Name</th>
699                 <th>Description</th>
700             </tr>
701             «FOR feature : module.features»
702             <tr>
703                 <td>
704                 «anchorLink(feature.QName.localName, strong(feature.QName.localName))»
705                 </td>
706                 <td>
707                 «feature.description»
708                 </td>
709             </tr>
710             «ENDFOR»
711         </table>
712         '''
713     }
714
715     private def objectsSummary(Module module) {
716         if (module.childNodes.empty) {
717             return '';
718         }
719         return '''
720         <h3>Child Nodes Summary</h3>
721         <table>
722             <tr>
723                 <th>Name</th>
724                 <th>Description</th>
725             </tr>
726             «FOR childNode : module.childNodes»
727             <tr>
728                 <td>
729                 «anchorLink(childNode.QName.localName, strong(childNode.QName.localName))»
730                 </td>
731                 <td>
732                 «childNode.description»
733                 </td>
734             </tr>
735             «ENDFOR»
736         </table>
737         '''
738     }
739
740     def header(Module module)
741     '''
742         <h1>«module.name»</h1>
743
744         <h2>Base Information</h2>
745         <table>
746             <tr>
747                 <td>«strong("prefix")»</td>
748                 <td>«module.prefix»</td>
749             </tr>
750             <tr>
751                 <td>«strong("namespace")»</td>
752                 <td>«module.namespace»</td>
753             </tr>
754             <tr>
755                 «IF module.revision.isPresent»
756                 <td>«strong("revision")»</td>
757                 <td>«module.revision.get.toString»</td>
758                 «ENDIF»
759             </tr>
760             <tr>
761                 <td>«strong("description")»</td>
762                 <td>«module.description»</td>
763             </tr>
764             <tr>
765                 <td>«strong("yang-version")»</td>
766                 <td>«module.yangVersion»</td>
767             </tr>
768             <tr>
769                 «FOR imp : module.imports BEFORE '''<td>«strong("imports")»</td><td>''' AFTER '''</td>'''»
770                     «imp.prefix»:«imp.moduleName»«IF imp.revision.isPresent» «imp.revision.get.toString»«ENDIF»;
771                 «ENDFOR»
772             </tr>
773         </table>
774     '''
775
776     def CharSequence schemaPathToId(SchemaPath path) {
777         if(path !== null) {
778             return '''«FOR qName : path.pathFromRoot SEPARATOR "/"»«qName.localName»«ENDFOR»'''
779         }
780     }
781
782     def code(String string) '''<code>«string»</code>'''
783
784     def process(Module module) {
785         throw new UnsupportedOperationException("TODO: auto-generated method stub")
786     }
787
788     def CharSequence tree(Module module) '''
789         «strong(module.name)»
790         «module.childNodes.treeSet(YangInstanceIdentifier.builder.build())»
791     '''
792
793     private def CharSequence tree(ChoiceSchemaNode node, YangInstanceIdentifier path) '''
794         «node.nodeName» (choice)
795         «casesTree(node.cases, path)»
796     '''
797
798     def casesTree(Collection<? extends CaseSchemaNode> nodes, YangInstanceIdentifier path) '''
799         <ul>
800         «FOR node : nodes»
801             <li>
802             «node.nodeName»
803             «node.childNodes.treeSet(path)»
804             </li>
805         «ENDFOR»
806         </ul>
807     '''
808
809     private def CharSequence tree(DataSchemaNode node, YangInstanceIdentifier path) {
810         if (node instanceof ChoiceSchemaNode) {
811             return tree(node, path)
812         } else if (node instanceof ListSchemaNode) {
813             return tree(node, path)
814         } else if (node instanceof ContainerSchemaNode) {
815             return tree(node, path)
816         }
817         return node.nodeName
818     }
819
820     private def CharSequence tree(ListSchemaNode node, YangInstanceIdentifier path) '''
821         «val newPath = path.append(node)»
822         «localLink(newPath,node.nodeName)»
823         «node.childNodes.treeSet(newPath)»
824     '''
825
826     private def CharSequence tree(ContainerSchemaNode node,YangInstanceIdentifier path) '''
827         «val newPath = path.append(node)»
828         «localLink(newPath,node.nodeName)»
829         «node.childNodes.treeSet(newPath)»
830     '''
831
832     def CharSequence childNodes(Module module) '''
833         «val childNodes = module.childNodes»
834         «IF !childNodes.nullOrEmpty»
835             <h2>Child nodes</h2>
836
837             «childNodes.printChildren(3,YangInstanceIdentifier.builder().build())»
838         «ENDIF»
839     '''
840
841     def CharSequence printSchemaNodeInfo(DataSchemaNode node) {
842         return '''
843             <ul>
844             «node.printBaseInfo»
845             «IF node instanceof DataNodeContainer»
846                 «val dataNode = node as DataNodeContainer»
847                 <ul>
848                 «FOR usesNode : dataNode.uses»
849                     «usesNode.printUses»
850                 «ENDFOR»
851                 </ul>
852                 <ul>
853                 «FOR typeDef : dataNode.typeDefinitions»
854                     «typeDef.restrictions»
855                 «ENDFOR»
856                 </ul>
857                 <ul>
858                 «FOR grouping : dataNode.groupings»
859                     «grouping.printGrouping»
860                 «ENDFOR»
861                 </ul>
862                 <ul>
863                 «FOR child : dataNode.childNodes»
864                     «child.printSchemaNodeInfo»
865                 «ENDFOR»
866                 </ul>
867             «ENDIF»
868             </ul>
869         '''
870     }
871
872     def String typeAnchorLink(SchemaPath path, CharSequence text) {
873         if(path !== null) {
874             val lastElement = path.lastComponent
875             val ns = lastElement.namespace
876             if (ns == this.currentModule.namespace) {
877                 return '''<a href="#«path.schemaPathToId»">«text»</a>'''
878             } else {
879                 return '''(«ns»)«text»'''
880                 //to enable external (import) links
881                 //return '''<a href="«module».html#«path.schemaPathToId»">«prefix»:«text»</a>'''
882             }
883         }
884     }
885
886     def CharSequence printBaseInfo(SchemaNode node) {
887         if(node instanceof LeafSchemaNode) {
888             return '''
889                 «printInfo(node, "leaf")»
890                 «listItem("type", typeAnchorLink(types.get(node.type), node.type.QName.localName))»
891                 «listItem("units", node.type.units.orElse(null))»
892                 «listItem("default", node.type.defaultValue.map([ Object o | o.toString]).orElse(null))»
893                 </ul>
894             '''
895         } else if(node instanceof LeafListSchemaNode) {
896             return '''
897                 «printInfo(node, "leaf-list")»
898                 «IF node.type !== null»
899                     «listItem("type", node.type.QName.localName)»
900                 «ENDIF»
901                 </ul>
902             '''
903         } else if(node instanceof ListSchemaNode) {
904             return '''
905                 «printInfo(node, "list")»
906                 «FOR keyDef : node.keyDefinition»
907                     «listItem("key definition", keyDef.localName)»
908                 «ENDFOR»
909                 </ul>
910             '''
911         } else if(node instanceof ChoiceSchemaNode) {
912             return '''
913                 «printInfo(node, "choice")»
914                 «listItem("default case", node.defaultCase.map([ CaseSchemaNode n | n.getQName.localName]).orElse(null))»
915                 «FOR caseNode : node.cases»
916                     «caseNode.printSchemaNodeInfo»
917                 «ENDFOR»
918                 </ul>
919             '''
920         } else if(node instanceof CaseSchemaNode) {
921             return '''
922                 «printInfo(node, "case")»
923                 </ul>
924             '''
925         } else if(node instanceof ContainerSchemaNode) {
926             return '''
927                 «printInfo(node, "container")»
928                 </ul>
929             '''
930         } else if(node instanceof AnyxmlSchemaNode) {
931             return '''
932                 «printInfo(node, "anyxml")»
933                 </ul>
934             '''
935         }
936     }
937
938     def CharSequence printInfo(SchemaNode node, String nodeType) {
939         return '''
940             «IF node instanceof AugmentationTarget»
941                 «IF node !== null»
942                     <strong>
943                     <li id="«node.path.schemaPathToId»">
944                         «nodeType»: «node.QName.localName»
945                     </li>
946                     </strong>
947                 «ENDIF»
948             «ELSE»
949                 «strong(listItem(nodeType, node.QName.localName))»
950             «ENDIF»
951             <ul>
952             «listItem("description", node.description.orElse(null))»
953             «listItem("reference", node.reference.orElse(null))»
954             «IF node instanceof DataSchemaNode»
955                 «IF node.whenCondition.present»
956                 «listItem("when condition", node.whenCondition.get.toString)»
957                 «ENDIF»
958             «ENDIF»
959             «IF node instanceof ElementCountConstraintAware»
960                 «IF node.elementCountConstraint.present»
961                     «val constraint = node.elementCountConstraint.get»
962                     «listItem("min elements", constraint.minElements?.toString)»
963                     «listItem("max elements", constraint.maxElements?.toString)»
964                 «ENDIF»
965             «ENDIF»
966         '''
967     }
968
969     def CharSequence printUses(UsesNode usesNode) {
970         return '''
971             «strong(listItem("uses", typeAnchorLink(usesNode.sourceGrouping.path, usesNode.sourceGrouping.QName.localName)))»
972             <ul>
973             <li>refines:
974                 <ul>
975                 «FOR sp : usesNode.refines.keySet»
976                     «listItem("node name", usesNode.refines.get(sp).QName.localName)»
977                 «ENDFOR»
978                 </ul>
979             </li>
980             «FOR augment : usesNode.augmentations»
981                 «typeAnchorLink(augment.targetPath.asSchemaPath, schemaPathToString(currentModule, augment.targetPath, ctx, augment))»
982             «ENDFOR»
983             </ul>
984         '''
985     }
986
987     def CharSequence printGrouping(GroupingDefinition grouping) {
988         return '''
989             «strong(listItem("grouping", grouping.QName.localName))»
990         '''
991     }
992
993     def CharSequence printChildren(Iterable<? extends DataSchemaNode> nodes, int level, YangInstanceIdentifier path) {
994         val anyxmlNodes = nodes.filter(AnyxmlSchemaNode)
995         val leafNodes = nodes.filter(LeafSchemaNode)
996         val leafListNodes = nodes.filter(LeafListSchemaNode)
997         val choices = nodes.filter(ChoiceSchemaNode)
998         val cases = nodes.filter(CaseSchemaNode)
999         val containers = nodes.filter(ContainerSchemaNode)
1000         val lists = nodes.filter(ListSchemaNode)
1001         return '''
1002             «IF ((anyxmlNodes.size + leafNodes.size + leafListNodes.size + containers.size + lists.size) > 0)»
1003             <h3>Direct children</h3>
1004             <ul>
1005             «FOR childNode : anyxmlNodes»
1006                 «childNode.printShortInfo(level,path)»
1007             «ENDFOR»
1008             «FOR childNode : leafNodes»
1009                 «childNode.printShortInfo(level,path)»
1010             «ENDFOR»
1011             «FOR childNode : leafListNodes»
1012                 «childNode.printShortInfo(level,path)»
1013             «ENDFOR»
1014             «FOR childNode : containers»
1015                 «childNode.printShortInfo(level,path)»
1016             «ENDFOR»
1017             «FOR childNode : lists»
1018                 «childNode.printShortInfo(level,path)»
1019             «ENDFOR»
1020             </ul>
1021             «ENDIF»
1022
1023             «IF path.pathArguments.iterator.hasNext»
1024             <h3>XML example</h3>
1025             «nodes.xmlExample(path.pathArguments.last.nodeType,path)»
1026             </h3>
1027             «ENDIF»
1028             «FOR childNode : containers»
1029                 «childNode.printInfo(level,path)»
1030             «ENDFOR»
1031             «FOR childNode : lists»
1032                 «childNode.printInfo(level,path)»
1033             «ENDFOR»
1034             «FOR childNode : choices»
1035                 «childNode.printInfo(level,path)»
1036             «ENDFOR»
1037             «FOR childNode : cases»
1038                 «childNode.printInfo(level,path)»
1039             «ENDFOR»
1040         '''
1041     }
1042
1043     def CharSequence xmlExample(Iterable<? extends DataSchemaNode> nodes, QName name, YangInstanceIdentifier path) '''
1044     <pre>
1045         «xmlExampleTag(name,nodes.xmplExampleTags(path))»
1046     </pre>
1047     '''
1048
1049     def CharSequence xmplExampleTags(Iterable<? extends DataSchemaNode> nodes, YangInstanceIdentifier identifier) '''
1050         <!-- Child nodes -->
1051         «FOR node : nodes»
1052         <!-- «node.QName.localName» -->
1053             «node.asXmlExampleTag(identifier)»
1054         «ENDFOR»
1055
1056     '''
1057
1058     private def CharSequence asXmlExampleTag(DataSchemaNode node, YangInstanceIdentifier identifier) {
1059         if (node instanceof LeafSchemaNode) {
1060             return '''«node.QName.xmlExampleTag("...")»'''
1061         }
1062         if (node instanceof LeafListSchemaNode) {
1063             return '''
1064             &lt!-- This node could appear multiple times --&gt
1065             «node.QName.xmlExampleTag("...")»
1066             '''
1067         }
1068         if (node instanceof ContainerSchemaNode) {
1069             return '''
1070             &lt!-- See «localLink(identifier.append(node),"definition")» for child nodes.  --&gt
1071             «node.QName.xmlExampleTag("...")»
1072             '''
1073         }
1074         if (node instanceof ListSchemaNode) {
1075             return '''
1076             &lt!-- See «localLink(identifier.append(node),"definition")» for child nodes.  --&gt
1077             &lt!-- This node could appear multiple times --&gt
1078             «node.QName.xmlExampleTag("...")»
1079             '''
1080         }
1081         return "<!-- noop -->"
1082     }
1083
1084     def xmlExampleTag(QName name, CharSequence data) {
1085         return '''&lt;«name.localName» xmlns="«name.namespace»"&gt;«data»&lt;/«name.localName»&gt;'''
1086     }
1087
1088     def header(int level,QName name) '''<h«level»>«name.localName»</h«level»>'''
1089
1090
1091     def header(int level,YangInstanceIdentifier name) '''
1092         <h«level» id="«FOR cmp : name.pathArguments SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»">
1093             «FOR cmp : name.pathArguments SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»
1094         </h«level»>
1095     '''
1096
1097     private def CharSequence printInfo(ContainerSchemaNode node, int level, YangInstanceIdentifier path) '''
1098         «val newPath = path.append(node)»
1099         «header(level,newPath)»
1100         <dl>
1101           <dt>XML Path</dt>
1102           <dd>«newPath.asXmlPath»</dd>
1103           <dt>Restconf path</dt>
1104           <dd>«code(newPath.asRestconfPath)»</dd>
1105         </dl>
1106         «node.childNodes.printChildren(level,newPath)»
1107     '''
1108
1109     private def CharSequence printInfo(ListSchemaNode node, int level, YangInstanceIdentifier path) '''
1110         «val newPath = path.append(node)»
1111         «header(level,newPath)»
1112         <dl>
1113           <dt>XML Path</dt>
1114           <dd>«newPath.asXmlPath»</dd>
1115           <dt>Restconf path</dt>
1116           <dd>«code(newPath.asRestconfPath)»</dd>
1117         </dl>
1118         «node.childNodes.printChildren(level,newPath)»
1119     '''
1120
1121     private def CharSequence printInfo(ChoiceSchemaNode node, int level, YangInstanceIdentifier path) '''
1122         «val Set<DataSchemaNode> choiceCases = new HashSet(node.cases)»
1123         «choiceCases.printChildren(level, path)»
1124     '''
1125
1126     private def CharSequence printInfo(CaseSchemaNode node, int level, YangInstanceIdentifier path) '''
1127         «node.childNodes.printChildren(level, path)»
1128     '''
1129
1130
1131
1132     def CharSequence printShortInfo(ContainerSchemaNode node, int level, YangInstanceIdentifier path) {
1133         val newPath = path.append(node);
1134         return '''
1135             <li>«strong(localLink(newPath,node.QName.localName))» (container)
1136             <ul>
1137                 «node.configurationDataItem»
1138             </ul>
1139             </li>
1140         '''
1141     }
1142
1143     def CharSequence printShortInfo(ListSchemaNode node, int level, YangInstanceIdentifier path) {
1144         val newPath = path.append(node);
1145         return '''
1146             <li>«strong(localLink(newPath,node.QName.localName))» (list)
1147             <ul>
1148                 «node.configurationDataItem»
1149             </ul>
1150             </li>
1151         '''
1152     }
1153
1154     def CharSequence printShortInfo(AnyxmlSchemaNode node, int level, YangInstanceIdentifier path) {
1155         return '''
1156             <li>«strong((node.QName.localName))» (anyxml)
1157             <ul>
1158                 «node.configurationDataItem»
1159                 «node.mandatoryItem»
1160             </ul>
1161             </li>
1162         '''
1163     }
1164
1165     def CharSequence printShortInfo(LeafSchemaNode node, int level, YangInstanceIdentifier path) {
1166         return '''
1167             <li>«strong((node.QName.localName))» (leaf)
1168             <ul>
1169                 «node.configurationDataItem»
1170                 «node.mandatoryItem»
1171             </ul>
1172             </li>
1173         '''
1174     }
1175
1176     def CharSequence printShortInfo(LeafListSchemaNode node, int level, YangInstanceIdentifier path) {
1177         return '''
1178             <li>«strong((node.QName.localName))» (leaf-list)
1179             <ul>
1180                 «node.configurationDataItem»
1181             </ul>
1182             </li>
1183         '''
1184     }
1185
1186     def CharSequence anchorLink(CharSequence anchor, CharSequence text) {
1187         return '''
1188             <a href="#«anchor»">«text»</a>
1189         '''
1190     }
1191
1192     def CharSequence localLink(YangInstanceIdentifier identifier, CharSequence text) '''
1193         <a href="#«FOR cmp : identifier.pathArguments SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»">«text»</a>
1194     '''
1195
1196     private static def String configurationDataItem(DataSchemaNode node) {
1197         return node.effectiveConfig
1198             .map([config | "<li>configuration data: " + strong(String.valueOf(config)) + "</li>"])
1199             .orElse("")
1200     }
1201
1202     private static def CharSequence mandatoryItem(MandatoryAware node) '''
1203         <li>mandatory: «strong(String.valueOf(node.mandatory))»</li>
1204     '''
1205
1206     private def dispatch YangInstanceIdentifier append(YangInstanceIdentifier identifier, ContainerSchemaNode node) {
1207         return identifier.node(node.QName);
1208     }
1209
1210     private def dispatch YangInstanceIdentifier append(YangInstanceIdentifier identifier, ListSchemaNode node) {
1211         val keyValues = new LinkedHashMap<QName,Object>();
1212         if(node.keyDefinition !== null) {
1213             for(definition : node.keyDefinition) {
1214                 keyValues.put(definition,new Object);
1215             }
1216         }
1217
1218         return identifier.node(NodeIdentifierWithPredicates.of(node.QName, keyValues));
1219     }
1220
1221
1222     def asXmlPath(YangInstanceIdentifier identifier) {
1223         return "";
1224     }
1225
1226     def asRestconfPath(YangInstanceIdentifier identifier) {
1227         val it = new StringBuilder();
1228         append(currentModule.name)
1229         append(':')
1230         var previous = false;
1231         for(arg : identifier.pathArguments) {
1232             if(previous) append('/')
1233             append(arg.nodeType.localName);
1234             previous = true;
1235             if(arg instanceof NodeIdentifierWithPredicates) {
1236                 for(qname : arg.keySet) {
1237                     append("/{");
1238                     append(qname.localName)
1239                     append('}')
1240                 }
1241             }
1242         }
1243
1244         return it.toString;
1245     }
1246
1247     private def String schemaPathToString(Module module, SchemaNodeIdentifier schemaPath, EffectiveModelContext ctx,
1248             DataNodeContainer dataNode) {
1249         val path = schemaPath.nodeIdentifiers
1250         val StringBuilder pathString = new StringBuilder()
1251         if (schemaPath instanceof Absolute) {
1252             pathString.append('/')
1253         }
1254
1255         val QName qname = path.get(0)
1256         var Object parent = ctx.findModule(qname.module).orElse(null)
1257
1258         for (name : path) {
1259             if (parent instanceof DataNodeContainer) {
1260                 var SchemaNode node = parent.dataChildByName(name)
1261                 if (node === null && (parent instanceof Module)) {
1262                     val notifications = (parent as Module).notifications;
1263                     for (notification : notifications) {
1264                         if (notification.QName.equals(name)) {
1265                             node = notification
1266                         }
1267                     }
1268                 }
1269                 if (node === null && (parent instanceof Module)) {
1270                     val rpcs = (parent as Module).rpcs;
1271                     for (rpc : rpcs) {
1272                         if (rpc.QName.equals(name)) {
1273                             node = rpc
1274                         }
1275                     }
1276                 }
1277
1278                 val pathElementModule = ctx.findModule(name.module).get
1279                 val String moduleName = pathElementModule.name
1280                 pathString.append(moduleName)
1281                 pathString.append(':')
1282                 pathString.append(name.localName)
1283                 pathString.append('/')
1284                 if(node instanceof ChoiceSchemaNode && dataNode !== null) {
1285                     val DataSchemaNode caseNode = dataNode.childNodes.findFirst[DataSchemaNode e | e instanceof CaseSchemaNode];
1286                     if(caseNode !== null) {
1287                         pathString.append("(case)");
1288                         pathString.append(caseNode.QName.localName);
1289                     }
1290                 }
1291                 parent = node
1292             }
1293         }
1294         return pathString.toString;
1295     }
1296
1297
1298     def CharSequence childNodesInfoTree(Map<SchemaPath, DataSchemaNode> childNodes) '''
1299         «IF childNodes !== null && !childNodes.empty»
1300             «FOR child : childNodes.values»
1301                 «childInfo(child, childNodes)»
1302             «ENDFOR»
1303         «ENDIF»
1304     '''
1305
1306     def CharSequence childInfo(DataSchemaNode node, Map<SchemaPath, DataSchemaNode> childNodes) '''
1307         «val String path = nodeSchemaPathToPath(node, childNodes)»
1308         «IF path !== null»
1309             «code(path)»
1310                 «IF node !== null»
1311                 <ul>
1312                 «node.descAndRefLi»
1313                 </ul>
1314             «ENDIF»
1315         «ENDIF»
1316     '''
1317
1318     private def CharSequence treeSet(Collection<? extends DataSchemaNode> childNodes, YangInstanceIdentifier path) '''
1319         «IF childNodes !== null && !childNodes.empty»
1320             <ul>
1321             «FOR child : childNodes»
1322                 <li>
1323                     «child.tree(path)»
1324                 </li>
1325             «ENDFOR»
1326             </ul>
1327         «ENDIF»
1328     '''
1329
1330     def listKeys(ListSchemaNode node) '''
1331         [«FOR key : node.keyDefinition SEPARATOR " "»«key.localName»«ENDFOR»]
1332     '''
1333
1334     private def CharSequence extensionInfo(ExtensionDefinition ext) '''
1335         <ul>
1336             «ext.descAndRefLi»
1337             «listItem("Argument", ext.argument)»
1338         </ul>
1339     '''
1340
1341
1342     /* #################### RESTRICTIONS #################### */
1343     private def restrictions(TypeDefinition<?> type) '''
1344         «type.baseType.toBaseStmt»
1345         «type.toLength»
1346         «type.toRange»
1347     '''
1348
1349     private def toLength(TypeDefinition<?> type) '''
1350         «IF type instanceof LengthRestrictedTypeDefinition»
1351             «type.lengthConstraint.toLengthStmt»
1352         «ENDIF»
1353     '''
1354
1355     private def toRange(TypeDefinition<?> type) '''
1356         «IF type instanceof RangeRestrictedTypeDefinition»
1357             «type.rangeConstraint.toRangeStmt»
1358         «ENDIF»
1359     '''
1360
1361     def toLengthStmt(Optional<LengthConstraint> lengths) '''
1362         «IF lengths.isPresent»
1363             «listItem("Length restrictions:")»
1364             <ul>
1365             «FOR length : lengths.get.allowedRanges.asRanges»
1366                 <li>
1367                 «IF length.lowerEndpoint == length.upperEndpoint»
1368                     «length.lowerEndpoint»
1369                 «ELSE»
1370                     &lt;«length.lowerEndpoint», «length.upperEndpoint»&gt;
1371                 «ENDIF»
1372                 </li>
1373             «ENDFOR»
1374             </ul>
1375         «ENDIF»
1376     '''
1377
1378     def toRangeStmt(Optional<? extends RangeConstraint<?>> constraint) '''
1379         «IF constraint.present»
1380             «listItem("Range restrictions:")»
1381             <ul>
1382             «FOR range : constraint.get.allowedRanges.asRanges»
1383                 <li>
1384                 «IF range.lowerEndpoint == range.upperEndpoint»
1385                     «range.lowerEndpoint»
1386                 «ELSE»
1387                     &lt;«range.lowerEndpoint», «range.upperEndpoint»&gt;
1388                 «ENDIF»
1389                 </li>
1390             «ENDFOR»
1391             </ul>
1392         «ENDIF»
1393     '''
1394
1395     def toBaseStmt(TypeDefinition<?> baseType) '''
1396         «IF baseType !== null»
1397         «listItem("Base type", typeAnchorLink(types.get(baseType), baseType.QName.localName))»
1398         «ENDIF»
1399     '''
1400
1401     /* #################### UTILITY #################### */
1402     private def static String strong(CharSequence str) '''<strong>«str»</strong>'''
1403     private def static italic(CharSequence str) '''<i>«str»</i>'''
1404
1405     def CharSequence descAndRefLi(SchemaNode node) '''
1406         «listItem("Description", node.description.orElse(null))»
1407         «listItem("Reference", node.reference.orElse(null))»
1408     '''
1409
1410     def CharSequence descAndRef(SchemaNode node) '''
1411         «node.description»
1412         «IF node.reference !== null»
1413             Reference «node.reference»
1414         «ENDIF»
1415     '''
1416
1417     private def listItem(String value) '''
1418         «IF value !== null && !value.empty»
1419             <li>
1420                 «value»
1421             </li>
1422         «ENDIF»
1423     '''
1424
1425     private def listItem(String name, String value) '''
1426         «IF value !== null && !value.empty»
1427             <li>
1428                 «name»: «value»
1429             </li>
1430         «ENDIF»
1431     '''
1432
1433     private def String nodeSchemaPathToPath(DataSchemaNode node, Map<SchemaPath, DataSchemaNode> childNodes) {
1434         if (node instanceof ChoiceSchemaNode || node instanceof CaseSchemaNode) {
1435             return null
1436         }
1437
1438         val path = node.path.pathFromRoot
1439         val absolute = node.path.absolute;
1440         var StringBuilder result = new StringBuilder
1441         if (absolute) {
1442             result.append('/')
1443         }
1444         if (path !== null && !path.empty) {
1445             val actual = new ArrayList()
1446             var i = 0;
1447             for (pathElement : path) {
1448                 actual.add(pathElement)
1449                 val DataSchemaNode nodeByPath = childNodes.get(SchemaPath.create(actual, absolute))
1450                 if (!(nodeByPath instanceof ChoiceSchemaNode) && !(nodeByPath instanceof CaseSchemaNode)) {
1451                     result.append(pathElement.localName)
1452                     if (i != path.size - 1) {
1453                         result.append('/')
1454                     }
1455                 }
1456                 i = i + 1
1457             }
1458         }
1459         return result.toString
1460     }
1461
1462     private def addedByInfo(SchemaNode node) {
1463         if (node instanceof DataSchemaNode) {
1464             return addedByInfo(node)
1465         }
1466         return ""
1467     }
1468
1469     private def addedByInfo(DataSchemaNode node) '''
1470         «IF node.augmenting»(A)«ENDIF»«IF node.addedByUses»(U)«ENDIF»
1471     '''
1472
1473     private def isAddedBy(SchemaNode node) {
1474         if (node instanceof DataSchemaNode) {
1475             return node.augmenting || node.addedByUses
1476         }
1477         return false
1478     }
1479
1480     private def nodeName(SchemaNode node) {
1481         if (node instanceof ContainerSchemaNode) {
1482             return nodeName(node);
1483         } else if (node instanceof ListSchemaNode) {
1484             return nodeName(node);
1485         }
1486         val addedByInfo = node.addedByInfo
1487         if (node.isAddedBy) {
1488             return '''«italic(node.QName.localName)»«addedByInfo»'''
1489         }
1490         return '''«node.QName.localName»«addedByInfo»'''
1491     }
1492
1493     private def nodeName(ContainerSchemaNode node) '''
1494         «IF node.isAddedBy»
1495             «strong(italic(node.QName.localName))»«node.addedByInfo»
1496         «ELSE»
1497             «strong(node.QName.localName)»«node.addedByInfo»
1498         «ENDIF»
1499     '''
1500
1501     private def nodeName(ListSchemaNode node) '''
1502         «IF node.isAddedBy»
1503             «strong(italic(node.QName.localName))» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF»«node.addedByInfo»
1504         «ELSE»
1505             «strong(node.QName.localName)» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF»
1506         «ENDIF»
1507     '''
1508
1509 }