RegEx для извлечения всех совпадений из строки, используя RegExp.exec

Я пытаюсь разобрать строку следующего типа:

[key:"val" key2:"val2"]

где есть пары произвольный ключ: "val". Я хочу получить имя ключа и значение. Для любопытных я пытаюсь разобрать формат базы задач воина.

Вот моя тестовая строка:

[description:"aoeu" uuid:"123sth"]

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

В узле это мой вывод:

[deuteronomy][gatlin][~]$ node
> var re = /^\[(?:(.+?):"(.+?)"\s*)+\]$/g
> re.exec('[description:"aoeu" uuid:"123sth"]');
[ '[description:"aoeu" uuid:"123sth"]',
  'uuid',
  '123sth',
  index: 0,
  input: '[description:"aoeu" uuid:"123sth"]' ]

Но description:"aoeu" также соответствует этому шаблону. Как я могу вернуть все спички?

Ответ 1

Продолжайте вызывать re.exec(s) в цикле, чтобы получить все совпадения:

var re = /\s*([^[:]+):\"([^"]+)"/g;
var s = '[description:"aoeu" uuid:"123sth"]';
var m;

do {
    m = re.exec(s);
    if (m) {
        console.log(m[1], m[2]);
    }
} while (m);

Попробуйте это с этим JSFiddle: https://jsfiddle.net/7yS2V/

Ответ 2

str.match(pattern), если pattern имеет глобальный флаг g, вернет все совпадения в виде массива.

Например:

const str = 'All of us except @Emran, @Raju and @Noman was there';
console.log(
  str.match(/@\w*/g)
);
// Will log ["@Emran", "@Raju", "@Noman"]

Ответ 3

Чтобы перебрать все совпадения, вы можете использовать функцию replace:

var re = /\s*([^[:]+):\"([^"]+)"/g;
var s = '[description:"aoeu" uuid:"123sth"]';

s.replace(re, function(match, g1, g2) { console.log(g1, g2); });

Ответ 4

Это решение

var s = '[description:"aoeu" uuid:"123sth"]';

var re = /\s*([^[:]+):\"([^"]+)"/g;
var m;
while (m = re.exec(s)) {
  console.log(m[1], m[2]);
}

Это основано на ответе lawnsea, но короче.

