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