"use strict";
/*=
* Release 1.03 page 152 OPC Unified Architecture, Part 4
* Annex A (informative) BNF definitions
* BNF for RelativePath
*/
const assert = require("node-opcua-assert").assert;
const _ = require("underscore");
const resolveNodeId = require("node-opcua-nodeid").resolveNodeId;
const QualifiedName = require("node-opcua-data-model").QualifiedName;
/*
“/2:Block&.Output” Follows any forward hierarchical Reference with target BrowseName = “2:Block.Output”.
“/3:Truck.0:NodeVersion”
Follows any forward hierarchical Reference with target BrowseName = “3:Truck” and from there a forward
Aggregates Reference to a target with BrowseName “0:NodeVersion”.
“<1:ConnectedTo>1:Boiler/1:HeatSensor”
Follows any forward Reference with a BrowseName = ‘1:ConnectedTo’ and
finds targets with BrowseName = ‘1:Boiler’. From there follows any hierarchical
Reference and find targets with BrowseName = ‘1:HeatSensor’.
“<1:ConnectedTo>1:Boiler/”
Follows any forward Reference with a BrowseName = ‘1:ConnectedTo’ and finds targets
with BrowseName = ‘1:Boiler’. From there it finds all targets of hierarchical References.
“<0:HasChild>2:Wheel”
Follows any forward Reference with a BrowseName = ‘HasChild’ and qualified
with the default OPC UA namespace. Then find targets with BrowseName =
‘Wheel’ qualified with namespace index ‘2’.
“<!HasChild>Truck”
Follows any inverse Reference with a BrowseName = ‘HasChild’. Then find targets with BrowseName = ‘Truck’.
In both cases, the namespace component of the BrowseName is assumed to be 0.
“<0:HasChild>”
Finds all targets of forward References with a BrowseName = ‘HasChild’
and qualified with the default OPC UA namespace.
*/
/*
* / The forward slash character indicates that the Server is to follow
* any subtype of HierarchicalReferences.
* . The period (dot) character indicates that the Server is to follow
* any subtype of a Aggregates ReferenceType.
* <[#!ns:]ReferenceType>
* A string delimited by the ‘<’ and ‘>’ symbols specifies the BrowseName
* of a ReferenceType to follow. By default, any References of the subtypes
* the ReferenceType are followed as well. A ‘#’ placed in front of the BrowseName
* indicates that subtypes should not be followed.
* A ‘!’ in front of the BrowseName is used to indicate that the inverse Reference
* should be followed.
* The BrowseName may be qualified with a namespace index (indicated by a numeric
* prefix followed by a colon). This namespace index is used specify the namespace
* component of the BrowseName for the ReferenceType. If the namespace prefix is
* omitted then namespace index 0 is used.
*/
const hierarchicalReferenceTypeNodeId = resolveNodeId("HierarchicalReferences");
const aggregatesReferenceTypeNodeId = resolveNodeId("Aggregates");
const RelativePath = require("../_generated_/_auto_generated_RelativePath").RelativePath;
// The following BNF describes the syntax of the RelativePath text format.
// <relative-path> ::= <reference-type> <browse-name> [relative-path]
// <reference-type> ::= '/' | '.' | '<' ['#'] ['!'] <browse-name> '>'
// <browse-name> ::= [<namespace-index> ':'] <name>
// <namespace-index> ::= <digit> [<digit>]
// <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
// <name> ::= (<name-char> | '&' <reserved-char>) [<name>]
// <reserved-char> ::= '/' | '.' | '<' | '>' | ':' | '#' | '!' | '&'
// <name-char> ::= All valid characters for a String (see Part 3) excluding reserved-chars.
//
const name_char = /[^/.<>:#!&]/;
const reserved_char = /[/.<>:#!&]/;
const regName = new RegExp( "(" + name_char.source + "|(&" + reserved_char.source +"))+");
const regNamespaceIndex = /[0-9]+/;
const regBrowseName = new RegExp("("+ regNamespaceIndex.source +":)?(" + regName.source+")");
const regReferenceType = new RegExp("/|\\.|(<(#)?(!)?("+ regBrowseName.source +")>)");
const regRelativePath = new RegExp("("+regReferenceType.source+")("+regBrowseName.source+")?");
function unescape(str) {
return str.replace(/&/g,"");
}
function makeQualifiedName(mm) {
const strName = mm[10];
if (!strName || strName.length===0) {
return new QualifiedName();
}
const namespaceIndex = mm[11] ? parseInt(mm[11]) : 0;
const name = unescape(mm[12]);
return new QualifiedName({namespaceIndex: namespaceIndex,name: name});
}
/**
* construct a RelativePath from a string containing the relative path description.
* The string must comply to the OPCUA BNF for RelativePath ( see part 4 - Annexe A)
* @method makeRelativePath
* @param str {String}
* @param addressSpace {AddressSpace}
* @return {RelativePath}
*
* @example:
*
* var relativePath = makeRelativePath("/Server.ServerStatus.CurrentTime");
*
*/
function makeRelativePath(str,addressSpace) {
let r ={
elements:[]
};
while (str.length>0) {
const matches = str.match(regRelativePath);
if (!matches) {
throw new Error("Malformed relative path :'" + str +"'");
}
// console.log(mm);
let referenceTypeId, includeSubtypes, isInverse;
//
// ------------ extract reference type
//
const refStr = matches[1];
if (refStr === "/" ) {
referenceTypeId= hierarchicalReferenceTypeNodeId;
isInverse= false;
includeSubtypes= true;
}else if (refStr === "." ) {
referenceTypeId= aggregatesReferenceTypeNodeId;
isInverse= false;
includeSubtypes= true;
} else {
// match 3 => "#" or null
includeSubtypes = (matches[3] !== "#");
// match 4 => "!" or null
isInverse = (matches[4] === "!");
// match 5
// namespace match 6 ( ns:)
// name match 7
const ns = matches[6] ? parseInt(matches[6]) :0;
const name = matches[7];
if ( !matches[6] ) {
//xx console.log( mm[6])
referenceTypeId = resolveNodeId(name);
} else {
// AddressSpace.prototype.findReferenceType = function (refType,namespace)
referenceTypeId = addressSpace.findReferenceType(name,ns);
}
assert(referenceTypeId && !referenceTypeId.isEmpty());
}
r.elements.push({
referenceTypeId: referenceTypeId,
isInverse: isInverse,
includeSubtypes: includeSubtypes,
targetName: makeQualifiedName(matches)
});
str = str.substr(matches[0].length);
}
r = new RelativePath(r);
//xx console.log(r.toString());
return r;
}
exports.makeRelativePath = makeRelativePath;