2

I need to find and replace variables from a JavaScript code.

Lets say I need to search "length" in given code, I want to exclude "x.length" or "linelength"

Sample input - to make it easier to debug

var sampleStringWhichWorks = 'function checkLength(l, ls){\n' +
    'var line, lines, len, length01, linelength11;\n' +
    'line = l;\n' +
    'lines = ls;\n' +
    'len = line.length12;\n' +
    'length02 = lines.length13;\n' +
    'if(len==length03){\n' +
        'len--;\n' +
        'length04= length05 + 1;\n' +
        '}\n' +
    'linelength14 = len + length06;\n' +
    'return linelength15;\n' +
'}\n';

This /([^a-z.])length[\d]{2}([^a-z])/mig expression solves the problem, but for lines like "length04=length05 + 1" it only captures the first length and ignores second.

It does work for "length04 = length05 + 1" and captures both instances.

I have added [\d]{2} just for better understanding, expected outcome here is to capture all the 'length' ending with 0 and ignore ending with 1.

I tried other options like specifying like [^a-z]{0,1}, but not solving the problem.

Check the jsFiddle here.

Actual input and regex (/([^a-z.])length([^a-z])/mig)

function checkLength(l, ls){
    var line, lines, len, length, lineLength;
    line = l;
    lines = ls;
    len = line.length;
    length = lines.length;
    if(len == length){
        len--;
        length=length+1;
    }
    lineLength = len + length;
    return lineLength;
}

Expected outcome from this

  1. var line, lines, len, length, lineLength;
  2. length = lines.length;
  3. if(len == length){
  4. length = length + 1;
  5. lineLength = len + length;
Ajay Bhosale
  • 1,872
  • 24
  • 35

2 Answers2

3

The issue that you have is that regular expression matches begins at the end of the previous match and as you can see on this sample :

([^a-z.])length[\d]{2}([^a-z])

Regular expression visualization

Debuggex Demo

length05 is not being captured because the = signs before it is already taken by the previous match...

So you can use the method indicated in this answer :

var regex = /([^a-z.])length[\d]{2}([^a-z])/mig

var matches = [], found;
while (found = regex.exec(string)) {
    matches.push(found[0]);
    regex.lastIndex = found.index+1;
}

console.log(matches);

You can see it in action in this fiddle : http://jsfiddle.net/x6ARj/3/


EDIT :

In response to your comment, if you want to replace the string, one solution is to use the slice method using found.index and regex.lastIndex indexes :

var replacement = "name", 
    regex = /([^a-z.])length([\d]{2}[^a-z])/mi,
    matches = [], found;

while (found = regex.exec(string)) {
    string = string.slice(0, found.index) + found[1] + replacement + found[2] + string.slice(regex.lastIndex);
    regex.lastIndex = found.index+1;
}

console.log(string);
Community
  • 1
  • 1
Samuel Caillerie
  • 8,259
  • 1
  • 27
  • 33
  • +1, Excellent, Thanks a lot, I also thought that the = is getting captured by earlier match, I now need to think how i can use it to replace it ... I was using '$1SOME-OTHER-NAMKE-INSTEAD-OF-LENGTH$2' – Ajay Bhosale Oct 18 '13 at 08:34
  • You mean a dynamical name, like this : `var name = "some_name", regex = new RegExp("([^a-z.])" + name + "[\\d]{2}([^a-z])", "mig");`. – Samuel Caillerie Oct 18 '13 at 08:45
  • yes, i want to replace "some_name" with "some_other_name" while keeping the captured parts, so i was doing code.replace(regex,'$1some_other_name$2') to replace "some_name" with "some_other_name" – Ajay Bhosale Oct 18 '13 at 08:49
  • 1
    @AjayBhosale: Note that lookahead assertions remove the need for any "manual resetting" of the match's index counter precisely because the lookahead doesn't participate in the actual match. – Tim Pietzcker Oct 18 '13 at 09:24
  • I agree with @TimPietzcker comment for this example (here we don't really need a validation on an extra character) but for some cases, we NEED this extra character in the regex, and in this case, this question is pertinent! – Samuel Caillerie Oct 18 '13 at 09:30
  • @TimPietzcker - Thanks for the pointers, I will explore lookahead assertions and see if i can get desired results without resetting index. – Ajay Bhosale Oct 18 '13 at 12:15
  • @SamuelCaillerie - As edited answer by Tim Pietzcker is more accurate i am going to change accepted answer, hope you are fine with this ... – Ajay Bhosale Oct 18 '13 at 14:28
  • No problem, as indicated above, I agree too with this solution, but I let mine as it can't be avoided in some problems! – Samuel Caillerie Oct 18 '13 at 16:01
2

If you want to match length if and only if it's not preceded by [a-z.] and not succeeded by [a-z], then you can use this:

/(^|[^a-z.])length(?![a-z])/mig

Explanation:

(^|[^a-z.]) # Match either the start of the line or a char other than [a-z.]
length      # Match "length0"
(?![a-z])   # Assert that there is no [a-z] following the current match

Note that the regex will have to capture the character before length if the match occurs anywhere else but the start of the string. This is unavoidable due to JavaScript's lamentable lack of support for lookbehind assertions. So you need to check the length of your match, and chop off the first character if the length is 7.

Tim Pietzcker
  • 328,213
  • 58
  • 503
  • 561