summaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/apps/configurationApp/src/yang
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wt/odlux/apps/configurationApp/src/yang')
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/yang/whenParser.ts235
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts844
2 files changed, 692 insertions, 387 deletions
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/yang/whenParser.ts b/sdnr/wt/odlux/apps/configurationApp/src/yang/whenParser.ts
new file mode 100644
index 000000000..fa2968c9c
--- /dev/null
+++ b/sdnr/wt/odlux/apps/configurationApp/src/yang/whenParser.ts
@@ -0,0 +1,235 @@
+enum WhenTokenType {
+ AND = 'AND',
+ OR = 'OR',
+ NOT = 'NOT',
+ EQUALS = 'EQUALS',
+ COMMA = 'COMMA',
+ STRING = 'STRING',
+ FUNCTION = 'FUNCTION',
+ IDENTIFIER = 'IDENTIFIER',
+ OPEN_PAREN = 'OPEN_PAREN',
+ CLOSE_PAREN = 'CLOSE_PAREN',
+ EXPRESSION = 'EXPRESSION',
+}
+
+type Token = {
+ type: WhenTokenType;
+ value: string;
+};
+
+const isAlpha = (char: string) => /[a-z]/i.test(char);
+
+const isAlphaNumeric = (char: string) => /[A-Za-z0-9_\-/:\.]/i.test(char);
+
+const lex = (input: string) : Token[] => {
+ let tokens = [] as any[];
+ let current = 0;
+
+ while (current < input.length) {
+ let char = input[current];
+
+ if (char === ' ') {
+ current++;
+ continue;
+ }
+
+ if (char === '(') {
+ tokens.push({ type: WhenTokenType.OPEN_PAREN, value: char });
+ current++;
+ continue;
+ }
+
+ if (char === ')') {
+ tokens.push({ type: WhenTokenType.CLOSE_PAREN, value: char });
+ current++;
+ continue;
+ }
+
+ if (char === '=') {
+ tokens.push({ type: WhenTokenType.EQUALS, value: char });
+ current++;
+ continue;
+ }
+
+ if (char === ',') {
+ tokens.push({ type: WhenTokenType.COMMA, value: char });
+ current++;
+ continue;
+ }
+
+ if (char === '\"' || char === '\'') {
+ let value = '';
+ let start = current;
+ current++;
+
+ while (current < input.length) {
+ let innerChar = input[current];
+ if (innerChar === '\\') {
+ value += input[current] + input[current + 1];
+ current += 2;
+ } else if (innerChar === input[start]) {
+ current++;
+ break;
+ } else {
+ value += innerChar;
+ current++;
+ }
+ }
+
+ tokens.push({ type: WhenTokenType.STRING, value });
+ continue;
+ }
+
+ if (isAlpha(char)) {
+ let value = '';
+ while (isAlpha(char)) {
+ value += char;
+ char = input[++current];
+ }
+
+ switch (value) {
+ case 'and':
+ tokens.push({ type: WhenTokenType.AND });
+ break;
+ case 'or':
+ tokens.push({ type: WhenTokenType.OR });
+ break;
+ case 'not':
+ tokens.push({ type: WhenTokenType.NOT });
+ break;
+ case 'eq':
+ tokens.push({ type: WhenTokenType.EQUALS });
+ break;
+ default:
+ while (isAlphaNumeric(char)) {
+ value += char;
+ char = input[++current];
+ }
+ tokens.push({ type: WhenTokenType.IDENTIFIER, value });
+ }
+
+ continue;
+ }
+ if (isAlphaNumeric(char)) {
+ let value = '';
+ while (isAlphaNumeric(char)) {
+ value += char;
+ char = input[++current];
+ }
+
+ tokens.push({ type: WhenTokenType.IDENTIFIER, value });
+ continue;
+ }
+ throw new TypeError(`I don't know what this character is: ${char}`);
+ }
+ return tokens;
+};
+
+type WhenAST = {
+ type: WhenTokenType;
+ left?: WhenAST;
+ right?: WhenAST;
+ value?: string | WhenAST;
+ name?: string;
+ args?: WhenAST[];
+};
+
+const precedence : { [index: string] : number } = {
+ 'EQUALS': 4,
+ 'NOT': 3,
+ 'AND': 2,
+ 'OR': 1,
+};
+
+const parseWhen = (whenExpression: string) => {
+ const tokens = lex(whenExpression);
+ let current = 0;
+
+ const walk = (precedenceLevel = 0) : WhenAST => {
+ let token = tokens[current];
+ let node: WhenAST | null = null;
+
+ if (token.type === WhenTokenType.OPEN_PAREN) {
+ token = tokens[++current];
+ let innerNode: WhenAST = { type: WhenTokenType.EXPRESSION, value: walk() };
+ token = tokens[current];
+
+ while (token.type !== WhenTokenType.CLOSE_PAREN) {
+ innerNode = {
+ type: token.type,
+ value: token.value,
+ left: innerNode,
+ right: walk(),
+ };
+ token = tokens[current];
+ }
+ current++;
+ return innerNode;
+ }
+
+ if (token.type === WhenTokenType.STRING ) {
+ current++;
+ node = { type: token.type, value: token.value };
+ }
+
+ if (token.type === WhenTokenType.NOT) {
+ token = tokens[++current];
+ node = { type: WhenTokenType.NOT, value: token.value, right: walk() };
+ }
+
+ if (token.type === WhenTokenType.IDENTIFIER) {
+ const nextToken = tokens[current + 1];
+ if (nextToken.type === WhenTokenType.OPEN_PAREN) {
+ let name = token.value;
+ token = tokens[++current];
+
+ let args = [];
+ token = tokens[++current];
+
+ while (token.type !== WhenTokenType.CLOSE_PAREN) {
+ if (token.type === WhenTokenType.COMMA) {
+ current++;
+ } else {
+ args.push(walk());
+ }
+ token = tokens[current];
+ }
+
+ current++;
+ node = { type: WhenTokenType.FUNCTION, name, args };
+ } else {
+ current++;
+ node = { type: WhenTokenType.IDENTIFIER, value: token.value };
+ }
+ }
+
+ if (!node) throw new TypeError('Unexpected token: ' + token.type);
+
+ token = tokens[current];
+ while (current < tokens.length && precedence[token.type] >= precedenceLevel) {
+ console.log(current, tokens[current], tokens[current].type, precedenceLevel, precedence[token.type]);
+ token = tokens[current];
+ if (token.type === WhenTokenType.EQUALS || token.type === WhenTokenType.AND || token.type === WhenTokenType.OR) {
+ current++;
+ node = {
+ type: token.type,
+ left: node,
+ right: walk(precedence[token.type]),
+ };
+ } else {
+ break;
+ }
+ }
+
+ return node;
+
+ };
+
+ return walk();
+};
+
+export {
+ parseWhen,
+ WhenAST,
+ WhenTokenType,
+}; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
index e8e636f9b..cc2520100 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
@@ -1,3 +1,6 @@
+/* eslint-disable @typescript-eslint/no-loss-of-precision */
+/* eslint-disable @typescript-eslint/no-unused-expressions */
+/* eslint-disable @typescript-eslint/naming-convention */
/**
* ============LICENSE_START========================================================================
* ONAP : ccsdk feature sdnr wt odlux
@@ -15,14 +18,30 @@
* the License.
* ============LICENSE_END==========================================================================
*/
-import { Token, Statement, Module, Identity, ModuleState } from "../models/yang";
+import { Token, Statement, Module, Identity, ModuleState } from '../models/yang';
import {
- ViewSpecification, ViewElement, isViewElementObjectOrList, ViewElementBase,
- isViewElementReference, ViewElementChoise, ViewElementBinary, ViewElementString, isViewElementString,
- isViewElementNumber, ViewElementNumber, Expression, YangRange, ViewElementUnion, ViewElementRpc, isViewElementRpc, ResolveFunction, ViewElementDate
-} from "../models/uiModels";
-import { yangService } from "../services/yangService";
-
+ Expression,
+ ViewElement,
+ ViewElementBase,
+ ViewSpecification,
+ ViewElementNumber,
+ ViewElementString,
+ ViewElementChoice,
+ ViewElementUnion,
+ ViewElementRpc,
+ isViewElementObjectOrList,
+ isViewElementNumber,
+ isViewElementString,
+ isViewElementRpc,
+ ResolveFunction,
+ YangRange,
+} from '../models/uiModels';
+import { yangService } from '../services/yangService';
+
+const LOGLEVEL = +(localStorage.getItem('log.odlux.app.configuration.yang.yangParser') || 0);
+
+import { LogLevel } from '../../../../framework/src/utilities/logLevel';
+import { parseWhen, WhenAST, WhenTokenType } from './whenParser';
export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => {
const pathParts: RegExpMatchArray[] = [];
@@ -32,21 +51,22 @@ export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray
if (partMatch) {
pathParts.push(partMatch);
}
- } while (partMatch)
+ } while (partMatch);
return pathParts;
-}
+};
class YangLexer {
private pos: number = 0;
- private buf: string = "";
+
+ private buf: string = '';
constructor(input: string) {
this.pos = 0;
this.buf = input;
}
- private _optable: { [key: string]: string } = {
+ private _opTable: { [key: string]: string } = {
';': 'SEMI',
'{': 'L_BRACE',
'}': 'R_BRACE',
@@ -66,7 +86,7 @@ class YangLexer {
private _isAlpha(char: string): boolean {
return (char >= 'a' && char <= 'z') ||
- (char >= 'A' && char <= 'Z')
+ (char >= 'A' && char <= 'Z');
}
private _isAlphanum(char: string): boolean {
@@ -74,7 +94,7 @@ class YangLexer {
char === '_' || char === '-' || char === '.';
}
- private _skipNontokens() {
+ private _skipNonTokens() {
while (this.pos < this.buf.length) {
const char = this.buf.charAt(this.pos);
if (this._isWhitespace(char)) {
@@ -90,11 +110,11 @@ class YangLexer {
let end_index = this.pos + 1;
while (end_index < this.buf.length) {
const char = this.buf.charAt(end_index);
- if (char === "\\") {
+ if (char === '\\') {
end_index += 2;
continue;
- };
- if (terminator === null && (this._isWhitespace(char) || this._optable[char] !== undefined) || char === terminator) {
+ }
+ if (terminator === null && (this._isWhitespace(char) || this._opTable[char] !== undefined) || char === terminator) {
break;
}
end_index++;
@@ -109,7 +129,7 @@ class YangLexer {
name: 'STRING',
value: this.buf.substring(start, end),
start,
- end
+ end,
};
this.pos = terminator ? end + 1 : end;
return tok;
@@ -122,8 +142,8 @@ class YangLexer {
++endpos;
}
- let name = 'IDENTIFIER'
- if (this.buf.charAt(endpos) === ":") {
+ let name = 'IDENTIFIER';
+ if (this.buf.charAt(endpos) === ':') {
name = 'IDENTIFIERREF';
++endpos;
while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) {
@@ -135,7 +155,7 @@ class YangLexer {
name: name,
value: this.buf.substring(this.pos, endpos),
start: this.pos,
- end: endpos
+ end: endpos,
};
this.pos = endpos;
@@ -153,7 +173,7 @@ class YangLexer {
name: 'NUMBER',
value: this.buf.substring(this.pos, endpos),
start: this.pos,
- end: endpos
+ end: endpos,
};
this.pos = endpos;
return tok;
@@ -171,7 +191,7 @@ class YangLexer {
private _processBlockComment() {
var endpos = this.pos + 2;
// Skip until the end of the line
- while (endpos < this.buf.length && !((this.buf.charAt(endpos) === "/" && this.buf.charAt(endpos - 1) === "*"))) {
+ while (endpos < this.buf.length && !((this.buf.charAt(endpos) === '/' && this.buf.charAt(endpos - 1) === '*'))) {
endpos++;
}
this.pos = endpos + 1;
@@ -179,87 +199,87 @@ class YangLexer {
public tokenize(): Token[] {
const result: Token[] = [];
- this._skipNontokens();
+ this._skipNonTokens();
while (this.pos < this.buf.length) {
const char = this.buf.charAt(this.pos);
- const op = this._optable[char];
+ const op = this._opTable[char];
if (op !== undefined) {
result.push({ name: op, value: char, start: this.pos, end: ++this.pos });
} else if (this._isAlpha(char)) {
result.push(this._processIdentifier());
- this._skipNontokens();
+ this._skipNonTokens();
const peekChar = this.buf.charAt(this.pos);
- if (this._optable[peekChar] === undefined) {
- result.push((peekChar !== "'" && peekChar !== '"')
+ if (this._opTable[peekChar] === undefined) {
+ result.push((peekChar !== '\'' && peekChar !== '"')
? this._processString(null)
: this._processString(peekChar));
}
- } else if (char === '/' && this.buf.charAt(this.pos + 1) === "/") {
+ } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') {
this._processLineComment();
- } else if (char === '/' && this.buf.charAt(this.pos + 1) === "*") {
+ } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') {
this._processBlockComment();
} else {
- throw Error('Token error at ' + this.pos + " " + this.buf[this.pos]);
+ throw Error('Token error at ' + this.pos + ' ' + this.buf[this.pos]);
}
- this._skipNontokens();
+ this._skipNonTokens();
}
return result;
}
public tokenize2(): Statement {
- let stack: Statement[] = [{ key: "ROOT", sub: [] }];
+ let stack: Statement[] = [{ key: 'ROOT', sub: [] }];
let current: Statement | null = null;
- this._skipNontokens();
+ this._skipNonTokens();
while (this.pos < this.buf.length) {
const char = this.buf.charAt(this.pos);
- const op = this._optable[char];
+ const op = this._opTable[char];
if (op !== undefined) {
- if (op === "L_BRACE") {
+ if (op === 'L_BRACE') {
current && stack.unshift(current);
current = null;
- } else if (op === "R_BRACE") {
+ } else if (op === 'R_BRACE') {
current = stack.shift() || null;
}
this.pos++;
- } else if (this._isAlpha(char) || char === "_") {
+ } else if (this._isAlpha(char) || char === '_') {
const key = this._processIdentifier().value;
- this._skipNontokens();
+ this._skipNonTokens();
let peekChar = this.buf.charAt(this.pos);
let arg = undefined;
- if (this._optable[peekChar] === undefined) {
- arg = (peekChar === '"' || peekChar === "'")
+ if (this._opTable[peekChar] === undefined) {
+ arg = (peekChar === '"' || peekChar === '\'')
? this._processString(peekChar).value
: this._processString(null).value;
}
do {
- this._skipNontokens();
+ this._skipNonTokens();
peekChar = this.buf.charAt(this.pos);
- if (peekChar !== "+") break;
+ if (peekChar !== '+') break;
this.pos++;
- this._skipNontokens();
+ this._skipNonTokens();
peekChar = this.buf.charAt(this.pos);
- arg += (peekChar === '"' || peekChar === "'")
+ arg += (peekChar === '"' || peekChar === '\'')
? this._processString(peekChar).value
: this._processString(null).value;
} while (true);
current = { key, arg, sub: [] };
stack[0].sub!.push(current);
- } else if (char === '/' && this.buf.charAt(this.pos + 1) === "/") {
+ } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') {
this._processLineComment();
- } else if (char === '/' && this.buf.charAt(this.pos + 1) === "*") {
+ } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') {
this._processBlockComment();
} else {
- throw Error('Token error at ' + this.pos + " " + this.buf.slice(this.pos - 10, this.pos + 10));
+ throw Error('Token error at ' + this.pos + ' ' + this.buf.slice(this.pos - 10, this.pos + 10));
}
- this._skipNontokens();
+ this._skipNonTokens();
}
- if (stack[0].key !== "ROOT" || !stack[0].sub![0]) {
- throw new Error("Internal Perser Error");
+ if (stack[0].key !== 'ROOT' || !stack[0].sub![0]) {
+ throw new Error('Internal Perser Error');
}
return stack[0].sub![0];
}
@@ -269,25 +289,33 @@ export class YangParser {
private _groupingsToResolve: ViewSpecification[] = [];
private _identityToResolve: (() => void)[] = [];
+
private _unionsToResolve: (() => void)[] = [];
+
private _modulesToResolve: (() => void)[] = [];
private _modules: { [name: string]: Module } = {};
+
private _views: ViewSpecification[] = [{
- id: "0",
- name: "root",
- language: "en-US",
+ id: '0',
+ name: 'root',
+ language: 'en-US',
canEdit: false,
config: true,
- parentView: "0",
- title: "root",
+ parentView: '0',
+ title: 'root',
elements: {},
}];
- public static ResolveStack = Symbol("ResolveStack");
+ public static ResolveStack = Symbol('ResolveStack');
+
+ constructor(
+ private nodeId: string,
+ private _capabilityRevisionMap: { [capability: string]: string } = {},
+ private _unavailableCapabilities: { failureReason: string; capability: string }[] = [],
+ private _importOnlyModules: { name: string; revision: string }[] = [],
+ ) {
- constructor(private _unavailableCapabilities: { failureReason: string; capability: string; }[] = [], private _importOnlyModules: { name: string; revision: string; }[] = [], private nodeId: string) {
-
}
public get modules() {
@@ -300,8 +328,12 @@ export class YangParser {
public async addCapability(capability: string, version?: string, parentImportOnlyModule?: boolean) {
// do not add twice
- if (this._modules[capability]) {
- // console.warn(`Skipped capability: ${capability} since already contained.` );
+ const existingCapability = this._modules[capability];
+ const latestVersionExisting = existingCapability && Object.keys(existingCapability.revisions).sort().reverse()[0];
+ if ((latestVersionExisting && version) && (version <= latestVersionExisting)) {
+ if (LOGLEVEL == LogLevel.Warning) {
+ console.warn(`Skipped capability: ${capability}:${version || ''} since already contained.`);
+ }
return;
}
@@ -310,14 +342,15 @@ export class YangParser {
// // console.warn(`Skipped capability: ${capability} since it is marked as unavailable.` );
// return;
// }
+
const data = await yangService.getCapability(capability, this.nodeId, version);
if (!data) {
- throw new Error(`Could not load yang file for ${capability}.`);
+ throw new Error(`Could not load yang file for ${capability}:${version || ''}.`);
}
const rootStatement = new YangLexer(data).tokenize2();
- if (rootStatement.key !== "module") {
+ if (rootStatement.key !== 'module') {
throw new Error(`Root element of ${capability} is not a module.`);
}
if (rootStatement.arg !== capability) {
@@ -326,10 +359,32 @@ export class YangParser {
const isUnavailable = this._unavailableCapabilities.some(c => c.capability === capability);
const isImportOnly = parentImportOnlyModule === true || this._importOnlyModules.some(c => c.name === capability);
+
+ // extract revisions
+ const revisions = this.extractNodes(rootStatement, 'revision').reduce<{ [version: string]: {} }>((acc, revision) => {
+ if (!revision.arg) {
+ throw new Error(`Module [${rootStatement.arg}] has a version w/o version number.`);
+ }
+ const description = this.extractValue(revision, 'description');
+ const reference = this.extractValue(revision, 'reference');
+ acc[revision.arg] = {
+ description,
+ reference,
+ };
+ return acc;
+ }, {});
+
+ const latestVersionLoaded = Object.keys(revisions).sort().reverse()[0];
+ if (existingCapability && latestVersionExisting >= latestVersionLoaded) {
+ if (LOGLEVEL == LogLevel.Warning) {
+ console.warn(`Skipped capability: ${capability}:${latestVersionLoaded} since ${capability}:${latestVersionExisting} already contained.`);
+ }
+ return;
+ }
const module = this._modules[capability] = {
name: rootStatement.arg,
- revisions: {},
+ revisions,
imports: {},
features: {},
identities: {},
@@ -339,10 +394,10 @@ export class YangParser {
views: {},
elements: {},
state: isUnavailable
- ? ModuleState.unavailable
- : isImportOnly
- ? ModuleState.importOnly
- : ModuleState.stable,
+ ? ModuleState.unavailable
+ : isImportOnly
+ ? ModuleState.importOnly
+ : ModuleState.stable,
};
await this.handleModule(module, rootStatement, capability);
@@ -351,84 +406,66 @@ export class YangParser {
private async handleModule(module: Module, rootStatement: Statement, capability: string) {
// extract namespace && prefix
- module.namespace = this.extractValue(rootStatement, "namespace");
- module.prefix = this.extractValue(rootStatement, "prefix");
+ module.namespace = this.extractValue(rootStatement, 'namespace');
+ module.prefix = this.extractValue(rootStatement, 'prefix');
if (module.prefix) {
module.imports[module.prefix] = capability;
}
- // extract revisions
- const revisions = this.extractNodes(rootStatement, "revision");
- module.revisions = {
- ...module.revisions,
- ...revisions.reduce<{ [version: string]: {} }>((acc, version) => {
- if (!version.arg) {
- throw new Error(`Module [${module.name}] has a version w/o version number.`);
- }
- const description = this.extractValue(version, "description");
- const reference = this.extractValue(version, "reference");
- acc[version.arg] = {
- description,
- reference,
- };
- return acc;
- }, {})
- };
-
// extract features
- const features = this.extractNodes(rootStatement, "feature");
+ const features = this.extractNodes(rootStatement, 'feature');
module.features = {
...module.features,
...features.reduce<{ [version: string]: {} }>((acc, feature) => {
if (!feature.arg) {
throw new Error(`Module [${module.name}] has a feature w/o name.`);
}
- const description = this.extractValue(feature, "description");
+ const description = this.extractValue(feature, 'description');
acc[feature.arg] = {
description,
};
return acc;
- }, {})
+ }, {}),
};
// extract imports
- const imports = this.extractNodes(rootStatement, "import");
+ const imports = this.extractNodes(rootStatement, 'import');
module.imports = {
...module.imports,
...imports.reduce<{ [key: string]: string }>((acc, imp) => {
- const prefix = imp.sub && imp.sub.filter(s => s.key === "prefix");
+ const prefix = imp.sub && imp.sub.filter(s => s.key === 'prefix');
if (!imp.arg) {
throw new Error(`Module [${module.name}] has an import with neither name nor prefix.`);
}
acc[prefix && prefix.length === 1 && prefix[0].arg || imp.arg] = imp.arg;
return acc;
- }, {})
+ }, {}),
};
// import all required files and set module state
if (imports) for (let ind = 0; ind < imports.length; ++ind) {
- const moduleName = imports[ind].arg!;
+ const moduleName = imports[ind].arg!;
- //TODO: Fix imports getting loaded without revision
- await this.addCapability(moduleName, undefined, module.state === ModuleState.importOnly);
+ const revision = this._capabilityRevisionMap[moduleName] || undefined;
+ await this.addCapability(moduleName, revision, module.state === ModuleState.importOnly);
const importedModule = this._modules[imports[ind].arg!];
if (importedModule && importedModule.state > ModuleState.stable) {
- module.state = Math.max(module.state, ModuleState.instable);
+ module.state = Math.max(module.state, ModuleState.instable);
}
}
- this.extractTypeDefinitions(rootStatement, module, "");
+ this.extractTypeDefinitions(rootStatement, module, '');
- this.extractIdentities(rootStatement, 0, module, "");
+ this.extractIdentities(rootStatement, 0, module, '');
- const groupings = this.extractGroupings(rootStatement, 0, module, "");
+ const groupings = this.extractGroupings(rootStatement, 0, module, '');
this._views.push(...groupings);
- const augments = this.extractAugments(rootStatement, 0, module, "");
+ const augments = this.extractAugments(rootStatement, 0, module, '');
this._views.push(...augments);
// the default for config on module level is config = true;
- const [currentView, subViews] = this.extractSubViews(rootStatement, 0, module, "");
+ const [currentView, subViews] = this.extractSubViews(rootStatement, 0, module, '');
this._views.push(currentView, ...subViews);
// create the root elements for this module
@@ -443,7 +480,7 @@ export class YangParser {
const viewIdIndex = Number(viewElement.viewId);
module.views[key] = this._views[viewIdIndex];
}
-
+
// add only the UI View if the module is available
if (module.state === ModuleState.stable || module.state === ModuleState.instable) this._views[0].elements[key] = module.elements[key];
});
@@ -462,7 +499,7 @@ export class YangParser {
// process all groupings
this._groupingsToResolve.filter(vs => vs.uses && vs.uses[ResolveFunction]).forEach(vs => {
- try { vs.uses![ResolveFunction] !== undefined && vs.uses![ResolveFunction]!("|"); } catch (error) {
+ try { vs.uses![ResolveFunction] !== undefined && vs.uses![ResolveFunction]!('|'); } catch (error) {
console.warn(`Error resolving: [${vs.name}] [${error.message}]`);
}
});
@@ -471,16 +508,16 @@ export class YangParser {
* This is to fix the issue for sequential execution of modules based on their child and parent relationship
* We are sorting the module object based on their augment status
*/
- Object.keys(this.modules)
+ Object.keys(this.modules)
.map(elem => {
- if(this.modules[elem].augments && Object.keys(this.modules[elem].augments).length > 0) {
- const {augments, ...rest} = this.modules[elem];
- const partsOfKeys = Object.keys(augments).map((key) => (key.split("/").length - 1))
- this.modules[elem].executionOrder= Math.max(...partsOfKeys)
- } else {
- this.modules[elem].executionOrder=0;
- }
- })
+ if (this.modules[elem].augments && Object.keys(this.modules[elem].augments).length > 0) {
+ const { augments, ..._rest } = this.modules[elem];
+ const partsOfKeys = Object.keys(augments).map((key) => (key.split('/').length - 1));
+ this.modules[elem].executionOrder = Math.max(...partsOfKeys);
+ } else {
+ this.modules[elem].executionOrder = 0;
+ }
+ });
// process all augmentations / sort by namespace changes to ensure proper order
Object.keys(this.modules).sort((a, b) => this.modules[a].executionOrder! - this.modules[b].executionOrder!).forEach(modKey => {
@@ -489,8 +526,8 @@ export class YangParser {
const pathParts = splitVPath(key, /(?:(?:([^\/\:]+):)?([^\/]+))/g); // 1 = opt: namespace / 2 = property
let nameSpaceChangeCounter = 0;
let currentNS = module.name; // init namespace
- pathParts.forEach(([ns, _])=> {
- if (ns === currentNS){
+ pathParts.forEach(([ns, _]) => {
+ if (ns === currentNS) {
currentNS = ns;
nameSpaceChangeCounter++;
}
@@ -498,11 +535,11 @@ export class YangParser {
return {
key,
nameSpaceChangeCounter,
- }
+ };
});
-
+
const augmentKeys = augmentKeysWithCounter
- .sort((a,b) => a.nameSpaceChangeCounter > b.nameSpaceChangeCounter ? 1 : a.nameSpaceChangeCounter === b.nameSpaceChangeCounter ? 0 : -1 )
+ .sort((a, b) => a.nameSpaceChangeCounter > b.nameSpaceChangeCounter ? 1 : a.nameSpaceChangeCounter === b.nameSpaceChangeCounter ? 0 : -1)
.map((a) => a.key);
augmentKeys.forEach(augKey => {
@@ -512,11 +549,23 @@ export class YangParser {
if (augments && viewSpec) {
augments.forEach(augment => Object.keys(augment.elements).forEach(key => {
const elm = augment.elements[key];
+
+ const when = elm.when && augment.when
+ ? {
+ type: WhenTokenType.AND,
+ left: elm.when,
+ right: augment.when,
+ }
+ : elm.when || augment.when;
+
+ const ifFeature = elm.ifFeature
+ ? `(${augment.ifFeature}) and (${elm.ifFeature})`
+ : augment.ifFeature;
+
viewSpec.elements[key] = {
...augment.elements[key],
-
- when: elm.when ? `(${augment.when}) and (${elm.when})` : augment.when,
- ifFeature: elm.ifFeature ? `(${augment.ifFeature}) and (${elm.ifFeature})` : augment.ifFeature,
+ when,
+ ifFeature,
};
}));
}
@@ -534,7 +583,7 @@ export class YangParser {
}
}
return result;
- }
+ };
const baseIdentities: Identity[] = [];
Object.keys(this.modules).forEach(modKey => {
@@ -565,30 +614,31 @@ export class YangParser {
}
});
- // resolve readOnly
- const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => {
-
- // update view config
- view.config = view.config && parentConfig;
-
- Object.keys(view.elements).forEach((key) => {
- const elm = view.elements[key];
-
- // update element config
- elm.config = elm.config && view.config;
-
- // update all sub-elements of objects
- if (elm.uiType === "object") {
- resolveReadOnly(this.views[+elm.viewId], elm.config);
- }
+ // // resolve readOnly
+ // const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => {
- })
- }
+ // // update view config
+ // view.config = view.config && parentConfig;
- const dump = resolveReadOnly(this.views[0], true);
- };
+ // Object.keys(view.elements).forEach((key) => {
+ // const elm = view.elements[key];
+
+ // // update element config
+ // elm.config = elm.config && view.config;
+
+ // // update all sub-elements of objects
+ // if (elm.uiType === 'object') {
+ // resolveReadOnly(this.views[+elm.viewId], elm.config);
+ // }
+
+ // });
+ // };
+
+ // const dump = resolveReadOnly(this.views[0], true);
+ }
private _nextId = 1;
+
private get nextId() {
return this._nextId++;
}
@@ -608,7 +658,7 @@ export class YangParser {
}
private extractTypeDefinitions(statement: Statement, module: Module, currentPath: string): void {
- const typedefs = this.extractNodes(statement, "typedef");
+ const typedefs = this.extractNodes(statement, 'typedef');
typedefs && typedefs.forEach(def => {
if (!def.arg) {
throw new Error(`Module: [${module.name}]. Found typefed without name.`);
@@ -620,7 +670,7 @@ export class YangParser {
/** Handles groupings like named Container */
private extractGroupings(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
const subViews: ViewSpecification[] = [];
- const groupings = this.extractNodes(statement, "grouping");
+ const groupings = this.extractNodes(statement, 'grouping');
if (groupings && groupings.length > 0) {
subViews.push(...groupings.reduce<ViewSpecification[]>((acc, cur) => {
if (!cur.arg) {
@@ -629,9 +679,9 @@ export class YangParser {
const grouping = cur.arg;
// the default for config on module level is config = true;
- const [currentView, subViews] = this.extractSubViews(cur, /* parentId */ -1, module, currentPath);
+ const [currentView, currentSubViews] = this.extractSubViews(cur, /* parentId */ -1, module, currentPath);
grouping && (module.groupings[grouping] = currentView);
- acc.push(currentView, ...subViews);
+ acc.push(currentView, ...currentSubViews);
return acc;
}, []));
}
@@ -642,7 +692,7 @@ export class YangParser {
/** Handles augments also like named container */
private extractAugments(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
const subViews: ViewSpecification[] = [];
- const augments = this.extractNodes(statement, "augment");
+ const augments = this.extractNodes(statement, 'augment');
if (augments && augments.length > 0) {
subViews.push(...augments.reduce<ViewSpecification[]>((acc, cur) => {
if (!cur.arg) {
@@ -651,12 +701,12 @@ export class YangParser {
const augment = this.resolveReferencePath(cur.arg, module);
// the default for config on module level is config = true;
- const [currentView, subViews] = this.extractSubViews(cur, parentId, module, currentPath);
+ const [currentView, currentSubViews] = this.extractSubViews(cur, parentId, module, currentPath);
if (augment) {
module.augments[augment] = module.augments[augment] || [];
module.augments[augment].push(currentView);
}
- acc.push(currentView, ...subViews);
+ acc.push(currentView, ...currentSubViews);
return acc;
}, []));
}
@@ -666,109 +716,109 @@ export class YangParser {
/** Handles identities */
private extractIdentities(statement: Statement, parentId: number, module: Module, currentPath: string) {
- const identities = this.extractNodes(statement, "identity");
+ const identities = this.extractNodes(statement, 'identity');
module.identities = identities.reduce<{ [name: string]: Identity }>((acc, cur) => {
if (!cur.arg) {
- throw new Error(`Module: [${module.name}][${currentPath}]. Found identiy without name.`);
+ throw new Error(`Module: [${module.name}][${currentPath}]. Found identity without name.`);
}
acc[cur.arg] = {
id: `${module.name}:${cur.arg}`,
label: cur.arg,
- base: this.extractValue(cur, "base"),
- description: this.extractValue(cur, "description"),
- reference: this.extractValue(cur, "reference"),
- children: []
- }
+ base: this.extractValue(cur, 'base'),
+ description: this.extractValue(cur, 'description'),
+ reference: this.extractValue(cur, 'reference'),
+ children: [],
+ };
return acc;
}, {});
}
- // Hint: use 0 as parentId for rootElements and -1 for rootGroupings.
+ // Hint: use 0 as parentId for rootElements and -1 for rootGroupings.
private extractSubViews(statement: Statement, parentId: number, module: Module, currentPath: string): [ViewSpecification, ViewSpecification[]] {
// used for scoped definitions
const context: Module = {
...module,
typedefs: {
- ...module.typedefs
- }
+ ...module.typedefs,
+ },
};
const currentId = this.nextId;
const subViews: ViewSpecification[] = [];
let elements: ViewElement[] = [];
- const configValue = this.extractValue(statement, "config");
- const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
+ const configValue = this.extractValue(statement, 'config');
+ const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false';
// extract conditions
- const ifFeature = this.extractValue(statement, "if-feature");
- const whenCondition = this.extractValue(statement, "when");
- if (whenCondition) console.warn("Found in [" + context.name + "]" + currentPath + " when: " + whenCondition);
+ const ifFeature = this.extractValue(statement, 'if-feature');
+ const whenCondition = this.extractValue(statement, 'when');
+ if (whenCondition) console.warn('Found in [' + context.name + ']' + currentPath + ' when: ' + whenCondition);
// extract all scoped typedefs
this.extractTypeDefinitions(statement, context, currentPath);
// extract all scoped groupings
subViews.push(
- ...this.extractGroupings(statement, parentId, context, currentPath)
+ ...this.extractGroupings(statement, parentId, context, currentPath),
);
// extract all container
- const container = this.extractNodes(statement, "container");
+ const container = this.extractNodes(statement, 'container');
if (container && container.length > 0) {
subViews.push(...container.reduce<ViewSpecification[]>((acc, cur) => {
if (!cur.arg) {
throw new Error(`Module: [${context.name}]${currentPath}. Found container without name.`);
}
- const [currentView, subViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
+ const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
elements.push({
id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
label: cur.arg,
path: currentPath,
module: context.name || module.name || '',
- uiType: "object",
+ uiType: 'object',
viewId: currentView.id,
config: currentView.config,
});
- acc.push(currentView, ...subViews);
+ acc.push(currentView, ...currentSubViews);
return acc;
}, []));
}
// process all lists
// a list is a list of containers with the leafs contained in the list
- const lists = this.extractNodes(statement, "list");
+ const lists = this.extractNodes(statement, 'list');
if (lists && lists.length > 0) {
subViews.push(...lists.reduce<ViewSpecification[]>((acc, cur) => {
let elmConfig = config;
if (!cur.arg) {
throw new Error(`Module: [${context.name}]${currentPath}. Found list without name.`);
}
- const key = this.extractValue(cur, "key") || undefined;
+ const key = this.extractValue(cur, 'key') || undefined;
if (elmConfig && !key) {
console.warn(`Module: [${context.name}]${currentPath}. Found configurable list without key. Assume config shell be false.`);
elmConfig = false;
}
- const [currentView, subViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
+ const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
elements.push({
id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
label: cur.arg,
path: currentPath,
module: context.name || module.name || '',
isList: true,
- uiType: "object",
+ uiType: 'object',
viewId: currentView.id,
key: key,
config: elmConfig && currentView.config,
});
- acc.push(currentView, ...subViews);
+ acc.push(currentView, ...currentSubViews);
return acc;
}, []));
}
// process all leaf-lists
// a leaf-list is a list of some type
- const leafLists = this.extractNodes(statement, "leaf-list");
+ const leafLists = this.extractNodes(statement, 'leaf-list');
if (leafLists && leafLists.length > 0) {
elements.push(...leafLists.reduce<ViewElement[]>((acc, cur) => {
const element = this.getViewElement(cur, context, parentId, currentPath, true);
@@ -779,7 +829,7 @@ export class YangParser {
// process all leafs
// a leaf is mainly a property of an object
- const leafs = this.extractNodes(statement, "leaf");
+ const leafs = this.extractNodes(statement, 'leaf');
if (leafs && leafs.length > 0) {
elements.push(...leafs.reduce<ViewElement[]>((acc, cur) => {
const element = this.getViewElement(cur, context, parentId, currentPath, false);
@@ -789,92 +839,92 @@ export class YangParser {
}
- const choiceStms = this.extractNodes(statement, "choice");
+ const choiceStms = this.extractNodes(statement, 'choice');
if (choiceStms && choiceStms.length > 0) {
- elements.push(...choiceStms.reduce<ViewElementChoise[]>((accChoise, curChoise) => {
- if (!curChoise.arg) {
+ elements.push(...choiceStms.reduce<ViewElementChoice[]>((accChoice, curChoice) => {
+ if (!curChoice.arg) {
throw new Error(`Module: [${context.name}]${currentPath}. Found choise without name.`);
}
// extract all cases like containers
- const cases: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } }[] = [];
- const caseStms = this.extractNodes(curChoise, "case");
+ const cases: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[] = [];
+ const caseStms = this.extractNodes(curChoice, 'case');
if (caseStms && caseStms.length > 0) {
cases.push(...caseStms.reduce((accCase, curCase) => {
if (!curCase.arg) {
- throw new Error(`Module: [${context.name}]${currentPath}/${curChoise.arg}. Found case without name.`);
+ throw new Error(`Module: [${context.name}]${currentPath}/${curChoice.arg}. Found case without name.`);
}
- const description = this.extractValue(curCase, "description") || undefined;
- const [caseView, caseSubViews] = this.extractSubViews(curCase, parentId, context, `${currentPath}/${context.name}:${curChoise.arg}`);
+ const description = this.extractValue(curCase, 'description') || undefined;
+ const [caseView, caseSubViews] = this.extractSubViews(curCase, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`);
subViews.push(caseView, ...caseSubViews);
- const caseDef: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } = {
+ const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = {
id: parentId === 0 ? `${context.name}:${curCase.arg}` : curCase.arg,
label: curCase.arg,
description: description,
- elements: caseView.elements
+ elements: caseView.elements,
};
accCase.push(caseDef);
return accCase;
- }, [] as { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } }[]));
+ }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[]));
}
// extract all simple cases (one case per leaf, container, etc.)
- const [choiseView, choiseSubViews] = this.extractSubViews(curChoise, parentId, context, `${currentPath}/${context.name}:${curChoise.arg}`);
- subViews.push(choiseView, ...choiseSubViews);
- cases.push(...Object.keys(choiseView.elements).reduce((accElm, curElm) => {
- const elm = choiseView.elements[curElm];
- const caseDef: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } = {
+ const [choiceView, choiceSubViews] = this.extractSubViews(curChoice, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`);
+ subViews.push(choiceView, ...choiceSubViews);
+ cases.push(...Object.keys(choiceView.elements).reduce((accElm, curElm) => {
+ const elm = choiceView.elements[curElm];
+ const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = {
id: elm.id,
label: elm.label,
description: elm.description,
- elements: { [elm.id]: elm }
+ elements: { [elm.id]: elm },
};
accElm.push(caseDef);
return accElm;
- }, [] as { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } }[]));
+ }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[]));
- const description = this.extractValue(curChoise, "description") || undefined;
- const configValue = this.extractValue(curChoise, "config");
- const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
+ const choiceDescription = this.extractValue(curChoice, 'description') || undefined;
+ const choiceConfigValue = this.extractValue(curChoice, 'config');
+ const choiceConfig = choiceConfigValue == null ? true : choiceConfigValue.toLocaleLowerCase() !== 'false';
- const mandatory = this.extractValue(curChoise, "mandatory") === "true" || false;
+ const mandatory = this.extractValue(curChoice, 'mandatory') === 'true' || false;
- const element: ViewElementChoise = {
- uiType: "choise",
- id: parentId === 0 ? `${context.name}:${curChoise.arg}` : curChoise.arg,
- label: curChoise.arg,
+ const element: ViewElementChoice = {
+ uiType: 'choice',
+ id: parentId === 0 ? `${context.name}:${curChoice.arg}` : curChoice.arg,
+ label: curChoice.arg,
path: currentPath,
module: context.name || module.name || '',
- config: config,
+ config: choiceConfig,
mandatory: mandatory,
- description: description,
+ description: choiceDescription,
cases: cases.reduce((acc, cur) => {
acc[cur.id] = cur;
return acc;
- }, {} as { [name: string]: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } })
+ }, {} as { [name: string]: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } }),
};
- accChoise.push(element);
- return accChoise;
+ accChoice.push(element);
+ return accChoice;
}, []));
}
- const rpcStms = this.extractNodes(statement, "rpc");
+ const rpcStms = this.extractNodes(statement, 'rpc');
if (rpcStms && rpcStms.length > 0) {
elements.push(...rpcStms.reduce<ViewElementRpc[]>((accRpc, curRpc) => {
if (!curRpc.arg) {
throw new Error(`Module: [${context.name}]${currentPath}. Found rpc without name.`);
}
- const description = this.extractValue(curRpc, "description") || undefined;
- const configValue = this.extractValue(curRpc, "config");
- const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
+ const rpcDescription = this.extractValue(curRpc, 'description') || undefined;
+ const rpcConfigValue = this.extractValue(curRpc, 'config');
+ const rpcConfig = rpcConfigValue == null ? true : rpcConfigValue.toLocaleLowerCase() !== 'false';
let inputViewId: string | undefined = undefined;
let outputViewId: string | undefined = undefined;
- const input = this.extractNodes(curRpc, "input") || undefined;
- const output = this.extractNodes(curRpc, "output") || undefined;
+ const input = this.extractNodes(curRpc, 'input') || undefined;
+ const output = this.extractNodes(curRpc, 'output') || undefined;
if (input && input.length > 0) {
const [inputView, inputSubViews] = this.extractSubViews(input[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`);
@@ -889,13 +939,13 @@ export class YangParser {
}
const element: ViewElementRpc = {
- uiType: "rpc",
+ uiType: 'rpc',
id: parentId === 0 ? `${context.name}:${curRpc.arg}` : curRpc.arg,
label: curRpc.arg,
path: currentPath,
module: context.name || module.name || '',
- config: config,
- description: description,
+ config: rpcConfig,
+ description: rpcDescription,
inputViewId: inputViewId,
outputViewId: outputViewId,
};
@@ -906,9 +956,16 @@ export class YangParser {
}, []));
}
- // if (!statement.arg) {
- // throw new Error(`Module: [${context.name}]. Found statement without name.`);
- // }
+ if (!statement.arg) {
+ console.error(new Error(`Module: [${context.name}]. Found statement without name.`));
+ }
+
+ let whenParsed: WhenAST | undefined = undefined;
+ try {
+ whenParsed = whenCondition && parseWhen(whenCondition) || undefined;
+ } catch (e) {
+ console.error(new Error(`Module: [${context.name}]. Found invalid when condition: ${whenCondition}`));
+ }
const viewSpec: ViewSpecification = {
id: String(currentId),
@@ -916,11 +973,11 @@ export class YangParser {
ns: context.name,
name: statement.arg != null ? statement.arg : undefined,
title: statement.arg != null ? statement.arg : undefined,
- language: "en-us",
+ language: 'en-us',
canEdit: false,
config: config,
ifFeature: ifFeature,
- when: whenCondition,
+ when: whenParsed,
elements: elements.reduce<{ [name: string]: ViewElement }>((acc, cur) => {
acc[cur.id] = cur;
return acc;
@@ -928,21 +985,21 @@ export class YangParser {
};
// evaluate canEdit depending on all conditions
- Object.defineProperty(viewSpec, "canEdit", {
+ Object.defineProperty(viewSpec, 'canEdit', {
get: () => {
return Object.keys(viewSpec.elements).some(key => {
const elm = viewSpec.elements[key];
return (!isViewElementObjectOrList(elm) && elm.config);
});
- }
+ },
});
// merge in all uses references and resolve groupings
- const usesRefs = this.extractNodes(statement, "uses");
+ const usesRefs = this.extractNodes(statement, 'uses');
if (usesRefs && usesRefs.length > 0) {
viewSpec.uses = (viewSpec.uses || []);
- const resolveFunctions : ((parentElementPath: string)=>void)[] = [];
+ const resolveFunctions: ((parentElementPath: string) => void)[] = [];
for (let i = 0; i < usesRefs.length; ++i) {
const groupingName = usesRefs[i].arg;
@@ -951,7 +1008,7 @@ export class YangParser {
}
viewSpec.uses.push(this.resolveReferencePath(groupingName, context));
-
+
resolveFunctions.push((parentElementPath: string) => {
const groupingViewSpec = this.resolveGrouping(groupingName, context);
if (groupingViewSpec) {
@@ -963,10 +1020,22 @@ export class YangParser {
Object.keys(groupingViewSpec.elements).forEach(key => {
const elm = groupingViewSpec.elements[key];
// a useRef on root level need a namespace
+ const resolvedWhen = elm.when && groupingViewSpec.when
+ ? {
+ type: WhenTokenType.AND,
+ left: elm.when,
+ right: groupingViewSpec.when,
+ }
+ : elm.when || groupingViewSpec.when;
+
+ const resolvedIfFeature = elm.ifFeature
+ ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})`
+ : groupingViewSpec.ifFeature;
+
viewSpec.elements[parentId === 0 ? `${module.name}:${key}` : key] = {
...elm,
- when: elm.when ? `(${groupingViewSpec.when}) and (${elm.when})` : groupingViewSpec.when,
- ifFeature: elm.ifFeature ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})` : groupingViewSpec.ifFeature,
+ when: resolvedWhen,
+ ifFeature: resolvedIfFeature,
};
});
}
@@ -974,19 +1043,19 @@ export class YangParser {
}
viewSpec.uses[ResolveFunction] = (parentElementPath: string) => {
- const currentElementPath = `${parentElementPath} -> ${viewSpec.ns}:${viewSpec.name}`;
+ const currentElementPath = `${parentElementPath} -> ${viewSpec.ns}:${viewSpec.name}`;
resolveFunctions.forEach(resolve => {
- try {
- resolve(currentElementPath);
- } catch (error) {
- console.error(error);
- }
+ try {
+ resolve(currentElementPath);
+ } catch (error) {
+ console.error(error);
+ }
});
// console.log("Resolved "+currentElementPath, viewSpec);
if (viewSpec?.uses) {
viewSpec.uses[ResolveFunction] = undefined;
}
- }
+ };
this._groupingsToResolve.push(viewSpec);
}
@@ -1020,28 +1089,28 @@ export class YangParser {
/** Extracts the UI View from the type in the cur statement. */
private getViewElement(cur: Statement, module: Module, parentId: number, currentPath: string, isList: boolean): ViewElement {
- const type = this.extractValue(cur, "type");
- const defaultVal = this.extractValue(cur, "default") || undefined;
- const description = this.extractValue(cur, "description") || undefined;
+ const type = this.extractValue(cur, 'type');
+ const defaultVal = this.extractValue(cur, 'default') || undefined;
+ const description = this.extractValue(cur, 'description') || undefined;
- const configValue = this.extractValue(cur, "config");
- const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
+ const configValue = this.extractValue(cur, 'config');
+ const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false';
- const extractRange = (min: number, max: number, property: string = "range"): { expression: Expression<YangRange> | undefined, min: number, max: number } => {
- const ranges = this.extractValue(this.extractNodes(cur, "type")[0]!, property) || undefined;
- const range = ranges ?.replace(/min/i, String(min)).replace(/max/i, String(max)).split("|").map(r => {
+ const extractRange = (min: number, max: number, property: string = 'range'): { expression: Expression<YangRange> | undefined; min: number; max: number } => {
+ const ranges = this.extractValue(this.extractNodes(cur, 'type')[0]!, property) || undefined;
+ const range = ranges?.replace(/min/i, String(min)).replace(/max/i, String(max)).split('|').map(r => {
let minValue: number;
let maxValue: number;
-
+
if (r.indexOf('..') > -1) {
- const [minStr, maxStr] = r.split('..');
- minValue = Number(minStr);
- maxValue = Number(maxStr);
- } else if (!isNaN(maxValue = Number(r && r.trim() )) ) {
- minValue = maxValue;
+ const [minStr, maxStr] = r.split('..');
+ minValue = Number(minStr);
+ maxValue = Number(maxStr);
+ } else if (!isNaN(maxValue = Number(r && r.trim()))) {
+ minValue = maxValue;
} else {
- minValue = min,
- maxValue = max;
+ minValue = min,
+ maxValue = max;
}
if (minValue > min) min = minValue;
@@ -1049,7 +1118,7 @@ export class YangParser {
return {
min: minValue,
- max: maxValue
+ max: maxValue,
};
});
return {
@@ -1058,21 +1127,22 @@ export class YangParser {
expression: range && range.length === 1
? range[0]
: range && range.length > 1
- ? { operation: "OR", arguments: range }
- : undefined
- }
+ ? { operation: 'OR', arguments: range }
+ : undefined,
+ };
};
const extractPattern = (): Expression<RegExp> | undefined => {
- const pattern = this.extractNodes(this.extractNodes(cur, "type")[0]!, "pattern").map(p => p.arg!).filter(p => !!p).map(p => `^${p.replace(/(?:\\(.))/g, '$1')}$`);
+ // 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
+ const pattern = this.extractNodes(this.extractNodes(cur, 'type')[0]!, 'pattern').map(p => p.arg!).filter(p => !!p).map(p => `^${p/*.replace(/(?:\\(.))/g, '$1')*/}$`);
return pattern && pattern.length == 1
? new RegExp(pattern[0])
: pattern && pattern.length > 1
- ? { operation: "AND", arguments: pattern.map(p => new RegExp(p)) }
+ ? { operation: 'AND', arguments: pattern.map(p => new RegExp(p)) }
: undefined;
- }
+ };
- const mandatory = this.extractValue(cur, "mandatory") === "true" || false;
+ const mandatory = this.extractValue(cur, 'mandatory') === 'true' || false;
if (!cur.arg) {
throw new Error(`Module: [${module.name}]. Found element without name.`);
@@ -1084,159 +1154,159 @@ export class YangParser {
const element: ViewElementBase = {
id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg,
- label: cur.arg,
+ label: cur.arg,
path: currentPath,
- module: module.name || "",
+ module: module.name || '',
config: config,
mandatory: mandatory,
isList: isList,
default: defaultVal,
- description: description
+ description: description,
};
- if (type === "string") {
- const length = extractRange(0, +18446744073709551615, "length");
+ if (type === 'string') {
+ const length = extractRange(0, +18446744073709551615, 'length');
return ({
...element,
- uiType: "string",
+ uiType: 'string',
length: length.expression,
pattern: extractPattern(),
});
- } else if (type === "boolean") {
+ } else if (type === 'boolean') {
return ({
...element,
- uiType: "boolean"
+ uiType: 'boolean',
});
- } else if (type === "uint8") {
+ } else if (type === 'uint8') {
const range = extractRange(0, +255);
return ({
...element,
- uiType: "number",
+ uiType: 'number',
range: range.expression,
min: range.min,
max: range.max,
- units: this.extractValue(cur, "units") || undefined,
- format: this.extractValue(cur, "format") || undefined,
+ units: this.extractValue(cur, 'units') || undefined,
+ format: this.extractValue(cur, 'format') || undefined,
});
- } else if (type === "uint16") {
+ } else if (type === 'uint16') {
const range = extractRange(0, +65535);
return ({
...element,
- uiType: "number",
+ uiType: 'number',
range: range.expression,
min: range.min,
max: range.max,
- units: this.extractValue(cur, "units") || undefined,
- format: this.extractValue(cur, "format") || undefined,
+ units: this.extractValue(cur, 'units') || undefined,
+ format: this.extractValue(cur, 'format') || undefined,
});
- } else if (type === "uint32") {
+ } else if (type === 'uint32') {
const range = extractRange(0, +4294967295);
return ({
...element,
- uiType: "number",
+ uiType: 'number',
range: range.expression,
min: range.min,
max: range.max,
- units: this.extractValue(cur, "units") || undefined,
- format: this.extractValue(cur, "format") || undefined,
+ units: this.extractValue(cur, 'units') || undefined,
+ format: this.extractValue(cur, 'format') || undefined,
});
- } else if (type === "uint64") {
+ } else if (type === 'uint64') {
const range = extractRange(0, +18446744073709551615);
return ({
...element,
- uiType: "number",
+ uiType: 'number',
range: range.expression,
min: range.min,
max: range.max,
- units: this.extractValue(cur, "units") || undefined,
- format: this.extractValue(cur, "format") || undefined,
+ units: this.extractValue(cur, 'units') || undefined,
+ format: this.extractValue(cur, 'format') || undefined,
});
- } else if (type === "int8") {
+ } else if (type === 'int8') {
const range = extractRange(-128, +127);
return ({
...element,
- uiType: "number",
+ uiType: 'number',
range: range.expression,
min: range.min,
max: range.max,
- units: this.extractValue(cur, "units") || undefined,
- format: this.extractValue(cur, "format") || undefined,
+ units: this.extractValue(cur, 'units') || undefined,
+ format: this.extractValue(cur, 'format') || undefined,
});
- } else if (type === "int16") {
+ } else if (type === 'int16') {
const range = extractRange(-32768, +32767);
return ({
...element,
- uiType: "number",
+ uiType: 'number',
range: range.expression,
min: range.min,
max: range.max,
- units: this.extractValue(cur, "units") || undefined,
- format: this.extractValue(cur, "format") || undefined,
+ units: this.extractValue(cur, 'units') || undefined,
+ format: this.extractValue(cur, 'format') || undefined,
});
- } else if (type === "int32") {
+ } else if (type === 'int32') {
const range = extractRange(-2147483648, +2147483647);
return ({
...element,
- uiType: "number",
+ uiType: 'number',
range: range.expression,
min: range.min,
max: range.max,
- units: this.extractValue(cur, "units") || undefined,
- format: this.extractValue(cur, "format") || undefined,
+ units: this.extractValue(cur, 'units') || undefined,
+ format: this.extractValue(cur, 'format') || undefined,
});
- } else if (type === "int64") {
+ } else if (type === 'int64') {
const range = extractRange(-9223372036854775808, +9223372036854775807);
return ({
...element,
- uiType: "number",
+ uiType: 'number',
range: range.expression,
min: range.min,
max: range.max,
- units: this.extractValue(cur, "units") || undefined,
- format: this.extractValue(cur, "format") || undefined,
+ units: this.extractValue(cur, 'units') || undefined,
+ format: this.extractValue(cur, 'format') || undefined,
});
- } else if (type === "decimal64") {
+ } else if (type === 'decimal64') {
// decimalRange
- const fDigits = Number(this.extractValue(this.extractNodes(cur, "type")[0]!, "fraction-digits")) || -1;
+ const fDigits = Number(this.extractValue(this.extractNodes(cur, 'type')[0]!, 'fraction-digits')) || -1;
if (fDigits === -1) {
throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found decimal64 with invalid fraction-digits.`);
}
const range = extractRange(YangParser.decimalRange[fDigits].min, YangParser.decimalRange[fDigits].max);
return ({
...element,
- uiType: "number",
+ uiType: 'number',
fDigits: fDigits,
range: range.expression,
min: range.min,
max: range.max,
- units: this.extractValue(cur, "units") || undefined,
- format: this.extractValue(cur, "format") || undefined,
+ units: this.extractValue(cur, 'units') || undefined,
+ format: this.extractValue(cur, 'format') || undefined,
});
- } else if (type === "enumeration") {
- const typeNode = this.extractNodes(cur, "type")[0]!;
- const enumNodes = this.extractNodes(typeNode, "enum");
+ } else if (type === 'enumeration') {
+ const typeNode = this.extractNodes(cur, 'type')[0]!;
+ const enumNodes = this.extractNodes(typeNode, 'enum');
return ({
...element,
- uiType: "selection",
+ uiType: 'selection',
options: enumNodes.reduce<{ key: string; value: string; description?: string }[]>((acc, enumNode) => {
if (!enumNode.arg) {
throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found option without name.`);
}
- const ifClause = this.extractValue(enumNode, "if-feature");
- const value = this.extractValue(enumNode, "value");
+ // const ifClause = this.extractValue(enumNode, 'if-feature');
+ const value = this.extractValue(enumNode, 'value');
const enumOption = {
key: enumNode.arg,
value: value != null ? value : enumNode.arg,
- description: this.extractValue(enumNode, "description") || undefined
+ description: this.extractValue(enumNode, 'description') || undefined,
};
// todo: ❗ handle the if clause ⚡
acc.push(enumOption);
return acc;
- }, [])
+ }, []),
});
- } else if (type === "leafref") {
- const typeNode = this.extractNodes(cur, "type")[0]!;
- const vPath = this.extractValue(typeNode, "path");
+ } else if (type === 'leafref') {
+ const typeNode = this.extractNodes(cur, 'type')[0]!;
+ const vPath = this.extractValue(typeNode, 'path');
if (!vPath) {
throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found leafref without path.`);
}
@@ -1244,11 +1314,11 @@ export class YangParser {
const resolve = this.resolveReference.bind(this);
const res: ViewElement = {
...element,
- uiType: "reference",
+ uiType: 'reference',
referencePath: refPath,
- ref(this: ViewElement, currentPath: string) {
- const elementPath = `${currentPath}/${cur.arg}`;
-
+ ref(this: ViewElement, basePath: string) {
+ const elementPath = `${basePath}/${cur.arg}`;
+
const result = resolve(refPath, elementPath);
if (!result) return undefined;
@@ -1262,20 +1332,20 @@ export class YangParser {
isList: this.isList,
default: this.default,
description: this.description,
- } as ViewElement , resolvedPath] || undefined;
- }
+ } as ViewElement, resolvedPath] || undefined;
+ },
};
return res;
- } else if (type === "identityref") {
- const typeNode = this.extractNodes(cur, "type")[0]!;
- const base = this.extractValue(typeNode, "base");
+ } else if (type === 'identityref') {
+ const typeNode = this.extractNodes(cur, 'type')[0]!;
+ const base = this.extractValue(typeNode, 'base');
if (!base) {
throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found identityref without base.`);
}
const res: ViewElement = {
...element,
- uiType: "selection",
- options: []
+ uiType: 'selection',
+ options: [],
};
this._identityToResolve.push(() => {
const identity: Identity = this.resolveIdentity(base, module);
@@ -1288,29 +1358,29 @@ export class YangParser {
res.options = identity.values.map(val => ({
key: val.id,
value: val.id,
- description: val.description
+ description: val.description,
}));
});
return res;
- } else if (type === "empty") {
+ } else if (type === 'empty') {
// todo: ❗ handle empty ⚡
/* 9.11. The empty Built-In Type
The empty built-in type represents a leaf that does not have any
value, it conveys information by its presence or absence. */
return {
...element,
- uiType: "empty",
+ uiType: 'empty',
};
- } else if (type === "union") {
+ } else if (type === 'union') {
// todo: ❗ handle union ⚡
/* 9.12. The union Built-In Type */
- const typeNode = this.extractNodes(cur, "type")[0]!;
- const typeNodes = this.extractNodes(typeNode, "type");
+ const typeNode = this.extractNodes(cur, 'type')[0]!;
+ const typeNodes = this.extractNodes(typeNode, 'type');
const resultingElement = {
...element,
- uiType: "union",
- elements: []
+ uiType: 'union',
+ elements: [],
} as ViewElementUnion;
const resolveUnion = () => {
@@ -1318,13 +1388,13 @@ export class YangParser {
const stm: Statement = {
...cur,
sub: [
- ...(cur.sub ?.filter(s => s.key !== "type") || []),
- node
- ]
+ ...(cur.sub?.filter(s => s.key !== 'type') || []),
+ node,
+ ],
};
return {
...this.getViewElement(stm, module, parentId, currentPath, isList),
- id: node.arg!
+ id: node.arg!,
};
}));
};
@@ -1332,34 +1402,34 @@ export class YangParser {
this._unionsToResolve.push(resolveUnion);
return resultingElement;
- } else if (type === "bits") {
- const typeNode = this.extractNodes(cur, "type")[0]!;
- const bitNodes = this.extractNodes(typeNode, "bit");
+ } else if (type === 'bits') {
+ const typeNode = this.extractNodes(cur, 'type')[0]!;
+ const bitNodes = this.extractNodes(typeNode, 'bit');
return {
...element,
- uiType: "bits",
- flags: bitNodes.reduce<{ [name: string]: number | undefined; }>((acc, bitNode) => {
+ uiType: 'bits',
+ flags: bitNodes.reduce<{ [name: string]: number | undefined }>((acc, bitNode) => {
if (!bitNode.arg) {
throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found bit without name.`);
}
- const ifClause = this.extractValue(bitNode, "if-feature");
- const pos = Number(this.extractValue(bitNode, "position"));
+ // const ifClause = this.extractValue(bitNode, 'if-feature');
+ const pos = Number(this.extractValue(bitNode, 'position'));
acc[bitNode.arg] = pos === pos ? pos : undefined;
return acc;
- }, {})
+ }, {}),
};
- } else if (type === "binary") {
+ } else if (type === 'binary') {
return {
...element,
- uiType: "binary",
- length: extractRange(0, +18446744073709551615, "length"),
+ uiType: 'binary',
+ length: extractRange(0, +18446744073709551615, 'length'),
};
- } else if (type === "instance-identifier") {
+ } else if (type === 'instance-identifier') {
// https://tools.ietf.org/html/rfc7950#page-168
return {
...element,
- uiType: "string",
- length: extractRange(0, +18446744073709551615, "length"),
+ uiType: 'string',
+ length: extractRange(0, +18446744073709551615, 'length'),
};
} else {
// not a build in type, need to resolve type
@@ -1374,13 +1444,13 @@ export class YangParser {
}
// spoof date type here from special string type
- if ((type === 'date-and-time' || type.endsWith(':date-and-time') ) && typeRef.module === "ietf-yang-types") {
- return {
- ...typeRef,
- ...element,
- description: description,
- uiType: "date",
- };
+ if ((type === 'date-and-time' || type.endsWith(':date-and-time')) && typeRef.module === 'ietf-yang-types') {
+ return {
+ ...typeRef,
+ ...element,
+ description: description,
+ uiType: 'date',
+ };
}
return ({
@@ -1391,27 +1461,27 @@ export class YangParser {
}
}
- private resolveStringType(parentElement: ViewElementString, pattern: Expression<RegExp> | undefined, length: { expression: Expression<YangRange> | undefined, min: number, max: number }) {
+ private resolveStringType(parentElement: ViewElementString, pattern: Expression<RegExp> | undefined, length: { expression: Expression<YangRange> | undefined; min: number; max: number }) {
return {
...parentElement,
pattern: pattern != null && parentElement.pattern
- ? { operation: "AND", arguments: [pattern, parentElement.pattern] }
+ ? { operation: 'AND', arguments: [pattern, parentElement.pattern] }
: parentElement.pattern
? parentElement.pattern
: pattern,
length: length.expression != null && parentElement.length
- ? { operation: "AND", arguments: [length.expression, parentElement.length] }
+ ? { operation: 'AND', arguments: [length.expression, parentElement.length] }
: parentElement.length
? parentElement.length
- : length ?.expression,
+ : length?.expression,
} as ViewElementString;
}
- private resolveNumberType(parentElement: ViewElementNumber, range: { expression: Expression<YangRange> | undefined, min: number, max: number }) {
+ private resolveNumberType(parentElement: ViewElementNumber, range: { expression: Expression<YangRange> | undefined; min: number; max: number }) {
return {
...parentElement,
range: range.expression != null && parentElement.range
- ? { operation: "AND", arguments: [range.expression, parentElement.range] }
+ ? { operation: 'AND', arguments: [range.expression, parentElement.range] }
: parentElement.range
? parentElement.range
: range,
@@ -1421,7 +1491,7 @@ export class YangParser {
}
private resolveReferencePath(vPath: string, module: Module) {
- const vPathParser = /(?:(?:([^\/\:]+):)?([^\/]+))/g // 1 = opt: namespace / 2 = property
+ const vPathParser = /(?:(?:([^\/\:]+):)?([^\/]+))/g; // 1 = opt: namespace / 2 = property
return vPath.replace(vPathParser, (_, ns, property) => {
const nameSpace = ns && module.imports[ns] || module.name;
return `${nameSpace}:${property}`;
@@ -1429,20 +1499,20 @@ export class YangParser {
}
private resolveReference(vPath: string, currentPath: string) {
- const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
+ const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
let element: ViewElement | null = null;
- let moduleName = "";
+ let moduleName = '';
const vPathParts = splitVPath(vPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] }));
- const resultPathParts = !vPath.startsWith("/")
- ? splitVPath(currentPath, vPathParser).map(p => { moduleName = p[1] || moduleName ; return { ns: moduleName, property: p[2], ind: p[3] } })
+ const resultPathParts = !vPath.startsWith('/')
+ ? splitVPath(currentPath, vPathParser).map(p => { moduleName = p[1] || moduleName; return { ns: moduleName, property: p[2], ind: p[3] }; })
: [];
for (let i = 0; i < vPathParts.length; ++i) {
const vPathPart = vPathParts[i];
- if (vPathPart.property === "..") {
+ if (vPathPart.property === '..') {
resultPathParts.pop();
- } else if (vPathPart.property !== ".") {
+ } else if (vPathPart.property !== '.') {
resultPathParts.push(vPathPart);
}
}
@@ -1453,30 +1523,30 @@ export class YangParser {
if (j === 0) {
moduleName = pathPart.ns;
const rootModule = this._modules[moduleName];
- if (!rootModule) throw new Error("Could not resolve module [" + moduleName + "].\r\n" + vPath);
+ if (!rootModule) throw new Error('Could not resolve module [' + moduleName + '].\r\n' + vPath);
element = rootModule.elements[`${pathPart.ns}:${pathPart.property}`];
} else if (element && isViewElementObjectOrList(element)) {
const view: ViewSpecification = this._views[+element.viewId];
if (moduleName !== pathPart.ns) {
moduleName = pathPart.ns;
- }
+ }
element = view.elements[pathPart.property] || view.elements[`${moduleName}:${pathPart.property}`];
} else {
- throw new Error("Could not resolve reference.\r\n" + vPath);
+ throw new Error('Could not resolve reference.\r\n' + vPath);
}
- if (!element) throw new Error("Could not resolve path [" + pathPart.property + "] in [" + currentPath + "] \r\n" + vPath);
+ if (!element) throw new Error('Could not resolve path [' + pathPart.property + '] in [' + currentPath + '] \r\n' + vPath);
}
- moduleName = ""; // create the vPath for the resolved element, do not add the element itself this will be done later in the res(...) function
- return [element, resultPathParts.slice(0,-1).map(p => `${moduleName !== p.ns ? `${moduleName=p.ns}:` : ""}${p.property}${p.ind || ''}`).join("/")];
+ moduleName = ''; // create the vPath for the resolved element, do not add the element itself this will be done later in the res(...) function
+ return [element, resultPathParts.slice(0, -1).map(p => `${moduleName !== p.ns ? `${moduleName = p.ns}:` : ''}${p.property}${p.ind || ''}`).join('/')];
}
private resolveView(vPath: string) {
- const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
+ const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
let element: ViewElement | null = null;
let partMatch: RegExpExecArray | null;
let view: ViewSpecification | null = null;
- let moduleName = "";
+ let moduleName = '';
if (vPath) do {
partMatch = vPathParser.exec(vPath);
if (partMatch) {
@@ -1498,13 +1568,13 @@ export class YangParser {
}
if (!element) return null;
}
- } while (partMatch)
+ } while (partMatch);
return element && isViewElementObjectOrList(element) && this._views[+element.viewId] || null;
}
private resolveType(type: string, module: Module) {
- const collonInd = type.indexOf(":");
- const preFix = collonInd > -1 ? type.slice(0, collonInd) : "";
+ const collonInd = type.indexOf(':');
+ const preFix = collonInd > -1 ? type.slice(0, collonInd) : '';
const typeName = collonInd > -1 ? type.slice(collonInd + 1) : type;
const res = preFix
@@ -1514,8 +1584,8 @@ export class YangParser {
}
private resolveGrouping(grouping: string, module: Module) {
- const collonInd = grouping.indexOf(":");
- const preFix = collonInd > -1 ? grouping.slice(0, collonInd) : "";
+ const collonInd = grouping.indexOf(':');
+ const preFix = collonInd > -1 ? grouping.slice(0, collonInd) : '';
const groupingName = collonInd > -1 ? grouping.slice(collonInd + 1) : grouping;
return preFix
@@ -1525,8 +1595,8 @@ export class YangParser {
}
private resolveIdentity(identity: string, module: Module) {
- const collonInd = identity.indexOf(":");
- const preFix = collonInd > -1 ? identity.slice(0, collonInd) : "";
+ const collonInd = identity.indexOf(':');
+ const preFix = collonInd > -1 ? identity.slice(0, collonInd) : '';
const identityName = collonInd > -1 ? identity.slice(collonInd + 1) : identity;
return preFix