Comment puis-je faire correspondre plusieurs occurrences avec une expression régulière en JavaScript similaire à preg_match_all () de PHP?


160

J'essaie d'analyser les chaînes encodées par URL qui sont composées de paires clé = valeur séparées par &ou &.

Ce qui suit ne correspondra qu'à la première occurrence, en séparant les clés et les valeurs en éléments de résultat distincts:

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

Les résultats pour la chaîne '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' seraient:

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

L'utilisation de l'indicateur global, 'g', correspondra à toutes les occurrences, mais ne retournera que les sous-chaînes entièrement correspondantes, pas les clés et les valeurs séparées:

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

Les résultats pour la chaîne '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' seraient:

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

Bien que je puisse diviser la chaîne &et séparer chaque paire clé / valeur individuellement, existe-t-il un moyen d'utiliser le support des expressions régulières de JavaScript pour faire correspondre plusieurs occurrences du modèle /(?:&|&)?([^=]+)=([^&]+)/similaire à la preg_match_all()fonction PHP ?

Je cherche un moyen d'obtenir des résultats avec les sous-matchs séparés comme:

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

ou

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

9
c'est un peu étrange que personne ne recommande d'utiliser replaceici. var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; });terminé. "matchAll" en JavaScript est "replace" par une fonction de gestionnaire de remplacement au lieu d'une chaîne.
Mike 'Pomax' Kamermans le

Notez que pour ceux qui trouvent encore cette question en 2020, la réponse est "n'utilisez pas de regex, utilisez URLSearchParams , qui fait tout cela pour vous."
Mike 'Pomax' Kamermans le

Réponses:


161

Tiré des commentaires

Commentaire 2020: plutôt que d'utiliser regex, nous avons maintenant URLSearchParams, qui fait tout cela pour nous, donc aucun code personnalisé, encore moins regex, n'est plus nécessaire.

- Mike 'Pomax' Kamermans

La prise en charge du navigateur est répertoriée ici https://caniuse.com/#feat=urlsearchparams


Je suggérerais une expression régulière alternative, utilisant des sous-groupes pour capturer le nom et la valeur des paramètres individuellement et 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 est un objet:

{
  f: "q"
  géocode: ""
  hl: "de"
  c'est-à-dire: "UTF8"
  iwloc: "addr"
  ll: "50.116616,8.680573"
  q: "Francfort-sur-le-Main"
  sll: "50.106047,8.679886"
  source: "s_q"
  spn: "0.35972,0.833588"
  sspn: "0,370369,0.833588"
  z: "11"
}

Le regex se décompose comme suit:

(?: # groupe non capturant
  \? | & # "?" ou "&"
  (?: amp;)? # (autoriser "& amp;", pour les URL mal encodées en HTML)
) # fin du groupe non capturant
( # groupe 1
  [^ = & #] + # n'importe quel caractère sauf "=", "&" ou "#"; au moins une fois
) # end group 1 - ce sera le nom du paramètre
(?: # groupe non capturant
  =? # an "=", facultatif
  (# groupe 2
    [^ & #] * # tout caractère sauf "&" ou "#"; n'importe quel nombre de fois
  ) # end group 2 - ce sera la valeur du paramètre
) # fin du groupe non capturant

23
C'est ce que j'espérais. Ce que je n'ai jamais vu dans la documentation JavaScript, c'est que la méthode exec () continuera à renvoyer le prochain jeu de résultats si elle est appelée plus d'une fois. Merci encore pour le bon conseil!
Adam Franco le

1
Il le fait à cause de ceci: regular-expressions.info/javascript.html (Lire: "Comment utiliser l'objet JavaScript RegExp")
Tomalak

1
il y a un bogue dans ce code: le point-virgule après le "while" doit être supprimé.
Jan Willem B

1
Parce que je n'utilise généralement que des groupes normaux (c'est-à-dire capturer) si je suis réellement intéressé par leur contenu.
Tomalak

1
@KnightYoshi Oui. En toute expression JavaScript produit également son propre résultat (comme x = yattribuerait yà xet produire également y). Lorsque nous appliquons ces connaissances à if (match = re.exec(url)): Ceci A) fait la tâche et B) renvoie le résultat de re.exec(url)au while. re.execRetourne maintenant nulls'il n'y a pas de correspondance, ce qui est une valeur erronée. Donc, en fait, la boucle continuera tant qu'il y aura une correspondance.
Tomalak

67

Vous devez utiliser le commutateur «g» pour une recherche globale

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

