Группа регулярных выражений, не исключающая точки

#javascript #regex

#javascript #регулярное выражение

Вопрос:

Допустим, у меня есть следующая строка: div.classOneA.classOneB#idOne

Пытаюсь написать регулярное выражение, которое извлекает из него классы (classOneA, classOneB). Я смог это сделать, но только с помощью утверждения Lookbehind.

Это выглядит следующим образом:

 'div.classOneA.classOneB#idOne'.match(/(?<=.)([^.#] )/g)
> (2) ["classOneA", "classOneB"]
  

Теперь я хотел бы заархивировать это без подхода lookbehind и не совсем понимаю, почему мое решение не работает.

 'div.classOneA.classOneB#idOne'.match(/.([^.#] )/g)
> (2) [".classOneA", ".classOneB"]
  

Думал, что группировка решит мою проблему, но все соответствующие элементы также содержат точку.

Ответ №1:

В Javascript нет хорошего способа как для многократного сопоставления (опция / g), так и для получения групп захвата (в скобках). Попробуйте это:

 var input = "div.classOneA.classOneB#idOne";
var regex = /.([^.#] )/g;

var matches, output = [];
while (matches = regex.exec(input)) {
    output.push(matches[1]);
}
  

Комментарии:

1. Ты добрался туда чуть раньше меня 🙂

Ответ №2:

Это потому, что с g модификатором вы получаете все совпадающие подстроки, но не соответствующие им группы (то есть, как если бы (...) пары работали точно так же, как (?:...) единицы.

Вы видите. Без g модификатора:

 > 'div.classOneA.classOneB#idOne'.match(/.([^.#] )/)
[ '.classOneA',
  'classOneA',
  index: 3,
  input: 'div.classOneA.classOneB#idOne',
  groups: undefined ]
  

С g модификатором:

 > 'div.classOneA.classOneB#idOne'.match(/.([^.#] )/g)
[ '.classOneA', '.classOneB' ]
  

Другими словами: вы получаете все совпадения, но только полное совпадение (0 элементов) для каждого.

Существует множество решений:

  1. Используйте утверждения LookBehind, как вы сами указали.

  2. Исправьте каждый результат позже, добавив .map(x=>x.replace(/^./, ""))

  3. Или, если ваша структура ввода не будет намного сложнее, чем приведенный вами пример, просто используйте более дешевый подход:

     > 'div.classOneA.classOneB#idOne'.replace(/#.*/, "").split(".").slice(1)
    [ 'classOneA', 'classOneB' ]
      
  4. Используйте .replace() обратный вызов вместо .match() , чтобы иметь доступ к группам захвата каждого соответствия:

     const str = 'div.classOneA.classOneB#idOne';
    const matches = [];
    str.replace(/.([^.#] )/g, (...args)=>matches.push(args[1]))
    console.log(matches); // [ 'classOneA', 'classOneB' ]
      

Я бы рекомендовал третий вариант (если нет других возможных входных данных, которые могли бы в конечном итоге его нарушить), потому что он намного эффективнее (фактические регулярные выражения используются только один раз для обрезки части ‘#idOne’).

Комментарии:

1. Вау, это четвертое решение довольно сложное. Никогда бы не подумал использовать replace таким образом. Спасибо за усилия, хотя я уже принял ответ.

2. Я сделал это просто для развлечения 🙂 Хотя ваш вопрос может показаться преждевременной оптимизацией, также верно, что могут быть веские причины избегать подходов lookbehind: Во-первых, потому что операции поиска вокруг являются одними из самых дорогостоящих в реализациях регулярных выражений, поэтому в коде, который будет выполняться слишком часто, их лучше избегать.

3. … с другой стороны, именно по этой причине они были недоступны в javascript до (если я не ошибаюсь) ES6, поэтому, если ваш код должен выполняться в старых движках javascript, поисковые подходы не являются жизнеспособным вариантом.

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

Ответ №3:

Если вы хотите расширить свое регулярное выражение. вы можете просто map использовать результаты и заменить . на пустую строку

 let op = 'div.classOneA.classOneB#idOne'.match(/.([^.#] )/g)
         .map(e=> e.replace(/./g,''))

console.log(op)  

Ответ №4:

Если вы знаете, что ищете текст, содержащий class , то вы можете использовать что-то вроде

 'div.classOneA.classOneB#idOne'.match(/class[^.#] /g)
  

Если единственное, что вы знаете, это то, что тексту предшествует точка, тогда вы должны использовать lookbehind.

Ответ №5:

Это регулярное выражение будет работать без утверждения lookbehind:

 'div.classOneA.classOneB#idOne'.match(/.[^.#] /g).map(item => item.substring(1));
  

Утверждение Lookbehind в последнее время недоступно в JavaScript.

Ответ №6:

Я не эксперт по использованию регулярных выражений — особенно в Javascript, — но после некоторого исследования MDN я выяснил, почему ваша попытка не сработала, и как это исправить.

Проблема в том, что использование .match с регулярным выражением с /g флагом будет игнорировать группы захвата. Таким образом, вместо этого вы должны использовать метод .exec для объекта regexp, используя цикл для выполнения его несколько раз, чтобы получить все результаты.

Итак, следующий код — это то, что работает, и может быть адаптирован для аналогичных случаев. (Обратите внимание на grp[1] — это потому, что первый элемент массива, возвращаемый .exec , является полным совпадением, группы — это последующие элементы.)

 var regExp = /.([^.#] )/g
var result = [];
var grp;
while ((grp = regExp.exec('div.classOneA.classOneB#idOne')) !== null) {
  result.push(grp[1]);
}
console.log(result)