Обратите внимание, что флаг `g 'должен быть установлен для перемещения внутреннего указателя вперед по вызовам.

Ответ 5

str.match(/regex/g)

возвращает все совпадения в виде массива.

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

function findMatches(regex, str, matches = []) {
   const res = regex.exec(str)
   res && matches.push(res) && findMatches(regex, str, matches)
   return matches
}

// Usage
const matches = findMatches(/regex/g, str)

как указывалось в комментариях ранее, важно иметь g в конце определения регулярного выражения для перемещения указателя вперед при каждом выполнении.

Ответ 6

На основе функции Agus, но я предпочитаю возвращать только значения соответствия:

var bob = "> bob <";
function matchAll(str, regex) {
    var res = [];
    var m;
    if (regex.global) {
        while (m = regex.exec(str)) {
            res.push(m[1]);
        }
    } else {
        if (m = regex.exec(str)) {
            res.push(m[1]);
        }
    }
    return res;
}
var Amatch = matchAll(bob, /(&.*?;)/g);
console.log(Amatch);  // yeilds: [>, <]

Ответ 7

Итерации лучше:

const matches = (text, pattern) => ({
  [Symbol.iterator]: function * () {
    const clone = new RegExp(pattern.source, pattern.flags);
    let match = null;
    do {
      match = clone.exec(text);
      if (match) {
        yield match;
      }
    } while (match);
  }
});

Использование в цикле:

for (const match of matches('abcdefabcdef', /ab/g)) {
  console.log(match);
}

Или, если вы хотите массив:

[ ...matches('abcdefabcdef', /ab/g) ]

Ответ 8

Наконец, мы начинаем видеть встроенную функцию matchAll, см. Здесь описание и таблицу совместимости. Похоже, что по состоянию на апрель 2019 года поддерживаются Chrome и Firefox, но не IE, Edge, Opera или Node.js. Похоже, что он был составлен в декабре 2018 года, поэтому дайте ему некоторое время, чтобы охватить все браузеры, но я верю, что он будет там.

Встроенная функция matchAll хороша тем, что возвращает итерацию. Он также возвращает группы захвата для каждого матча! Так что вы можете делать такие вещи, как

// get the letters before and after "o"
let matches = "stackoverflow".matchAll(/(\w)o(\w)/g);

for (match of matches) {
    console.log("letter before:" + match[1]);
    console.log("letter after:" + match[2]);
}

arrayOfAllMatches = [...matches]; // you can also turn the iterable into an array

Также кажется, что каждый объект сопоставления использует тот же формат, что и match(). Таким образом, каждый объект представляет собой массив групп соответствия и захвата, а также три дополнительных свойства index, input и groups. Так это выглядит так:

[<match>, <group1>, <group2>, ..., index: <match offset>, input: <original string>, groups: <named capture groups>]

Для получения дополнительной информации о matchAll есть также страница разработчиков Google. Также доступны полифилы/прокладки.

Ответ 9

Вот моя функция для получения совпадений:

function getAllMatches(regex, text) {
    if (regex.constructor !== RegExp) {
        throw new Error('not RegExp');
    }

    var res = [];
    var match = null;

    if (regex.global) {
        while (match = regex.exec(text)) {
            res.push(match);
        }
    }
    else {
        if (match = regex.exec(text)) {
            res.push(match);
        }
    }

    return res;
}

var regex = /abc|def|ghi/g;
var res = getAllMatches(regex, 'abcdefghi');

res.forEach(function (item) {
    console.log(item[0]);
});

Ответ 10

Если ваша система (Chrome/Node.js/Firefox) поддерживает ES9, используйте новую a_string.matchAll(regex). Если у вас более старая система, здесь есть функция для простого копирования и вставки

function findAll(regexPattern, sourceString) {
    let output = []
    let match
    // make sure the pattern has the global flag
    let regexPatternWithGlobal = RegExp(regexPattern,"g")
    while (match = regexPatternWithGlobal.exec(sourceString)) {
        // get rid of the string copy
        delete match.input
        // store the match data
        output.push(match)
    } 
    return output
}

пример использования:

console.log(   findAll(/blah/g,'blah1 blah2')   ) 

выходы:

[ [ 'blah', index: 0 ], [ 'blah', index: 6 ] ]

Ответ 11

Начиная с ES9, теперь существует более простой и лучший способ получения всех совпадений вместе с информацией о группах захвата и их индексах:

const string = 'Mice like to dice rice';
const regex = /.ice/gu;
for(const match of string.matchAll(regex)) {
    console.log(match);
}

//["мыши", индекс: 0, ввод: "мыши любят нарезать рис", группы: не определено]

//["dice", index: 13, input: "мыши любят нарезать рис", группы: undefined]

//["rice", index: 18, input: "мыши любят нарезать рис", группы: undefined]

В настоящее время поддерживается в Chrome, Firefox, Opera. В зависимости от того, когда вы читаете это, проверьте эту ссылку, чтобы увидеть ее текущую поддержку.

Ответ 12

Использовать этот...

var all_matches = your_string.match(re);
console.log(all_matches)

Он вернет массив всех совпадений... Это будет работать просто отлично... Но помните, что он не будет учитывать группы... Он просто вернет полные совпадения...

Ответ 13

Я бы определенно рекомендовал использовать функцию String.match() и создать для нее соответствующий RegEx. Мой пример со списком строк, который часто необходим при сканировании пользовательского ввода для ключевых слов и фраз.

    // 1) Define keywords
    var keywords = ['apple', 'orange', 'banana'];

    // 2) Create regex, pass "i" for case-insensitive and "g" for global search
    regex = new RegExp("(" + keywords.join('|') + ")", "ig");
    => /(apple|orange|banana)/gi

    // 3) Match it against any string to get all matches 
    "Test string for ORANGE or apples were mentioned".match(regex);
    => ["ORANGE", "apple"]

Надеюсь это поможет!

Ответ 14

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

Я упростил регулярное выражение в ответе, чтобы он был более понятным (это не решение вашей конкретной проблемы).

var re = /^(.+?):"(.+)"$/
var regExResult = re.exec('description:"aoeu"');
var purifiedResult = purify_regex(regExResult);

// We only want the group matches in the array
function purify_regex(reResult){

  // Removes the Regex specific values and clones the array to prevent mutation
  let purifiedArray = [...reResult];

  // Removes the full match value at position 0
  purifiedArray.shift();

  // Returns a pure array without mutating the original regex result
  return purifiedArray;
}

// purifiedResult= ["description", "aoeu"]

Это выглядит более многословно, чем из-за комментариев, вот как это выглядит без комментариев

var re = /^(.+?):"(.+)"$/
var regExResult = re.exec('description:"aoeu"');
var purifiedResult = purify_regex(regExResult);

function purify_regex(reResult){
  let purifiedArray = [...reResult];
  purifiedArray.shift();
  return purifiedArray;
}

Обратите внимание, что любые группы, которые не совпадают, будут перечислены в массиве как undefined значения.

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

Ответ 15

Здесь однострочное решение без цикла while.

Порядок сохраняется в результирующем списке.

Потенциальные недостатки

  1. Он клонирует регулярное выражение для каждого матча.
  2. Результат находится в другой форме, чем ожидаемые решения. Вам нужно будет обработать их еще раз.
let re = /\s*([^[:]+):\"([^"]+)"/g
let str = '[description:"aoeu" uuid:"123sth"]'

(str.match(re) || []).map(e => RegExp(re.source, re.flags).exec(e))

[ [ 'description:"aoeu"',
    'description',
    'aoeu',
    index: 0,
    input: 'description:"aoeu"',
    groups: undefined ],
  [ ' uuid:"123sth"',
    'uuid',
    '123sth',
    index: 0,
    input: ' uuid:"123sth"',
    groups: undefined ] ]

Ответ 16

Я использовал класс из документации Mozilla:

class MyRegExp extends RegExp {
    [Symbol.matchAll](str) {
        let result = RegExp.prototype[Symbol.matchAll].call(this, str);
        if (!result) {
            return null;
        }
        return Array.from(result);
    }
}

let re = new MyRegExp('-[0-9]+', 'g');
console.log('2016-01-02|2019-03-07'.matchAll(re));
// expected output: Array [Array ["-01"], Array ["-02"], Array ["-03"], Array ["-07"]]

Ответ 17

Я предполагаю, что если бы были крайние случаи, такие как лишние или пропущенные пробелы, это выражение с меньшими границами также могло бы быть вариантом:

^\s*\[\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*\]\s*$

Если вы хотите изучить/упростить/изменить выражение, это было объяснил на верхней правой панели regex101.com. Если вы хотите, вы можно также посмотреть в этом ссылка, как она будет соответствовать с некоторыми примерами входных данных.


Тест

const regex = /^\s*\[\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*\]\s*$/gm;
const str = '[description:"aoeu" uuid:"123sth"]
[description : "aoeu" uuid: "123sth"]
[ description : "aoeu" uuid: "123sth" ]
 [ description : "aoeu"   uuid : "123sth" ]
 [ description : "aoeu"uuid  : "123sth" ] ';
let m;

while ((m = regex.exec(str)) !== null) {
    // This is necessary to avoid infinite loops with zero-width matches
    if (m.index === regex.lastIndex) {
        regex.lastIndex++;
    }
    
    // The result can be accessed through the 'm'-variable.
    m.forEach((match, groupIndex) => {
        console.log('Found match, group ${groupIndex}: ${match}');
    });
}

Ответ 18

Вот мой ответ:

var str = '[me nombre es] : My name is. [Yo puedo] is the right word'; 

var reg = /\[(.*?)\]/g;

var a = str.match(reg);

a = a.toString().replace(/[\[\]]/g, "").split(','));