33
Cela ne résout pas réellement le problème: "L'utilisation de l'indicateur global, 'g', correspondra à toutes les occurrences, mais ne renverra que les sous-chaînes entièrement correspondantes, pas les clés et les valeurs séparées."
Adam Franco

40

2020 Modifier

Utilisez URLSearchParams , car ce travail ne nécessite plus aucun type de code personnalisé. Les navigateurs peuvent le faire pour vous avec un seul constructeur:

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

rendements

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

Il n'y a donc plus de raison d'utiliser regex pour cela.

Réponse originale

Si vous ne voulez pas vous fier à la "correspondance aveugle" qui accompagne la execcorrespondance des styles en cours d'exécution , JavaScript est fourni avec une fonctionnalité de correspondance complète intégrée, mais cela fait partie de l' replaceappel de fonction, lorsque vous utilisez un "que faire avec la capture fonction de gestion des groupes :

var data = {};

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

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

terminé.

Au lieu d'utiliser la fonction de gestion du groupe de capture pour renvoyer réellement des chaînes de remplacement (pour la gestion du remplacement, le premier argument est la correspondance de modèle complète, et les arguments suivants sont des groupes de capture individuels), nous prenons simplement les captures des groupes 2 et 3, et mettons en cache cette paire.

Ainsi, plutôt que d'écrire des fonctions d'analyse complexes, rappelez-vous que la fonction «matchAll» en JavaScript est simplement «remplacer» par une fonction de gestion de remplacement, et que la correspondance de modèles peut être très efficace.


J'ai une ficelle something "this one" and "that one". Je veux placer toutes les chaînes entre guillemets dans une liste, c'est-à-dire [celle-ci, celle-là]. Jusqu'à présent, cela mystring.match(/"(.*?)"/)fonctionne bien pour détecter le premier, mais je ne sais pas comment adapter votre solution à un seul groupe de capture.
nu everest

2
Il semble que vous devriez publier une question sur Stackoverflow pour cela, plutôt que d'essayer de la résoudre dans les commentaires.
Mike 'Pomax' Kamermans

J'ai créé une nouvelle question: stackoverflow.com/questions/26174122/…
nu everest

1
Je ne sais pas pourquoi cette réponse a si peu de votes positifs, mais c'est la meilleure réponse à la question.
Calin

Salut @ Mike'Pomax'Kamermans, les guides de la communauté recommandent spécifiquement de modifier les entrées pour les améliorer, voir: stackoverflow.com/help/behavior . Le cœur de votre réponse est extrêmement utile, mais j'ai trouvé que le langage «rappelez-vous que matchAll est remplacer» n'était pas clair et ne permettait pas d'expliquer pourquoi votre code (qui n'est pas évident) fonctionne. J'ai pensé que vous devriez obtenir le représentant bien mérité, j'ai donc modifié votre réponse plutôt que de la dupliquer avec un texte amélioré. En tant que demandeur initial de cette question, je suis heureux de revenir sur l'acceptation - de cette réponse (et de la modification) si vous le souhaitez toujours.
Adam Franco

21

Pour capturer des groupes, j'ai l'habitude d'utiliser preg_match_allen PHP et j'ai essayé de reproduire sa fonctionnalité ici:

<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 vous devez spécifier le modificateur global, /gsinon l'exécution exec()ne changera pas l'index actuel et bouclera indéfiniment.
Aram Kocharyan

Si j'appelle pour valider ce code myRe.test (str) et que j'essaye de faire execAll, il joue au deuxième match et nous avons perdu le premier match.
fdrv

@fdrv Vous devez remettre le lastIndex à zéro avant de démarrer la boucle: this.lastIndex = 0;
CF

15

Définissez le gmodificateur pour une correspondance globale:

/…/g

11
Cela ne résout pas réellement le problème: "L'utilisation de l'indicateur global, 'g', correspondra à toutes les occurrences, mais ne renverra que les sous-chaînes entièrement correspondantes, pas les clés et les valeurs séparées."
Adam Franco

11

Source:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec

Trouver des correspondances successives

Si votre expression régulière utilise l'indicateur "g", vous pouvez utiliser la méthode exec () plusieurs fois pour rechercher des correspondances successives dans la même chaîne. Lorsque vous procédez ainsi, la recherche commence à la sous-chaîne de str spécifiée par la propriété lastIndex de l'expression régulière (test () avancera également la propriété lastIndex). Par exemple, supposons que vous ayez ce script:

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

Ce script affiche le texte suivant:

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

