Check if string start with an Array of strings - javascript

I have an array of strings :
listOfTypes = ['blogPost', 'work']
And I have a routeName
routeName = blogPost-slug or routeName = blogPost;
When routeName contains = blogPost I want to return string "page".
If routeName start with string- (example: blogPost-), I want to return the type (from listOfTypes)
I made a forEach function that works. Only, I get several results (it's normal) when I only want one (if it is present in the array, then I return either the type or page)
listOfTypes = ['blogPost', 'work'];
// routeName is retrieved dynamically. This is for example, "blogPost" or "blogPost-slug" or "work-slug"
listOfTypes.forEach(type => {
// Check if route name starts with type + "-" because it's separator with subroute
// (/blogPost is a page, /blogPost-slug is a blogPost so check for blogPost-)
if(routeName.startsWith(type + '-')) {
return console.log(type);
} else {
return console.log('page');
}
});
// result: 'blogPost' and 'page'
// expected result: 'blogPost'
How can I do this and only get either the type or "page" if it doesn't match?
Thank you

Using Array#find:
const listOfTypes = ['blogPost', 'work'];
const getType = route =>
listOfTypes.find(type => route.startsWith(`${type}-`)) ?? 'page';
console.log( getType('blogPost') );
console.log( getType('blogPost-slug') );
console.log( getType('work-slug') );

Related

How to replace text with substitute that contains JSX code?

interface ICard {
content: string,
blanks: Array<{word: string, hidden: boolean}>
}
function processCards():Array<any>{
if (cards !==null ){
const text = cards.map((card,cardIndex)=>{
var content = card.content
card.blanks.map((blank,blankIndex)=>{
// replace content
const visibility = (blank.hidden)?'hidden':'visible'
const click_blank = <span className={visibility} onClick={()=>toggleBlank(cardIndex,blankIndex)}>{blank.word}</span>
content = content.replace(blank.word,click_blank)
})
return content
})
return text
} else {
return []
}
}
I have an array of objects of type ICard.
Whenever card.blanks.word appears in card.content, I want to wrap that word in tags that contain a className style AND an onClick parameter.
It seems like I can't just replace the string using content.replace like I've tried, as replace() does not like the fact I have JSX in the code.
Is there another way to approach this problem?
You need to construct a new ReactElement from the parts of string preceding and following each blank.word, with the new span stuck in the middle. You can do this by iteratively building an array and then returning it wrapped in <> (<React.Fragment>). Here's a (javascript) example:
export default function App() {
const toggleBlankPlaceholder = (cardIndex, blankIndex) => {};
const cardIndexPlaceholder = 0;
const blanks = [
{ word: "foo", hidden: true },
{ word: "bar", hidden: false },
];
const content = "hello foo from bar!";
const res = [content];
for (const [blankIndex, { word, hidden }] of blanks.entries()) {
const re = new RegExp(`(.*?)${word}(.*)`);
const match = res[res.length - 1].match(re);
if (match) {
const [, prefix, suffix] = match;
res[res.length - 1] = prefix;
const visibility = hidden ? "hidden" : "visible";
res.push(
<span
className={visibility}
onClick={() =>
toggleBlankPlaceholder(cardIndexPlaceholder, blankIndex)
}
>
{word}
</span>
);
res.push(suffix);
}
}
return <>{res}</>;
}
The returned value will be hello <span class="hidden">foo</span> from <span class="visible">bar</span>!
A couple of things:
In your example, you used map over card.blanks without consuming the value. Please don't do that! If you don't intend to use the new array that map creates, use forEach instead.
In my example, I assumed for simplicity that each entry in blanks occurs 0 or 1 times in order in content. Your usage of replace in your example code would only have replaced the first occurrence of blank.word (see the docs), though I'm not sure that's what you intended. Your code did not make an ordering assumption, so you'll need to rework my example code a little depending on the desired behavior.

Trying to use regex to find a sub-string's urls in array of strings

I have an array of strings. I need to grab the URL's value on each string. I have created a function with Regex to accomplish this. It works fine, but there's an edge case I haven't been able to cover. When there is not any URL:'s value, I get the error: Cannot read property '1' of null.
This is my code:
const arrStr = [
`describe
url: https://url-goes-here-/1,
https://url-goes-here-/2,
https://url-goes-here-/3
});`
,
`
before(() => {
url: https://url-goes-here-/4
});
`,
`
before(() => {
url: https://url-goes-here-/5
});
`,
`describe
// nothing http link here
});
`
]
const getXrayUrl = str => str.match(/url:([^;]+)/)[1].trim().split('(')[0]; // cannot read property '1' of null
const allXrayUrls = arrStr.map(item => getXrayUrl(item));
If I removed the string with no URL value from the array, I get this output:
[ 'https://url-goes-here-/1,\n https://url-goes-here-/2,\n https://url-goes-here-/3\n })', 
'https://url-goes-here-/4\n })', 
'https://url-goes-here-/5\n })' ]
How do I cover this edge case & return another array with all the string in the level?
According to match function documentation, it returns an array of matches, or null if no matches are found.
If you need to handle the case of a missing URL attribute so check match array is null before accessing the capture group as following:
const match = str.match(/url:\s*([^;]+)\n/)
// in case no match retrun empty string
// split the match on , to handle multi URL case
const url = match? match[1].split(",").map(item => item.trim()) : [""];
after that filter match results removing empty values as following:
arrStr.map(getXrayUrl).flat().filter(item => item !== "");
so final solution as following:
const arrStr = [
`describe
url: https://url-goes-here-/1,
https://url-goes-here-/2,
https://url-goes-here-/3
});`
,
`
before(() => {
url: https://url-goes-here-/4
});
`,
`
before(() => {
url: https://url-goes-here-/5
});
`,
`describe
// nothing http link here
});
`
]
const getXrayUrl = str => {
const match = str.match(/url:\s*([^;]+)\n/)
// in case no match retrun empty string
// split the match on , to handle multi URL case
return match? match[1].split(",").map(item => item.trim()) : [""];
}
const allXrayUrls = arrStr.map(getXrayUrl).flat().filter(item => item !== "");
console.log(allXrayUrls)
console output:
["https://url-goes-here-/1", "https://url-goes-here-/2", "https://url-goes-here-/3", "https://url-goes-here-/4", "https://url-goes-here-/5"]

javascript code to extract values in curly braces with specific keyword in line

I have a set of text files in Google cloud storage(new files come to storage at every 5 minutes[batch processing]). what I want to do is put it into Google BigQuery via Dataflow. In dataflow, we can directly import text files in cloud storage to Google BigQuery(in my case I want batch processing). It requires a javascript code to transform textfiles in GCS into a table in bigquery.
Here is my sample of my one text file.
I want to write javascript code to select topic name is Bat and the values between curly braces into Bigquery table.According to above sample below is the required one.(BigQuery schema will be added later)
Im really new to javascript(actually this is the first time) and I want to implement a javascript function to do this.I don't know select the topic name Bat** in line.
Below is what I tried.(please fix, if this is wrong)
function transform(line) {
const paramsPattern = /[^{\}]+(?=})/g;
let new = line.match(paramsPattern);
var values = new.split(',');
var obj = new Object();
obj.ID = values[0];
obj.AGE = values[1];
var jsonString = JSON.stringify(obj);
return jsonString;
}
Thanks in advance !!!
If it comes to reliability, as well as convenience and/or maintainability one should think about a more generic approach that does reduce a list of lines/strings and in addition takes even a specific topic into account by assembling the correct regex ...
const fileData = `
Topic:Cat [0] at offset:216712{"ID":55689534,"NAME":6}
Topic:Cat [1] at offset:216719{"ID":55689524,"NAME":6}
Topic : Bat [0] at offset:216716 {"CODE":94762151097,"AGE":32}
Topic:Cat [0] at offset:216713{"ID":55689524,"NAME":6}
Topic:Bat [1] at offset:216723{"CODE":947080272531,"AGE":43}
Topic:Cat [1] at offset:216738{"ID":55689525,"NAME":6}
`;
const dataItemList = fileData.split(/\n/);
function getTopicSpecificDataCaptureRegX(topic) {
// see also: [https://regex101.com/r/AD31R6/1/]
//return (/^\s*Topic\s*\:\s*Bat[^{]+(\{.*\})\s*$/);
//return (/^\s*Topic\s*\:\s*Cat[^{]+(\{.*\})\s*$/);
return RegExp('^\\s*Topic\\s*\\:\\s*' + topic + '[^{]+(\\{.*\\})\\s*$');
}
function collectTopicSpecificData(collector, dataItem) {
const result = dataItem.match(collector.regX);
if (result !== null) {
collector.list.push(JSON.parse(result[1]));
}
return collector;
}
console.log(
'"Cat" specific data list : ',
dataItemList.reduce(collectTopicSpecificData, {
regX: getTopicSpecificDataCaptureRegX('Cat'),
list: []
}).list
);
console.log(
'"Bat" specific data list : ',
dataItemList.reduce(collectTopicSpecificData, {
regX: getTopicSpecificDataCaptureRegX('Bat'),
list: []
}).list
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
let new = line.match(paramsPattern);
you should not assign this to a variable new... new is special.
This is a regex that would work with your example: https://regex101.com/r/tBHRIY/1
Here is an example:
const testLine = 'Topic:Bat [0] at offset:216812{"ID":51255125, "NAME":6}';
function transform(line) {
const paramsPattern = /({[\s\S]+})/g;
const match = line.match( paramsPattern );
if ( line.indexOf( 'Bat' ) === -1 )
return null;
if ( match === null )
return null;
// This will verify that the json is valid ( it will throw ) but you can skip this and return match[0] directly if you are sure it is valid
return JSON.stringify( JSON.parse( match[0] ) );
}
console.log( transform( testLine ) );
Edit: sorry i missed checking for BAT, added

ReactJS / ES6: Searching Japanese text using includes?

So, I'm writing a client-side search and I need to look through strings of Japanese characters. I'm wondering how to do this properly?... i.e. Do I change the format of the text into utf-8 something and then search the utf-8?
Example:
All my data has japaneseData.title : "フェリーチェ三田"
When I type in my search.value as : "フェ" using japaneseData.title.includes(search.value) I don't get a match...
How do I do this correctly?
Okay, after further inspection, the comments were correct and includes was finding the substring. This is all happening inside of a filter() and I'm trying to return the objects that match...
After changing my code to:
let filteredArrayofObjects = Lists.houseLists.filter(house => house.building_name.includes(query.search));
I was getting back some but not all. Problem cases:
"アーバイルスパシエ芝浦BAY-SIDE".includes("エ芝浦"); // this evaluates to true, but does not get included in my filtered array...
Okay, further digging, it seems the issue is I need to wait for the filter process before returning the results... haven't yet found a solution to that just yet.
async filter(arr, callback) {
return (await Promise.all(
arr.map(async item => {
return (await callback(item)) ? item : undefined;
})
)).filter(i => i !== undefined);
}
handleFilterLists = async (query = {}) => {
const { Lists } = this.props;
let searchResults = await this.filter(Lists.houseLists, async house => {
return house.building_name.includes(query.search);
// the final evaluation to look similar to this:
// var newArray = homes.filter(function (el) {
// return el.price <= 1000 &&
// el.sqft >= 500 &&
// el.num_of_beds >=2 &&
// el.num_of_baths >= 2.5;
// });
});
this.setState({ searchResults });
}
Okay, so, I'm trying to set state.searchResults after the filter method has checked for matching objects in the array Lists.houseLists...
includes returns true or false if the substring is detected or not. If you want the index of where the first detected substring begins, use indexOf.
I used your sample source and search text with includes and it returns true.
Edit:
I used your updated data and this still works. https://codepen.io/anon/pen/RMWpwe
const sourceText = 'アーバイルスパシエ芝浦BAY-SIDE';
const searchText = 'エ芝浦';
const lists = [
'スパシエ',
'芝浦BAY-SIDE',
'エ芝浦',
'パシエ芝浦BAY'
];
console.log(lists.filter(item => item.includes(searchText)));
// ["エ芝浦", "パシエ芝浦BAY"]

Determine if a path is subdirectory of another in Node.js

I am working on a MQTT handler for which I want to emit an event for each parent directory where there is a event listener. For example:
If there are the following MQTT paths available, where there are subscriptors –there are event listeners for these paths–
test
replyer/request
test/replyer/request
And someone publishes on topic test/replyer/request/#issuer, there should be 2 events emmited: test, test/replyer/request.
Given than any path is possible and there is no list of available valid events, we must check only if a path is a parent of another. Can we do this with regex? If so, how would it look like? Is there a simpler/more efficient solution?
Let Node itself do the work.
const path = require('path');
const relative = path.relative(parent, dir);
return relative && !relative.startsWith('..') && !path.isAbsolute(relative);
It does normalisation for you as well.
const path = require('path');
const tests = [
['/foo', '/foo'],
['/foo', '/bar'],
['/foo', '/foobar'],
['/foo', '/foo/bar'],
['/foo', '/foo/../bar'],
['/foo', '/foo/./bar'],
['/bar/../foo', '/foo/bar'],
['/foo', './bar'],
['C:\\Foo', 'C:\\Foo\\Bar'],
['C:\\Foo', 'C:\\Bar'],
['C:\\Foo', 'D:\\Foo\\Bar'],
];
tests.forEach(([parent, dir]) => {
const relative = path.relative(parent, dir);
const isSubdir = relative && !relative.startsWith('..') && !path.isAbsolute(relative);
console.log(`[${parent}, ${dir}] => ${isSubdir} (${relative})`);
});
Works on Windows across drives too.
[/foo, /foo] => false ()
[/foo, /bar] => false (..\bar)
[/foo, /foobar] => false (..\foobar)
[/foo, /foo/bar] => true (bar)
[/foo, /foo/../bar] => false (..\bar)
[/foo, /foo/./bar] => true (bar)
[/bar/../foo, /foo/bar] => true (bar)
[/foo, ./bar] => false (..\Users\kozhevnikov\Desktop\bar)
[C:\Foo, C:\Foo\Bar] => true (Bar)
[C:\Foo, C:\Bar] => false (..\Bar)
[C:\Foo, D:\Foo\Bar] => false (D:\Foo\Bar)
2021 Answer
Use #Ilya's solution.
2017 Answer
In ES6.
const isChildOf = (child, parent) => {
if (child === parent) return false
let parentTokens = parent.split('/').filter(i => i.length)
let childTokens = child.split('/').filter(i => i.length)
return parentTokens.every((t, i) => childTokens[i] === t)
}
If you're working in node.js and you want to make it cross-platform, include the path module and replace split('/') with split(path.sep).
How it works:
So, you want to find out if a directory (like home/etc/subdirectory) is a subdirectory of another directory (like home/etc).
It takes both the hypothesised child and parent paths and convert them into arrays using split:
['home', 'etc', 'subdirectory'], ['home', 'etc']
It then iterates through all of the tokens in the parent array and checks them one-by-one against their relative position in the child array using ES6's .every().
If everything in parent matches up to everything in child, knowing that we've ruled out they are exactly the same directory (using child !== parent), we will have our answer.
For those who care about performances, which seem to pass unnoticed to people who have already answered, checking whether the sub path starts with its parent path should be enough.
const path = require('path');
function isSubPathOf(subPath, parentPath) {
parentPath = normalize(parentPath);
if (subPath.length <= parentPath.length)
return false;
function normalize(p) {
p = path.normalize(p);
if (!p.endsWith(path.sep))
p += path.sep;
return p;
}
subPath = normalize(subPath);
return subPath.startsWith(parentPath);
}
console.log(isSubPathOf('/a/b/c/d/e', '/a/b/c'));
console.log(isSubPathOf('/a/b/c/de', '/a/b/c'));
console.log(isSubPathOf('/a/b/c', '/a/y/c'));
console.log(isSubPathOf('/a/y/c/k', '/a/y/c'));
This is a really old question, but I came up with a dead simple solution for this using Node's built–in path.relative: if the child is inside the parent, the relative path from the former to the latter will always start with '..'.
import { relative } from 'path';
function isSubDirectory(parent, child) {
return relative(child, parent).startsWith('..');
}
There are a couple of things going on here that are needed to prevent failure:
Should we attempt to resolve filesystem paths? (I think so)
Checking if one directory contains another should work with symlinks
I came up with a solution that attempts to resolve filesystem paths as much as possible while allowing paths which may or may not exist:
Split the path on the OS's path separator
Resolve as many of those path components in the filesystem as possible
Append remaining components that couldn't be resolved
If the relative path between parent and child doesn't start with .. + path.sep and isn't .. then the parent path contains the child path
This all works, assuming that any nonexistent path components would be created using only directories and files (no symlinks). For example say your script needs to write only to whitelisted paths and you're accepting untrusted (user-supplied) filenames. You could create subdirectories using something like PHP's mkdir with $recursive = true to create the directory structure in one step, similar to this example.
Here is the code (not runnable until Stack Overflow supports Node.js), the important functions are resolveFileSystemPath() and pathContains():
const kWin32 = false;
const fs = require('fs');
const path = kWin32 ? require('path').win32 : require('path');
////////// functions //////////
// resolves (possibly nonexistent) path in filesystem, assuming that any missing components would be files or directories (not symlinks)
function resolveFileSystemPath(thePath) {
let remainders = [];
for (
let parts = path.normalize(thePath).split(path.sep); // handle any combination of "/" or "\" path separators
parts.length > 0;
remainders.unshift(parts.pop())
) {
try {
thePath =
fs.realpathSync(parts.join('/')) + // fs expects "/" for cross-platform compatibility
(remainders.length ? path.sep + remainders.join(path.sep) : ''); // if all attempts fail, then path remains unchanged
break;
} catch (e) {}
}
return path.normalize(thePath);
}
// returns true if parentPath contains childPath, assuming that any missing components would be files or directories (not symlinks)
function pathContains(parentPath, childPath, resolveFileSystemPaths = true) {
if (resolveFileSystemPaths) {
parentPath = resolveFileSystemPath(parentPath);
childPath = resolveFileSystemPath(childPath);
}
const relativePath = path.relative(parentPath, childPath);
return !relativePath.startsWith('..' + path.sep) && relativePath != '..';
}
////////// file/directory/symlink creation //////////
console.log('directory contents:');
console.log();
try {
fs.mkdirSync('parent');
} catch (e) {} // suppress error if already exists
fs.writeFileSync('parent/child.txt', 'Hello, world!');
try {
fs.mkdirSync('outside');
} catch (e) {} // suppress error if already exists
try {
fs.symlinkSync(path.relative('parent', 'outside'), 'parent/child-symlink');
} catch (e) {} // suppress error if already exists
fs.readdirSync('.').forEach(file => {
const stat = fs.lstatSync(file);
console.log(
stat.isFile()
? 'file'
: stat.isDirectory() ? 'dir ' : stat.isSymbolicLink() ? 'link' : ' ',
file
);
});
fs.readdirSync('parent').forEach(file => {
file = 'parent/' + file;
const stat = fs.lstatSync(file);
console.log(
stat.isFile()
? 'file'
: stat.isDirectory() ? 'dir ' : stat.isSymbolicLink() ? 'link' : ' ',
file
);
});
////////// tests //////////
console.log();
console.log(
"path.resolve('parent/child.txt'): ",
path.resolve('parent/child.txt')
);
console.log(
"fs.realpathSync('parent/child.txt'): ",
fs.realpathSync('parent/child.txt')
);
console.log(
"path.resolve('parent/child-symlink'): ",
path.resolve('parent/child-symlink')
);
console.log(
"fs.realpathSync('parent/child-symlink'):",
fs.realpathSync('parent/child-symlink')
);
console.log();
console.log(
'parent contains .: ',
pathContains('parent', '.', true)
);
console.log(
'parent contains ..: ',
pathContains('parent', '..', true)
);
console.log(
'parent contains parent: ',
pathContains('parent', 'parent', true)
);
console.log(
'parent contains parent/.: ',
pathContains('parent', 'parent/.', true)
);
console.log(
'parent contains parent/..: ',
pathContains('parent', 'parent/..', true)
);
console.log(
'parent contains parent/child.txt (unresolved): ',
pathContains('parent', 'parent/child.txt', false)
);
console.log(
'parent contains parent/child.txt (resolved): ',
pathContains('parent', 'parent/child.txt', true)
);
console.log(
'parent contains parent/child-symlink (unresolved):',
pathContains('parent', 'parent/child-symlink', false)
);
console.log(
'parent contains parent/child-symlink (resolved): ',
pathContains('parent', 'parent/child-symlink', true)
);
Output:
directory contents:
file .bash_logout
file .bashrc
file .profile
file config.json
dir node_modules
dir outside
dir parent
link parent/child-symlink
file parent/child.txt
path.resolve('parent/child.txt'): /home/runner/parent/child.txt
fs.realpathSync('parent/child.txt'): /home/runner/parent/child.txt
path.resolve('parent/child-symlink'): /home/runner/parent/child-symlink
fs.realpathSync('parent/child-symlink'): /home/runner/outside
parent contains .: false
parent contains ..: false
parent contains parent: true
parent contains parent/.: true
parent contains parent/..: false
parent contains parent/child.txt (unresolved): true
parent contains parent/child.txt (resolved): true
parent contains parent/child-symlink (unresolved): true
parent contains parent/child-symlink (resolved): false
Live example: https://repl.it/repls/LawngreenWorriedGreyware
The last line of the output is the important one, showing how resolved filesystem paths lead to the correct result (unlike the unresolved result above it).
Limiting filesystem reads/writes to certain directories is so important for security that I hope Node.js incorporates this functionality into their builtins. I haven't tested this on a native Windows box so please let me know if the kWin32 flag is working. I'll try to curate this answer as time allows.
Based on & improved Dom Vinyard's code:
const path = require('path');
function isAncestorDir(papa, child) {
const papaDirs = papa.split(path.sep).filter(dir => dir!=='');
const childDirs = child.split(path.sep).filter(dir => dir!=='');
return papaDirs.every((dir, i) => childDirs[i] === dir);
}
Result in:
assert(isAncestorDir('/path/to/parent', '/path/to/parent/and/child')===true);
assert(isAncestorDir('/path/to/parent', '/path/to')===false);
assert(isAncestorDir('/path/to/parent', '/path/to/parent')===true);
Doing it with regex is one way to go about it (for every path that has an event listener, check whether the published topic starts with that path), but since it's more likely you'll have many different paths than you having absurdly long URLs, breaking down the published topic might be more efficient.
Something like this is probably easier to read, too:
Edit: #huaoguo is definitely right, indexOf === 0 is all we really need!
let paths = [
'test',
'replyer/request',
'test/replyer/request'
]
let topic = 'test/replyer/request/#issuer'
let respondingPaths = (paths, topic) => paths.filter(path => topic.indexOf(path) === 0)
console.log(respondingPaths(paths, topic)) // ['test', 'test/replyer/request']
I would also like to point to the npm package path-is-inside which does exactly what the TO has been asking for:
Usage (quoted from the readme):
Pretty simple. First the path being tested; then the potential parent. Like so:
var pathIsInside = require("path-is-inside");
pathIsInside("/x/y/z", "/x/y") // true
pathIsInside("/x/y", "/x/y/z") // false
Paths are considered to be inside themselves:
pathIsInside("/x/y", "/x/y"); // true
For me, it does the job, and it is for sure a better to maintain such non-trivial logic in an extra package instead of a StackOverflow answer. :-)
#dom-vinyard's idea is good but code is not working correctly, for instance with this input:
isChildOf('/x/y', '/x') //false
I wrote my own version here:
function isParentOf(child, parent) {
const childTokens = child.split('/').filter(i => i.length);
const parentTokens = parent.split('/').filter(i => i.length);
if (parentTokens.length > childTokens.length || childTokens.length === parentTokens.length) {
return false;
}
return childTokens
.slice(0, parentTokens.length)
.every((childToken, index) => parentTokens[index] === childToken);
}
Here another solution that use indexOf (or that work by comparing strings).
In the function bellow i didn't use indexOf in order to support multiple path separators. You can check, but if you are sure you have just one separator, you can use indexOf with no problem.
The trick is to check if the path end with a separator, if not you just add such a separator to it. In that, there will be no problem of having a substring that is not a complete path in the child path. [/this/isme_man and /this/isme] (the first is child of the second, if we simply use indexOf (which of course if false), but if you do using the trick like this [/this/isme/ and /this/isme_man/] and you compare using same indexOf there will be no problem, and it work nikel)].
Notice too that there is an option, to allow a check with orEqual (a child or equal), it's the third optional parameter.
Check the code bellow.
const PATH_SEPA = ['\\', '/'];
function isPathChildOf(path, parentPath, orEqual) {
path = path.trim();
parentPath = parentPath.trim();
// trick: making sure the paths end with a separator
let lastChar_path = path[path.length - 1];
let lastChar_parentPath = path[parentPath.length - 1];
if (lastChar_parentPath !== '\\' && lastChar_parentPath !== '/') parentPath += '/';
if (lastChar_path !== '\\' && lastChar_path !== '/') path += '/';
if (!orEqual && parentPath.length >= path.length) return false; // parent path should be smaller in characters then the child path (and they should be all the same from the start , if they differ in one char then they are not related)
for (let i = 0; i < parentPath.length; i++) {
// if both are not separators, then we compare (if one is separator, the other is not, the are different, then it return false, if they are both no separators, then it come down to comparaison, if they are same nothing happen, if they are different it return false)
if (!(isPathSeparator(parentPath[i]) && isPathSeparator(path[i])) && parentPath[i] !== path[i]) {
return false;
}
}
return true;
}
function isPathSeparator(chr) {
for (let i = 0; i < PATH_SEPA.length; i++) {
if (chr === PATH_SEPA[i]) return true;
}
return false;
}
Here a test example:
let path = '/ok/this/is/the/path';
let parentPath = '/ok/this/is';
let parentPath2 = '/ok/this/is/';
let parentPath3 = '/notok/this/is/different';
console.log("/ok/this/is/the/path' is child of /ok/this/is => " + isPathChildOf(path, parentPath));
console.log("/ok/this/is/the/path' is child of /ok/this/is/=> " + isPathChildOf(path, parentPath2));
console.log("/ok/this/is/' is child of /ok/this/is/ => " + isPathChildOf(parentPath2, parentPath2));
console.log("/ok/this/is/the/path' is child of /notok/this/is/different => " + isPathChildOf(path, parentPath3));
// test number 2:
console.log('test number 2 : ');
console.log("=============================");
let pthParent = '/look/at/this/path';
let pth = '/look/at/this/patholabi/hola'; // in normal use of indexof it will return true (know too we didn't use indexof just to support the different path separators, otherwise we would have used indexof in our function)
//expected result is false
console.log(`${pth} is a child of ${pthParent} ===> ${isPathChildOf(pth, pthParent)}`);
let pthParent2 = '/look/at/this/path';
let pth2 = '/look/at/this/path/hola';
//expected result is true
console.log(`${pth2} is a child of ${pthParent2} ===> ${isPathChildOf(pth2, pthParent2)}`);
let pthParent3 = '/look/at/this/path';
let pth3 = '/look/at/this/pathholabi';
//expected result is false
console.log(`${pth3} is a child of ${pthParent3} ===> ${isPathChildOf(pth3, pthParent3)}`);
// test 3: equality
console.log('\ntest 3 : equality');
console.log("==========================");
let pParent = "/this/is/same/Path";
let p = "/this\\is/same/Path/";
console.log(`${p} is child of ${pParent} ====> ${isPathChildOf(p, pParent, true)}`);
You can see in the last example how we used the function to check for both being a child or equal (which can be really handful).
Also know that, You can check my two related github repos, that include too another implementation for the split method (a spliting method with multiple separator without using the regex engine)), also this method too, and some good explanation (check the comments within the codes):
https://github.com/MohamedLamineAllal/isPathChildOfJS
https://github.com/MohamedLamineAllal/splitStrJS
In my understanding, the issue is a little more complex than what my colleagues understand.
Paths could be tested with fs.existsSync(), but in this case we would create a dependency on the fs library and limit it to testing only absolute directories, both should be tested for their respective existence unless that the user would be interested in not using Windows as the Operating System so as not to need to inform the root directory, which is formed by default by "letter:\" treating the partitions as if they were sisters to each other, no none being nested to a larger directory.
The operation is totally different on Unix systems. The root directory is by default / and all mounted disks are nested to it, making it unique for the entire OS. It can be seen, therefore, that this is not ideal.
It is not possible to solve only with regular expression, considering that, if "pathFoldersLength" is greater than "path2CheckIfIsSupposedParentFoldersLength", a false negative could be obtained if, for example, "path" was equal to "laden /subfolder-1" and "path2CheckIfIsParent" to "bin/laden/subfolder-1/subfolder-1.1" and if the search was done with $ at the end of "path"; leaving without the $ would give a false positive if, for example, "path" was equal to "bin/laden/subfolder-1/subfolder-1.1" and "path2CheckIfIsParent" to "laden/subfolder-1";
If "pathFoldersLength" is less than "path2CheckIfIsSupposedParentFoldersLength, you could get a false negative if, for example, "path" is equal to "laden/subpath-1" and "path2CheckIfIsParent" to "bin/laden/ subpath-1" if the search was done with ^ at the beginning of "path2CheckIfIsParent", or it could give a false positive if, for example, "path" was equal to "bin/laden" and "path2CheckIfIsParent" to " bin/laden/subpath-1".
In this way we eliminate dependencies, leaving the method as little language dependent as possible.
import Path from 'path';
const isASubpathFromAnyOfSubpathSet = (path, path2CheckIfIsParent, isAbsolute = true) => {
const pathSeparatorPattern = new RegExp(/\\+/, 'g');
const pathSeparatorAtStartOrAtEndPattern = new RegExp(/^\/+|\/+$/, 'g');
path = path.replace(pathSeparatorPattern, `/`);
path2CheckIfIsParent = path2CheckIfIsParent.replace(pathSeparatorPattern, `/`);
path = path.replace(pathSeparatorAtStartOrAtEndPattern , ``);
path2CheckIfIsParent = path2CheckIfIsParent.replace(pathSeparatorAtStartOrAtEndPattern , ``)
if (path === path2CheckIfIsParent) return false;
const pathFolders = path.split(`/`);
const path2CheckIfIsSupposedParentFolders = path2CheckIfIsParent.split(`/`);
const pathFoldersLength = pathFolders.length;
const path2CheckIfIsSupposedParentFoldersLength = path2CheckIfIsSupposedParentFolders.length;
const indexesOf = [];
let pathFolderIndex = 0;
let supposedParentFolderIndex = 0;
let stopCriterian;
for (let i = 0; i < path2CheckIfIsSupposedParentFoldersLength; i++) {
if (pathFolders[0] === path2CheckIfIsSupposedParentFolders[i]) indexesOf.push(i);
}
if (indexesOf.length) {
if (isAbsolute) {
if (pathFoldersLength > path2CheckIfIsSupposedParentFoldersLength) {
path2CheckIfIsParent = path2CheckIfIsParent.replace(/\./g, `\\.`);
return (new RegExp(`^${path2CheckIfIsParent}`)).test(path);
}
} else {
if (indexesOf[0] === 0) indexesOf.shift();
if (pathFoldersLength < path2CheckIfIsSupposedParentFoldersLength) {
stopCriterian = () => pathFolderIndex < pathFoldersLength - 1;
} else {
stopCriterian = () => supposedParentFolderIndex < path2CheckIfIsSupposedParentFoldersLength - 1;
}
for (let indexOf of indexesOf) {
pathFolderIndex = 0;
for (supposedParentFolderIndex = indexOf; stopCriterian();) {
if (path2CheckIfIsSupposedParentFolders[supposedParentFolderIndex] !== pathFolders[pathFolderIndex]) break;
supposedParentFolderIndex++;
pathFolderIndex++;
}
}
if (pathFoldersLength < path2CheckIfIsSupposedParentFoldersLength) {
return pathFolderIndex === pathFoldersLength - 1;
} else {
return supposedParentFolderIndex === path2CheckIfIsSupposedParentFoldersLength - 1;
}
}
}
return false;
}
/*
// >
console.log(isASubpathFromAnyOfSubpathSet (`bin/laden/subfolder-1`, `bin/laden`)) // => true
console.log(isASubpathFromAnyOfSubpathSet (`laden/subfolder-1/subfolder-1.1/subfolder-1.1.1`, `bin/laden/subfolder-1`)) // => false
console.log(isASubpathFromAnyOfSubpathSet (`laden/subfolder-1/subfolder-1.1`, `bin/laden`)) // => false
console.log(isASubpathFromAnyOfSubpathSet (`laden/subfolder-1/subfolder-1.1/subfolder-1.1.1`, `bin/laden/subfolder-1`, false)) // => true
console.log(isASubpathFromAnyOfSubpathSet (`laden/subfolder-1/subfolder-1.1`, `bin/laden`, false)) // => true
// <
console.log(isASubpathFromAnyOfSubpathSet (`laden/subfolder-1`, `bin/laden/subfolder-1/subfolder-1.1`, false)) // => true
console.log(isASubpathFromAnyOfSubpathSet (`laden/subfolder-1`, `bin/laden/subfolder-1`, false)) // => true
console.log(isASubpathFromAnyOfSubpathSet (`subfolder-1/subfolder-1.1`, `bin/laden/subfolder-1`, false)) // => true
// ===
console.log(isASubpathFromAnyOfSubpathSet (`laden/subfolder-1/subfolder-1.1`, `bin/laden/subfolder-1`, false)) // => true
console.log(isASubpathFromAnyOfSubpathSet (`laden/subfolder-1`, `bin/laden`, false)) // => true
/**/
Use indexOf to check that the path to the child directory starts with the path to the parent directory is enough:
function isParentOf(parent, dir) {
return dir.indexOf(parent) === 0;
}
isParentOf('test/replyer/request/#issuer', 'test') // true
isParentOf('test/replyer/request/#issuer', 'replyer/request') // false
isParentOf('test/replyer/request/#issuer', 'test/replyer/request') // true

Categories