简体   繁体   中英

javascript regex replace last pattern in string?

I have a string wich looks like

var std = new Bammer({mode:"deg"}).bam(0, 112).bam(177.58, (line-4)/2).bam(0, -42)
  .ramBam(8.1, 0).bam(8.1, (slot_height-thick)/2)

I want to put a tag around the last .bam() or .ramBam() .

str.replace(/(\.(ram)?bam\(.*?\))$/i, '<span class="focus">$1</span>');

And I hope to get:

new Bammer({mode:"deg"}).bam(0, 112).bam(177.58, (line-4)/2).bam(0, -42).ramBam(8.1, 0)<span class="focus">.bam(8.1, (slot_height-thick)/2)</span>

But somehow I keep on fighting with the non greedy parameter, it wraps everything after new Bammer with the span tags. Also tried a questionmark after before the $ to make the group non greedy.

I was hoping to do this easy, and with the bam or ramBam I thought that regex would be the easiest solution but I think I'm wrong.

Where do I go wrong?

You can use the following regex:

(?!.*\)\.)(\.(?:bam|ramBam)\(.*\))$

Demo

(?!.*\)\.)         # do not match ').' later in the string
(                  # begin capture group 1                   
  .\               # match '.'
  (?:bam|ramBam)   # match 'bam' or 'ramBam' in non-cap group
  \(.*\)           # match '(', 0+ chars, ')'
)                  # end capture group 1
$                  # match end of line

For the example given in the question the negative lookahead (?!.*\\)\\.) moves an internal pointer to just before the substring:

.bam(8.1, (slot_height-thick)/2)

as that is the first location where there is no substring ). later in the string.

If there were no end-of-line anchor $ and the string ended:

...0).bam(8.1, (slot_height-thick)/2)abc

then the substitution would still be made, resulting in a string that ends:

...0)<span class="focus">.bam(8.1, (slot_height-thick)/2)</span>abc

Including the end-of-line anchor prevents the substitution if the string does not end with the contents of the intended capture group.

Regex to use:

/\.((?:ram)?[bB]am\([^)]*\))(?!.*\.(ram)?[bB]am\()/
  1. \\. Matches period.
  2. (?:ram)? Optionally matches ram in a non-capturing group.
  3. [bB]am Matches bam or Bam .
  4. \\( Matches ( .
  5. [^)]* Matches 0 or more characters as long as they are not a ) .
  6. ) Matches a ) . Items 2. through 6. are placed in Capture Group 1.
  7. (?!.*\\.(ram)?[bB]am\\() This is a negative lookahead assertion stating that the rest of the string contains no further instance of .ram( or .rambam( or .ramBam( and therefore this is the last instance.

See Regex Demo

 let str = 'var std = new Bammer({mode:"deg"}).bam(0, 112).bam(177.58, 0).bam(0, -42).ramBam(8.1, 0).bam(8.1, slot_height)'; console.log(str.replace(/\\.((?:ram)?[bB]am\\([^)]*\\))(?!.*\\.(ram)?[bB]am\\()/, '<span class="focus">.$1</span>'));

Update

The JavaScript regular expression engine is not powerful enough to handle nested parentheses. The only way I know of solving this is if we can make the assumption that after the final call to bam or ramBam there are no more extraneous right parentheses in the string. Then where I had been scanning the parenthesized expression with \\([^)]*\\) , which would fail to pick up final parentheses, we must now use \\(.*\\) to scan everything until the final parentheses. At least I know no other way. But that also means that the way that I had been using to determine the final instance of ram or ramBam by using a negative lookahead needs a slight adjustment. I need to make sure that I have the final instance of ram or ramBam before I start doing any greedy matches:

(\.(?:bam|ramBam)(?!.*\.(bam|ramBam)\()\((.*)\))

See Regex Demo

  1. \\. Matches . .
  2. (?:bam|ramBam) Matches bam or ramBam.
  3. (?!.*\\.(bam|ramBam)\\() Asserts that Item 1. was the final instance
  4. \\( Matches ( .
  5. (.*) Greedily matches everything until ...
  6. \\) the final ) .
  7. ) Items 1. through 6. are placed in Capture Group 1.

 let str = 'var std = new Bammer({mode:"deg"}).bam(0, 112).bam(177.58, (line-4)/2).bam(0, -42) .ramBam(8.1, 0).bam(8.1, (slot_height-thick)/2)'; console.log(str.replace(/(\\.(?:bam|ramBam)(?!.*\\.(bam|ramBam)\\()\\((.*)\\))/, '<span class="focus">$1</span>'));

The non-greedy flag isn't quite right here, as that will just make the regex select the minimal number of characters to fit the pattern. I'd suggest that you do something with a negative lookahead like this:

str.replace(/(\.(?:ram)?[Bb]am\([^)]*\)(?!.*(ram)?[Bb]am))/i, '<span class="focus">$1</span>');

Note that this will only replace the last function name ( bam OR ramBam ), but not both. You'd need to take a slightly different approach to be able to replace both of them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM