Как я могу сопоставить несколько вхождений с регулярным выражением в JavaScript, похожим на PHP preg_match_all ()?


160

Я пытаюсь проанализировать строки в кодировке URL, которые состоят из пар ключ = значение, разделенных либо &или &.

Следующее будет соответствовать только первому вхождению, разбивая ключи и значения на отдельные элементы результата:

var result = mystring.match(/(?:&|&)?([^=]+)=([^&]+)/)

Результаты для строки «1111342 = Адам% 20Franco & 348572 = Боб% 20Jones» будут следующими:

['1111342', 'Adam%20Franco']

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

var result = mystring.match(/(?:&|&)?([^=]+)=([^&]+)/g)

Результаты для строки «1111342 = Адам% 20Franco & 348572 = Боб% 20Jones» будут следующими:

['1111342=Adam%20Franco', '&348572=Bob%20Jones']

Хотя я мог бы разбить строку &и разбить каждую пару ключ / значение по отдельности, есть ли способ использовать поддержку регулярных выражений JavaScript, чтобы сопоставить множественные вхождения шаблона, /(?:&|&)?([^=]+)=([^&]+)/аналогичные PHPpreg_match_all() функции ?

Я стремлюсь к какому-то способу получить результаты с разделенными под-матчами, например:

[['1111342', '348572'], ['Adam%20Franco', 'Bob%20Jones']]

или

[['1111342', 'Adam%20Franco'], ['348572', 'Bob%20Jones']]

9
немного странно, что никто не рекомендовал использовать replaceздесь. var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; });сделано. «matchAll» в JavaScript - это «заменить» функцией замены вместо строки.
Майк 'Pomax' Камерманс

Обратите внимание, что для тех, кто все еще находит этот вопрос в 2020 году, ответ «не используйте регулярные выражения, используйте URLSearchParams , который сделает все это за вас».
Майк 'Pomax' Камерманс

Ответы:


161

Поднято из комментариев

Комментарий 2020: вместо использования регулярных выражений мы имеем теперь URLSearchParams , что делает все это для нас, так что никакой специальный код, не говоря уже о регулярном выражении, больше не нужен.

- Майк 'Pomax' Камерманс

Поддержка браузера указана здесь https://caniuse.com/#feat=urlsearchparams


Я бы предложил альтернативное регулярное выражение, используя подгруппы для индивидуального ввода имени и значения параметров и re.exec():

