web-dev-qa-db-fra.com

Regex pour faire correspondre toutes les instances pas entre guillemets

De ce q/a , j'ai déduit qu'il est impossible de faire correspondre toutes les instances d'une expression régulière donnée et non entre guillemets. Autrement dit, il ne peut pas correspondre aux guillemets échappés (ex: "this whole \"match\" should be taken"). S'il y a un moyen de le faire que je ne connais pas, cela résoudrait mon problème.

Sinon, cependant, j'aimerais savoir s'il existe une alternative efficace qui pourrait être utilisée en JavaScript. J'y ai réfléchi un peu, mais je ne peux pas proposer de solutions élégantes qui fonctionneraient dans la plupart des cas, sinon tous.

Plus précisément, j'ai juste besoin de l'alternative pour travailler avec les méthodes .split () et .replace (), mais si elle pouvait être plus généralisée, ce serait la meilleure.

Par exemple:
Une chaîne d'entrée de:
+bar+baz"not+or\"+or+\"this+"foo+bar+
remplacer + par #, pas entre guillemets, retournerait:
#bar#baz"not+or\"+or+\"this+"foo#bar#

54
Azmisov

En fait, vous pouvez faire correspondre toutes les instances d'une expression régulière ne se trouvant pas entre guillemets pour n'importe quelle chaîne, où chaque guillemet d'ouverture est refermé. Dites, comme dans l'exemple ci-dessus, vous voulez faire correspondre \+.

L'observation clé ici est qu'un mot est en dehors des guillemets s'il y a un nombre pair de guillemets qui le suivent. Cela peut être modélisé comme une affirmation prospective:

\+(?=([^"]*"[^"]*")*[^"]*$)

Maintenant, vous ne voulez pas compter les citations échappées. Cela devient un peu plus compliqué. Au lieu de [^"]*, qui est passé à la citation suivante, vous devez également prendre en compte les barres obliques inverses et utiliser [^"\\]*. Après avoir atteint une barre oblique inverse ou une citation, vous devez ignorer le caractère suivant si vous rencontrez une barre oblique inverse, ou bien passer à la citation suivante non échappée. Cela ressemble à (\\.|"([^"\\]*\\.)*[^"\\]*"). Combiné, vous arrivez à

\+(?=([^"\\]*(\\.|"([^"\\]*\\.)*[^"\\]*"))*[^"]*$)

J'admets que c'est un petit cryptique. =)

91
Jens

Azmisov, ressuscitant cette question parce que vous avez dit que vous cherchiez any efficient alternative that could be used in JavaScript Et any elegant solutions that would work in most, if not all, cases.

Il se trouve qu'il existe une solution simple et générale qui n'a pas été mentionnée.

Par rapport aux alternatives, l'expression régulière de cette solution est incroyablement simple:

"[^"]+"|(\+)

L'idée est que nous faisons correspondre mais ignorons tout ce qui se trouve entre guillemets pour neutraliser ce contenu (sur le côté gauche de l'alternance). Sur le côté droit, nous capturons tous les + Qui n'ont pas été neutralisés dans le groupe 1, et la fonction de remplacement examine le groupe 1. Voici le code de travail complet:

<script>
var subject = '+bar+baz"not+these+"foo+bar+';
var regex = /"[^"]+"|(\+)/g;
replaced = subject.replace(regex, function(m, group1) {
    if (!group1) return m;
    else return "#";
});
document.write(replaced);

Démo en ligne

Vous pouvez utiliser le même principe pour faire correspondre ou diviser. Voir la question et l'article dans la référence, qui vous indiqueront également des exemples de code.

J'espère que cela vous donne une idée différente d'une manière très générale de procéder. :)

Qu'en est-il des chaînes vides?

Ce qui précède est une réponse générale pour présenter la technique. Il peut être modifié en fonction de vos besoins exacts. Si vous craignez que votre texte puisse contenir des chaînes vides, changez simplement le quantificateur à l'intérieur de l'expression de capture de chaîne de + À *:

