1 /* eslint-disable @typescript-eslint/no-loss-of-precision */
2 /* eslint-disable @typescript-eslint/no-unused-expressions */
3 /* eslint-disable @typescript-eslint/naming-convention */
5 * ============LICENSE_START========================================================================
6 * ONAP : ccsdk feature sdnr wt odlux
7 * =================================================================================================
8 * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
9 * =================================================================================================
10 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
11 * in compliance with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software distributed under the License
16 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
17 * or implied. See the License for the specific language governing permissions and limitations under
19 * ============LICENSE_END==========================================================================
21 import { Token, Statement, Module, Identity, ModuleState } from '../models/yang';
32 isViewElementObjectOrList,
38 } from '../models/uiModels';
39 import { yangService } from '../services/yangService';
41 const LOGLEVEL = +(localStorage.getItem('log.odlux.app.configuration.yang.yangParser') || 0);
43 import { LogLevel } from '../../../../framework/src/utilities/logLevel';
44 import { parseWhen, WhenAST, WhenTokenType } from './whenParser';
46 export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => {
47 const pathParts: RegExpMatchArray[] = [];
48 let partMatch: RegExpExecArray | null;
50 partMatch = vPathParser.exec(vPath);
52 pathParts.push(partMatch);
60 private pos: number = 0;
62 private buf: string = '';
64 constructor(input: string) {
69 private _opTable: { [key: string]: string } = {
75 private _isNewline(char: string): boolean {
76 return char === '\r' || char === '\n';
79 private _isWhitespace(char: string): boolean {
80 return char === ' ' || char === '\t' || this._isNewline(char);
83 private _isDigit(char: string): boolean {
84 return char >= '0' && char <= '9';
87 private _isAlpha(char: string): boolean {
88 return (char >= 'a' && char <= 'z') ||
89 (char >= 'A' && char <= 'Z');
92 private _isAlphanum(char: string): boolean {
93 return this._isAlpha(char) || this._isDigit(char) ||
94 char === '_' || char === '-' || char === '.';
97 private _skipNonTokens() {
98 while (this.pos < this.buf.length) {
99 const char = this.buf.charAt(this.pos);
100 if (this._isWhitespace(char)) {
108 private _processString(terminator: string | null): Token {
109 // this.pos points at the opening quote. Find the ending quote.
110 let end_index = this.pos + 1;
111 while (end_index < this.buf.length) {
112 const char = this.buf.charAt(end_index);
117 if (terminator === null && (this._isWhitespace(char) || this._opTable[char] !== undefined) || char === terminator) {
123 if (end_index >= this.buf.length) {
124 throw Error('Unterminated quote at ' + this.pos);
126 const start = this.pos + (terminator ? 1 : 0);
127 const end = end_index;
130 value: this.buf.substring(start, end),
134 this.pos = terminator ? end + 1 : end;
139 private _processIdentifier(): Token {
140 let endpos = this.pos + 1;
141 while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) {
145 let name = 'IDENTIFIER';
146 if (this.buf.charAt(endpos) === ':') {
147 name = 'IDENTIFIERREF';
149 while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) {
156 value: this.buf.substring(this.pos, endpos),
165 private _processNumber(): Token {
166 let endpos = this.pos + 1;
167 while (endpos < this.buf.length &&
168 this._isDigit(this.buf.charAt(endpos))) {
174 value: this.buf.substring(this.pos, endpos),
182 private _processLineComment() {
183 var endpos = this.pos + 2;
184 // Skip until the end of the line
185 while (endpos < this.buf.length && !this._isNewline(this.buf.charAt(endpos))) {
188 this.pos = endpos + 1;
191 private _processBlockComment() {
192 var endpos = this.pos + 2;
193 // Skip until the end of the line
194 while (endpos < this.buf.length && !((this.buf.charAt(endpos) === '/' && this.buf.charAt(endpos - 1) === '*'))) {
197 this.pos = endpos + 1;
200 public tokenize(): Token[] {
201 const result: Token[] = [];
202 this._skipNonTokens();
203 while (this.pos < this.buf.length) {
205 const char = this.buf.charAt(this.pos);
206 const op = this._opTable[char];
208 if (op !== undefined) {
209 result.push({ name: op, value: char, start: this.pos, end: ++this.pos });
210 } else if (this._isAlpha(char)) {
211 result.push(this._processIdentifier());
212 this._skipNonTokens();
213 const peekChar = this.buf.charAt(this.pos);
214 if (this._opTable[peekChar] === undefined) {
215 result.push((peekChar !== '\'' && peekChar !== '"')
216 ? this._processString(null)
217 : this._processString(peekChar));
219 } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') {
220 this._processLineComment();
221 } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') {
222 this._processBlockComment();
224 throw Error('Token error at ' + this.pos + ' ' + this.buf[this.pos]);
226 this._skipNonTokens();
231 public tokenize2(): Statement {
232 let stack: Statement[] = [{ key: 'ROOT', sub: [] }];
233 let current: Statement | null = null;
235 this._skipNonTokens();
236 while (this.pos < this.buf.length) {
238 const char = this.buf.charAt(this.pos);
239 const op = this._opTable[char];
241 if (op !== undefined) {
242 if (op === 'L_BRACE') {
243 current && stack.unshift(current);
245 } else if (op === 'R_BRACE') {
246 current = stack.shift() || null;
249 } else if (this._isAlpha(char) || char === '_') {
250 const key = this._processIdentifier().value;
251 this._skipNonTokens();
252 let peekChar = this.buf.charAt(this.pos);
254 if (this._opTable[peekChar] === undefined) {
255 arg = (peekChar === '"' || peekChar === '\'')
256 ? this._processString(peekChar).value
257 : this._processString(null).value;
260 this._skipNonTokens();
261 peekChar = this.buf.charAt(this.pos);
262 if (peekChar !== '+') break;
264 this._skipNonTokens();
265 peekChar = this.buf.charAt(this.pos);
266 arg += (peekChar === '"' || peekChar === '\'')
267 ? this._processString(peekChar).value
268 : this._processString(null).value;
270 current = { key, arg, sub: [] };
271 stack[0].sub!.push(current);
272 } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') {
273 this._processLineComment();
274 } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') {
275 this._processBlockComment();
277 throw Error('Token error at ' + this.pos + ' ' + this.buf.slice(this.pos - 10, this.pos + 10));
279 this._skipNonTokens();
281 if (stack[0].key !== 'ROOT' || !stack[0].sub![0]) {
282 throw new Error('Internal Perser Error');
284 return stack[0].sub![0];
288 export class YangParser {
289 private _groupingsToResolve: ViewSpecification[] = [];
291 private _typeRefToResolve: (() => void)[] = [];
293 private _identityToResolve: (() => void)[] = [];
295 private _unionsToResolve: (() => void)[] = [];
297 private _modulesToResolve: (() => void)[] = [];
299 private _modules: { [name: string]: Module } = {};
301 private _views: ViewSpecification[] = [{
312 public static ResolveStack = Symbol('ResolveStack');
315 private nodeId: string,
316 private _capabilityRevisionMap: { [capability: string]: string } = {},
317 private _unavailableCapabilities: { failureReason: string; capability: string }[] = [],
318 private _importOnlyModules: { name: string; revision: string }[] = [],
323 public get modules() {
324 return this._modules;
331 public async addCapability(capability: string, version?: string, parentImportOnlyModule?: boolean) {
333 const existingCapability = this._modules[capability];
334 const latestVersionExisting = existingCapability && Object.keys(existingCapability.revisions).sort().reverse()[0];
335 if ((latestVersionExisting && version) && (version <= latestVersionExisting)) {
336 if (LOGLEVEL == LogLevel.Warning) {
337 console.warn(`Skipped capability: ${capability}:${version || ''} since already contained.`);
342 // // do not add unavailable capabilities
343 // if (this._unavailableCapabilities.some(c => c.capability === capability)) {
344 // // console.warn(`Skipped capability: ${capability} since it is marked as unavailable.` );
348 const data = await yangService.getCapability(capability, this.nodeId, version);
350 throw new Error(`Could not load yang file for ${capability}:${version || ''}.`);
353 const rootStatement = new YangLexer(data).tokenize2();
355 if (rootStatement.key !== 'module') {
356 throw new Error(`Root element of ${capability} is not a module.`);
358 if (rootStatement.arg !== capability) {
359 throw new Error(`Root element capability ${rootStatement.arg} does not requested ${capability}.`);
362 const isUnavailable = this._unavailableCapabilities.some(c => c.capability === capability);
363 const isImportOnly = parentImportOnlyModule === true || this._importOnlyModules.some(c => c.name === capability);
366 const revisions = this.extractNodes(rootStatement, 'revision').reduce<{ [version: string]: {} }>((acc, revision) => {
368 throw new Error(`Module [${rootStatement.arg}] has a version w/o version number.`);
370 const description = this.extractValue(revision, 'description');
371 const reference = this.extractValue(revision, 'reference');
372 acc[revision.arg] = {
379 const latestVersionLoaded = Object.keys(revisions).sort().reverse()[0];
380 if (existingCapability && latestVersionExisting >= latestVersionLoaded) {
381 if (LOGLEVEL == LogLevel.Warning) {
382 console.warn(`Skipped capability: ${capability}:${latestVersionLoaded} since ${capability}:${latestVersionExisting} already contained.`);
387 const module = this._modules[capability] = {
388 name: rootStatement.arg,
399 ? ModuleState.unavailable
401 ? ModuleState.importOnly
402 : ModuleState.stable,
405 await this.handleModule(module, rootStatement, capability);
408 private async handleModule(module: Module, rootStatement: Statement, capability: string) {
410 // extract namespace && prefix
411 module.namespace = this.extractValue(rootStatement, 'namespace');
412 module.prefix = this.extractValue(rootStatement, 'prefix');
414 module.imports[module.prefix] = capability;
418 const features = this.extractNodes(rootStatement, 'feature');
421 ...features.reduce<{ [version: string]: {} }>((acc, feature) => {
423 throw new Error(`Module [${module.name}] has a feature w/o name.`);
425 const description = this.extractValue(feature, 'description');
434 const imports = this.extractNodes(rootStatement, 'import');
437 ...imports.reduce<{ [key: string]: string }>((acc, imp) => {
438 const prefix = imp.sub && imp.sub.filter(s => s.key === 'prefix');
440 throw new Error(`Module [${module.name}] has an import with neither name nor prefix.`);
442 acc[prefix && prefix.length === 1 && prefix[0].arg || imp.arg] = imp.arg;
447 // import all required files and set module state
448 if (imports) for (let ind = 0; ind < imports.length; ++ind) {
449 const moduleName = imports[ind].arg!;
451 const revision = this._capabilityRevisionMap[moduleName] || undefined;
452 await this.addCapability(moduleName, revision, module.state === ModuleState.importOnly);
453 const importedModule = this._modules[imports[ind].arg!];
454 if (importedModule && importedModule.state > ModuleState.stable) {
455 module.state = Math.max(module.state, ModuleState.instable);
459 this.extractTypeDefinitions(rootStatement, module, '');
461 this.extractIdentities(rootStatement, 0, module, '');
463 const groupings = this.extractGroupings(rootStatement, 0, module, '');
464 this._views.push(...groupings);
466 const augments = this.extractAugments(rootStatement, 0, module, '');
467 this._views.push(...augments);
469 // the default for config on module level is config = true;
470 const [currentView, subViews] = this.extractSubViews(rootStatement, 0, module, '');
471 this._views.push(currentView, ...subViews);
473 // create the root elements for this module
474 module.elements = currentView.elements;
475 this._modulesToResolve.push(() => {
476 Object.keys(module.elements).forEach(key => {
477 const viewElement = module.elements[key];
478 if (!(isViewElementObjectOrList(viewElement) || isViewElementRpc(viewElement))) {
479 console.error(new Error(`Module: [${module}]. Only Object, List or RPC are allowed on root level.`));
481 if (isViewElementObjectOrList(viewElement)) {
482 const viewIdIndex = Number(viewElement.viewId);
483 module.views[key] = this._views[viewIdIndex];
486 // add only the UI View if the module is available
487 if (module.state === ModuleState.stable || module.state === ModuleState.instable) this._views[0].elements[key] = module.elements[key];
493 public postProcess() {
494 // process all type refs
495 this._typeRefToResolve.forEach(cb => {
496 try { cb(); } catch (error) {
497 console.warn(error.message);
501 * This is to fix the issue for sequential execution of modules based on their child and parent relationship
502 * We are sorting the module object based on their augment status
504 Object.keys(this.modules)
506 if (this.modules[elem].augments && Object.keys(this.modules[elem].augments).length > 0) {
507 const { augments, ..._rest } = this.modules[elem];
508 const partsOfKeys = Object.keys(augments).map((key) => (key.split('/').length - 1));
509 this.modules[elem].executionOrder = Math.max(...partsOfKeys);
511 this.modules[elem].executionOrder = 0;
515 // process all augmentations / sort by namespace changes to ensure proper order
516 Object.keys(this.modules).sort((a, b) => this.modules[a].executionOrder! - this.modules[b].executionOrder!).forEach(modKey => {
517 const module = this.modules[modKey];
518 const augmentKeysWithCounter = Object.keys(module.augments).map((key) => {
519 const pathParts = splitVPath(key, /(?:(?:([^\/\:]+):)?([^\/]+))/g); // 1 = opt: namespace / 2 = property
520 let nameSpaceChangeCounter = 0;
521 let currentNS = module.name; // init namespace
522 pathParts.forEach(([ns, _]) => {
523 if (ns === currentNS) {
525 nameSpaceChangeCounter++;
530 nameSpaceChangeCounter,
534 const augmentKeys = augmentKeysWithCounter
535 .sort((a, b) => a.nameSpaceChangeCounter > b.nameSpaceChangeCounter ? 1 : a.nameSpaceChangeCounter === b.nameSpaceChangeCounter ? 0 : -1)
538 augmentKeys.forEach(augKey => {
539 const augments = module.augments[augKey];
540 const viewSpec = this.resolveView(augKey);
541 if (!viewSpec) console.warn(`Could not find view to augment [${augKey}] in [${module.name}].`);
542 if (augments && viewSpec) {
543 augments.forEach(augment => Object.keys(augment.elements).forEach(key => {
544 const elm = augment.elements[key];
546 const when = elm.when && augment.when
548 type: WhenTokenType.AND,
552 : elm.when || augment.when;
554 const ifFeature = elm.ifFeature
555 ? `(${augment.ifFeature}) and (${elm.ifFeature})`
558 viewSpec.elements[key] = {
559 ...augment.elements[key],
568 // process Identities
569 const traverseIdentity = (identities: Identity[]) => {
570 const result: Identity[] = [];
571 for (let identity of identities) {
572 if (identity.children && identity.children.length > 0) {
573 result.push(...traverseIdentity(identity.children));
575 result.push(identity);
581 const baseIdentities: Identity[] = [];
582 Object.keys(this.modules).forEach(modKey => {
583 const module = this.modules[modKey];
584 Object.keys(module.identities).forEach(idKey => {
585 const identity = module.identities[idKey];
586 if (identity.base != null) {
587 const base = this.resolveIdentity(identity.base, module);
588 base?.children?.push(identity);
590 baseIdentities.push(identity);
594 baseIdentities.forEach(identity => {
595 identity.values = identity.children && traverseIdentity(identity.children) || [];
598 this._identityToResolve.forEach(cb => {
599 try { cb(); } catch (error) {
600 console.warn(error.message);
604 this._modulesToResolve.forEach(cb => {
605 try { cb(); } catch (error) {
606 console.warn(error.message);
610 // execute all post processes like resolving in proper order
611 this._unionsToResolve.forEach(cb => {
612 try { cb(); } catch (error) {
613 console.warn(error.message);
617 // process all groupings
618 this._groupingsToResolve.filter(vs => vs.uses && vs.uses[ResolveFunction]).forEach(vs => {
619 try { vs.uses![ResolveFunction] !== undefined && vs.uses![ResolveFunction]!('|'); } catch (error) {
620 console.warn(`Error resolving: [${vs.name}] [${error.message}]`);
624 const knownViews: ViewSpecification[] = [];
626 const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => {
627 if (knownViews.includes(view)) return;
628 knownViews.push(view);
630 // update view config
631 view.config = view.config && parentConfig;
633 Object.keys(view.elements).forEach((key) => {
634 const elm = view.elements[key];
636 // update element config
637 elm.config = elm.config && view.config;
639 // update all sub-elements of objects
640 if (elm.uiType === 'object') {
641 resolveReadOnly(this.views[+elm.viewId], elm.config);
647 const dump = resolveReadOnly(this.views[0], true);
649 console.log('Resolved views:', dump);
655 private get nextId() {
656 return this._nextId++;
659 private extractNodes(statement: Statement, key: string): Statement[] {
660 return statement.sub && statement.sub.filter(s => s.key === key) || [];
663 private extractValue(statement: Statement, key: string): string | undefined;
664 private extractValue(statement: Statement, key: string, parser: RegExp): RegExpExecArray | undefined;
665 private extractValue(statement: Statement, key: string, parser?: RegExp): string | RegExpExecArray | undefined {
666 const typeNodes = this.extractNodes(statement, key);
667 const rawValue = typeNodes.length > 0 && typeNodes[0].arg || undefined;
669 ? rawValue && parser.exec(rawValue) || undefined
673 private extractTypeDefinitions(statement: Statement, module: Module, currentPath: string): void {
674 const typedefs = this.extractNodes(statement, 'typedef');
675 typedefs && typedefs.forEach(def => {
677 throw new Error(`Module: [${module.name}]. Found typedef without name.`);
679 module.typedefs[def.arg] = this.getViewElement(def, module, 0, currentPath, false);
683 /** Handles groupings like named Container */
684 private extractGroupings(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
685 const subViews: ViewSpecification[] = [];
686 const groupings = this.extractNodes(statement, 'grouping');
687 if (groupings && groupings.length > 0) {
688 subViews.push(...groupings.reduce<ViewSpecification[]>((acc, cur) => {
690 throw new Error(`Module: [${module.name}][${currentPath}]. Found grouping without name.`);
692 const grouping = cur.arg;
694 // the default for config on module level is config = true;
695 const [currentView, currentSubViews] = this.extractSubViews(cur, /* parentId */ -1, module, currentPath);
696 grouping && (module.groupings[grouping] = currentView);
697 acc.push(currentView, ...currentSubViews);
705 /** Handles augments also like named container */
706 private extractAugments(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
707 const subViews: ViewSpecification[] = [];
708 const augments = this.extractNodes(statement, 'augment');
709 if (augments && augments.length > 0) {
710 subViews.push(...augments.reduce<ViewSpecification[]>((acc, cur) => {
712 throw new Error(`Module: [${module.name}][${currentPath}]. Found augment without path.`);
714 const augment = this.resolveReferencePath(cur.arg, module);
716 // the default for config on module level is config = true;
717 const [currentView, currentSubViews] = this.extractSubViews(cur, parentId, module, currentPath);
719 module.augments[augment] = module.augments[augment] || [];
720 module.augments[augment].push(currentView);
722 acc.push(currentView, ...currentSubViews);
730 /** Handles identities */
731 private extractIdentities(statement: Statement, parentId: number, module: Module, currentPath: string) {
732 const identities = this.extractNodes(statement, 'identity');
733 module.identities = identities.reduce<{ [name: string]: Identity }>((acc, cur) => {
735 throw new Error(`Module: [${module.name}][${currentPath}]. Found identity without name.`);
738 id: `${module.name}:${cur.arg}`,
740 base: this.extractValue(cur, 'base'),
741 description: this.extractValue(cur, 'description'),
742 reference: this.extractValue(cur, 'reference'),
749 // Hint: use 0 as parentId for rootElements and -1 for rootGroupings.
750 private extractSubViews(statement: Statement, parentId: number, module: Module, currentPath: string): [ViewSpecification, ViewSpecification[]] {
751 // used for scoped definitions
752 const context: Module = {
759 const currentId = this.nextId;
760 const subViews: ViewSpecification[] = [];
761 let elements: ViewElement[] = [];
763 const configValue = this.extractValue(statement, 'config');
764 const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false';
766 // extract conditions
767 const ifFeature = this.extractValue(statement, 'if-feature');
768 const whenCondition = this.extractValue(statement, 'when');
769 if (whenCondition) console.warn('Found in [' + context.name + ']' + currentPath + ' when: ' + whenCondition);
771 // extract all scoped typedefs
772 this.extractTypeDefinitions(statement, context, currentPath);
774 // extract all scoped groupings
776 ...this.extractGroupings(statement, parentId, context, currentPath),
779 // extract all container
780 const container = this.extractNodes(statement, 'container');
781 if (container && container.length > 0) {
782 subViews.push(...container.reduce<ViewSpecification[]>((acc, cur) => {
784 throw new Error(`Module: [${context.name}]${currentPath}. Found container without name.`);
786 const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
788 id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
791 module: context.name || module.name || '',
793 viewId: currentView.id,
794 config: currentView.config,
796 acc.push(currentView, ...currentSubViews);
802 // a list is a list of containers with the leafs contained in the list
803 const lists = this.extractNodes(statement, 'list');
804 if (lists && lists.length > 0) {
805 subViews.push(...lists.reduce<ViewSpecification[]>((acc, cur) => {
806 let elmConfig = config;
808 throw new Error(`Module: [${context.name}]${currentPath}. Found list without name.`);
810 const key = this.extractValue(cur, 'key') || undefined;
811 if (elmConfig && !key) {
812 console.warn(`Module: [${context.name}]${currentPath}. Found configurable list without key. Assume config shell be false.`);
815 const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
817 id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
820 module: context.name || module.name || '',
823 viewId: currentView.id,
825 config: elmConfig && currentView.config,
827 acc.push(currentView, ...currentSubViews);
832 // process all leaf-lists
833 // a leaf-list is a list of some type
834 const leafLists = this.extractNodes(statement, 'leaf-list');
835 if (leafLists && leafLists.length > 0) {
836 elements.push(...leafLists.reduce<ViewElement[]>((acc, cur) => {
837 const element = this.getViewElement(cur, context, parentId, currentPath, true);
838 element && acc.push(element);
844 // a leaf is mainly a property of an object
845 const leafs = this.extractNodes(statement, 'leaf');
846 if (leafs && leafs.length > 0) {
847 elements.push(...leafs.reduce<ViewElement[]>((acc, cur) => {
848 const element = this.getViewElement(cur, context, parentId, currentPath, false);
849 element && acc.push(element);
855 const choiceStms = this.extractNodes(statement, 'choice');
856 if (choiceStms && choiceStms.length > 0) {
857 elements.push(...choiceStms.reduce<ViewElementChoice[]>((accChoice, curChoice) => {
858 if (!curChoice.arg) {
859 throw new Error(`Module: [${context.name}]${currentPath}. Found choise without name.`);
861 // extract all cases like containers
862 const cases: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[] = [];
863 const caseStms = this.extractNodes(curChoice, 'case');
864 if (caseStms && caseStms.length > 0) {
865 cases.push(...caseStms.reduce((accCase, curCase) => {
867 throw new Error(`Module: [${context.name}]${currentPath}/${curChoice.arg}. Found case without name.`);
869 const description = this.extractValue(curCase, 'description') || undefined;
870 const [caseView, caseSubViews] = this.extractSubViews(curCase, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`);
871 subViews.push(caseView, ...caseSubViews);
873 const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = {
874 id: parentId === 0 ? `${context.name}:${curCase.arg}` : curCase.arg,
876 description: description,
877 elements: caseView.elements,
879 accCase.push(caseDef);
881 }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[]));
884 // extract all simple cases (one case per leaf, container, etc.)
885 const [choiceView, choiceSubViews] = this.extractSubViews(curChoice, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`);
886 subViews.push(choiceView, ...choiceSubViews);
887 cases.push(...Object.keys(choiceView.elements).reduce((accElm, curElm) => {
888 const elm = choiceView.elements[curElm];
889 const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = {
892 description: elm.description,
893 elements: { [elm.id]: elm },
895 accElm.push(caseDef);
897 }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[]));
899 const choiceDescription = this.extractValue(curChoice, 'description') || undefined;
900 const choiceConfigValue = this.extractValue(curChoice, 'config');
901 const choiceConfig = choiceConfigValue == null ? true : choiceConfigValue.toLocaleLowerCase() !== 'false';
903 const mandatory = this.extractValue(curChoice, 'mandatory') === 'true' || false;
905 const element: ViewElementChoice = {
907 id: parentId === 0 ? `${context.name}:${curChoice.arg}` : curChoice.arg,
908 label: curChoice.arg,
910 module: context.name || module.name || '',
911 config: choiceConfig,
912 mandatory: mandatory,
913 description: choiceDescription,
914 cases: cases.reduce((acc, cur) => {
917 }, {} as { [name: string]: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } }),
920 accChoice.push(element);
925 const rpcStms = this.extractNodes(statement, 'rpc');
926 if (rpcStms && rpcStms.length > 0) {
927 elements.push(...rpcStms.reduce<ViewElementRpc[]>((accRpc, curRpc) => {
929 throw new Error(`Module: [${context.name}]${currentPath}. Found rpc without name.`);
932 const rpcDescription = this.extractValue(curRpc, 'description') || undefined;
933 const rpcConfigValue = this.extractValue(curRpc, 'config');
934 const rpcConfig = rpcConfigValue == null ? true : rpcConfigValue.toLocaleLowerCase() !== 'false';
936 let inputViewId: string | undefined = undefined;
937 let outputViewId: string | undefined = undefined;
939 const input = this.extractNodes(curRpc, 'input') || undefined;
940 const output = this.extractNodes(curRpc, 'output') || undefined;
942 if (input && input.length > 0) {
943 const [inputView, inputSubViews] = this.extractSubViews(input[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`);
944 subViews.push(inputView, ...inputSubViews);
945 inputViewId = inputView.id;
948 if (output && output.length > 0) {
949 const [outputView, outputSubViews] = this.extractSubViews(output[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`);
950 subViews.push(outputView, ...outputSubViews);
951 outputViewId = outputView.id;
954 const element: ViewElementRpc = {
956 id: parentId === 0 ? `${context.name}:${curRpc.arg}` : curRpc.arg,
959 module: context.name || module.name || '',
961 description: rpcDescription,
962 inputViewId: inputViewId,
963 outputViewId: outputViewId,
966 accRpc.push(element);
972 if (!statement.arg) {
973 console.error(new Error(`Module: [${context.name}]. Found statement without name.`));
976 let whenParsed: WhenAST | undefined = undefined;
978 whenParsed = whenCondition && parseWhen(whenCondition) || undefined;
980 console.error(new Error(`Module: [${context.name}]. Found invalid when condition: ${whenCondition}`));
983 const viewSpec: ViewSpecification = {
984 id: String(currentId),
985 parentView: String(parentId),
987 name: statement.arg != null ? statement.arg : undefined,
988 title: statement.arg != null ? statement.arg : undefined,
992 ifFeature: ifFeature,
994 elements: elements.reduce<{ [name: string]: ViewElement }>((acc, cur) => {
1000 // evaluate canEdit depending on all conditions
1001 Object.defineProperty(viewSpec, 'canEdit', {
1003 return Object.keys(viewSpec.elements).some(key => {
1004 const elm = viewSpec.elements[key];
1005 return (!isViewElementObjectOrList(elm) && elm.config);
1010 // merge in all uses references and resolve groupings
1011 const usesRefs = this.extractNodes(statement, 'uses');
1012 if (usesRefs && usesRefs.length > 0) {
1014 viewSpec.uses = (viewSpec.uses || []);
1015 const resolveFunctions: ((parentElementPath: string) => void)[] = [];
1017 for (let i = 0; i < usesRefs.length; ++i) {
1018 const groupingName = usesRefs[i].arg;
1019 if (!groupingName) {
1020 throw new Error(`Module: [${context.name}]. Found an uses statement without a grouping name.`);
1023 viewSpec.uses.push(this.resolveReferencePath(groupingName, context));
1025 resolveFunctions.push((parentElementPath: string) => {
1026 const groupingViewSpec = this.resolveGrouping(groupingName, context);
1027 if (groupingViewSpec) {
1029 // resolve recursive
1030 const resolveFunc = groupingViewSpec.uses && groupingViewSpec.uses[ResolveFunction];
1031 resolveFunc && resolveFunc(parentElementPath);
1033 Object.keys(groupingViewSpec.elements).forEach(key => {
1034 const elm = groupingViewSpec.elements[key];
1035 // a useRef on root level need a namespace
1036 const resolvedWhen = elm.when && groupingViewSpec.when
1038 type: WhenTokenType.AND,
1040 right: groupingViewSpec.when,
1042 : elm.when || groupingViewSpec.when;
1044 const resolvedIfFeature = elm.ifFeature
1045 ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})`
1046 : groupingViewSpec.ifFeature;
1048 viewSpec.elements[parentId === 0 ? `${module.name}:${key}` : key] = {
1051 ifFeature: resolvedIfFeature,
1058 viewSpec.uses[ResolveFunction] = (parentElementPath: string) => {
1059 const currentElementPath = `${parentElementPath} -> ${viewSpec.ns}:${viewSpec.name}`;
1060 resolveFunctions.forEach(resolve => {
1062 resolve(currentElementPath);
1064 console.error(error);
1067 // console.log("Resolved "+currentElementPath, viewSpec);
1068 if (viewSpec?.uses) {
1069 viewSpec.uses[ResolveFunction] = undefined;
1073 this._groupingsToResolve.push(viewSpec);
1076 return [viewSpec, subViews];
1079 // https://tools.ietf.org/html/rfc7950#section-9.3.4
1080 private static decimalRange = [
1081 { min: -9223372036854775808, max: 9223372036854775807 },
1082 { min: -922337203685477580.8, max: 922337203685477580.7 },
1083 { min: -92233720368547758.08, max: 92233720368547758.07 },
1084 { min: -9223372036854775.808, max: 9223372036854775.807 },
1085 { min: -922337203685477.5808, max: 922337203685477.5807 },
1086 { min: -92233720368547.75808, max: 92233720368547.75807 },
1087 { min: -9223372036854.775808, max: 9223372036854.775807 },
1088 { min: -922337203685.4775808, max: 922337203685.4775807 },
1089 { min: -92233720368.54775808, max: 92233720368.54775807 },
1090 { min: -9223372036.854775808, max: 9223372036.854775807 },
1091 { min: -922337203.6854775808, max: 922337203.6854775807 },
1092 { min: -92233720.36854775808, max: 92233720.36854775807 },
1093 { min: -9223372.036854775808, max: 9223372.036854775807 },
1094 { min: -922337.2036854775808, max: 922337.2036854775807 },
1095 { min: -92233.72036854775808, max: 92233.72036854775807 },
1096 { min: -9223.372036854775808, max: 9223.372036854775807 },
1097 { min: -922.3372036854775808, max: 922.3372036854775807 },
1098 { min: -92.23372036854775808, max: 92.23372036854775807 },
1099 { min: -9.223372036854775808, max: 9.223372036854775807 },
1102 /** Extracts the UI View from the type in the cur statement. */
1103 private getViewElement(cur: Statement, module: Module, parentId: number, currentPath: string, isList: boolean): ViewElement {
1105 const type = this.extractValue(cur, 'type');
1106 const defaultVal = this.extractValue(cur, 'default') || undefined;
1107 const description = this.extractValue(cur, 'description') || undefined;
1109 const configValue = this.extractValue(cur, 'config');
1110 const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false';
1112 const extractRange = (min: number, max: number, property: string = 'range'): { expression: Expression<YangRange> | undefined; min: number; max: number } => {
1113 const ranges = this.extractValue(this.extractNodes(cur, 'type')[0]!, property) || undefined;
1114 const range = ranges?.replace(/min/i, String(min)).replace(/max/i, String(max)).split('|').map(r => {
1115 let minValue: number;
1116 let maxValue: number;
1118 if (r.indexOf('..') > -1) {
1119 const [minStr, maxStr] = r.split('..');
1120 minValue = Number(minStr);
1121 maxValue = Number(maxStr);
1122 } else if (!isNaN(maxValue = Number(r && r.trim()))) {
1123 minValue = maxValue;
1129 if (minValue > min) min = minValue;
1130 if (maxValue < max) max = maxValue;
1140 expression: range && range.length === 1
1142 : range && range.length > 1
1143 ? { operation: 'OR', arguments: range }
1148 const extractPattern = (): Expression<RegExp> | undefined => {
1149 // 2023.01.26 decision MF & SKO: we will no longer remove the backslashes from the pattern, seems to be a bug in the original code
1150 const pattern = this.extractNodes(this.extractNodes(cur, 'type')[0]!, 'pattern').map(p => p.arg!).filter(p => !!p).map(p => `^${p/*.replace(/(?:\\(.))/g, '$1')*/}$`);
1151 return pattern && pattern.length == 1
1152 ? new RegExp(pattern[0])
1153 : pattern && pattern.length > 1
1154 ? { operation: 'AND', arguments: pattern.map(p => new RegExp(p)) }
1158 const mandatory = this.extractValue(cur, 'mandatory') === 'true' || false;
1161 throw new Error(`Module: [${module.name}]. Found element without name.`);
1165 throw new Error(`Module: [${module.name}].[${cur.arg}]. Found element without type.`);
1168 const element: ViewElementBase = {
1169 id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg,
1172 module: module.name || '',
1174 mandatory: mandatory,
1176 default: defaultVal,
1177 description: description,
1180 if (type === 'string') {
1181 const length = extractRange(0, +18446744073709551615, 'length');
1185 length: length.expression,
1186 pattern: extractPattern(),
1188 } else if (type === 'boolean') {
1193 } else if (type === 'uint8') {
1194 const range = extractRange(0, +255);
1198 range: range.expression,
1201 units: this.extractValue(cur, 'units') || undefined,
1202 format: this.extractValue(cur, 'format') || undefined,
1204 } else if (type === 'uint16') {
1205 const range = extractRange(0, +65535);
1209 range: range.expression,
1212 units: this.extractValue(cur, 'units') || undefined,
1213 format: this.extractValue(cur, 'format') || undefined,
1215 } else if (type === 'uint32') {
1216 const range = extractRange(0, +4294967295);
1220 range: range.expression,
1223 units: this.extractValue(cur, 'units') || undefined,
1224 format: this.extractValue(cur, 'format') || undefined,
1226 } else if (type === 'uint64') {
1227 const range = extractRange(0, +18446744073709551615);
1231 range: range.expression,
1234 units: this.extractValue(cur, 'units') || undefined,
1235 format: this.extractValue(cur, 'format') || undefined,
1237 } else if (type === 'int8') {
1238 const range = extractRange(-128, +127);
1242 range: range.expression,
1245 units: this.extractValue(cur, 'units') || undefined,
1246 format: this.extractValue(cur, 'format') || undefined,
1248 } else if (type === 'int16') {
1249 const range = extractRange(-32768, +32767);
1253 range: range.expression,
1256 units: this.extractValue(cur, 'units') || undefined,
1257 format: this.extractValue(cur, 'format') || undefined,
1259 } else if (type === 'int32') {
1260 const range = extractRange(-2147483648, +2147483647);
1264 range: range.expression,
1267 units: this.extractValue(cur, 'units') || undefined,
1268 format: this.extractValue(cur, 'format') || undefined,
1270 } else if (type === 'int64') {
1271 const range = extractRange(-9223372036854775808, +9223372036854775807);
1275 range: range.expression,
1278 units: this.extractValue(cur, 'units') || undefined,
1279 format: this.extractValue(cur, 'format') || undefined,
1281 } else if (type === 'decimal64') {
1283 const fDigits = Number(this.extractValue(this.extractNodes(cur, 'type')[0]!, 'fraction-digits')) || -1;
1284 if (fDigits === -1) {
1285 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found decimal64 with invalid fraction-digits.`);
1287 const range = extractRange(YangParser.decimalRange[fDigits].min, YangParser.decimalRange[fDigits].max);
1292 range: range.expression,
1295 units: this.extractValue(cur, 'units') || undefined,
1296 format: this.extractValue(cur, 'format') || undefined,
1298 } else if (type === 'enumeration') {
1299 const typeNode = this.extractNodes(cur, 'type')[0]!;
1300 const enumNodes = this.extractNodes(typeNode, 'enum');
1303 uiType: 'selection',
1304 options: enumNodes.reduce<{ key: string; value: string; description?: string }[]>((acc, enumNode) => {
1305 if (!enumNode.arg) {
1306 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found option without name.`);
1308 // const ifClause = this.extractValue(enumNode, 'if-feature');
1309 const value = this.extractValue(enumNode, 'value');
1310 const enumOption = {
1312 value: value != null ? value : enumNode.arg,
1313 description: this.extractValue(enumNode, 'description') || undefined,
1315 // todo: ❗ handle the if clause ⚡
1316 acc.push(enumOption);
1320 } else if (type === 'leafref') {
1321 const typeNode = this.extractNodes(cur, 'type')[0]!;
1322 const vPath = this.extractValue(typeNode, 'path');
1324 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found leafref without path.`);
1326 const refPath = this.resolveReferencePath(vPath, module);
1327 const resolve = this.resolveReference.bind(this);
1328 const res: ViewElement = {
1330 uiType: 'reference',
1331 referencePath: refPath,
1332 ref(this: ViewElement, basePath: string) {
1333 const elementPath = `${basePath}/${cur.arg}`;
1335 const result = resolve(refPath, elementPath);
1336 if (!result) return undefined;
1338 const [resolvedElement, resolvedPath] = result;
1339 return resolvedElement && [{
1343 config: this.config,
1344 mandatory: this.mandatory,
1345 isList: this.isList,
1346 default: this.default,
1347 description: this.description,
1348 } as ViewElement, resolvedPath] || undefined;
1352 } else if (type === 'identityref') {
1353 const typeNode = this.extractNodes(cur, 'type')[0]!;
1354 const base = this.extractValue(typeNode, 'base');
1356 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found identityref without base.`);
1358 const res: ViewElement = {
1360 uiType: 'selection',
1363 this._identityToResolve.push(() => {
1364 const identity: Identity = this.resolveIdentity(base, module);
1366 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Could not resolve identity [${base}].`);
1368 if (!identity.values || identity.values.length === 0) {
1369 throw new Error(`Identity: [${base}] has no values.`);
1371 res.options = identity.values.map(val => ({
1374 description: val.description,
1378 } else if (type === 'empty') {
1379 // todo: ❗ handle empty ⚡
1380 /* 9.11. The empty Built-In Type
1381 The empty built-in type represents a leaf that does not have any
1382 value, it conveys information by its presence or absence. */
1387 } else if (type === 'union') {
1388 // todo: ❗ handle union ⚡
1389 /* 9.12. The union Built-In Type */
1390 const typeNode = this.extractNodes(cur, 'type')[0]!;
1391 const typeNodes = this.extractNodes(typeNode, 'type');
1393 const resultingElement = {
1397 } as ViewElementUnion;
1399 const resolveUnion = () => {
1400 resultingElement.elements.push(...typeNodes.map(node => {
1401 const stm: Statement = {
1404 ...(cur.sub?.filter(s => s.key !== 'type') || []),
1409 ...this.getViewElement(stm, module, parentId, currentPath, isList),
1415 this._unionsToResolve.push(resolveUnion);
1417 return resultingElement;
1418 } else if (type === 'bits') {
1419 const typeNode = this.extractNodes(cur, 'type')[0]!;
1420 const bitNodes = this.extractNodes(typeNode, 'bit');
1424 flags: bitNodes.reduce<{ [name: string]: number | undefined }>((acc, bitNode) => {
1426 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found bit without name.`);
1428 // const ifClause = this.extractValue(bitNode, 'if-feature');
1429 const pos = Number(this.extractValue(bitNode, 'position'));
1430 acc[bitNode.arg] = pos === pos ? pos : undefined;
1434 } else if (type === 'binary') {
1438 length: extractRange(0, +18446744073709551615, 'length'),
1440 } else if (type === 'instance-identifier') {
1441 // https://tools.ietf.org/html/rfc7950#page-168
1445 length: extractRange(0, +18446744073709551615, 'length'),
1448 // not a build in type, need to resolve type
1449 let typeRef = this.resolveType(type, module);
1450 if (typeRef == null) console.error(new Error(`Could not resolve type ${type} in [${module.name}][${currentPath}].`));
1452 if (isViewElementString(typeRef)) {
1453 typeRef = this.resolveStringType(typeRef, extractPattern(), extractRange(0, +18446744073709551615));
1454 } else if (isViewElementNumber(typeRef)) {
1455 typeRef = this.resolveNumberType(typeRef, extractRange(typeRef.min, typeRef.max));
1462 this._typeRefToResolve.push(() => {
1463 // spoof date type here from special string type
1464 if ((type === 'date-and-time' || type.endsWith(':date-and-time')) && typeRef.module === 'ietf-yang-types') {
1465 Object.assign(res, {
1468 description: description,
1472 Object.assign(res, {
1475 description: description,
1484 private resolveStringType(parentElement: ViewElementString, pattern: Expression<RegExp> | undefined, length: { expression: Expression<YangRange> | undefined; min: number; max: number }) {
1487 pattern: pattern != null && parentElement.pattern
1488 ? { operation: 'AND', arguments: [pattern, parentElement.pattern] }
1489 : parentElement.pattern
1490 ? parentElement.pattern
1492 length: length.expression != null && parentElement.length
1493 ? { operation: 'AND', arguments: [length.expression, parentElement.length] }
1494 : parentElement.length
1495 ? parentElement.length
1496 : length?.expression,
1497 } as ViewElementString;
1500 private resolveNumberType(parentElement: ViewElementNumber, range: { expression: Expression<YangRange> | undefined; min: number; max: number }) {
1503 range: range.expression != null && parentElement.range
1504 ? { operation: 'AND', arguments: [range.expression, parentElement.range] }
1505 : parentElement.range
1506 ? parentElement.range
1510 } as ViewElementNumber;
1513 private resolveReferencePath(vPath: string, module: Module) {
1514 const vPathParser = /(?:(?:([^\/\:]+):)?([^\/]+))/g; // 1 = opt: namespace / 2 = property
1515 return vPath.replace(vPathParser, (_, ns, property) => {
1516 const nameSpace = ns && module.imports[ns] || module.name;
1517 return `${nameSpace}:${property}`;
1521 private resolveReference(vPath: string, currentPath: string) {
1522 const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
1523 let element: ViewElement | null = null;
1524 let moduleName = '';
1526 const vPathParts = splitVPath(vPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] }));
1527 const resultPathParts = !vPath.startsWith('/')
1528 ? splitVPath(currentPath, vPathParser).map(p => { moduleName = p[1] || moduleName; return { ns: moduleName, property: p[2], ind: p[3] }; })
1531 for (let i = 0; i < vPathParts.length; ++i) {
1532 const vPathPart = vPathParts[i];
1533 if (vPathPart.property === '..') {
1534 resultPathParts.pop();
1535 } else if (vPathPart.property !== '.') {
1536 resultPathParts.push(vPathPart);
1540 // resolve element by path
1541 for (let j = 0; j < resultPathParts.length; ++j) {
1542 const pathPart = resultPathParts[j];
1544 moduleName = pathPart.ns;
1545 const rootModule = this._modules[moduleName];
1546 if (!rootModule) throw new Error('Could not resolve module [' + moduleName + '].\r\n' + vPath);
1547 element = rootModule.elements[`${pathPart.ns}:${pathPart.property}`];
1548 } else if (element && isViewElementObjectOrList(element)) {
1549 const view: ViewSpecification = this._views[+element.viewId];
1550 if (moduleName !== pathPart.ns) {
1551 moduleName = pathPart.ns;
1553 element = view.elements[pathPart.property] || view.elements[`${moduleName}:${pathPart.property}`];
1555 throw new Error('Could not resolve reference.\r\n' + vPath);
1557 if (!element) throw new Error('Could not resolve path [' + pathPart.property + '] in [' + currentPath + '] \r\n' + vPath);
1560 moduleName = ''; // create the vPath for the resolved element, do not add the element itself this will be done later in the res(...) function
1561 return [element, resultPathParts.slice(0, -1).map(p => `${moduleName !== p.ns ? `${moduleName = p.ns}:` : ''}${p.property}${p.ind || ''}`).join('/')];
1564 private resolveView(vPath: string) {
1565 const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
1566 let element: ViewElement | null = null;
1567 let partMatch: RegExpExecArray | null;
1568 let view: ViewSpecification | null = null;
1569 let moduleName = '';
1571 partMatch = vPathParser.exec(vPath);
1573 if (element === null) {
1574 moduleName = partMatch[1]!;
1575 const rootModule = this._modules[moduleName];
1576 if (!rootModule) return null;
1577 element = rootModule.elements[`${moduleName}:${partMatch[2]!}`];
1578 } else if (isViewElementObjectOrList(element)) {
1579 view = this._views[+element.viewId];
1580 if (moduleName !== partMatch[1]) {
1581 moduleName = partMatch[1];
1582 element = view.elements[`${moduleName}:${partMatch[2]}`];
1584 element = view.elements[partMatch[2]];
1589 if (!element) return null;
1591 } while (partMatch);
1592 return element && isViewElementObjectOrList(element) && this._views[+element.viewId] || null;
1595 private resolveType(type: string, module: Module) {
1596 const colonInd = type.indexOf(':');
1597 const preFix = colonInd > -1 ? type.slice(0, colonInd) : '';
1598 const typeName = colonInd > -1 ? type.slice(colonInd + 1) : type;
1601 ? this._modules[module.imports[preFix]].typedefs[typeName]
1602 : module.typedefs[typeName];
1606 private resolveGrouping(grouping: string, module: Module) {
1607 const collonInd = grouping.indexOf(':');
1608 const preFix = collonInd > -1 ? grouping.slice(0, collonInd) : '';
1609 const groupingName = collonInd > -1 ? grouping.slice(collonInd + 1) : grouping;
1612 ? this._modules[module.imports[preFix]].groupings[groupingName]
1613 : module.groupings[groupingName];
1617 private resolveIdentity(identity: string, module: Module) {
1618 const collonInd = identity.indexOf(':');
1619 const preFix = collonInd > -1 ? identity.slice(0, collonInd) : '';
1620 const identityName = collonInd > -1 ? identity.slice(collonInd + 1) : identity;
1623 ? this._modules[module.imports[preFix]].identities[identityName]
1624 : module.identities[identityName];