Javascript Lookbehind с перекрытием глобального поиска

Есть несколько (иногда хитрых) решений для просмотра регулярных выражений в Javascript. Но это самый простой способ, если мне нужна нулевая ширина! посмотреть за выражением с глобальным поиском, которые могут перекрываться. Например. используя /(?<=[01])\d/g, мы могли бы сделать следующее:

let a = "--1--01001--1087---";
let result = a.replace(/(?<=[01])\d/g, "#");
// This should print "--1--0####--1##7---" if lookbehind would be supported
console.log(result);

Или другой пример: как мы можем создать выражение, похожее на \b, которое работает только с буквами ([a-zA-Z]). (Lookforward не вопрос. Просто JS не поддерживает lookbehind).


person FERcsI    schedule 22.07.2015    source источник


Ответы (1)


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

function revStr(str) {
    return str.split('').reverse().join('');
}

var s = "--1--01001--1087---";
var rxp = /\d(?=[01])/g;

var result = revStr(revStr(s).replace(rxp, "#"));
document.write(result);

Логика:

  • \d(?=[01]) — это перевернутое регулярное выражение для (?<=[01])\d
  • Мы обращаем входную строку с помощью функции revStr(s)
  • Мы снова обращаем результат замены, чтобы получить окончательный результат.

ПРИМЕЧАНИЕ:

В случае, если вам нужны и переменная ширина просмотра и просмотр вперед в JavaScript, я могу порекомендовать прочитать JavaScript Regex Lookbehind Redux, статья Стивена Левитана, в которой вы можете найти пример функции, показывающий, как реализовать такое поведение с помощью XRegExp. Вот функции:

// Simulating infinite-length leading lookbehind in JavaScript. Uses XRegExp.
// Captures within lookbehind are not included in match results. Lazy
// repetition in lookbehind may lead to unexpected results.

(function (XRegExp) {

    function prepareLb(lb) {
        // Allow mode modifier before lookbehind
        var parts = /^((?:\(\?[\w$]+\))?)\(\?<([=!])([\s\S]*)\)$/.exec(lb);
        return {
            // $(?!\s) allows use of (?m) in lookbehind
            lb: XRegExp(parts ? parts[1] + "(?:" + parts[3] + ")$(?!\\s)" : lb),
            // Positive or negative lookbehind. Use positive if no lookbehind group
            type: parts ? parts[2] === "=" : !parts
        };
    }

    XRegExp.execLb = function (str, lb, regex) {
        var pos = 0, match, leftContext;
        lb = prepareLb(lb);
        while (match = XRegExp.exec(str, regex, pos)) {
            leftContext = str.slice(0, match.index);
            if (lb.type === lb.lb.test(leftContext)) {
                return match;
            }
            pos = match.index + 1;
        }
        return null;
    };

    XRegExp.testLb = function (str, lb, regex) {
        return !!XRegExp.execLb(str, lb, regex);
    };

    XRegExp.searchLb = function (str, lb, regex) {
        var match = XRegExp.execLb(str, lb, regex);
        return match ? match.index : -1;
    };

    XRegExp.matchAllLb = function (str, lb, regex) {
        var matches = [], pos = 0, match, leftContext;
        lb = prepareLb(lb);
        while (match = XRegExp.exec(str, regex, pos)) {
            leftContext = str.slice(0, match.index);
            if (lb.type === lb.lb.test(leftContext)) {
                matches.push(match[0]);
                pos = match.index + (match[0].length || 1);
            } else {
                pos = match.index + 1;
            }
        }
        return matches;
    };

    XRegExp.replaceLb = function (str, lb, regex, replacement) {
        var output = "", pos = 0, lastEnd = 0, match, leftContext;
        lb = prepareLb(lb);
        while (match = XRegExp.exec(str, regex, pos)) {
            leftContext = str.slice(0, match.index);
            if (lb.type === lb.lb.test(leftContext)) {
                // Doesn't work correctly if lookahead in regex looks outside of the match
                output += str.slice(lastEnd, match.index) + XRegExp.replace(match[0], regex, replacement);
                lastEnd = match.index + match[0].length;
                if (!regex.global) {
                    break;
                }
                pos = match.index + (match[0].length || 1);
            } else {
                pos = match.index + 1;
            }
        }
        return output + str.slice(lastEnd);
    };

}(XRegExp));

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

Ниже приведены примеры использования:

XRegExp.execLb("Fluffy cat", "(?i)(?<=fluffy\\W+)", XRegExp("(?i)(?<first>c)at"));
// -> ["cat", "c"]
// Result has named backref: result.first -> "c"

XRegExp.execLb("Fluffy cat", "(?i)(?<!fluffy\\W+)", /cat/i);
// -> null

XRegExp.testLb("Fluffy cat", "(?i)(?<=fluffy\\W+)", /cat/i);
// -> true

XRegExp.testLb("Fluffy cat", "(?i)(?<!fluffy\\W+)", /cat/i);
// -> false

XRegExp.searchLb("Catwoman's fluffy cat", "(?i)(?<=fluffy\\W+)", /cat/i);
// -> 18

XRegExp.searchLb("Catwoman's fluffy cat", "(?i)(?<!fluffy\\W+)", /cat/i);
// -> 0

XRegExp.matchAllLb("Catwoman's cats are fluffy cats", "(?i)(?<=fluffy\\W+)", /cat\w*/i);
// -> ["cats"]

XRegExp.matchAllLb("Catwoman's cats are fluffy cats", "(?i)(?<!fluffy\\W+)", /cat\w*/i);
// -> ["Catwoman", "cats"]

XRegExp.replaceLb("Catwoman's fluffy cat is a cat", "(?i)(?<=fluffy\\W+)", /cat/ig, "dog");
// -> "Catwoman's fluffy dog is a cat"

XRegExp.replaceLb("Catwoman's fluffy cat is a cat", "(?i)(?<!fluffy\\W+)", /cat/ig, "dog");
// -> "dogwoman's fluffy cat is a dog"

XRegExp.replaceLb("Catwoman's fluffy cat is a cat", "(?i)(?<!fluffy\\W+)", /cat/ig, function ($0) {
    var first = $0.charAt(0);
    return first === first.toUpperCase() ? "Dog" : "dog";
});
// -> "Dogwoman's fluffy cat is a dog"
person Wiktor Stribiżew    schedule 22.07.2015
comment
Если вам не нужен ретроспективный просмотр переменной ширины с опережающим просмотром переменной ширины, этот реверсивный подход, по-видимому, лучше всего работает для перекрывающихся совпадений. И количество кода не такое уж большое. Кроме того, шаблоны регулярных выражений не так уж сложно отменить. - person Wiktor Stribiżew; 24.07.2015
comment
Я нахожу это сказанным, но похоже, что другого выхода нет. На самом деле, иногда нам тоже нужен и look-behind, и look-ahead (не часто: по-моему, я использовал его раз 5 за 16 лет работы с Perl, но все же). - person FERcsI; 25.07.2015
comment
Если в JavaScript вам нужен как просмотр назад, так и просмотр вперед с переменной шириной, я могу порекомендовать прочитать JavaScript Regex Lookbehind Redux», статья Стивена Левитана, в которой вы можете найти пример функции, показывающий, как реализовать такое поведение с помощью XRegExp. - person Wiktor Stribiżew; 25.07.2015