Files
xSaherElmClient/platforms/electron/modules/tools/x-value.tools.js
T
2026-04-17 17:04:44 +03:30

1397 lines
29 KiB
JavaScript

const XTypeTools = require('./x-type-detector.tools');
/**
* validate a value as an argument ...
*
* @param {string} value the value which going to validate ...
* @returns
*/
function isValidArg(value) {
//
let result = false;
//
result = value &&
value.length > 0;
//
return result;
}
/**
* validate a collection of valuea as an arguments ...
*
* @param {string|string[]} values the value collection which going to checked ...
* @returns
*/
function isValidArgs(values) {
//
// Normalize Values ...
const normalValues = toNormalArray(values);
if (!hasChildArray(normalValues)) {
return false;
}
//
let result = normalValues.every(nv => isValidArg(nv));
return result;
}
/**
* converts content to an array object ...
*
* @param {string|string[]} content the content which required to normalized ...
* @returns {string[]} array object ...
*/
function toNormalArray(content = '') {
//
// Validate Args ...
if (!isValidArg(content)) {
return [];
}
//
const result = Array.isArray(content) ?
[...content] :
content.includes(',') &&
content !== ',' ?
[...content.split(',')] :
[content];
//
return result;
}
/**
* determines value is array and has atleast one child ...
*
* @param {any} value the value which going to checked ...
* @returns {boolean} result of checking ...
*/
function hasChildArray(value) {
//
let result = false;
result = value &&
Array.isArray(value) &&
value.length > 0;
//
return result;
}
/**
* check an string is ends with specified symbol ...
*
* @param {string} symbol specified search symbol ...
* @param {string} value specified search content ...
* @param {boolean} ignoreCase specified search case sensitive or not ...
* @returns
*/
function endsWidth(
symbol = '',
value = '',
ignoreCase = true
) {
//
if (!isValidArg(value)) {
return false;
}
//
if (!isValidArg(symbol)) {
return true;
}
//
// TODO: uncomment it if necessary ...
// symbol = symbol.trim();
//
let result = false;
const subtractedContent = value.substring(value.length - symbol.length);
result = !!ignoreCase ?
subtractedContent
.toLowerCase() === symbol
.toLowerCase() :
subtractedContent === symbol;
//
return result;
}
/**
* surround specified content by provided symbol ...
*
* @param {string} symbol surround string ...
* @param {string} content used content to surrounded by symbol ...
* @returns surrounded content ...
*/
function surroundBy(
symbol = '',
content = ''
) {
//
// Validate Args ...
if (!isValidArg(content)) {
content = '';
}
//
if (!isValidArg(symbol)) {
symbol = '';
}
//
if (isSurrounded(symbol, content)) {
return content;
}
//
const result = `${symbol}${content}${symbol}`;
return result;
}
/**
* convert a parsed sign to clear string ...
*
* @param {string} value a parsed content ...
* @returns clear parsed signs ...
*/
function toStringExpression(value) {
//
let result = '';
result = value;
//
if (
!value ||
!isValidArg(value) ||
!isSurroundedString(value)
) {
return result;
}
//
const startSymbol = value.charAt(0);
const endSymbol = value.charAt(value.length - 1);
//
if (startSymbol !== endSymbol) {
return result;
}
//
if (
startSymbol === "\"" ||
startSymbol === "\'"
) {
result = clearSurround(startSymbol, value);
}
//
return result;
}
/**
* find and cleare proposed candidates from specified content ...
*
* @param {string|string[]} candidates the proposed string(s) whic going to cleared from content ...
* @param {string} content the destination content which used to find and replace candidates on it ...
* @returns cleared content ...
*/
function clearContent(
candidates,
content
) {
//
// Validate Args ...
if (
!candidates ||
!isValidArg(content)
) {
return '';
}
//
// Normalize candidates ...
const normalCandidates = toNormalArray(candidates)
.filter(c => isValidArg(c));
if (!hasChildArray(normalCandidates)) {
return content;
}
//
// define regexp for content ...
const regExpExpression = normalCandidates.join('|');
const regExp = new RegExp(
regExpExpression,
'gi'
);
//
let result = '';
result = content.replace(regExp, '');
//
return result;
}
/**
* clear surrounded symbol from content ...
*
* @param {string} symbol specified search symbol ...
* @param {string} content specified content to clear ...
* @returns
*/
function clearSurround(
symbol = '',
content = ''
) {
//
// Validate Args ...
if (
!isValidArg(content) ||
(isValidArg(symbol) &&
symbol.length >= content.length - 1)
) {
return '';
}
//
// Validate Surround ...
if (!isSurrounded(symbol, content)) {
return content;
}
//
if (!isValidArg(symbol)) {
return content;
}
//
let result = content.substring(symbol.length, content.length - symbol.length);
return result;
}
/**
* clear object sign ...
*
* @param {string} content
* @returns cleared content ...
*/
function clearObjectSurround(content = '') {
//
// Validate Args ...
if (
!isValidArg(content) ||
!isSurroundedObject(content)
) {
return content;
}
//
const endIndex = content.length - (
endsWidth('}', content) ?
1 :
endsWidth('},', content) ||
endsWidth('};', content) ||
endsWidth('}\n', content) ?
2 :
0
);
//
const result = content.substring(1, endIndex);
return result;
}
/**
* clear array sign ...
*
* @param {string} content
* @returns cleared content ...
*/
function clearArraySurround(content = '') {
//
// Validate Args ...
if (
!isValidArg(content) ||
!isSurroundedArray(content)
) {
return content;
}
//
const endIndex = content.length - (
endsWidth(']', content) ?
1 :
endsWidth('],', content) ||
endsWidth('];', content) ||
endsWidth(']\n', content) ?
2 :
0
);
//
const result = content.substring(1, endIndex);
return result;
}
/**
* slice a content to individual parts ...
*
* @param {number|number[]} indexes which indexes used to slice ...
* @param {strng} content the content which going to sliced ...
* @returns sliced parts of content ...
*/
function sliceContent(indexes, content) {
//
// Validate Args ...
if (
!isValidArg(indexes) ||
!isValidArg(content)
) {
return [];
}
//
// Normalize indexes ...
const normalIndexes = toNormalArray(indexes)
.filter(index => index > -1 && index < content.length);
if (!hasChildArray(normalIndexes)) {
return [];
}
//
let result = [''];
result.pop();
//
let lastIndex = 0;
for (const index of normalIndexes) {
//
const part = content.substring(lastIndex, index);
if (isValidArg(part)) {
result.push(part.trim());
}
//
lastIndex = index + 1;
}
//
// check remained parts ...
if (lastIndex < content.length) {
//
const part = content.substring(lastIndex);
if (isValidArg(part)) {
result.push(part.trim());
}
}
//
return result;
}
/**
* check an string is starts and ends with specified symbol ...
*
* @param {string} symbol specified search symbol ...
* @param {string} content specified search content ...
* @param {boolean} ignoreCase specified search case sensitive or not ...
* @returns
*/
function isSurrounded(
symbol = '',
content = '',
ignoreCase = true
) {
//
// Validate Args ...
if (!isValidArg(content)) {
return false;
}
//
if (!isValidArg(symbol)) {
return true;
}
//
symbol = symbol.trim();
//
let result = false;
result = (!!ignoreCase ?
content
.toLowerCase()
.startsWith(symbol.toLowerCase()) :
content.startsWith(symbol)
) &&
endsWidth(symbol, content, ignoreCase);
//
return result;
}
/**
* determines a content is an string surrounded value or not ...
*
* @param {string} content a content which going to check ...
* @returns
*/
function isSurroundedString(content = '') {
//
// Validate Args ...
if (!isValidArg(content)) {
return false;
}
//
let result = false;
result = (
content.startsWith('\'') &&
(
endsWidth('\'', content) ||
endsWidth('\',', content) ||
endsWidth('\';', content) ||
endsWidth('\'\n', content)
) ||
content.startsWith('\"') &&
(
endsWidth('\"', content) ||
endsWidth('\",', content) ||
endsWidth('\";', content) ||
endsWidth('\"\n', content)
) ||
content.startsWith('`') &&
(
endsWidth('\`', content) ||
endsWidth('\`,', content) ||
endsWidth('\`;', content) ||
endsWidth('\`\n', content)
)
);
return result;
}
/**
* check a content is an array content or not ...
*
* @param {string} content
* @returns
*/
function isSurroundedArray(content = '') {
//
// Validate Args ...
if (!isValidArg(content)) {
return false;
}
//
let result = false;
result = content.startsWith('[') &&
(
endsWidth(']', content) ||
endsWidth('],', content) ||
endsWidth('];', content) ||
endsWidth(']\n', content)
);
return result;
}
/**
* check a content is an object content or not ...
*
* @param {string} content
* @returns
*/
function isSurroundedObject(content = '') {
//
// Validate Args ...
if (!isValidArg(content)) {
return false;
}
//
let result = false;
result = content.startsWith('{') &&
(
endsWidth('}', content) ||
endsWidth('},', content) ||
endsWidth('};', content) ||
endsWidth('}\n', content)
);
return result;
}
/**
* check an index model is contains inside anothers or not ...
*
* @param {{ start: number, end: number}} source the source index model to check ...
* @param {...{ start: number, end: number}} dest the collection of index models which going to check ...
* @returns
*/
function isIndexInside(source, ...dest) {
//
let result = false;
//
// Validate Args ...
if (!source || !dest) {
return result;
}
//
// Check idx is standard ...
if (
!source ||
!source.end ||
!source.start ||
source.start > source.end
) {
return false;
}
//
// Check types of destinations ...
for (let index of dest) {
//
// Check idx is standard ...
if (
!index ||
!index.end ||
!index.start ||
index.start > index.end
) {
return false;
}
}
//
if (dest.includes(source)) {
dest = dest.filter(d => d !== source);
}
//
const insideContentIndex = dest.find(dIndex => {
//
const result = source.start >= dIndex.start &&
source.start <= dIndex.end &&
source.end >= dIndex.start &&
source.end <= dIndex.end;
return result;
});
//
result = insideContentIndex;
return result;
}
/**
* check a number exists in
* @param {number} idx the number which going to check ...
* @param {{ start: number, end: number}} index the index model
* @returns
*/
function isInsideIndex(idx = -1, index) {
//
let result = false;
//
// Validate Args ...
if (
!index ||
!index.hasOwnProperty('end') ||
!index.hasOwnProperty('start')
) {
return result;
}
//
result = idx > index.start && idx < index.end;
return result;
}
/**
* determines an object is key/value and type ...
*
* @param {any} content the object which we are going to check ...
* @returns
*/
function isKeyValueType(content) {
//
const result = content &&
content.hasOwnProperty('key') &&
content.hasOwnProperty('type') &&
content.hasOwnProperty('value');
//
return result;
}
/**
* find all indexes of token(s) in content ...
*
* @param {string|string[]} tokens the tokens which require to search ...
* @param {string} content the content which using to search ...
* @returns {number[]} all occured indexes ...
*/
function findAllIndexes(
tokens,
content
) {
//
// Validate Args ...
if (
!isValidArg(tokens) ||
!isValidArg(content)
) {
return [];
}
//
// normalize tokens ...
const normalTokens = toNormalArray(tokens);
if (!hasChildArray(normalTokens)) {
return [];
}
//
// Parse contents ...
let match;
const result = [];
const regExp = new RegExp(
normalTokens.join('|'),
'g'
);
while ((match = regExp.exec(content)) !== null) {
result.push(match.index);
}
if (!hasChildArray(result)) {
return [];
}
//
return result;
}
/**
* find nearest symbol in a content from specified index ...
*
* @param {string|string[]} candidates specifies which symbols to find ...
* @param {string} content te content for seasrch ...
* @param {number} startFromIndex the index of start position ...
* @returns
*/
function findNearest(
candidates,
content = '',
startFromIndex = 0
) {
//
// Validate Args ...
if (
!isValidArg(content) ||
!isValidArg(candidates) ||
!isValidArg(content.trim())
) {
return '';
}
//
// Normalize Content ...
content = content.trim();
//
// Normalize Starts From ...
startFromIndex = startFromIndex < 0 || startFromIndex > content.length - 1 ? 0 : startFromIndex;
//
// Normalize Symbols ...
const normalCandidates = toNormalArray(candidates);
if (!hasChildArray(normalCandidates)) {
return '';
}
//
let nearestIndex = findNearestIndex(
normalCandidates,
content,
startFromIndex
);
if (nearestIndex < 0) {
return '';
}
//
let result = content.substring(
nearestIndex,
nearestIndex + Math.max(...normalCandidates.map(nc => nc.length))
).trim();
return result;
}
/**
* search candidates in content and find nearest one and return it ...
*
* @param {string|string[]} candidates which candidates required to check ...
* @param {string} content the content which required to search candidates ...
* @param {number} startFromIndex the proposed index to start searching content from on ...
* @returns
*/
function findNearestIndex(
candidates = [''],
content = '',
startFromIndex = 0,
ignoreClosedItems = false
) {
//
// Validate Args ...
if (
!isValidArg(content) ||
!isValidArg(candidates)
) {
return -1;
}
//
// Mormalize startFromIndex value ...
startFromIndex = startFromIndex < 0 ?
0 :
startFromIndex > content.length - 1 ?
content.length - 1 :
startFromIndex;
//
// Normalize candidates ...
let normalCandidates = toNormalArray(candidates);
//
if (!!ignoreClosedItems) {
normalCandidates = normalCandidates
.map(c => content.indexOf(
c,
startFromIndex
)
).map(c => +c)
.filter(c => +c > -1);
} else {
normalCandidates = findAllIndexesOutOfCloseds(normalCandidates, content)
.filter(c => c >= startFromIndex);
}
if (!hasChildArray(normalCandidates)) {
return -1;
}
//
let result = -1;
result = Math.min(...normalCandidates);
//
return result;
}
/**
* find close index of specific sign in destination content ...
*
* @param {string} openSymbol open of area sign ...
* @param {string} closeSymbol close of area sign ...
* @param {string} content the destination content for parsing ...
* @param {number} startsFrom an start index of content for start parsing ...
* @returns
*/
function findCloseIndex(
openSymbol = '',
closeSymbol = '',
content = '',
startsFrom = -1
) {
//
// Validate Args ...
if (
startsFrom < 0 ||
!isValidArg(content) ||
!isValidArg(openSymbol) ||
!isValidArg(closeSymbol) ||
openSymbol === closeSymbol ||
!content.includes(openSymbol) ||
!content.includes(closeSymbol) ||
startsFrom > content.length - 1
) {
return -1;
}
//
let result = -1;
//
let index = startsFrom;
const openSignStack = [];
while (index < content.length) {
//
const openCandidate = content.substring(index, index + openSymbol.length);
const closeCandidate = content.substring(index, index + closeSymbol.length);
//
if (openCandidate === openSymbol) {
openSignStack.push(index);
} else if (closeCandidate === closeSymbol) {
//
if (openSignStack.length > 0) {
openSignStack.pop();
}
//
if (openSignStack.length === 0) {
result = index;
break;
}
}
//
index++;
}
//
return result;
}
/**
* find closed contents of specific sign in destination content ...
*
* @param {string} openSymbol open of area sign ...
* @param {string} closeSymbol close of area sign ...
* @param {string} content the destination content for parsing ...
* @param {number} startsFrom an start index of content for start parsing ...
* @returns
*/
function findClosedContent(
openSymbol = '',
closeSymbol = '',
content = '',
startsFrom = 0,
) {
//
let result = [{
start: -1,
end: -1,
content: ''
}];
result.pop();
//
// Validate Args ...
if (
!isValidArg(content) ||
!isValidArg(openSymbol) ||
!isValidArg(closeSymbol) ||
!content.includes(openSymbol) ||
!content.includes(closeSymbol) ||
openSymbol.length >= content.length ||
closeSymbol.length >= content.length
) {
return result;
}
//
// Normalize Start From ...
startsFrom = startsFrom < 0 || startsFrom >= content.length ? 0 : startsFrom;
//
let canContinue = true;
while (!!canContinue) {
//
let openSymbolIndex = content.indexOf(openSymbol, startsFrom);
if (openSymbolIndex < 0) {
//
canContinue = false;
continue;
}
//
let closeSymbolIndex = findCloseIndex(
openSymbol,
closeSymbol,
content,
openSymbolIndex
);
if (closeSymbolIndex < 0) {
//
canContinue = false;
continue;
}
if (content.charAt(closeSymbolIndex) !== closeSymbol) {
closeSymbolIndex++;
}
//
// Prevent ${} string interpolations ...
if (
openSymbol === '{' &&
openSymbolIndex - 1 >= 0
) {
//
const prevChar = content.charAt(openSymbolIndex - 1);
if (prevChar === '$') {
continue;
}
}
//
const item = content
.substring(openSymbolIndex, closeSymbolIndex + 1)
.trim();
startsFrom = closeSymbolIndex + 1;
result.push({
start: openSymbolIndex,
end: closeSymbolIndex,
content: item
});
}
//
if (!hasChildArray(result)) {
return result;
}
//
return result;
}
/**
* extract a collection of closed contents exists in a content ...
*
* @param symbols a collection of open and close contents ...
* @param {string} content a content for searching inside it ...
* @returns a collection of closed content ...
*/
function findClosedContents(
symbols = [
{
openSymbol: '',
closeSymbol: ''
}
],
content = ''
) {
//
let result = [{
start: -1,
end: -1,
content: ''
}];
result.pop();
//
if (
!isValidArg(content) ||
!hasChildArray(symbols)
) {
return [];
}
//
symbols.forEach(symbol => {
//
const closedContent = findClosedContent(
symbol.openSymbol,
symbol.closeSymbol,
content
);
//
if (hasChildArray(closedContent)) {
result.push(
...closedContent
);
}
});
//
return result;
}
/**
* find all string content closed items ...
*
* @param {string} content a content which going to search ...
* @returns a collection of index descriptors ...
*/
function findClosedStrings(content = '') {
//
// Validate Args ...
if (!isValidArg(content)) {
return [];
}
//
// there are 3 types of strings ...
// ' " and `
//
const token1 = '\'';
const token1Stack = [0];
token1Stack.pop();
//
const token2 = '"';
const token2Stack = [0];
token2Stack.pop();
//
const token3 = '`';
const token3Stack = [0];
token3Stack.pop();
//
const result = [
{
start: -1,
end: -1,
content: ''
}
];
result.pop();
//
for (let i = 0; i < content.length; i++) {
//
const currentChart = content.charAt(i);
//
//#region Token 1 ...
if (currentChart === token1) {
//
if (!hasChildArray(token1Stack)) {
token1Stack.push(i);
} else {
//
const start = token1Stack.pop();
const end = i + 1;
const pContent = content.substring(start, end);
result.push({
start,
end,
content: pContent
});
}
}
//#endregion
//
//#region Token 2 ...
if (currentChart === token2) {
//
if (!hasChildArray(token2Stack)) {
token2Stack.push(i);
} else {
//
const start = token2Stack.pop();
const end = i + 1;
const pContent = content.substring(start, end);
result.push({
start,
end,
content: pContent
});
}
}
//#endregion
//
//#region Token 3 ...
if (currentChart === token3) {
//
if (!hasChildArray(token3Stack)) {
token3Stack.push(i);
} else {
//
const start = token3Stack.pop();
const end = i + 1;
const pContent = content.substring(start, end);
result.push({
start,
end,
content: pContent
});
}
}
//#endregion
}
//
return result;
}
/**
* find container indexes from within indexes ...
*
* @param {...{start: number, end: number}} index the collection of indexes to find beiggers inside ...
* @returns
*/
function findContainerIndexes(...index) {
//
let result = [
{
start: -1,
end: -1,
content: ''
}
];
result.pop();
//
let concatedIndexes = [];
for (let idx of index) {
//
// Index Childs ...
for (let iix of idx) {
//
// Check idx is standard ...
if (
!iix ||
!iix.end ||
!iix.start ||
iix.start > iix.end
) {
continue;
}
//
// Check index inside another index ...
concatedIndexes.push(iix);
}
}
//
if (!hasChildArray(concatedIndexes)) {
return result;
}
//
for (let ccIdx of concatedIndexes) {
//
const isInside = isIndexInside(ccIdx, ...concatedIndexes);
if (isInside) {
continue;
}
//
if (hasChildArray(result)) {
//
const isInsideResult = isIndexInside(ccIdx, ...result);
if (isInsideResult) {
continue;
}
}
//
result.push(ccIdx);
}
//
return result;
}
/**
* find all tokens indexes inside a content which they are not inside closed items ...
*
* @param {string|string[]} tokens the tokens which require to search ...
* @param {string} content the content which using to search ...
* @returns
*/
function findAllIndexesOutOfCloseds1(
tokens,
content,
) {
//
// Validate Args ...
if (!isValidArg(content)) {
return [];
}
//
// Extract token Indexes ...
const tokensIndexes = findAllIndexes(tokens, content);
if (!hasChildArray(tokensIndexes)) {
return [];
}
//
// Extract Closed Objects Indexes ...
const closedArrays = findClosedContent('[', ']', content);
const closedObjects = findClosedContent('{', '}', content);
const closedStrings = findClosedStrings(content);
if (
!hasChildArray(closedObjects) &&
!hasChildArray(closedArrays) &&
!hasChildArray(closedStrings)
) {
return tokensIndexes;
}
//
// find bigger sloded ...
let containerClosedItems = findContainerIndexes(
closedArrays,
closedObjects,
closedStrings
).filter(i => !isSurroundedString(i.content));
//
let result = [-1];
result.pop();
//
for (let tIdx of tokensIndexes) {
//
let canAdd = true;
for (let ccIndex of containerClosedItems) {
//
canAdd = isInsideIndex(tIdx, ccIndex);
if (canAdd) {
break;
}
}
//
if (!canAdd) {
result.push(tIdx);
}
}
//
return result;
}
/**
* find all tokens indexes inside a content which they are not inside closed items ...
*
* @param {string|string[]} tokens the tokens which require to search ...
* @param {string} content the content which using to search ...
* @returns
*/
function findAllIndexesOutOfCloseds(
tokens,
content,
) {
//
// Define Result ...
let result = [-1];
result.pop();
//
// Validate Args ...
if (!isValidArg(content)) {
return [];
}
//
// Normalize Tokens ...
const normalizeTokens = toNormalArray(tokens);
if (!hasChildArray(normalizeTokens)) {
return result;
}
//
// Extract Closed Objects Indexes ...
let openSymbols = [
'{',
'[',
'\'',
'"',
'`'
];
let closeSymbols = [
'}',
']',
'\'',
'"',
'`'
];
let openStack = [];
let lastOpenSymbol = '';
let lastClosedIndex = -1;
for (let i = 0; i < content.length; i++) {
//
const prevChar = content.charAt(i - 1) || '';
const char = content.charAt(i);
const nextChar = content.charAt(i + 1) || '';
//
const isToken = normalizeTokens.includes(char);
const isOpenSymbol = openSymbols.includes(char);
const isCloseSymbol = closeSymbols.includes(char);
//
if (isOpenSymbol && lastOpenSymbol !== char) {
openStack.push(i);
lastOpenSymbol = char === '\'' || char === '"' || char === '`' ? char : lastOpenSymbol;
} else if (isCloseSymbol) {
//
lastOpenSymbol = char === '\'' || char === '"' || char === '`' ? '' : lastOpenSymbol;
let lastPop = openStack.pop();
if (openStack.length === 0) {
lastClosedIndex = lastPop;
}
} else if (isToken) {
if (
i > lastClosedIndex &&
openStack.length === 0
) {
result.push(i);
}
}
}
//
return result;
}
//
// Module Exports ...
module.exports = {
//
endsWidth,
surroundBy,
isValidArg,
isValidArgs,
toNormalArray,
hasChildArray,
toStringExpression,
//
sliceContent,
clearContent,
clearSurround,
clearArraySurround,
clearObjectSurround,
//
isSurrounded,
isIndexInside,
isInsideIndex,
isKeyValueType,
isSurroundedArray,
isSurroundedString,
isSurroundedObject,
//
findNearest,
findAllIndexes,
findCloseIndex,
findNearestIndex,
findClosedContent,
findClosedStrings,
findClosedContents,
findAllIndexesOutOfCloseds,
//
}