2021-11-14 22:16:07 +00:00
|
|
|
/**
|
|
|
|
* @author Martin Karkowski
|
|
|
|
* @email m.karkowski@zema.de
|
|
|
|
* @desc [description]
|
|
|
|
*/
|
|
|
|
|
|
|
|
import { SPLITCHAR } from "./objectMethods";
|
|
|
|
export const SEPARATOR = "/";
|
|
|
|
export const SINGLE_LEVEL_WILDCARD = "+";
|
|
|
|
export const MULTI_LEVEL_WILDCARD = "#";
|
|
|
|
|
|
|
|
export interface TPathCompareResult {
|
|
|
|
/**
|
|
|
|
* The Path to access the data. If a pattern is required to extract the
|
|
|
|
* data, this property is set to false and the property "patternToExtractData"
|
2021-12-04 07:25:26 +00:00
|
|
|
* is filled with the pattern.
|
|
|
|
*
|
|
|
|
* The PathToExtractData is allways false, if the path is smaller then the
|
2021-11-14 22:16:07 +00:00
|
|
|
* pattern
|
2021-12-04 07:25:26 +00:00
|
|
|
*
|
2021-11-14 22:16:07 +00:00
|
|
|
* @example path = "a/b/c"; pattern = "a/#"; => pathToExtractData = "a/b/c"
|
|
|
|
* @example path = "a"; pattern = "a/b/#"; => pathToExtractData = false
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {(string | false)}
|
|
|
|
* @memberof TPathCompareResult
|
|
|
|
*/
|
2021-12-04 07:25:26 +00:00
|
|
|
pathToExtractData: string | false;
|
2021-11-14 22:16:07 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The Pattern to access the data. If no pattern is required to extract the
|
|
|
|
* data, this property is set to false and the property "pathToExtractData"
|
|
|
|
* is filled with the defined path. If the path is longer than the pattern,
|
|
|
|
* than we need to extract the data.
|
2021-12-04 07:25:26 +00:00
|
|
|
*
|
2021-11-14 22:16:07 +00:00
|
|
|
* @example path = "a/b/c"; pattern = "a/#"; => patternToExtractData = "a/#"
|
|
|
|
* @example path = "a"; pattern = "a/b/#"; => patternToExtractData = "a/b/#"
|
|
|
|
* @example path = "a"; pattern = "a"; => patternToExtractData = false
|
|
|
|
* @example path = "a/b"; pattern = "a"; => patternToExtractData = false
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {(string | false)}
|
|
|
|
*/
|
2021-12-04 07:25:26 +00:00
|
|
|
patternToExtractData: string | false;
|
2021-11-14 22:16:07 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* True if the pattern is shorter / equals the pattern and matches.
|
|
|
|
* This means, the path changes a child attribue of the data requested
|
|
|
|
* by the path.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {boolean}
|
|
|
|
* @memberof TPathCompareResult
|
|
|
|
*/
|
|
|
|
affectedByChild: boolean;
|
|
|
|
|
|
|
|
/**
|
2021-12-04 07:25:26 +00:00
|
|
|
* Generally set to true if the pattern is longer then the
|
|
|
|
* path, but they still match in the beginning. This means,
|
2021-11-14 22:16:07 +00:00
|
|
|
* the parent might change the data requested with this pattern
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {boolean}
|
|
|
|
* @memberof TPathCompareResult
|
|
|
|
*/
|
2021-12-04 07:25:26 +00:00
|
|
|
affectedByParent: boolean;
|
2021-11-14 22:16:07 +00:00
|
|
|
|
|
|
|
/**
|
2021-12-04 07:25:26 +00:00
|
|
|
* Generally set to true if the size pf the pattern matches then the
|
2021-11-14 22:16:07 +00:00
|
|
|
* path. This means the data requested with this pattern if directly
|
|
|
|
* changed by the path.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {boolean}
|
|
|
|
* @memberof TPathCompareResult
|
|
|
|
*/
|
2021-12-04 07:25:26 +00:00
|
|
|
affectedOnSameLevel: boolean;
|
2021-11-14 22:16:07 +00:00
|
|
|
|
|
|
|
/**
|
2021-12-04 07:25:26 +00:00
|
|
|
* Shows that there might be matcht. Just the combination of
|
2021-11-14 22:16:07 +00:00
|
|
|
* affectedByChild | affectedOnSameLevel | affectedByChild
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {boolean}
|
|
|
|
* @memberof TPathCompareResult
|
|
|
|
*/
|
|
|
|
affected: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag, indicating whether the pattern contains a pattern or is just
|
|
|
|
* a regular path.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {boolean}
|
|
|
|
* @memberof TPathCompareResult
|
|
|
|
*/
|
2021-12-04 07:25:26 +00:00
|
|
|
containsWildcards: boolean;
|
2021-11-14 22:16:07 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A Flag showing, that the pattern contains more segments than
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {boolean}
|
|
|
|
* @memberof TPathCompareResult
|
|
|
|
*/
|
|
|
|
patternLengthComparedToPathLength: ">" | "=" | "<";
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
2021-11-14 22:16:07 +00:00
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
export type TcomparePatternAndPathFunc = (
|
|
|
|
pattern: string,
|
|
|
|
path: string,
|
|
|
|
options?: {
|
|
|
|
matchTopicsWithoutWildcards?: boolean;
|
|
|
|
}
|
|
|
|
) => TPathCompareResult;
|
2021-11-14 22:16:07 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper to generate a Result.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @export
|
|
|
|
* @param {Partial<TPathCompareResult>} [res={}]
|
|
|
|
* @return {*} {TPathCompareResult}
|
|
|
|
*/
|
2021-12-04 07:25:26 +00:00
|
|
|
export function generateResult(
|
|
|
|
res: Partial<TPathCompareResult> = {}
|
|
|
|
): TPathCompareResult {
|
2021-11-14 22:16:07 +00:00
|
|
|
let defaultResult: TPathCompareResult = {
|
|
|
|
affected: false,
|
|
|
|
affectedByChild: false,
|
|
|
|
affectedByParent: false,
|
|
|
|
affectedOnSameLevel: false,
|
|
|
|
containsWildcards: false,
|
|
|
|
patternToExtractData: false,
|
|
|
|
patternLengthComparedToPathLength: "=",
|
2021-12-04 07:25:26 +00:00
|
|
|
pathToExtractData: false,
|
2021-11-14 22:16:07 +00:00
|
|
|
};
|
|
|
|
defaultResult = Object.assign(defaultResult, res);
|
2021-12-04 07:25:26 +00:00
|
|
|
defaultResult.affected =
|
|
|
|
defaultResult.affectedByChild ||
|
|
|
|
defaultResult.affectedByParent ||
|
|
|
|
defaultResult.affectedOnSameLevel;
|
2021-11-14 22:16:07 +00:00
|
|
|
return Object.assign(defaultResult, res);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Matches the given path, with the pattern and determines, if the path might affect
|
2021-12-04 07:25:26 +00:00
|
|
|
* the given pattern.
|
2021-11-14 22:16:07 +00:00
|
|
|
*
|
|
|
|
* @example path = "a/b/c"; pattern = "a/#"; => totalPath = "a/b/c"; diffPath = "b/c"
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @export
|
|
|
|
* @param {string} pathPattern The pattern to test
|
|
|
|
* @param {string} contentPath The path to use as basis
|
|
|
|
* @return {*} {TPathCompareResult}
|
|
|
|
*/
|
2021-12-04 07:25:26 +00:00
|
|
|
export function comparePatternAndPath(
|
|
|
|
pathPattern: string,
|
|
|
|
contentPath: string,
|
|
|
|
options: {
|
|
|
|
matchTopicsWithoutWildcards?: boolean;
|
|
|
|
} = {
|
|
|
|
matchTopicsWithoutWildcards: false,
|
|
|
|
}
|
|
|
|
): TPathCompareResult {
|
2021-11-14 22:16:07 +00:00
|
|
|
if (containsWildcards(contentPath)) {
|
2021-12-04 07:25:26 +00:00
|
|
|
throw Error(
|
|
|
|
"The Path is invalid. The path should not contain pattern-related chars '#' or '+'."
|
|
|
|
);
|
2021-11-14 22:16:07 +00:00
|
|
|
}
|
|
|
|
if (!patternIsValid(pathPattern)) {
|
|
|
|
throw Error("The Pattern is invalid.");
|
|
|
|
}
|
|
|
|
if (!patternIsValid(contentPath)) {
|
|
|
|
throw Error("The Path is invalid.");
|
|
|
|
}
|
|
|
|
|
|
|
|
const _containsWildcards = containsWildcards(pathPattern);
|
|
|
|
const patternSegments = pathPattern.split(SEPARATOR);
|
|
|
|
const contentPathSegments = contentPath.split(SEPARATOR);
|
|
|
|
|
|
|
|
const patternLength = patternSegments.length;
|
|
|
|
const contentPathLength = contentPathSegments.length;
|
|
|
|
|
|
|
|
// Define the Char for the comparer
|
|
|
|
let patternLengthComparedToPathLength: ">" | "=" | "<" = "=";
|
2021-12-04 07:25:26 +00:00
|
|
|
if (patternLength > contentPathLength)
|
|
|
|
patternLengthComparedToPathLength = ">";
|
|
|
|
else if (patternLength < contentPathLength)
|
|
|
|
patternLengthComparedToPathLength = "<";
|
2021-11-14 22:16:07 +00:00
|
|
|
|
|
|
|
// If both, the pattern and the path are equal => return the result.
|
|
|
|
if (pathPattern === contentPath) {
|
|
|
|
return generateResult({
|
|
|
|
affectedOnSameLevel: true,
|
|
|
|
pathToExtractData: contentPath,
|
|
|
|
patternLengthComparedToPathLength,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the Path is not realy defined.
|
|
|
|
if (contentPath === "") {
|
|
|
|
return generateResult({
|
|
|
|
affectedByParent: true,
|
|
|
|
patternToExtractData: _containsWildcards ? pathPattern : false,
|
|
|
|
pathToExtractData: _containsWildcards ? false : pathPattern,
|
|
|
|
patternLengthComparedToPathLength: ">",
|
2021-12-04 07:25:26 +00:00
|
|
|
containsWildcards: _containsWildcards,
|
2021-11-14 22:16:07 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
if (pathPattern === "") {
|
|
|
|
return generateResult({
|
|
|
|
affectedByChild: true,
|
|
|
|
pathToExtractData: "",
|
|
|
|
patternLengthComparedToPathLength: "<",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.matchTopicsWithoutWildcards) {
|
|
|
|
if (contentPath.startsWith(pathPattern)) {
|
|
|
|
// Path is longer then the Pattern;
|
|
|
|
// => A Change is performed by "Child",
|
|
|
|
if (_containsWildcards) {
|
|
|
|
return generateResult({
|
|
|
|
affectedByChild: true,
|
|
|
|
pathToExtractData: contentPath,
|
|
|
|
patternLengthComparedToPathLength: patternLengthComparedToPathLength,
|
2021-12-04 07:25:26 +00:00
|
|
|
containsWildcards: _containsWildcards,
|
2021-11-14 22:16:07 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return generateResult({
|
|
|
|
affectedByChild: true,
|
|
|
|
pathToExtractData: pathPattern,
|
|
|
|
patternLengthComparedToPathLength: patternLengthComparedToPathLength,
|
|
|
|
containsWildcards: _containsWildcards,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (pathPattern.startsWith(contentPath)) {
|
|
|
|
// Pattern is longer then the path;
|
2021-12-04 07:25:26 +00:00
|
|
|
// => A Change might be initated by
|
2021-11-14 22:16:07 +00:00
|
|
|
// the super element
|
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
// The PathToExtractData is allways false, if the path is smaller then the
|
2021-11-14 22:16:07 +00:00
|
|
|
// pattern
|
|
|
|
|
|
|
|
if (_containsWildcards) {
|
|
|
|
return generateResult({
|
|
|
|
affectedByParent: true,
|
|
|
|
patternToExtractData: pathPattern,
|
|
|
|
patternLengthComparedToPathLength: patternLengthComparedToPathLength,
|
2021-12-04 07:25:26 +00:00
|
|
|
containsWildcards: _containsWildcards,
|
2021-11-14 22:16:07 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return generateResult({
|
|
|
|
affectedByParent: true,
|
|
|
|
// No Pattern is used.
|
|
|
|
pathToExtractData: pathPattern,
|
|
|
|
patternLengthComparedToPathLength: patternLengthComparedToPathLength,
|
|
|
|
containsWildcards: _containsWildcards,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let partialPath = "";
|
|
|
|
|
|
|
|
// Iterate over the Segments.
|
|
|
|
for (let i = 0; i < patternLength; i++) {
|
|
|
|
// Store the current Pattern Segment
|
|
|
|
const currentPattern = patternSegments[i];
|
|
|
|
|
|
|
|
// We need to know, if there is SINGLE_LEVEL_WILDCARD or MULTI_LEVEL_WILDCARD
|
|
|
|
// there fore we will extract the Wildlevels.
|
|
|
|
const patternChar = currentPattern[0];
|
|
|
|
const currentPath = contentPathSegments[i];
|
|
|
|
|
|
|
|
if (currentPath === undefined) {
|
|
|
|
// Our Pattern is larger then our contentPath.
|
2021-12-04 07:25:26 +00:00
|
|
|
// So we dont know, whether we will get some
|
2021-11-14 22:16:07 +00:00
|
|
|
// data. Therefore we have to perform a query
|
|
|
|
// later ==> Set The Path / Pattern.
|
|
|
|
|
|
|
|
if (_containsWildcards) {
|
|
|
|
// But we contain Patterns.
|
|
|
|
// So we are not allowed to build a
|
|
|
|
// diff object.
|
|
|
|
return generateResult({
|
|
|
|
affectedByParent: true,
|
|
|
|
patternToExtractData: pathPattern,
|
|
|
|
patternLengthComparedToPathLength: patternLengthComparedToPathLength,
|
2021-12-04 07:25:26 +00:00
|
|
|
containsWildcards: _containsWildcards,
|
2021-11-14 22:16:07 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
throw Error("Implementation Error! This should not happen");
|
|
|
|
}
|
|
|
|
} else if (currentPath == currentPattern) {
|
|
|
|
// The Patterns Match
|
|
|
|
// We now store the correct path of our segment.
|
2021-12-04 07:25:26 +00:00
|
|
|
partialPath =
|
|
|
|
partialPath.length > 0
|
|
|
|
? `${partialPath}${SEPARATOR}${currentPath}`
|
|
|
|
: currentPath;
|
2021-11-14 22:16:07 +00:00
|
|
|
} else if (patternChar === MULTI_LEVEL_WILDCARD) {
|
|
|
|
// We know, that MULTI_LEVEL_WILDCARDs are only at the end of the
|
|
|
|
// pattern. So it might happen, that:
|
|
|
|
// a) our length of the pattern is the same length as the content path
|
|
|
|
// b) our length of the pattern is smaller then length as the content path
|
|
|
|
//
|
|
|
|
// Our statement before alread tested, that either case a) or b) fits. Otherwise
|
|
|
|
// another ifstatement is valid and we wont enter this statement here.
|
|
|
|
|
|
|
|
// // We add the segment to testedCorrectPath
|
|
|
|
// testedCorrectPath = testedCorrectPath.length > 0 ? `${testedCorrectPath}${SEPARATOR}${currentPath}` : currentPath;
|
|
|
|
|
|
|
|
if (patternLengthComparedToPathLength == "=") {
|
|
|
|
// Case a)
|
|
|
|
return generateResult({
|
|
|
|
affectedOnSameLevel: true,
|
|
|
|
pathToExtractData: contentPath,
|
|
|
|
patternLengthComparedToPathLength,
|
|
|
|
containsWildcards: _containsWildcards,
|
|
|
|
});
|
|
|
|
} else if (patternLengthComparedToPathLength == "<") {
|
|
|
|
// Case b)
|
|
|
|
return generateResult({
|
|
|
|
affectedByChild: true,
|
|
|
|
pathToExtractData: contentPath,
|
|
|
|
patternLengthComparedToPathLength,
|
|
|
|
containsWildcards: _containsWildcards,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
throw Error("Implementation Error!");
|
|
|
|
}
|
|
|
|
} else if (patternChar === SINGLE_LEVEL_WILDCARD) {
|
|
|
|
// Store the correct path.
|
2021-12-04 07:25:26 +00:00
|
|
|
partialPath =
|
|
|
|
partialPath.length > 0
|
|
|
|
? `${partialPath}${exports.SEPARATOR}${currentPath}`
|
|
|
|
: currentPath;
|
|
|
|
} else if (
|
|
|
|
patternChar !== SINGLE_LEVEL_WILDCARD &&
|
|
|
|
currentPattern !== currentPath
|
|
|
|
) {
|
2021-11-14 22:16:07 +00:00
|
|
|
return generateResult({
|
|
|
|
patternLengthComparedToPathLength: patternLengthComparedToPathLength,
|
|
|
|
containsWildcards: _containsWildcards,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const diff = contentPath.slice(partialPath.length + 1);
|
|
|
|
|
|
|
|
return generateResult({
|
|
|
|
affectedOnSameLevel: diff.length == 0,
|
|
|
|
affectedByChild: diff.length > 1,
|
|
|
|
pathToExtractData: partialPath,
|
|
|
|
patternLengthComparedToPathLength,
|
|
|
|
containsWildcards: _containsWildcards,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines, whether the given string contains a single level card or not.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @export
|
|
|
|
* @param {string} str String to check
|
|
|
|
* @return {*} {boolean}
|
|
|
|
*/
|
|
|
|
export function containsWildcards(str: string): boolean {
|
2021-12-04 07:25:26 +00:00
|
|
|
return (
|
|
|
|
str.includes(SINGLE_LEVEL_WILDCARD) || str.includes(MULTI_LEVEL_WILDCARD)
|
|
|
|
);
|
2021-11-14 22:16:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to test if a pattern is valid
|
2021-12-04 07:25:26 +00:00
|
|
|
*
|
2021-11-14 22:16:07 +00:00
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @export
|
|
|
|
* @param {string} str
|
|
|
|
* @return {*} {boolean}
|
|
|
|
*/
|
|
|
|
export function patternIsValid(str: string): boolean {
|
|
|
|
if (str === "") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const splitted = str.split(SPLITCHAR);
|
|
|
|
const lastIndex = splitted.length - 1;
|
2021-12-04 07:25:26 +00:00
|
|
|
return splitted
|
|
|
|
.map((value, idx) => {
|
|
|
|
if (value) {
|
|
|
|
if (value === MULTI_LEVEL_WILDCARD) {
|
|
|
|
return idx === lastIndex;
|
|
|
|
}
|
|
|
|
return true;
|
2021-11-14 22:16:07 +00:00
|
|
|
}
|
2021-12-04 07:25:26 +00:00
|
|
|
return false;
|
|
|
|
})
|
|
|
|
.reduce((prev, current) => prev && current, true);
|
|
|
|
}
|