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, // }