Regex negative lookbehind недействителен в JavaScript

Рассмотрим:

var re = /(?<=foo)bar/gi;

Это недопустимое регулярное выражение в Plunker. Почему?

Ответ 1

JavaScript не поддерживает lookbehinds, например (?<=…) (положительный) и (?<!…) (отрицательный), но это не значит, что вы еще не можете реализовать это своего рода логика в JavaScript.

Соответствие (не глобальное)

Положительное соответствие lookbehind:

// from /(?<=foo)bar/i
var matcher = mystring.match( /foo(bar)/i );
if (matcher) {
  // do stuff with matcher[1] which is the part that matches "bar"
}

Фиксированная ширина отрицательного соответствия lookbehind:

// from /(?<!foo)bar/i
var matcher = mystring.match( /(?!foo)(?:^.{0,2}|.{3})(bar)/i );
if (matcher) {
  // do stuff with matcher[1] ("bar"), knowing that it does not follow "foo"
}

Отрицательные lookbehind могут быть выполнены без глобального флага, но только с фиксированной шириной, и вам нужно рассчитать эту ширину (что может затрудниться с чередования). Использование (?!foo).{3}(bar) было бы более простым и примерно эквивалентным, но оно не будет соответствовать строке, начинающейся с "арматуры", поскольку . не может соответствовать символам новой строки, поэтому нам нужно, чтобы чередование этого кода было чередованием строк соответствия с "баром" перед символом четыре.

Если вам нужна переменная ширина, используйте нижнее глобальное решение и поставьте break в конце строфы if. (Это ограничение довольно распространено. . NET, vim и JGsoft являются only regex, которые поддерживают переменную ширину. PCRE, PHP, а Perl ограниченный фиксированной шириной. Python требует альтернативный регулярный модуль, чтобы поддержать это. Таким образом, логика обходного пути ниже должна работать для всех языков, поддерживающих регулярное выражение.)

Соответствие (глобальное)

Если вам нужно зацикливать на каждом совпадении в заданной строке (модификатор g, глобальное сопоставление), вы должны переопределить переменную matcher в каждой итерации цикла, и вы должны использовать RegExp.exec() (с RegExp создан перед циклом), потому что String.match() интерпретирует глобальный модификатор иначе и создаст бесконечный цикл!

Глобальный положительный lookbehind:

var re = /foo(bar)/gi;  // from /(?<=foo)bar/gi
while ( matcher = re.exec(mystring) ) {
  // do stuff with matcher[1] which is the part that matches "bar"
}

"Stuff" может, конечно, включать заполнение массива для дальнейшего использования.

Глобальный негативный вид:

var re = /(foo)?bar/gi;  // from /(?<!foo)bar/gi
while ( matcher = re.exec(mystring) ) {
  if (!matcher[1]) {
    // do stuff with matcher[0] ("bar"), knowing that it does not follow "foo"
  }
}

Обратите внимание, что есть случаи, в которых это не будет полностью отражать отрицательный lookbehind. Рассмотрим /(?<!ba)ll/g совпадение с Fall ball bill balll llama. Он найдет только три из четырех желаемых совпадений, потому что, когда он анализирует balll, он находит ball, а затем продолжает один символ в конце с l llama. Это происходит только тогда, когда частичное совпадение в конце может помешать частичному совпадению на другом конце (balll breaks (ba)?ll, но foobarbar в порядке с (foo)?bar). Единственным решением для этого является использование вышеуказанного фиксированного метод ширины.

Замена

Там есть замечательная статья под названием Mimicking Lookbehind в JavaScript, которая описывает, как это сделать.
У него даже есть продолжение, которое указывает на набор коротких функций, которые реализуют это в JS.

Реализация lookbehind в String.replace() намного проще, поскольку вы можете создать анонимную функцию

Это принимает целевую строку и заменяет экземпляры bar на baz, пока они следуют за foo. Если они это сделают, $1 сопоставляется, и тернарный оператор () возвращает согласованный текст и заменяющий текст (но не bar часть). В противном случае тернарный оператор возвращает исходный текст.

Отрицательная замена lookbehind:

// assuming you wanted mystring.replace(/(?<!foo)bar/i, "baz"):
mystring = mystring.replace( /(foo)?bar/i,
  function ($0, $1) { return ($1 ? $0 : "baz") }
);

Это по сути то же самое, но поскольку это отрицательный lookbehind, он действует, когда $1 отсутствует (нам не нужно говорить $1 + "baz" здесь, потому что мы знаем, что $1 пуст).

Это имеет ту же оговорку, что и другое обходное решение с отрицательной обратной связью динамической ширины и аналогично фиксируется с использованием метода фиксированной ширины.

Ответ 2

Вот способ разбора строки HTML с использованием DOM в JS и выполнять замены только за пределами тегов:

var s = '<span class="css">55</span> 2 >= 1 2 > 1';
var doc = document.createDocumentFragment();
var wrapper = document.createElement('myelt');
wrapper.innerHTML = s;
doc.appendChild( wrapper );

function textNodesUnder(el){
  var n, walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false);
  while(n=walk.nextNode())
  {
       if (n.parentNode.nodeName.toLowerCase() === 'myelt')
      		n.nodeValue =  n.nodeValue.replace(/>=?/g, "EQUAL"); 
  }
  return el.firstChild.innerHTML;
} 
var res = textNodesUnder(doc);
console.log(res);
alert(res);