Регулярное выражение: соответствует словам с подчеркиванием, если они не начинаются с @ / #

Я пытаюсь обойти эту ошибку в Tiptap (редактор WYSIWYG для Vue), передав настраиваемое регулярное выражение, чтобы регулярное выражение, определяющее курсивную нотацию в Markdown (_value_) не будет применяться к строкам, начинающимся с @ или #, например #some_tag_valueне будет преобразовано в значениетега#some.

Пока это мое регулярное выражение - /(^|[^@#_\w])(?:\w?)(_([^_]+)_)/g
Изменить: новое регулярное выражение с помощью @ Wiktor Stribiew /(^|[^@#_\w])(_([^_]+)_)/g

Хотя он удовлетворяет большинству распространенных случаев, в настоящее время он все еще не работает, когда символы подчеркивания находятся в середине слова, например, ant_farm_ следует сопоставить (муравьинаяферма)

Я также предоставил несколько случаев "должно совпадать" и "не должно совпадать" здесь https://regexr.com/50ibf для упрощения тестирования.

Должно совпадать (между подчеркиванием)

_italic text here_
police_woman_
_fire_fighter
a thousand _words_
_brunch_ on a Sunday

Не должно совпадать

@ta_g_
__value__
#some_tag_value
@some_value_here
@some_tag_
#some_val_
#_hello_

3 ответа

Решение

Вы можете использовать следующий шаблон:

(?:^|\s)[^@#\s_]*(_([^_]+)_)

См. Демонстрацию регулярного выражения

Детали

  • (?:^|\s) - начало строки или пробела
  • [^@#\s_]* - 0 или более символов, кроме @, #, _ и пробелы
  • (_([^_]+)_) - Группа 1: _, 1+ символов, кроме _ (захвачены в Группу 2), а затем _.

Для науки это чудовище работает в Chrome (и Node.js).

let text = `
<strong>Should match</strong> (between underscores)

_italic text here_
police_woman_
_fire_fighter
a thousand _words_
_brunch_ on a Sunday

<strong>Should not match</strong>

@ta_g_
__value__
#some_tag_value
@some_value_here
@some_tag_
#some_val_
#_hello_
`;

let re = /(?<=(?:\s|^)(?![@#])[^_\n]*)_([^_]+)_/g;
document.querySelector('div').innerHTML = text.replace(re, '<em>$1</em>');
div { white-space: pre; }
<div/>

Это захватывает _something_ как полное совпадение, и somethingкак 1-я группа захвата (чтобы убрать подчеркивания). Вы не можете захватить простоsomething, потому что тогда вы потеряете способность различать, что находится внутри подчеркивания, а что снаружи (попробуйте (?<=(?:\s|^)(?![@#])[^_\n]*_)([^_]+)(?=_)).

Есть две вещи, которые мешают его универсальному применению:

  • Просмотр назад поддерживается не всеми движками JavaScript.
  • Большинство движков регулярных выражений не поддерживают просмотр назад переменной длины

РЕДАКТИРОВАТЬ: это немного сильнее и должно позволить вам дополнительно match_this_and_that_ but not @match_this_and_that правильно:

/(?<=(?:\s|^)(?![@#])(?!__)\S*)_([^_]+)_/

Пояснение:

_([^_]+)_    Match non-underscory bit between two underscores
(?<=...)     that is preceded by
(?:\s|^)     either a whitespace or a start of a line/string
             (i.e. a proper word boundary, since we can't use `\b`)
\S*          and then some non-space characters
(?![@#])     that don't start with `@`, `#`,
(?!__)       or `__`.

regex101 демо

Вот кое-что, это не так компактно, как другие ответы, но я думаю, что легче понять, что происходит. Группа матчей\3 это то, что вы хотите.

Требуется многострочный флаг

^([a-zA-Z\s]+|_)(([a-zA-Z\s]+)_)+?[a-zA-Z\s]*?$
  • ^ - совпадение начала строки
  • ([a-zA-Z\s]+|_) - несколько слов или _
  • (([a-zA-Z\s]+)_)+? - несколько слов, за которыми следует _ хоть один раз, но минимальное совпадение.
  • [a-zA-Z\s]*? - любые заключительные слова
  • $ - конец строки

В итоге разбивка вещей, чтобы соответствовать одному из

  • _<words>_
  • <words>_<words>_
  • <words>_<words>_<words>
  • _<words>_<words>
Другие вопросы по тегам