Регулярное отрицательное выражение в JavaScript недопустимо

Рассматривать:

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

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

2 ответа

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

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

Позитивный взгляд за спиной:

// 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"
}

Фиксированная ширина негативного вида за совпадением:

// 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"
}

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

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

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

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

Глобальный позитивный взгляд позади:

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"
}

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

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

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"
  }
}

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

Замена

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

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

Они работают в первом матче, но их можно сделать глобальными, просто добавив g модификатор.

Положительный взгляд за замену:

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

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

Отрицательный взгляд за замену:

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

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

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

Вот способ проанализировать строку 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);

Другие вопросы по тегам