Merge "update odlux for notification change"
[ccsdk/features.git] / sdnr / wt / odlux / apps / configurationApp / src / yang / yangParser.ts
1 /**
2  * ============LICENSE_START========================================================================
3  * ONAP : ccsdk feature sdnr wt odlux
4  * =================================================================================================
5  * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
6  * =================================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software distributed under the License
13  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14  * or implied. See the License for the specific language governing permissions and limitations under
15  * the License.
16  * ============LICENSE_END==========================================================================
17  */
18 import { Token, Statement, Module, Identity, ModuleState } from "../models/yang";
19 import {
20   ViewSpecification, ViewElement, isViewElementObjectOrList, ViewElementBase,
21   isViewElementReference, ViewElementChoise, ViewElementBinary, ViewElementString, isViewElementString,
22   isViewElementNumber, ViewElementNumber, Expression, YangRange, ViewElementUnion, ViewElementRpc, isViewElementRpc, ResolveFunction, ViewElementDate
23 } from "../models/uiModels";
24 import { yangService } from "../services/yangService";
25
26
27 export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => {
28   const pathParts: RegExpMatchArray[] = [];
29   let partMatch: RegExpExecArray | null;
30   if (vPath) do {
31     partMatch = vPathParser.exec(vPath);
32     if (partMatch) {
33       pathParts.push(partMatch);
34     }
35   } while (partMatch)
36   return pathParts;
37 }
38
39 class YangLexer {
40
41   private pos: number = 0;
42   private buf: string = "";
43
44   constructor(input: string) {
45     this.pos = 0;
46     this.buf = input;
47   }
48
49   private _optable: { [key: string]: string } = {
50     ';': 'SEMI',
51     '{': 'L_BRACE',
52     '}': 'R_BRACE',
53   };
54
55   private _isNewline(char: string): boolean {
56     return char === '\r' || char === '\n';
57   }
58
59   private _isWhitespace(char: string): boolean {
60     return char === ' ' || char === '\t' || this._isNewline(char);
61   }
62
63   private _isDigit(char: string): boolean {
64     return char >= '0' && char <= '9';
65   }
66
67   private _isAlpha(char: string): boolean {
68     return (char >= 'a' && char <= 'z') ||
69       (char >= 'A' && char <= 'Z')
70   }
71
72   private _isAlphanum(char: string): boolean {
73     return this._isAlpha(char) || this._isDigit(char) ||
74       char === '_' || char === '-' || char === '.';
75   }
76
77   private _skipNontokens() {
78     while (this.pos < this.buf.length) {
79       const char = this.buf.charAt(this.pos);
80       if (this._isWhitespace(char)) {
81         this.pos++;
82       } else {
83         break;
84       }
85     }
86   }
87
88   private _processString(terminator: string | null): Token {
89     // this.pos points at the opening quote. Find the ending quote.
90     let end_index = this.pos + 1;
91     while (end_index < this.buf.length) {
92       const char = this.buf.charAt(end_index);
93       if (char === "\\") {
94         end_index += 2;
95         continue;
96       };
97       if (terminator === null && (this._isWhitespace(char) || this._optable[char] !== undefined) || char === terminator) {
98         break;
99       }
100       end_index++;
101     }
102
103     if (end_index >= this.buf.length) {
104       throw Error('Unterminated quote at ' + this.pos);
105     } else {
106       const start = this.pos + (terminator ? 1 : 0);
107       const end = end_index;
108       const tok = {
109         name: 'STRING',
110         value: this.buf.substring(start, end),
111         start,
112         end
113       };
114       this.pos = terminator ? end + 1 : end;
115       return tok;
116     }
117   }
118
119   private _processIdentifier(): Token {
120     let endpos = this.pos + 1;
121     while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) {
122       ++endpos;
123     }
124
125     let name = 'IDENTIFIER'
126     if (this.buf.charAt(endpos) === ":") {
127       name = 'IDENTIFIERREF';
128       ++endpos;
129       while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) {
130         ++endpos;
131       }
132     }
133
134     const tok = {
135       name: name,
136       value: this.buf.substring(this.pos, endpos),
137       start: this.pos,
138       end: endpos
139     };
140
141     this.pos = endpos;
142     return tok;
143   }
144
145   private _processNumber(): Token {
146     let endpos = this.pos + 1;
147     while (endpos < this.buf.length &&
148       this._isDigit(this.buf.charAt(endpos))) {
149       endpos++;
150     }
151
152     const tok = {
153       name: 'NUMBER',
154       value: this.buf.substring(this.pos, endpos),
155       start: this.pos,
156       end: endpos
157     };
158     this.pos = endpos;
159     return tok;
160   }
161
162   private _processLineComment() {
163     var endpos = this.pos + 2;
164     // Skip until the end of the line
165     while (endpos < this.buf.length && !this._isNewline(this.buf.charAt(endpos))) {
166       endpos++;
167     }
168     this.pos = endpos + 1;
169   }
170
171   private _processBlockComment() {
172     var endpos = this.pos + 2;
173     // Skip until the end of the line
174     while (endpos < this.buf.length && !((this.buf.charAt(endpos) === "/" && this.buf.charAt(endpos - 1) === "*"))) {
175       endpos++;
176     }
177     this.pos = endpos + 1;
178   }
179
180   public tokenize(): Token[] {
181     const result: Token[] = [];
182     this._skipNontokens();
183     while (this.pos < this.buf.length) {
184
185       const char = this.buf.charAt(this.pos);
186       const op = this._optable[char];
187
188       if (op !== undefined) {
189         result.push({ name: op, value: char, start: this.pos, end: ++this.pos });
190       } else if (this._isAlpha(char)) {
191         result.push(this._processIdentifier());
192         this._skipNontokens();
193         const peekChar = this.buf.charAt(this.pos);
194         if (this._optable[peekChar] === undefined) {
195           result.push((peekChar !== "'" && peekChar !== '"')
196             ? this._processString(null)
197             : this._processString(peekChar));
198         }
199       } else if (char === '/' && this.buf.charAt(this.pos + 1) === "/") {
200         this._processLineComment();
201       } else if (char === '/' && this.buf.charAt(this.pos + 1) === "*") {
202         this._processBlockComment();
203       } else {
204         throw Error('Token error at ' + this.pos + " " + this.buf[this.pos]);
205       }
206       this._skipNontokens();
207     }
208     return result;
209   }
210
211   public tokenize2(): Statement {
212     let stack: Statement[] = [{ key: "ROOT", sub: [] }];
213     let current: Statement | null = null;
214
215     this._skipNontokens();
216     while (this.pos < this.buf.length) {
217
218       const char = this.buf.charAt(this.pos);
219       const op = this._optable[char];
220
221       if (op !== undefined) {
222         if (op === "L_BRACE") {
223           current && stack.unshift(current);
224           current = null;
225         } else if (op === "R_BRACE") {
226           current = stack.shift() || null;
227         }
228         this.pos++;
229       } else if (this._isAlpha(char) || char === "_") {
230         const key = this._processIdentifier().value;
231         this._skipNontokens();
232         let peekChar = this.buf.charAt(this.pos);
233         let arg = undefined;
234         if (this._optable[peekChar] === undefined) {
235           arg = (peekChar === '"' || peekChar === "'")
236             ? this._processString(peekChar).value
237             : this._processString(null).value;
238         }
239         do {
240           this._skipNontokens();
241           peekChar = this.buf.charAt(this.pos);
242           if (peekChar !== "+") break;
243           this.pos++;
244           this._skipNontokens();
245           peekChar = this.buf.charAt(this.pos);
246           arg += (peekChar === '"' || peekChar === "'")
247             ? this._processString(peekChar).value
248             : this._processString(null).value;
249         } while (true);
250         current = { key, arg, sub: [] };
251         stack[0].sub!.push(current);
252       } else if (char === '/' && this.buf.charAt(this.pos + 1) === "/") {
253         this._processLineComment();
254       } else if (char === '/' && this.buf.charAt(this.pos + 1) === "*") {
255         this._processBlockComment();
256       } else {
257         throw Error('Token error at ' + this.pos + " " + this.buf.slice(this.pos - 10, this.pos + 10));
258       }
259       this._skipNontokens();
260     }
261     if (stack[0].key !== "ROOT" || !stack[0].sub![0]) {
262       throw new Error("Internal Perser Error");
263     }
264     return stack[0].sub![0];
265   }
266 }
267
268 export class YangParser {
269   private _groupingsToResolve: ViewSpecification[] = [];
270
271   private _identityToResolve: (() => void)[] = [];
272   private _unionsToResolve: (() => void)[] = [];
273   private _modulesToResolve: (() => void)[] = [];
274
275   private _modules: { [name: string]: Module } = {};
276   private _views: ViewSpecification[] = [{
277     id: "0",
278     name: "root",
279     language: "en-US",
280     canEdit: false,
281     config: true,
282     parentView: "0",
283     title: "root",
284     elements: {},
285   }];
286
287   public static ResolveStack = Symbol("ResolveStack");
288
289   constructor(private _unavailableCapabilities: { failureReason: string; capability: string; }[] = []) {
290    
291   }
292
293   public get modules() {
294     return this._modules;
295   }
296
297   public get views() {
298     return this._views;
299   }
300
301   public async addCapability(capability: string, version?: string) {
302     // do not add twice
303     if (this._modules[capability]) {
304       // console.warn(`Skipped capability: ${capability} since allready contained.` );
305       return;
306     }
307
308     // // do not add unavaliabe capabilities
309     // if (this._unavailableCapabilities.some(c => c.capability === capability)) {
310     //   // console.warn(`Skipped capability: ${capability} since it is marked as unavaliable.` );
311     //   return;
312     // }
313
314     const data = await yangService.getCapability(capability, version);
315     if (!data) {
316       throw new Error(`Could not load yang file for ${capability}.`);
317     }
318
319     const rootStatement = new YangLexer(data).tokenize2();
320
321     if (rootStatement.key !== "module") {
322       throw new Error(`Root element of ${capability} is not a module.`);
323     }
324     if (rootStatement.arg !== capability) {
325       throw new Error(`Root element capability ${rootStatement.arg} does not requested ${capability}.`);
326     }
327
328     const isUnavaliabe = this._unavailableCapabilities.some(c => c.capability === capability);
329     
330     const module = this._modules[capability] = {
331       name: rootStatement.arg,
332       revisions: {},
333       imports: {},
334       features: {},
335       identities: {},
336       augments: {},
337       groupings: {},
338       typedefs: {},
339       views: {},
340       elements: {},
341       state: isUnavaliabe
342            ? ModuleState.unabaliabe 
343            : ModuleState.stable,
344     };
345
346     await this.handleModule(module, rootStatement, capability);
347   }
348
349   private async handleModule(module: Module, rootStatement: Statement, capability: string) {
350
351     // extract namespace && prefix
352     module.namespace = this.extractValue(rootStatement, "namespace");
353     module.prefix = this.extractValue(rootStatement, "prefix");
354     if (module.prefix) {
355       module.imports[module.prefix] = capability;
356     }
357
358     // extract revisions
359     const revisions = this.extractNodes(rootStatement, "revision");
360     module.revisions = {
361       ...module.revisions,
362       ...revisions.reduce<{ [version: string]: {} }>((acc, version) => {
363         if (!version.arg) {
364           throw new Error(`Module [${module.name}] has a version w/o version number.`);
365         }
366         const description = this.extractValue(version, "description");
367         const reference = this.extractValue(version, "reference");
368         acc[version.arg] = {
369           description,
370           reference,
371         };
372         return acc;
373       }, {})
374     };
375
376     // extract features
377     const features = this.extractNodes(rootStatement, "feature");
378     module.features = {
379       ...module.features,
380       ...features.reduce<{ [version: string]: {} }>((acc, feature) => {
381         if (!feature.arg) {
382           throw new Error(`Module [${module.name}] has a feature w/o name.`);
383         }
384         const description = this.extractValue(feature, "description");
385         acc[feature.arg] = {
386           description,
387         };
388         return acc;
389       }, {})
390     };
391
392     // extract imports
393     const imports = this.extractNodes(rootStatement, "import");
394     module.imports = {
395       ...module.imports,
396       ...imports.reduce<{ [key: string]: string }>((acc, imp) => {
397         const prefix = imp.sub && imp.sub.filter(s => s.key === "prefix");
398         if (!imp.arg) {
399           throw new Error(`Module [${module.name}] has an import with neither name nor prefix.`);
400         }
401         acc[prefix && prefix.length === 1 && prefix[0].arg || imp.arg] = imp.arg;
402         return acc;
403       }, {})
404     };
405
406     // import all required files and set module state 
407     if (imports) for (let ind = 0; ind < imports.length; ++ind) {
408       const moduleName = imports[ind].arg!; 
409       await this.addCapability(moduleName);
410       const importedModule = this._modules[imports[ind].arg!];
411       if (importedModule && importedModule.state > ModuleState.stable) {
412           module.state = Math.max(module.state, ModuleState.instable);
413       }
414     }
415
416     this.extractTypeDefinitions(rootStatement, module, "");
417
418     this.extractIdentites(rootStatement, 0, module, "");
419
420     const groupings = this.extractGroupings(rootStatement, 0, module, "");
421     this._views.push(...groupings);
422
423     const augments = this.extractAugments(rootStatement, 0, module, "");
424     this._views.push(...augments);
425
426     // the default for config on module level is config = true;
427     const [currentView, subViews] = this.extractSubViews(rootStatement, 0, module, "");
428     this._views.push(currentView, ...subViews);
429
430     // create the root elements for this module
431     module.elements = currentView.elements;
432     this._modulesToResolve.push(() => {
433       Object.keys(module.elements).forEach(key => {
434         const viewElement = module.elements[key];
435         if (!(isViewElementObjectOrList(viewElement) || isViewElementRpc(viewElement))) {
436           console.error(new Error(`Module: [${module}]. Only Object, List or RPC are allowed on root level.`));
437         }
438         if (isViewElementObjectOrList(viewElement)) {
439           const viewIdIndex = Number(viewElement.viewId);
440           module.views[key] = this._views[viewIdIndex];
441         }
442         
443         // add only the UI View if the module is avliable
444         if (module.state !== ModuleState.unabaliabe) this._views[0].elements[key] = module.elements[key];
445       });
446     });
447     return module;
448   }
449
450   public postProcess() {
451
452     // execute all post processes like resolving in propper order
453     this._unionsToResolve.forEach(cb => {
454       try { cb(); } catch (error) {
455         console.warn(error.message);
456       }
457     });
458
459     // process all groupings
460     this._groupingsToResolve.filter(vs => vs.uses && vs.uses[ResolveFunction]).forEach(vs => {
461       try { vs.uses![ResolveFunction] !== undefined && vs.uses![ResolveFunction]!("|"); } catch (error) {
462         console.warn(`Error resolving: [${vs.name}] [${error.message}]`);
463       }
464     });
465
466     // process all augmentations / sort by namespace changes to ensure propper order 
467     Object.keys(this.modules).forEach(modKey => {
468       const module = this.modules[modKey];
469       const augmentKeysWithCounter = Object.keys(module.augments).map((key) => {
470         const pathParts = splitVPath(key, /(?:(?:([^\/\:]+):)?([^\/]+))/g);  // 1 = opt: namespace / 2 = property 
471         let nameSpaceChangeCounter = 0;
472         let currentNS = module.name; // init namespace
473         pathParts.forEach(([ns, _])=> {
474           if (ns === currentNS){
475             currentNS = ns;
476             nameSpaceChangeCounter++;
477           }
478         });
479         return {
480           key,
481           nameSpaceChangeCounter,
482         }
483       });
484       
485       const augmentKeys = augmentKeysWithCounter
486         .sort((a,b) => a.nameSpaceChangeCounter > b.nameSpaceChangeCounter ? 1 : a.nameSpaceChangeCounter === b.nameSpaceChangeCounter ? 0 : -1 )
487         .map((a) => a.key);
488
489       augmentKeys.forEach(augKey => {
490         const augments = module.augments[augKey];
491         const viewSpec = this.resolveView(augKey);
492         if (!viewSpec) console.warn(`Could not find view to augment [${augKey}] in [${module.name}].`);
493         if (augments && viewSpec) {
494           augments.forEach(augment => Object.keys(augment.elements).forEach(key => {
495             const elm = augment.elements[key];
496             viewSpec.elements[key] = {
497               ...augment.elements[key],
498               
499               when: elm.when ? `(${augment.when}) and (${elm.when})` : augment.when,
500               ifFeature: elm.ifFeature ? `(${augment.ifFeature}) and (${elm.ifFeature})` : augment.ifFeature,
501             };
502           }));
503         }
504       });
505     });
506
507     // process Identities
508     const traverseIdentity = (identities: Identity[]) => {
509       const result: Identity[] = [];
510       for (let identity of identities) {
511         if (identity.children && identity.children.length > 0) {
512           result.push(...traverseIdentity(identity.children));
513         } else {
514           result.push(identity);
515         }
516       }
517       return result;
518     }
519
520     const baseIdentites: Identity[] = [];
521     Object.keys(this.modules).forEach(modKey => {
522       const module = this.modules[modKey];
523       Object.keys(module.identities).forEach(idKey => {
524         const identity = module.identities[idKey];
525         if (identity.base != null) {
526           const base = this.resolveIdentity(identity.base, module);
527           base.children?.push(identity);
528         } else {
529           baseIdentites.push(identity);
530         }
531       });
532     });
533     baseIdentites.forEach(identity => {
534       identity.values = identity.children && traverseIdentity(identity.children) || [];
535     });
536
537     this._identityToResolve.forEach(cb => {
538       try { cb(); } catch (error) {
539         console.warn(error.message);
540       }
541     });
542
543     this._modulesToResolve.forEach(cb => {
544       try { cb(); } catch (error) {
545         console.warn(error.message);
546       }
547     });
548
549     // resolve readOnly
550     const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => {
551       
552       // update view config
553       view.config = view.config && parentConfig;
554       
555       Object.keys(view.elements).forEach((key) => {
556         const elm = view.elements[key];
557
558         // update element config
559         elm.config = elm.config && view.config;
560         
561         // update all sub-elements of objects
562         if (elm.uiType === "object") {
563           resolveReadOnly(this.views[+elm.viewId], elm.config);
564         }
565
566       })
567     }
568
569     const dump = resolveReadOnly(this.views[0], true); 
570   };
571
572   private _nextId = 1;
573   private get nextId() {
574     return this._nextId++;
575   }
576
577   private extractNodes(statement: Statement, key: string): Statement[] {
578     return statement.sub && statement.sub.filter(s => s.key === key) || [];
579   }
580
581   private extractValue(statement: Statement, key: string): string | undefined;
582   private extractValue(statement: Statement, key: string, parser: RegExp): RegExpExecArray | undefined;
583   private extractValue(statement: Statement, key: string, parser?: RegExp): string | RegExpExecArray | undefined {
584     const typeNodes = this.extractNodes(statement, key);
585     const rawValue = typeNodes.length > 0 && typeNodes[0].arg || undefined;
586     return parser
587       ? rawValue && parser.exec(rawValue) || undefined
588       : rawValue;
589   }
590
591   private extractTypeDefinitions(statement: Statement, module: Module, currentPath: string): void {
592     const typedefs = this.extractNodes(statement, "typedef");
593     typedefs && typedefs.forEach(def => {
594       if (!def.arg) {
595         throw new Error(`Module: [${module.name}]. Found typefed without name.`);
596       }
597       module.typedefs[def.arg] = this.getViewElement(def, module, 0, currentPath, false);
598     });
599   }
600
601   /** Handles Goupings like named Container */
602   private extractGroupings(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
603     const subViews: ViewSpecification[] = [];
604     const groupings = this.extractNodes(statement, "grouping");
605     if (groupings && groupings.length > 0) {
606       subViews.push(...groupings.reduce<ViewSpecification[]>((acc, cur) => {
607         if (!cur.arg) {
608           throw new Error(`Module: [${module.name}][${currentPath}]. Found grouping without name.`);
609         }
610         const grouping = cur.arg;
611
612         // the default for config on module level is config = true;
613         const [currentView, subViews] = this.extractSubViews(cur, /* parentId */ -1, module, currentPath);
614         grouping && (module.groupings[grouping] = currentView);
615         acc.push(currentView, ...subViews);
616         return acc;
617       }, []));
618     }
619
620     return subViews;
621   }
622
623   /** Handles Augmants also like named Container */
624   private extractAugments(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
625     const subViews: ViewSpecification[] = [];
626     const augments = this.extractNodes(statement, "augment");
627     if (augments && augments.length > 0) {
628       subViews.push(...augments.reduce<ViewSpecification[]>((acc, cur) => {
629         if (!cur.arg) {
630           throw new Error(`Module: [${module.name}][${currentPath}]. Found augment without path.`);
631         }
632         const augment = this.resolveReferencePath(cur.arg, module);
633
634         // the default for config on module level is config = true;
635         const [currentView, subViews] = this.extractSubViews(cur, parentId, module, currentPath);
636         if (augment) {
637           module.augments[augment] = module.augments[augment] || [];
638           module.augments[augment].push(currentView);
639         }
640         acc.push(currentView, ...subViews);
641         return acc;
642       }, []));
643     }
644
645     return subViews;
646   }
647
648   /** Handles Identities  */
649   private extractIdentites(statement: Statement, parentId: number, module: Module, currentPath: string) {
650     const identities = this.extractNodes(statement, "identity");
651     module.identities = identities.reduce<{ [name: string]: Identity }>((acc, cur) => {
652       if (!cur.arg) {
653         throw new Error(`Module: [${module.name}][${currentPath}]. Found identiy without name.`);
654       }
655       acc[cur.arg] = {
656         id: `${module.name}:${cur.arg}`,
657         label: cur.arg,
658         base: this.extractValue(cur, "base"),
659         description: this.extractValue(cur, "description"),
660         reference: this.extractValue(cur, "reference"),
661         children: []
662       }
663       return acc;
664     }, {});
665   }
666
667    // Hint: use 0 as parentId for rootElements and -1 for rootGroupings.
668   private extractSubViews(statement: Statement, parentId: number, module: Module, currentPath: string): [ViewSpecification, ViewSpecification[]] {
669     // used for scoped definitions
670     const context: Module = {
671       ...module,
672       typedefs: {
673         ...module.typedefs
674       }
675     };
676
677     const currentId = this.nextId;
678     const subViews: ViewSpecification[] = [];
679     let elements: ViewElement[] = [];
680
681     const configValue = this.extractValue(statement, "config");
682     const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
683
684     // extract conditions
685     const ifFeature = this.extractValue(statement, "if-feature");
686     const whenCondition = this.extractValue(statement, "when");
687     if (whenCondition) console.warn("Found in [" + context.name + "]" + currentPath + " when: " + whenCondition);
688
689     // extract all scoped typedefs
690     this.extractTypeDefinitions(statement, context, currentPath);
691
692     // extract all scoped groupings
693     subViews.push(
694       ...this.extractGroupings(statement, parentId, context, currentPath)
695     );
696
697     // extract all container
698     const container = this.extractNodes(statement, "container");
699     if (container && container.length > 0) {
700       subViews.push(...container.reduce<ViewSpecification[]>((acc, cur) => {
701         if (!cur.arg) {
702           throw new Error(`Module: [${context.name}]${currentPath}. Found container without name.`);
703         }
704         const [currentView, subViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
705         elements.push({
706           id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
707           label: cur.arg,
708           path: currentPath,
709           module: context.name || module.name || '',
710           uiType: "object",
711           viewId: currentView.id,
712           config: currentView.config,
713         });
714         acc.push(currentView, ...subViews);
715         return acc;
716       }, []));
717     }
718
719     // process all lists
720     // a list is a list of containers with the leafs contained in the list
721     const lists = this.extractNodes(statement, "list");
722     if (lists && lists.length > 0) {
723       subViews.push(...lists.reduce<ViewSpecification[]>((acc, cur) => {
724         let elmConfig = config;
725         if (!cur.arg) {
726           throw new Error(`Module: [${context.name}]${currentPath}. Found list without name.`);
727         }
728         const key = this.extractValue(cur, "key") || undefined;
729         if (elmConfig && !key) {
730           console.warn(`Module: [${context.name}]${currentPath}. Found configurable list without key. Assume config shell be false.`);
731           elmConfig = false;
732         }
733         const [currentView, subViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
734         elements.push({
735           id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
736           label: cur.arg,
737           path: currentPath,
738           module: context.name || module.name || '',
739           isList: true,
740           uiType: "object",
741           viewId: currentView.id,
742           key: key,
743           config: elmConfig && currentView.config,
744         });
745         acc.push(currentView, ...subViews);
746         return acc;
747       }, []));
748     }
749
750     // process all leaf-lists
751     // a leaf-list is a list of some type
752     const leafLists = this.extractNodes(statement, "leaf-list");
753     if (leafLists && leafLists.length > 0) {
754       elements.push(...leafLists.reduce<ViewElement[]>((acc, cur) => {
755         const element = this.getViewElement(cur, context, parentId, currentPath, true);
756         element && acc.push(element);
757         return acc;
758       }, []));
759     }
760
761     // process all leafs
762     // a leaf is mainly a property of an object
763     const leafs = this.extractNodes(statement, "leaf");
764     if (leafs && leafs.length > 0) {
765       elements.push(...leafs.reduce<ViewElement[]>((acc, cur) => {
766         const element = this.getViewElement(cur, context, parentId, currentPath, false);
767         element && acc.push(element);
768         return acc;
769       }, []));
770     }
771
772
773     const choiceStms = this.extractNodes(statement, "choice");
774     if (choiceStms && choiceStms.length > 0) {
775       elements.push(...choiceStms.reduce<ViewElementChoise[]>((accChoise, curChoise) => {
776         if (!curChoise.arg) {
777           throw new Error(`Module: [${context.name}]${currentPath}. Found choise without name.`);
778         }
779         // extract all cases like containers
780         const cases: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } }[] = [];
781         const caseStms = this.extractNodes(curChoise, "case");
782         if (caseStms && caseStms.length > 0) {
783           cases.push(...caseStms.reduce((accCase, curCase) => {
784             if (!curCase.arg) {
785               throw new Error(`Module: [${context.name}]${currentPath}/${curChoise.arg}. Found case without name.`);
786             }
787             const description = this.extractValue(curCase, "description") || undefined;
788             const [caseView, caseSubViews] = this.extractSubViews(curCase, parentId, context, `${currentPath}/${context.name}:${curChoise.arg}`);
789             subViews.push(caseView, ...caseSubViews);
790
791             const caseDef: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } = {
792               id: parentId === 0 ? `${context.name}:${curCase.arg}` : curCase.arg,
793               label: curCase.arg,
794               description: description,
795               elements: caseView.elements
796             };
797             accCase.push(caseDef);
798             return accCase;
799           }, [] as { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } }[]));
800         }
801
802         // extract all simple cases (one case per leaf, container, etc.)
803         const [choiseView, choiseSubViews] = this.extractSubViews(curChoise, parentId, context, `${currentPath}/${context.name}:${curChoise.arg}`);
804         subViews.push(choiseView, ...choiseSubViews);
805         cases.push(...Object.keys(choiseView.elements).reduce((accElm, curElm) => {
806           const elm = choiseView.elements[curElm];
807           const caseDef: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } = {
808             id: elm.id,
809             label: elm.label,
810             description: elm.description,
811             elements: { [elm.id]: elm }
812           };
813           accElm.push(caseDef);
814           return accElm;
815         }, [] as { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } }[]));
816
817         const description = this.extractValue(curChoise, "description") || undefined;
818         const configValue = this.extractValue(curChoise, "config");
819         const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
820
821         const mandatory = this.extractValue(curChoise, "mandatory") === "true" || false;
822
823         const element: ViewElementChoise = {
824           uiType: "choise",
825           id: parentId === 0 ? `${context.name}:${curChoise.arg}` : curChoise.arg,
826           label: curChoise.arg,
827           path: currentPath,
828           module: context.name || module.name || '',
829           config: config,
830           mandatory: mandatory,
831           description: description,
832           cases: cases.reduce((acc, cur) => {
833             acc[cur.id] = cur;
834             return acc;
835           }, {} as { [name: string]: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } })
836         };
837
838         accChoise.push(element);
839         return accChoise;
840       }, []));
841     }
842
843     const rpcStms = this.extractNodes(statement, "rpc");
844     if (rpcStms && rpcStms.length > 0) {
845       elements.push(...rpcStms.reduce<ViewElementRpc[]>((accRpc, curRpc) => {
846         if (!curRpc.arg) {
847           throw new Error(`Module: [${context.name}]${currentPath}. Found rpc without name.`);
848         }
849
850         const description = this.extractValue(curRpc, "description") || undefined;
851         const configValue = this.extractValue(curRpc, "config");
852         const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
853
854         let inputViewId: string | undefined = undefined;
855         let outputViewId: string | undefined = undefined;
856
857         const input = this.extractNodes(curRpc, "input") || undefined;
858         const output = this.extractNodes(curRpc, "output") || undefined;
859
860         if (input && input.length > 0) {
861           const [inputView, inputSubViews] = this.extractSubViews(input[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`);
862           subViews.push(inputView, ...inputSubViews);
863           inputViewId = inputView.id;
864         }
865
866         if (output && output.length > 0) {
867           const [outputView, outputSubViews] = this.extractSubViews(output[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`);
868           subViews.push(outputView, ...outputSubViews);
869           outputViewId = outputView.id;
870         }
871
872         const element: ViewElementRpc = {
873           uiType: "rpc",
874           id: parentId === 0 ? `${context.name}:${curRpc.arg}` : curRpc.arg,
875           label: curRpc.arg,
876           path: currentPath,
877           module: context.name || module.name || '',
878           config: config,
879           description: description,
880           inputViewId: inputViewId,
881           outputViewId: outputViewId,
882         };
883
884         accRpc.push(element);
885
886         return accRpc;
887       }, []));
888     }
889
890     // if (!statement.arg) {
891     //   throw new Error(`Module: [${context.name}]. Found statement without name.`);
892     // }
893
894     const viewSpec: ViewSpecification = {
895       id: String(currentId),
896       parentView: String(parentId),
897       ns: context.name,
898       name: statement.arg != null ? statement.arg : undefined,
899       title: statement.arg != null ? statement.arg : undefined,
900       language: "en-us",
901       canEdit: false,
902       config: config,
903       ifFeature: ifFeature,
904       when: whenCondition,
905       elements: elements.reduce<{ [name: string]: ViewElement }>((acc, cur) => {
906         acc[cur.id] = cur;
907         return acc;
908       }, {}),
909     };
910
911     // evaluate canEdit depending on all conditions
912     Object.defineProperty(viewSpec, "canEdit", {
913       get: () => {
914         return Object.keys(viewSpec.elements).some(key => {
915           const elm = viewSpec.elements[key];
916           return (!isViewElementObjectOrList(elm) && elm.config);
917         });
918       }
919     });
920
921     // merge in all uses references and resolve groupings
922     const usesRefs = this.extractNodes(statement, "uses");
923     if (usesRefs && usesRefs.length > 0) {
924
925       viewSpec.uses = (viewSpec.uses || []);
926       const resolveFunctions : ((parentElementPath: string)=>void)[] = [];
927
928       for (let i = 0; i < usesRefs.length; ++i) {
929         const groupingName = usesRefs[i].arg;
930         if (!groupingName) {
931           throw new Error(`Module: [${context.name}]. Found an uses statement without a grouping name.`);
932         }
933
934         viewSpec.uses.push(this.resolveReferencePath(groupingName, context));
935         
936         resolveFunctions.push((parentElementPath: string) => {
937           const groupingViewSpec = this.resolveGrouping(groupingName, context);
938           if (groupingViewSpec) {
939
940             // resolve recursive
941             const resolveFunc = groupingViewSpec.uses && groupingViewSpec.uses[ResolveFunction];
942             resolveFunc && resolveFunc(parentElementPath);
943
944             Object.keys(groupingViewSpec.elements).forEach(key => {
945               const elm = groupingViewSpec.elements[key];
946               // a useRef on root level need a namespace
947               viewSpec.elements[parentId === 0 ? `${module.name}:${key}` : key] = {
948                 ...elm,
949                 when: elm.when ? `(${groupingViewSpec.when}) and (${elm.when})` : groupingViewSpec.when,
950                 ifFeature: elm.ifFeature ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})` : groupingViewSpec.ifFeature,
951               };
952             });
953           }
954         });
955       }
956
957       viewSpec.uses[ResolveFunction] = (parentElementPath: string) => {
958         const currentElementPath = `${parentElementPath} -> ${viewSpec.ns}:${viewSpec.name}`;  
959         resolveFunctions.forEach(resolve => {
960             try {
961                 resolve(currentElementPath);
962             } catch (error) {
963                 console.error(error);
964             }
965         });
966         // console.log("Resolved "+currentElementPath, viewSpec);
967         if (viewSpec?.uses) {
968           viewSpec.uses[ResolveFunction] = undefined;
969         }
970       }
971
972       this._groupingsToResolve.push(viewSpec);
973     }
974
975     return [viewSpec, subViews];
976   }
977
978   // https://tools.ietf.org/html/rfc7950#section-9.3.4
979   private static decimalRange = [
980     { min: -9223372036854775808, max: 9223372036854775807 },
981     { min: -922337203685477580.8, max: 922337203685477580.7 },
982     { min: -92233720368547758.08, max: 92233720368547758.07 },
983     { min: -9223372036854775.808, max: 9223372036854775.807 },
984     { min: -922337203685477.5808, max: 922337203685477.5807 },
985     { min: -92233720368547.75808, max: 92233720368547.75807 },
986     { min: -9223372036854.775808, max: 9223372036854.775807 },
987     { min: -922337203685.4775808, max: 922337203685.4775807 },
988     { min: -92233720368.54775808, max: 92233720368.54775807 },
989     { min: -9223372036.854775808, max: 9223372036.854775807 },
990     { min: -922337203.6854775808, max: 922337203.6854775807 },
991     { min: -92233720.36854775808, max: 92233720.36854775807 },
992     { min: -9223372.036854775808, max: 9223372.036854775807 },
993     { min: -922337.2036854775808, max: 922337.2036854775807 },
994     { min: -92233.72036854775808, max: 92233.72036854775807 },
995     { min: -9223.372036854775808, max: 9223.372036854775807 },
996     { min: -922.3372036854775808, max: 922.3372036854775807 },
997     { min: -92.23372036854775808, max: 92.23372036854775807 },
998     { min: -9.223372036854775808, max: 9.223372036854775807 },
999   ];
1000
1001   /** Extracts the UI View from the type in the cur statement. */
1002   private getViewElement(cur: Statement, module: Module, parentId: number, currentPath: string, isList: boolean): ViewElement {
1003
1004     const type = this.extractValue(cur, "type");
1005     const defaultVal = this.extractValue(cur, "default") || undefined;
1006     const description = this.extractValue(cur, "description") || undefined;
1007
1008     const configValue = this.extractValue(cur, "config");
1009     const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
1010
1011     const extractRange = (min: number, max: number, property: string = "range"): { expression: Expression<YangRange> | undefined, min: number, max: number } => {
1012       const ranges = this.extractValue(this.extractNodes(cur, "type")[0]!, property) || undefined;
1013       const range = ranges ?.replace(/min/i, String(min)).replace(/max/i, String(max)).split("|").map(r => {
1014         let minValue: number;
1015         let maxValue: number;
1016         
1017         if (r.indexOf('..') > -1) {
1018             const [minStr, maxStr] = r.split('..');
1019             minValue = Number(minStr);
1020             maxValue = Number(maxStr);
1021         } else if (!isNaN(maxValue = Number(r && r.trim() )) ) {
1022             minValue = maxValue;
1023         } else {
1024             minValue = min,
1025             maxValue = max;
1026         }
1027
1028         if (minValue > min) min = minValue;
1029         if (maxValue < max) max = maxValue;
1030
1031         return {
1032           min: minValue,
1033           max: maxValue
1034         };
1035       });
1036       return {
1037         min: min,
1038         max: max,
1039         expression: range && range.length === 1
1040           ? range[0]
1041           : range && range.length > 1
1042             ? { operation: "OR", arguments: range }
1043             : undefined
1044       }
1045     };
1046
1047     const extractPattern = (): Expression<RegExp> | undefined => {
1048       const pattern = this.extractNodes(this.extractNodes(cur, "type")[0]!, "pattern").map(p => p.arg!).filter(p => !!p).map(p => `^${p}$`);
1049       return pattern && pattern.length == 1
1050         ? new RegExp(pattern[0])
1051         : pattern && pattern.length > 1
1052           ? { operation: "AND", arguments: pattern.map(p => new RegExp(p)) }
1053           : undefined;
1054     }
1055
1056     const mandatory = this.extractValue(cur, "mandatory") === "true" || false;
1057
1058     if (!cur.arg) {
1059       throw new Error(`Module: [${module.name}]. Found element without name.`);
1060     }
1061
1062     if (!type) {
1063       throw new Error(`Module: [${module.name}].[${cur.arg}]. Found element without type.`);
1064     }
1065
1066     const element: ViewElementBase = {
1067       id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg,
1068       label: cur.arg, 
1069       path: currentPath,
1070       module: module.name || "",
1071       config: config,
1072       mandatory: mandatory,
1073       isList: isList,
1074       default: defaultVal,
1075       description: description
1076     };
1077
1078     if (type === "string") {
1079       const length = extractRange(0, +18446744073709551615, "length");
1080       return ({
1081         ...element,
1082         uiType: "string",
1083         length: length.expression,
1084         pattern: extractPattern(),
1085       });
1086     } else if (type === "boolean") {
1087       return ({
1088         ...element,
1089         uiType: "boolean"
1090       });
1091     } else if (type === "uint8") {
1092       const range = extractRange(0, +255);
1093       return ({
1094         ...element,
1095         uiType: "number",
1096         range: range.expression,
1097         min: range.min,
1098         max: range.max,
1099         units: this.extractValue(cur, "units") || undefined,
1100         format: this.extractValue(cur, "format") || undefined,
1101       });
1102     } else if (type === "uint16") {
1103       const range = extractRange(0, +65535);
1104       return ({
1105         ...element,
1106         uiType: "number",
1107         range: range.expression,
1108         min: range.min,
1109         max: range.max,
1110         units: this.extractValue(cur, "units") || undefined,
1111         format: this.extractValue(cur, "format") || undefined,
1112       });
1113     } else if (type === "uint32") {
1114       const range = extractRange(0, +4294967295);
1115       return ({
1116         ...element,
1117         uiType: "number",
1118         range: range.expression,
1119         min: range.min,
1120         max: range.max,
1121         units: this.extractValue(cur, "units") || undefined,
1122         format: this.extractValue(cur, "format") || undefined,
1123       });
1124     } else if (type === "uint64") {
1125       const range = extractRange(0, +18446744073709551615);
1126       return ({
1127         ...element,
1128         uiType: "number",
1129         range: range.expression,
1130         min: range.min,
1131         max: range.max,
1132         units: this.extractValue(cur, "units") || undefined,
1133         format: this.extractValue(cur, "format") || undefined,
1134       });
1135     } else if (type === "int8") {
1136       const range = extractRange(-128, +127);
1137       return ({
1138         ...element,
1139         uiType: "number",
1140         range: range.expression,
1141         min: range.min,
1142         max: range.max,
1143         units: this.extractValue(cur, "units") || undefined,
1144         format: this.extractValue(cur, "format") || undefined,
1145       });
1146     } else if (type === "int16") {
1147       const range = extractRange(-32768, +32767);
1148       return ({
1149         ...element,
1150         uiType: "number",
1151         range: range.expression,
1152         min: range.min,
1153         max: range.max,
1154         units: this.extractValue(cur, "units") || undefined,
1155         format: this.extractValue(cur, "format") || undefined,
1156       });
1157     } else if (type === "int32") {
1158       const range = extractRange(-2147483648, +2147483647);
1159       return ({
1160         ...element,
1161         uiType: "number",
1162         range: range.expression,
1163         min: range.min,
1164         max: range.max,
1165         units: this.extractValue(cur, "units") || undefined,
1166         format: this.extractValue(cur, "format") || undefined,
1167       });
1168     } else if (type === "int64") {
1169       const range = extractRange(-9223372036854775808, +9223372036854775807);
1170       return ({
1171         ...element,
1172         uiType: "number",
1173         range: range.expression,
1174         min: range.min,
1175         max: range.max,
1176         units: this.extractValue(cur, "units") || undefined,
1177         format: this.extractValue(cur, "format") || undefined,
1178       });
1179     } else if (type === "decimal64") {
1180       // decimalRange
1181       const fDigits = Number(this.extractValue(this.extractNodes(cur, "type")[0]!, "fraction-digits")) || -1;
1182       if (fDigits === -1) {
1183         throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found decimal64 with invalid fraction-digits.`);
1184       }
1185       const range = extractRange(YangParser.decimalRange[fDigits].min, YangParser.decimalRange[fDigits].max);
1186       return ({
1187         ...element,
1188         uiType: "number",
1189         fDigits: fDigits,
1190         range: range.expression,
1191         min: range.min,
1192         max: range.max,
1193         units: this.extractValue(cur, "units") || undefined,
1194         format: this.extractValue(cur, "format") || undefined,
1195       });
1196     } else if (type === "enumeration") {
1197       const typeNode = this.extractNodes(cur, "type")[0]!;
1198       const enumNodes = this.extractNodes(typeNode, "enum");
1199       return ({
1200         ...element,
1201         uiType: "selection",
1202         options: enumNodes.reduce<{ key: string; value: string; description?: string }[]>((acc, enumNode) => {
1203           if (!enumNode.arg) {
1204             throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found option without name.`);
1205           }
1206           const ifClause = this.extractValue(enumNode, "if-feature");
1207           const value = this.extractValue(enumNode, "value");
1208           const enumOption = {
1209             key: enumNode.arg,
1210             value: value != null ? value : enumNode.arg,
1211             description: this.extractValue(enumNode, "description") || undefined
1212           };
1213           // todo: ❗ handle the if clause ⚡
1214           acc.push(enumOption);
1215           return acc;
1216         }, [])
1217       });
1218     } else if (type === "leafref") {
1219       const typeNode = this.extractNodes(cur, "type")[0]!;
1220       const vPath = this.extractValue(typeNode, "path");
1221       if (!vPath) {
1222         throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found leafref without path.`);
1223       }
1224       const refPath = this.resolveReferencePath(vPath, module);
1225       const resolve = this.resolveReference.bind(this);
1226       const res: ViewElement = {
1227         ...element,
1228         uiType: "reference",
1229         referencePath: refPath,
1230         ref(this: ViewElement, currentPath: string) {
1231           const elementPath = `${currentPath}/${cur.arg}`;  
1232           
1233           const result = resolve(refPath, elementPath);
1234           if (!result) return undefined;
1235
1236           const [resolvedElement, resolvedPath] = result;
1237           return resolvedElement && [{
1238             ...resolvedElement,
1239             id: this.id,
1240             label: this.label,
1241             config: this.config,
1242             mandatory: this.mandatory,
1243             isList: this.isList,
1244             default: this.default,
1245             description: this.description,
1246           } as ViewElement , resolvedPath] || undefined;
1247         }
1248       };
1249       return res;
1250     } else if (type === "identityref") {
1251       const typeNode = this.extractNodes(cur, "type")[0]!;
1252       const base = this.extractValue(typeNode, "base");
1253       if (!base) {
1254         throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found identityref without base.`);
1255       }
1256       const res: ViewElement = {
1257         ...element,
1258         uiType: "selection",
1259         options: []
1260       };
1261       this._identityToResolve.push(() => {
1262         const identity: Identity = this.resolveIdentity(base, module);
1263         if (!identity) {
1264           throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Could not resolve identity [${base}].`);
1265         }
1266         if (!identity.values || identity.values.length === 0) {
1267           throw new Error(`Identity: [${base}] has no values.`);
1268         }
1269         res.options = identity.values.map(val => ({
1270           key: val.id,
1271           value: val.id,
1272           description: val.description
1273         }));
1274       });
1275       return res;
1276     } else if (type === "empty") {
1277       // todo: ❗ handle empty ⚡
1278       /*  9.11.  The empty Built-In Type
1279           The empty built-in type represents a leaf that does not have any
1280           value, it conveys information by its presence or absence. */
1281       return {
1282         ...element,
1283         uiType: "empty",
1284       };
1285     } else if (type === "union") {
1286       // todo: ❗ handle union ⚡
1287       /* 9.12.  The union Built-In Type */
1288       const typeNode = this.extractNodes(cur, "type")[0]!;
1289       const typeNodes = this.extractNodes(typeNode, "type");
1290
1291       const resultingElement = {
1292         ...element,
1293         uiType: "union",
1294         elements: []
1295       } as ViewElementUnion;
1296
1297       const resolveUnion = () => {
1298         resultingElement.elements.push(...typeNodes.map(node => {
1299           const stm: Statement = {
1300             ...cur,
1301             sub: [
1302               ...(cur.sub ?.filter(s => s.key !== "type") || []),
1303               node
1304             ]
1305           };
1306           return {
1307             ...this.getViewElement(stm, module, parentId, currentPath, isList),
1308             id: node.arg!
1309           };
1310         }));
1311       };
1312
1313       this._unionsToResolve.push(resolveUnion);
1314
1315       return resultingElement;
1316     } else if (type === "bits") {
1317       const typeNode = this.extractNodes(cur, "type")[0]!;
1318       const bitNodes = this.extractNodes(typeNode, "bit");
1319       return {
1320         ...element,
1321         uiType: "bits",
1322         flags: bitNodes.reduce<{ [name: string]: number | undefined; }>((acc, bitNode) => {
1323           if (!bitNode.arg) {
1324             throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found bit without name.`);
1325           }
1326           const ifClause = this.extractValue(bitNode, "if-feature");
1327           const pos = Number(this.extractValue(bitNode, "position"));
1328           acc[bitNode.arg] = pos === pos ? pos : undefined;
1329           return acc;
1330         }, {})
1331       };
1332     } else if (type === "binary") {
1333       return {
1334         ...element,
1335         uiType: "binary",
1336         length: extractRange(0, +18446744073709551615, "length"),
1337       };
1338     } else if (type === "instance-identifier") {
1339       // https://tools.ietf.org/html/rfc7950#page-168
1340       return {
1341         ...element,
1342         uiType: "string",
1343         length: extractRange(0, +18446744073709551615, "length"),
1344       };
1345     } else {
1346       // not a build in type, need to resolve type
1347       let typeRef = this.resolveType(type, module);
1348       if (typeRef == null) console.error(new Error(`Could not resolve type ${type} in [${module.name}][${currentPath}].`));
1349
1350
1351       if (isViewElementString(typeRef)) {
1352         typeRef = this.resolveStringType(typeRef, extractPattern(), extractRange(0, +18446744073709551615));
1353       } else if (isViewElementNumber(typeRef)) {
1354         typeRef = this.resolveNumberType(typeRef, extractRange(typeRef.min, typeRef.max));
1355       }
1356
1357       // spoof date type here from special string type
1358       if ((type === 'date-and-time' || type.endsWith(':date-and-time') ) && typeRef.module === "ietf-yang-types") {
1359           return {
1360              ...typeRef,
1361              ...element,
1362              description: description,
1363              uiType: "date", 
1364           };
1365       }
1366
1367       return ({
1368         ...typeRef,
1369         ...element,
1370         description: description,
1371       }) as ViewElement;
1372     }
1373   }
1374
1375   private resolveStringType(parentElement: ViewElementString, pattern: Expression<RegExp> | undefined, length: { expression: Expression<YangRange> | undefined, min: number, max: number }) {
1376     return {
1377       ...parentElement,
1378       pattern: pattern != null && parentElement.pattern
1379         ? { operation: "AND", arguments: [pattern, parentElement.pattern] }
1380         : parentElement.pattern
1381           ? parentElement.pattern
1382           : pattern,
1383       length: length.expression != null && parentElement.length
1384         ? { operation: "AND", arguments: [length.expression, parentElement.length] }
1385         : parentElement.length
1386           ? parentElement.length
1387           : length ?.expression,
1388     } as ViewElementString;
1389   }
1390
1391   private resolveNumberType(parentElement: ViewElementNumber, range: { expression: Expression<YangRange> | undefined, min: number, max: number }) {
1392     return {
1393       ...parentElement,
1394       range: range.expression != null && parentElement.range
1395         ? { operation: "AND", arguments: [range.expression, parentElement.range] }
1396         : parentElement.range
1397           ? parentElement.range
1398           : range,
1399       min: range.min,
1400       max: range.max,
1401     } as ViewElementNumber;
1402   }
1403
1404   private resolveReferencePath(vPath: string, module: Module) {
1405     const vPathParser = /(?:(?:([^\/\:]+):)?([^\/]+))/g // 1 = opt: namespace / 2 = property
1406     return vPath.replace(vPathParser, (_, ns, property) => {
1407       const nameSpace = ns && module.imports[ns] || module.name;
1408       return `${nameSpace}:${property}`;
1409     });
1410   }
1411
1412   private resolveReference(vPath: string, currentPath: string) {
1413     const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
1414     let element: ViewElement | null = null;
1415     let moduleName = "";
1416
1417     const vPathParts = splitVPath(vPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] }));
1418     const resultPathParts = !vPath.startsWith("/")
1419       ? splitVPath(currentPath, vPathParser).map(p => { moduleName = p[1] || moduleName ; return { ns: moduleName, property: p[2], ind: p[3] } })
1420       : [];
1421
1422     for (let i = 0; i < vPathParts.length; ++i) {
1423       const vPathPart = vPathParts[i];
1424       if (vPathPart.property === "..") {
1425         resultPathParts.pop();
1426       } else if (vPathPart.property !== ".") {
1427         resultPathParts.push(vPathPart);
1428       }
1429     }
1430
1431     // resolve element by path
1432     for (let j = 0; j < resultPathParts.length; ++j) {
1433       const pathPart = resultPathParts[j];
1434       if (j === 0) {
1435         moduleName = pathPart.ns;
1436         const rootModule = this._modules[moduleName];
1437         if (!rootModule) throw new Error("Could not resolve module [" + moduleName + "].\r\n" + vPath);
1438         element = rootModule.elements[`${pathPart.ns}:${pathPart.property}`];
1439       } else if (element && isViewElementObjectOrList(element)) {
1440         const view: ViewSpecification = this._views[+element.viewId];
1441         if (moduleName !== pathPart.ns) {
1442           moduleName = pathPart.ns;
1443         }   
1444         element = view.elements[pathPart.property] || view.elements[`${moduleName}:${pathPart.property}`];
1445       } else {
1446         throw new Error("Could not resolve reference.\r\n" + vPath);
1447       }
1448       if (!element) throw new Error("Could not resolve path [" + pathPart.property + "] in [" + currentPath + "] \r\n" + vPath);
1449     }
1450
1451     moduleName = ""; // create the vPath for the resolved element, do not add the element itself this will be done later in the res(...) function
1452     return [element, resultPathParts.slice(0,-1).map(p => `${moduleName !== p.ns ? `${moduleName=p.ns}:` : ""}${p.property}${p.ind || ''}`).join("/")];
1453   }
1454
1455   private resolveView(vPath: string) {
1456     const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
1457     let element: ViewElement | null = null;
1458     let partMatch: RegExpExecArray | null;
1459     let view: ViewSpecification | null = null;
1460     let moduleName = "";
1461     if (vPath) do {
1462       partMatch = vPathParser.exec(vPath);
1463       if (partMatch) {
1464         if (element === null) {
1465           moduleName = partMatch[1]!;
1466           const rootModule = this._modules[moduleName];
1467           if (!rootModule) return null;
1468           element = rootModule.elements[`${moduleName}:${partMatch[2]!}`];
1469         } else if (isViewElementObjectOrList(element)) {
1470           view = this._views[+element.viewId];
1471           if (moduleName !== partMatch[1]) {
1472             moduleName = partMatch[1];
1473             element = view.elements[`${moduleName}:${partMatch[2]}`];
1474           } else {
1475             element = view.elements[partMatch[2]];
1476           }
1477         } else {
1478           return null;
1479         }
1480         if (!element) return null;
1481       }
1482     } while (partMatch)
1483     return element && isViewElementObjectOrList(element) && this._views[+element.viewId] || null;
1484   }
1485
1486   private resolveType(type: string, module: Module) {
1487     const collonInd = type.indexOf(":");
1488     const preFix = collonInd > -1 ? type.slice(0, collonInd) : "";
1489     const typeName = collonInd > -1 ? type.slice(collonInd + 1) : type;
1490
1491     const res = preFix
1492       ? this._modules[module.imports[preFix]].typedefs[typeName]
1493       : module.typedefs[typeName];
1494     return res;
1495   }
1496
1497   private resolveGrouping(grouping: string, module: Module) {
1498     const collonInd = grouping.indexOf(":");
1499     const preFix = collonInd > -1 ? grouping.slice(0, collonInd) : "";
1500     const groupingName = collonInd > -1 ? grouping.slice(collonInd + 1) : grouping;
1501
1502     return preFix
1503       ? this._modules[module.imports[preFix]].groupings[groupingName]
1504       : module.groupings[groupingName];
1505
1506   }
1507
1508   private resolveIdentity(identity: string, module: Module) {
1509     const collonInd = identity.indexOf(":");
1510     const preFix = collonInd > -1 ? identity.slice(0, collonInd) : "";
1511     const identityName = collonInd > -1 ? identity.slice(collonInd + 1) : identity;
1512
1513     return preFix
1514       ? this._modules[module.imports[preFix]].identities[identityName]
1515       : module.identities[identityName];
1516
1517   }
1518 }