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

Я пытаюсь разобрать строки с кодировкой 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 для соответствия множественным вхождениям шаблона /(?:&|&)?([^=]+)=([^&]+)/, аналогичному функции PHP preg_match_all()?

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

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

или

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

Ответы

Ответ 1

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

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"
  geocode: ""
  hl: "de"
  ie: "UTF8"
  iwloc: "addr"
  ll: "50.116616,8.680573"
  q: "Frankfurt am Main"
  sll: "50.106047,8.679886"
  source: "s_q"
  spn: "0.35972,0.833588"
  sspn: "0.370369,0.833588"
  z: "11"
}

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

(?:            # non-capturing group
  \?|&         #   "?" or "&"
  (?:amp;)?    #   (allow "&", for wrongly HTML-encoded URLs)
)              # end non-capturing group
(              # group 1
  [^=]+      #   any character except "=", "&" or "#"; at least once
)              # end group 1 - this will be the parameter name
(?:            # non-capturing group
  =?           #   an "=", optional
  (            #   group 2
    [^]*     #     any character except "&" or "#"; any number of times
  )            #   end group 2 - this will be the parameter value
)              # end non-capturing group

Ответ 2

Для глобального поиска вам нужно использовать переключатель "g"

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

Ответ 3

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

var data = {};

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

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

сделанный.

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

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

Ответ 4

Для захвата групп я использую 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>

Ответ 5

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

/…/g

Ответ 6

Источник:
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 сбрасывается при каждой итерации. Также убедитесь, что установлен глобальный флаг, или здесь также произойдет цикл.

Ответ 7

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

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 (ранее возвращаемый только: вещи)

Ответ 8

Чтобы придерживаться предложенного вопроса, как указано в названии, вы можете фактически перебирать каждое соответствие в строке с помощью 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"]

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

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(). Функция более подробно описана ниже:

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;
}

Ответ 9

Если вы можете избежать использования 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);

Ответ 10

Используйте 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"]]

Ответ 11

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

После много интернет-поиска... как всегда (теперь это привычка) Я попал в StackOverflow и нашел ответ...

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

Ответ 12

Чтобы захватить несколько параметров с использованием одного и того же имени, я изменил цикл 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];
  }

input: ?firstname=george&lastname=bush&firstname=bill&lastname=clinton

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

Ответ 13

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

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

Ответ 14

Чтобы избежать регулярного выражения ада, вы можете найти свой первый матч, отрубите кусок и попытайтесь найти следующий в подстроке. В С# это выглядит примерно так, извините, я не перенес это на 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;