Remarque: ne placez pas le littéral d'expression régulière (ou le constructeur RegExp) dans la condition while ou cela créera une boucle infinie s'il y a une correspondance en raison de la réinitialisation de la propriété lastIndex à chaque itération. Assurez-vous également que l'indicateur global est défini ou une boucle se produira ici également.


Si j'appelle pour valider ce code myRe.test (str) et que j'essaie de faire pendant, il joue au deuxième match et nous avons perdu le premier match.
fdrv

Vous pouvez également combiner String.prototype.matchavec le gdrapeau: 'abbcdefabh'.match(/ab*/g)retours['abb', 'ab']
thom_nic

2

Si quelqu'un (comme moi) a besoin de la méthode de Tomalak avec prise en charge des tableaux (c.-à-d. Sélection multiple), la voici:

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

contribution ?my=1&my=2&my=things

résultat 1,2,things(précédemment renvoyé uniquement: choses)


1

Pour vous en tenir à la question proposée comme indiqué par le titre, vous pouvez en fait parcourir chaque correspondance dans une chaîne en utilisant String.prototype.replace(). Par exemple, ce qui suit fait exactement cela pour obtenir un tableau de tous les mots basé sur une expression régulière:

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"]

Si je voulais obtenir des groupes de capture ou même l'index de chaque correspondance, je pourrais le faire aussi. Ce qui suit montre comment chaque correspondance est renvoyée avec la correspondance entière, le premier groupe de capture et l'index:

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?");

Après avoir exécuté ce qui précède, wordssera comme suit:

[
  {
    "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
  }
]

Afin de faire correspondre plusieurs occurrences similaires à ce qui est disponible en PHP, preg_match_allvous pouvez utiliser ce type de réflexion pour créer la vôtre ou utiliser quelque chose comme YourJS.matchAll(). YourJS définit plus ou moins cette fonction comme suit:

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

Puisque vous souhaitez analyser la chaîne de requête d'une URL, vous pouvez également utiliser quelque chose comme YourJS.parseQS()( yourjs.com/snippets/56 ), bien que de nombreuses autres bibliothèques offrent également cette fonctionnalité.
Chris West

La modification d'une variable à partir d'une portée externe dans une boucle censée renvoyer un remplacement est plutôt mauvaise. Votre utilisation abusive remplace ici
Juan Mendes

1

Si vous pouvez vous en tirer, voici mapune solution en quatre lignes:

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

Ce n'est pas joli, ce n'est pas efficace, mais au moins c'est compact. ;)


1

Utilisez 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

Bonjour à partir de 2020. Permettez-moi de porter String.prototype.matchAll () à votre attention:

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

Les sorties:

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

Finalement! Une mise en garde: "ECMAScript 2020, la 11ème édition, introduit la méthode matchAll pour Strings, pour produire un itérateur pour tous les objets de correspondance générés par une expression régulière globale" . Selon le site lié dans la réponse, la plupart des navigateurs et nodeJS le prennent actuellement en charge, mais pas IE, Safari ou Samsung Internet. Espérons que le soutien s'élargira bientôt, mais YMMV pendant un certain temps.
Adam Franco

0

Pour capturer plusieurs paramètres en utilisant le même nom, j'ai modifié la boucle while dans la méthode de Tomalak comme ceci:

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

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

Retour: {firstname : ["george", "bill"], lastname : ["bush", "clinton"]}


Bien que j'aime votre idée, cela ne fonctionne pas très bien avec des paramètres uniques, comme ?cinema=1234&film=12&film=34je m'y attendais {cinema: 1234, film: [12, 34]}. A modifié votre réponse pour refléter cela.
TWiStErRob

0

Eh bien ... j'ai eu un problème similaire ... Je veux une recherche incrémentielle / étape avec RegExp (par exemple: démarrer la recherche ... faire un traitement ... continuer la recherche jusqu'à la dernière correspondance)

Après de nombreuses recherches sur Internet ... comme toujours (cela devient une habitude maintenant), je me retrouve dans StackOverflow et j'ai trouvé la réponse ...

Ce qui n'est pas référencé et il est important de le mentionner est " lastIndex" Je comprends maintenant pourquoi l'objet RegExp implémente la lastIndexpropriété " "


0

Le fractionner semble être la meilleure option pour moi:

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

0

Pour éviter l'enfer des regex, vous pourriez trouver votre première correspondance, couper un morceau puis essayer de trouver le suivant sur la sous-chaîne. En C #, cela ressemble à quelque chose comme ça, désolé de ne pas l'avoir porté en JavaScript pour vous.

        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;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.