function getUrlParams(url) {
  var re = /(?:\?|&(?:amp;)?)([^=&#]+)(?:=?([^&#]*))/g,
      match, params = {},
      decode = function (s) {return decodeURIComponent(s.replace(/\+/g, " "));};

  if (typeof url == "undefined") url = document.location.href;

  while (match = re.exec(url)) {
    params[decode(match[1])] = decode(match[2]);
  }
  return params;
}

var result = getUrlParams("http://maps.google.de/maps?f=q&source=s_q&hl=de&geocode=&q=Frankfurt+am+Main&sll=50.106047,8.679886&sspn=0.370369,0.833588&ie=UTF8&ll=50.116616,8.680573&spn=0.35972,0.833588&z=11&iwloc=addr");

result это объект:

{
  f: "q"
  геокод: ""
  гл: "де"
  то есть: "UTF8"
  iwloc: "адрес"
  ll: "50.116616,8.680573"
  q: "Франкфурт на Майне"
  sll: "50.106047,8.679886"
  источник: "s_q"
  spn: "0.35972,0.833588"
  sspn: "0.370369,0.833588"
  z: "11"
}

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

(?: # группа без захвата
  \? | & # "?" или "&"
  (: Усилитель;?)? # (разрешить "& amp;", для неправильно закодированных URL-адресов в HTML)
) # конец группы без захвата
( # группа 1
  [^ = & #] + # любой символ, кроме "=", "&" или "#"; Хотя бы один раз
) # end group 1 - это будет имя параметра
(?: # группа без захвата
  знак равно # an "=", необязательно
  (№ группа 2
    [^ & #] * # любой символ, кроме "&" или "#"; любое количество раз
  ) # end group 2 - это значение параметра
) # конец группы без захвата

23
Это то, на что я надеялся. То, что я никогда не видел в документации JavaScript, это упоминание о том, что метод exec () будет продолжать возвращать следующий набор результатов, если вызывается более одного раза. Еще раз спасибо за отличный совет!
Адам Франко

1
Это происходит из-за этого: регулярные выражения.info/javascript.html (Прочитано: «Как использовать объект JavaScript RegExp»)
Томалак,

1
В этом коде есть ошибка: точка с запятой после «while» должна быть удалена.
Ян Виллем Б

1
Потому что я обычно использую обычные (то есть собирающие) группы, только если я действительно заинтересован в их содержании.
Томалак

1
@KnightYoshi Да. В JavaScript любое выражение также производит свой собственный результат (как x = yбы приписывать yк , xа также производить y). Когда мы применяем эти знания к if (match = re.exec(url)): А) выполняет задание и Б) возвращает результат re.exec(url)в while. Теперь re.execвозвращает, nullесли совпадения нет, что является ложным значением. Таким образом, в действительности цикл будет продолжаться до тех пор, пока есть совпадение.
Томалак

67

Вам нужно использовать переключатель «g» для глобального поиска

var result = mystring.match(/(&|&)?([^=]+)=([^&]+)/g)

33
На самом деле это не решает проблему: «Использование глобального флага« g »будет соответствовать всем вхождениям, но только будет возвращать полностью совпадающие подстроки, а не разделенные ключи и значения».
Адам Франко

40

2020 редактировать

Используйте URLSearchParams , так как для этого задания больше не требуется никакого специального кода. Браузеры могут сделать это за вас с помощью одного конструктора:

const str = "1111342=Adam%20Franco&348572=Bob%20Jones";
const data = new URLSearchParams(str);
for (pair of data) console.log(pair)

доходность

Array [ "1111342", "Adam Franco" ]
Array [ "348572", "Bob Jones" ]

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

Оригинальный ответ

Если вы не хотите полагаться на «слепое сопоставление», которое идет с execсопоставлением стилей бега , JavaScript имеет встроенную функциональность сопоставления всех, но это часть replaceвызова функции, когда используется «что делать с захватом». группы» функции обработки :

var data = {};

var getKeyValue = function(fullPattern, group1, group2, group3) {
  data[group2] = group3;
};

mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, getKeyValue);

сделано.

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

Таким образом, вместо того, чтобы писать сложные функции синтаксического анализа, помните, что функция «matchAll» в JavaScript просто «заменяет» на функцию обработчика замены, и можно добиться значительной эффективности сопоставления с образцом.


У меня есть строка something "this one" and "that one". Я хочу поместить все строки в двойных кавычках в список, т. Е. [Этот, тот]. До сих пор mystring.match(/"(.*?)"/)хорошо работает при обнаружении первого, но я не знаю, как адаптировать ваше решение для одной группы захвата.
Ню Эверест

2
Похоже, вы должны опубликовать вопрос о Stackoverflow для этого, а не пытаться решить его в комментариях.
Майк 'Pomax' Камерманс

Я создал новый вопрос: stackoverflow.com/questions/26174122/…
nu everest

1
Не уверен, почему в этом ответе так мало голосов, но это лучший ответ на вопрос.
Калин

Привет @ Mike'Pomax'Kamermans, руководящие принципы сообщества, в частности, рекомендуют редактировать записи, чтобы улучшить их, см .: stackoverflow.com/help/behavior . Суть вашего ответа чрезвычайно полезна, но я обнаружил, что язык «помните, что matchAll является заменой» не был ясен и не объяснял, почему ваш код (который неочевиден) работает. Я подумал, что вы должны получить заслуженного представителя, поэтому я отредактировал ваш ответ, а не продублировал его с улучшенным текстом. Как первоначальный задающий этот вопрос, я с радостью откажусь от принятия этого ответа (и редактирования), если вы все еще хотите, чтобы я.
Адам Франко

21

Для захвата групп я привык использовать preg_match_allв PHP, и я попытался повторить его функциональность здесь:

<script>

// Return all pattern matches with captured groups
RegExp.prototype.execAll = function(string) {
    var match = null;
    var matches = new Array();
    while (match = this.exec(string)) {
        var matchArray = [];
        for (i in match) {
            if (parseInt(i) == i) {
                matchArray.push(match[i]);
            }
        }
        matches.push(matchArray);
    }
    return matches;
}

// Example
var someTxt = 'abc123 def456 ghi890';
var results = /[a-z]+(\d+)/g.execAll(someTxt);

// Output
[["abc123", "123"],
 ["def456", "456"],
 ["ghi890", "890"]]

</script>

3
@teh_senaus, вам нужно указать глобальный модификатор, в /gпротивном случае выполнение exec()не изменит текущий индекс и зациклится навсегда.
Арам Кочарян

Если я позвоню, чтобы проверить этот код myRe.test (str), а затем попробую сделать execAll, он помечается во втором матче, и мы проиграли первый.
2016 г.

@fdrv Вы должны сбросить lastIndex на ноль перед началом цикла: this.lastIndex = 0;
CF

15

Установите gмодификатор для глобального соответствия:

/…/g

11
На самом деле это не решает проблему: «Использование глобального флага« g »будет соответствовать всем вхождениям, но только будет возвращать полностью совпадающие подстроки, а не разделенные ключи и значения».
Адам Франко

11

Источник:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec

Поиск последовательных матчей

Если ваше регулярное выражение использует флаг "g", вы можете использовать метод exec () несколько раз, чтобы найти последовательные совпадения в одной и той же строке. Когда вы это делаете, поиск начинается с подстроки str, указанной в свойстве регулярного выражения lastIndex (test () также опережает свойство lastIndex). Например, предположим, что у вас есть этот скрипт:

var myRe = /ab*/g;
var str = 'abbcdefabh';
var myArray;
while ((myArray = myRe.exec(str)) !== null) {
  var msg = 'Found ' + myArray[0] + '. ';
  msg += 'Next match starts at ' + myRe.lastIndex;
  console.log(msg);
}

Этот скрипт отображает следующий текст:

Found abb. Next match starts at 3
Found ab. Next match starts at 912

Примечание. Не помещайте литерал регулярного выражения (или конструктор RegExp) в условие while, иначе он создаст бесконечный цикл при совпадении из-за того, что свойство lastIndex сбрасывается при каждой итерации. Также убедитесь, что установлен глобальный флаг или здесь также произойдет цикл.


Если я позвоню, чтобы проверить этот код myRe.test (str), а затем попробую сделать while, он помечается во втором матче, и мы проиграли первый.
2016 г.

Вы также можете комбинировать String.prototype.matchс gфлагом: 'abbcdefabh'.match(/ab*/g)возврат['abb', 'ab']
thom_nic

2

Если кому-то (как и мне) нужен метод Томалака с поддержкой массивов (т.е. множественный выбор), вот он:

function getUrlParams(url) {
  var re = /(?:\?|&(?:amp;)?)([^=&#]+)(?:=?([^&#]*))/g,
      match, params = {},
      decode = function (s) {return decodeURIComponent(s.replace(/\+/g, " "));};

  if (typeof url == "undefined") url = document.location.href;

  while (match = re.exec(url)) {
    if( params[decode(match[1])] ) {
        if( typeof params[decode(match[1])] != 'object' ) {
            params[decode(match[1])] = new Array( params[decode(match[1])], decode(match[2]) );
        } else {
            params[decode(match[1])].push(decode(match[2]));
        }
    }
    else
        params[decode(match[1])] = decode(match[2]);
  }
  return params;
}
var urlParams = getUrlParams(location.search);

вход ?my=1&my=2&my=things

результат 1,2,things(ранее возвращалось только: вещи)


1

Просто для того, чтобы придерживаться предложенного вопроса, как указано в заголовке, вы можете фактически перебирать каждое совпадение в строке, используя String.prototype.replace(). Например, следующее делает именно это, чтобы получить массив всех слов на основе регулярного выражения:

function getWords(str) {
  var arr = [];
  str.replace(/\w+/g, function(m) {
    arr.push(m);
  });
  return arr;
}

var words = getWords("Where in the world is Carmen Sandiego?");
// > ["Where", "in", "the", "world", "is", "Carmen", "Sandiego"]

Если бы я хотел получить группы захвата или даже индекс каждого матча, я мог бы сделать это тоже. Ниже показано, как возвращается каждое совпадение с полным совпадением, 1-й группой захвата и индексом:

function getWords(str) {
  var arr = [];
  str.replace(/\w+(?=(.*))/g, function(m, remaining, index) {
    arr.push({ match: m, remainder: remaining, index: index });
  });
  return arr;
}

var words = getWords("Where in the world is Carmen Sandiego?");

После запуска вышеперечисленного, wordsбудет сделано следующее:

[
  {
    "match": "Where",
    "remainder": " in the world is Carmen Sandiego?",
    "index": 0
  },
  {
    "match": "in",
    "remainder": " the world is Carmen Sandiego?",
    "index": 6
  },
  {
    "match": "the",
    "remainder": " world is Carmen Sandiego?",
    "index": 9
  },
  {
    "match": "world",
    "remainder": " is Carmen Sandiego?",
    "index": 13
  },
  {
    "match": "is",
    "remainder": " Carmen Sandiego?",
    "index": 19
  },
  {
    "match": "Carmen",
    "remainder": " Sandiego?",
    "index": 22
  },
  {
    "match": "Sandiego",
    "remainder": "?",
    "index": 29
  }
]

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

function matchAll(str, rgx) {
  var arr, extras, matches = [];
  str.replace(rgx.global ? rgx : new RegExp(rgx.source, (rgx + '').replace(/[\s\S]+\//g , 'g')), function() {
    matches.push(arr = [].slice.call(arguments));
    extras = arr.splice(-2);
    arr.index = extras[0];
    arr.input = extras[1];
  });
  return matches[0] ? matches : null;
}

Так как вы хотите проанализировать строку запроса URL-адреса, вы также можете использовать что-то вроде YourJS.parseQS()( yourjs.com/snippets/56 ), хотя многие другие библиотеки также предлагают эту функцию.
Крис Вест

Модификация переменной из внешней области видимости в цикле, которая должна возвращать замену, является чем-то плохим. Твое злоупотребление заменой здесь
Хуан Мендес

1

Если вы можете обойтись без использования, mapэто решение из четырех строк:

var mystring = '1111342=Adam%20Franco&348572=Bob%20Jones';

var result = mystring.match(/(&|&amp;)?([^=]+)=([^&]+)/g) || [];
result = result.map(function(i) {
  return i.match(/(&|&amp;)?([^=]+)=([^&]+)/);
});

console.log(result);

Это не красиво, не эффективно, но, по крайней мере, оно компактно. ;)


1

Используйте window.URL:

> s = 'http://www.example.com/index.html?1111342=Adam%20Franco&348572=Bob%20Jones'
> u = new URL(s)
> Array.from(u.searchParams.entries())
[["1111342", "Adam Franco"], ["348572", "Bob Jones"]]

1

Привет с 2020 года. Позвольте мне представить String.prototype.matchAll () вашему вниманию:

let regexp = /(?:&|&amp;)?([^=]+)=([^&]+)/g;
let str = '1111342=Adam%20Franco&348572=Bob%20Jones';

for (let match of str.matchAll(regexp)) {
    let [full, key, value] = match;
    console.log(key + ' => ' + value);
}

Выходы:

1111342 => Adam%20Franco
348572 => Bob%20Jones

В заключение! Предупреждение : «ECMAScript 2020, 11-е издание, вводит метод matchAll для Strings, чтобы создать итератор для всех объектов соответствия, сгенерированных глобальным регулярным выражением» . Согласно сайту, указанному в ответе, большинство браузеров и nodeJS поддерживают его в настоящее время, но не IE, Safari или Samsung Internet. Надеюсь, поддержка будет расширяться в ближайшее время, но YMMV на некоторое время.
Адам Франко

0

Чтобы захватить несколько параметров с использованием одного и того же имени, я изменил цикл while в методе Томалака следующим образом:

  while (match = re.exec(url)) {
    var pName = decode(match[1]);
    var pValue = decode(match[2]);
    params[pName] ? params[pName].push(pValue) : params[pName] = [pValue];
  }

вход: ?firstname=george&lastname=bush&firstname=bill&lastname=clinton

возвращает: {firstname : ["george", "bill"], lastname : ["bush", "clinton"]}


Хотя мне нравится ваша идея, она не очень хорошо работает с отдельными параметрами, как ?cinema=1234&film=12&film=34я и ожидал {cinema: 1234, film: [12, 34]}. Отредактировал ваш ответ, чтобы отразить это.
TWiStErRob

0

Ну ... у меня была похожая проблема ... Я хочу пошаговый поиск с помощью RegExp (например: начать поиск ... выполнить некоторую обработку ... продолжить поиск до последнего соответствия)

После большого количества интернет-поиска ... как всегда (это становится привычкой), я оказался в StackOverflow и нашел ответ ...

На что не ссылаются и что стоит упомянуть, так это " lastIndex" Теперь я понимаю, почему объект RegExp реализует lastIndexсвойство " "


0

Разделение это выглядит как лучший вариант для меня:

'1111342=Adam%20Franco&348572=Bob%20Jones'.split('&').map(x => x.match(/(?:&|&amp;)?([^=]+)=([^&]+)/))

0

Чтобы избежать регулярного выражения ада, вы можете найти свой первый матч, отрубите кусок и попытайтесь найти следующий в подстроке. В C # это выглядит примерно так, извините, я не перенес это на JavaScript для вас.

        long count = 0;
        var remainder = data;
        Match match = null;
        do
        {
            match = _rgx.Match(remainder);
            if (match.Success)
            {
                count++;
                remainder = remainder.Substring(match.Index + 1, remainder.Length - (match.Index+1));
            }
        } while (match.Success);
        return count;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.