2

Having a string (URL path) as for example: /this/is/some/url I would like to remove the first occurrence of / and change the string after the second forward slash (in the example some) by another string let's say is. The result should be this/is/a/url and this is what I have:

let myUrl = '/this/is/some/url';
let splitUrl = myUrl.split('/').filter(v => v !== '');

splitUrl[2] = 'a';
let newUrl = splitUrl.join('/');

console.log(newUrl); // this/is/a/url

But now I wanted to change that into a RegEx so I came up with this:

const myUrl = '/this/is/some/url';
const modifiedUrl = myUrl.replace(/[a-zA-Z0-9]/, 'a').replace(/^\//, '');

console.log(modifiedUrl); // ahis/is/some/url

But the result is not what I want since it outputs: ahis/is/some/url. I'll admit that I am not so good with RegEx.

A few SO posts here and here did not help me since I still did not get how to replace only the content after the second forward slash.

Can I get some help?

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
ReynierPM
  • 17,594
  • 53
  • 193
  • 363

2 Answers2

1

You can use

let myUrl = '/this/is/some/url';
const modifiedUrl = myUrl.replace(/\/((?:[^\/]*\/){2})[^\/]*/, '$1a');
console.log(modifiedUrl); // this/is/a/url

See this regex demo. Details:

  • \/ - a / char
  • ((?:[^\/]*\/){2}) - Group 1: two occurrences of any zero or more chars other than / + a / char
  • [^\/]* - zero or more chars other than /.

The replacement is the backreference to the Group 1 value + the new string that will be inserted in the resulting string.

To also replace the next URL subpart, just add \/[^\/]* to the regex pattern:

let myUrl = '/this/is/some/url';
const str1 = 'content1';
const str2 = 'content2'
const rx = /\/((?:[^\/]*\/){2})[^\/]*\/[^\/]*/;

const modifiedUrl = myUrl.replace(rx, '$1' + str1 + '/' + str2);

console.log(modifiedUrl); // this/is/content1/content2

The main idea stays the same: capture the text before the text you want to replace, then just match what you need to replace, and - in the replacement - use a backreference to the text captured, and append the text to replace with.

NOTE: If your replacement text contains a literal $ followed with a digit that you actually do not want to treat as a backreference, but as a literal text, you should add .replace(/\$/g, '$$$$') to each variable, str1 and str2 in the code above.

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
  • Here is what I understood from your solution everything contained within `()` is a group but what `$1` refers to? Based on what you left me here I am trying to modify your code a little bit to match this: the input string now is `/this/is/some/url` and I want `is` and `url` to be changed by some string contained within a variable. So let's say `is` will be replaced by `let str1 = 'content1'` and `url` will be replaced by `let str2 = 'content2'` ... – ReynierPM Jul 12 '23 at 13:25
  • (cont) however since I am not sure what `$1` refers too still not sure how to rewrite the entire RegEx to match this new requirement. I guess that I should create another group to capture `some` and then use it as `$2`? If you can leave another example I will appreciate it a lot. Apologies for the first comment BAD enter :| – ReynierPM Jul 12 '23 at 13:25
  • @ReynierPM `$1` refers to the text captured by the Group 1 pattern. If you want to replace an Nth occurrence, just modify the number inside curly braces. To replace `some`, replace `{2}` with `{3}`. The replacement is `'$1' + str2.replace(/\$/g, '$$$$')`. This last replace is just in case your replacement text contains a *literal* `$` followed with a digit that you actually do not want to treat as a backreference, but as a literal text. – Wiktor Stribiżew Jul 12 '23 at 13:36
  • Hmm but in this case I need to replace `is` with `content1` and `url` with `content2` if I change 2 with 3 in the curly braces it just replaces the `url`. See https://jsfiddle.net/reynierpm/1gj4pkoL/6/ – ReynierPM Jul 12 '23 at 13:51
  • @ReynierPM If you need to do the replacements in one go, see [the updated JS fiddle](https://jsfiddle.net/wiktor_stribizew/gztf2wc8/). – Wiktor Stribiżew Jul 12 '23 at 14:01
  • I know this is not part of the OP but could you add this too to your answer and add the explanation, it will be helpful for others as well pretty sure :) again thanks – ReynierPM Jul 12 '23 at 14:03
1

I would choose an approach which breaks the task into two

  • where the 1st replacement uses a regex which exclusively targets a string value's leading slash ... /^\// ... and ...

  • where the 2nd replacement uses a regex which targets exactly the 3rd path-segment or path-partial ... /(?<=^(?:[^/]+\/){2})[^/]+/. The direct replacement here gets achieved by utilizing a positive lookbehind, where the latter targets a sequence of two following path-segments of the form xxx/yyy/.

The advantage comes with being able of processing both path variants alike, the ones with leading slash and the ones without the latter.

console.log(
  'this/is/some/url ...',

  'this/is/some/url'
    // see ... [https://regex101.com/r/jfcZBU/2]
    .replace(/^\//, '')
    // see ... [https://regex101.com/r/jfcZBU/1]
    .replace(/(?<=^(?:[^/]+\/){2})[^/]+/, 'a')
);
console.log(
  '/this/is/some/url ...',

  '/this/is/some/url'
    .replace(/^\//, '')
    .replace(/(?<=^(?:[^/]+\/){2})[^/]+/, 'a')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

In case of combining/merging both regex patterns, also in favor of not having to use a positive lookbehind (though every relevant browser meanwhile does support this feature), the regex then becomes more complex, therefore more difficult to read ... /^\/*((?:[^/]+\/){2})[^/]+/.

const regXPathCapture =
  // see ... [https://regex101.com/r/jfcZBU/3]
  /^\/*((?:[^/]+\/){2})[^/]+/;

console.log(
  "'this/is/some/url'.replace(regXPathCapture, '$1a') ...",
  'this/is/some/url'.replace(regXPathCapture, '$1a')
);
console.log(
  "'/this/is/some/url'.replace(regXPathCapture, '$1a') ...",
  '/this/is/some/url'.replace(regXPathCapture, '$1a')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37