Терпеть определенные символы в RegEx

Я пишу синтаксический анализатор форматирования сообщений, который имеет возможность (среди прочего) анализировать ссылки. Этот конкретный случай требует синтаксического анализа ссылки из <url|linkname> и заменить этот текст только linkname, Проблема здесь в том, что оба url или же linkname может содержать или не содержать \1 или же \2 символы в любом месте в любом порядке (но не более одного). Я хочу соответствовать шаблону, но оставляю "недействительные" символы. Эта проблема решает сама для linkname так как эта часть шаблона просто ([^\n+]), но url фрагмент сопоставляется с гораздо более сложным шаблоном, в частности с шаблоном проверки URL из is.js. Не было бы тривиально изменить весь шаблон вручную, чтобы терпеть [\1\2] везде, и мне нужен шаблон для сохранения этих символов, так как они используются для целей отслеживания (поэтому я не могу просто .replace(/\1|\2/g, "") до сопоставления).

Если такой тип сопоставления невозможен, существует ли какой-либо автоматизированный способ надежного изменения RegExp для добавления [\1\2]{0,2} между каждым совпадением символов, добавить \1\2 все [chars] спички и т. д.

Это url шаблон взят из is.js:

/(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?/i

Этот шаблон был адаптирован для моих целей и для <url|linkname> отформатировать следующим образом:

let namedUrlRegex = /<((?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?)\|([^\n]+)>/ig;

Код, где это используется, находится здесь: JSFiddle

Примеры для уточнения (... представляет namedUrlRegex переменная сверху и $2 это группа захвата, которая захватывает linkname):

Current behavior:
"<googl\1e.com|Google>".replace(..., "$2") // "<googl\1e.com|Google>" WRONG
"<google.com|Goo\1gle>".replace(..., "$2") // "Goo\1gle"              CORRECT
"<not_\1a_url|Google>".replace(..., "$2") // "<not_\1a_url|Google>"   CORRECT

Expected behavior:
"<googl\1e.com|Google>".replace(..., "$2") // "Google" (note there is no \1)
"<google.com|Goo\1gle>".replace(..., "$2") // "Goo\1gle"
"<not_\1a_url|Google>".replace(..., "$2") // "<not_\1a_url|Google>"

Обратите внимание на те же правила для \1 применить к \2, \1\2, \1...\2, \2...\1 так далее

Контекст: используется для нормализации строки из редактора WYSIWYG в соответствии с длиной / содержимым, которое будет отображаться, сохраняя местоположение текущего выделения (обозначается как \1 а также \2 так что его можно восстановить после разбора). Если курсор полностью удаляется (например, если курсор был в URL-адресе ссылки), он выберет всю строку целиком. Все работает как положено, за исключением случаев, когда выбор начинается или заканчивается во фрагменте URL.

Изменить для уточнения: я хочу изменить сегмент в строке, только если он соответствует формату <url|linkname> где url соответствует шаблону URL (допускается \1, \2) а также linkname состоит из \n персонажи. Если это условие не выполняется в течение <...|...> строка должна быть оставлена без изменений в соответствии с not_a_url пример выше.

1 ответ

Решение

Я закончил тем, что сделал RegEx, который соответствует всем "символам" в выражении. Одним из причуд этого является то, что он ожидает :, =, ! символы, которые нужно экранировать, даже вне (?:...), (?=...), (?!...) выражение. Это решается путем экранирования их перед обработкой.

Fiddle

let r = /(\\.|\[.+?\]|\w|[^\\\/\[\]\^\$\(\)\?\*\+\{\}\|\+\:\=\!]|(\{.+?\}))(?:((?:\{.+?\}|\+|\*)\??)|\??)/g;

let url = /((?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?)/

function tolerate(regex, insert) {
    let first = true;
        // convert to string
    return regex.toString().replace(/\/(.+)\//, "$1").
        // escape :=!
        replace(/((?:^|[^\\])\\(?:\\)*\(\?|[^?])([:=!]+)/g, (m, g1, g2) => g1 + (g2.split("").join("\\"))).
        // substitute string
        replace(r, function(m, g1, g2, g3, g4) {
            // g2 = {...} multiplier (to prevent matching digits as symbols)
            if (g2) return m;
            // g3 = multiplier after symbol (must wrap in parenthesis to preserve behavior)
            if (g3) return "(?:" + insert + g1 + ")" + g3;
            // prevent matching tolerated characters at beginning, remove to change this behavior
            if (first) {
                first = false;
                return m;
            }
            // insert the insert
            return insert + m;
        }
    );
}

alert(tolerate(url, "\1?\2?"));
Другие вопросы по тегам