Як я можу зіставити кілька вторгнень із регулярним виразом у JavaScript, подібному до preg_match_all () PHP?


160

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

Далі буде відповідати лише перше виникнення, розбиваючи ключі та значення на окремі результати результату:

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

Результати для рядка '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' будуть:

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

Використання глобального прапора 'g' відповідатиме всім явищам, але повертає лише повністю відповідні підрядки, а не відокремлені ключі та значення:

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

Результати для рядка '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' будуть:

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

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

Я маю на меті якимось чином досягти результатів із розділеними підвідборами на зразок:

[['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 - це "замінити" функцією обробника заміни замість рядка.
Майк 'Помакс' Камерманс

Зауважте, що для тих, хто все ще знайде це питання у 2020 році, відповідь "не використовуйте регулярні вирази, використовуйте URLSearchParams , який робить все це для вас".
Майк 'Помакс' Камерманс

Відповіді:


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"
  геокод: ""
  hl: "de"
  тобто: "UTF8"
  iwloc: "addr"
  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;" для неправильно кодованих HTML-адрес)
) # кінцева група, яка не захоплює
(# група 1
  [^ = & #] + # будь-який символ, крім "=", "&" або "#"; принаймні, один раз
) # кінцева група 1 - це буде ім'я параметра
(?: # група, яка не захоплює
  =? # a "=", необов'язково
  (# група 2
    [^ & #] * # будь-який символ, крім "&" або "#"; будь-яку кількість разів
  ) # кінцева група 2 - це буде значення параметра
) # кінцева група, яка не захоплює

23
Це те, на що я сподівався. Те, що я ніколи не бачив у документації JavaScript, - це те, що метод exec () продовжить повертати наступний набір результатів, якщо викликається більше одного разу. Ще раз дякую за чудову пораду!
Адам Франко

1
Це відбувається через це: regular-expressions.info/javascript.html (Читати через: "Як користуватися об'єктом JavaScript RegExp")
Tomalak

1
в цьому коді є помилка: крапку з комою після символу "while" слід видалити.
Ян Віллем Б

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

1
@KnightYoshi Так. В JavaScript будь-який вираз також виробляє свій власний результат (як x = yби приписувати yдо , xа також виробляти y). Коли ми застосуємо ці знання до if (match = re.exec(url)): Це A) виконує завдання і B) повертає результат 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(/"(.*?)"/)добре працює при виявленні першого, але я не знаю, як адаптувати ваше рішення для однієї групи захоплення.
nu everest

2
здається, що для цього слід написати питання про Stackoverflow, а не намагатися вирішити його в коментарях.
Майк 'Pomax' Kamermans

Я створив нове питання: 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, він зірочне на другому матчі, і ми програли перший матч.
fdrv

@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 () кілька разів, щоб знайти послідовні збіги в одному рядку. Коли ви це зробите, пошук починається в підрядковій строці, вказаній властивістю 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), а потім спробувати "робити", він зірочки на другому матчі, і ми програли перший матч.
fdrv

Ви можете також комбінувати 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

Hеllo з 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

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

0

Щоб захопити кілька параметрів за допомогою одного і того ж імені, я змінив цикл while у методі Tomalak таким чином:

  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.