"[^"]*"|(\+)

Voir démo .

Qu'en est-il des citations d'échappement?

Encore une fois, ce qui précède est une réponse générale pour présenter la technique. Non seulement le regex " ignorer cette correspondance" peut être affiné selon vos besoins, vous pouvez ajouter plusieurs expressions à ignorer. Par exemple, si vous voulez vous assurer que les guillemets échappés sont correctement ignorés, vous pouvez commencer par ajouter une alternance \\"| Devant les deux autres afin de faire correspondre (et d'ignorer) les guillemets échappés échappés.

Ensuite, dans la section "[^"]*" Qui capture le contenu des chaînes entre guillemets doubles, vous pouvez ajouter une alternance pour vous assurer que les guillemets doubles échappés sont mis en correspondance avant que leur " Ait la chance de se transformer en sentinelle de fermeture , le transformant en "(?:\\"|[^"])*"

L'expression résultante a trois branches:

  1. \\" Pour correspondre et ignorer
  2. "(?:\\"|[^"])*" pour correspondre et ignorer
  3. (\+) Pour correspondre, capturer et gérer

Notez que dans d'autres versions regex, nous pourrions faire ce travail plus facilement avec lookbehind, mais JS ne le prend pas en charge.

Le regex complet devient:

\\"|"(?:\\"|[^"])*"|(\+)

Voir démo regex et script complet .

Référence

  1. Comment faire correspondre le modèle sauf dans les situations s1, s2, s
  2. Comment faire correspondre un motif à moins que ...
50
zx81

Vous pouvez le faire en trois étapes.

  1. Utilisez un remplacement global regex pour extraire tout le contenu du corps de chaîne dans une table latérale.
  2. Faites votre traduction de virgule
  3. Utilisez un remplacement global regex pour échanger les corps de chaîne

Code ci-dessous

// Step 1
var sideTable = [];
myString = myString.replace(
    /"(?:[^"\\]|\\.)*"/g,
    function (_) {
      var index = sideTable.length;
      sideTable[index] = _;
      return '"' + index + '"';
    });
// Step 2, replace commas with newlines
myString = myString.replace(/,/g, "\n");
// Step 3, swap the string bodies back
myString = myString.replace(/"(\d+)"/g,
    function (_, index) {
      return sideTable[index];
    });

Si vous exécutez cela après avoir défini

myString = '{:a "ab,cd, efg", :b "ab,def, egf,", :c "Conjecture"}';

tu devrais obtenir

{:a "ab,cd, efg"
 :b "ab,def, egf,"
 :c "Conjecture"}

Cela fonctionne, car après l'étape 1,

myString = '{:a "0", :b "1", :c "2"}'
sideTable = ["ab,cd, efg", "ab,def, egf,", "Conjecture"];

les seules virgules dans myString sont donc des chaînes extérieures. Étape 2, puis transforme les virgules en nouvelles lignes:

myString = '{:a "0"\n :b "1"\n :c "2"}'

Enfin, nous remplaçons les chaînes qui ne contiennent que des nombres avec leur contenu d'origine.

6
Mike Samuel

Bien que la réponse de zx81 semble être la plus performante et la plus propre, elle a besoin de ces correctifs pour intercepter correctement les guillemets échappés:

var subject = '+bar+baz"not+or\\"+or+\\"this+"foo+bar+';

et

var regex = /"(?:[^"\\]|\\.)*"|(\+)/g;

Aussi le "group1 === undefined" ou "! Group1" déjà mentionné. Surtout 2. semble important de prendre en compte tout ce qui est demandé dans la question d'origine.

Il convient de mentionner cependant que cette méthode nécessite implicitement que la chaîne ne comporte pas de guillemets échappés en dehors des paires de guillemets non échappées.

1
Marius