2010-07-20 6 views
5

Je vais bien avec les expressions régulières de base, mais je me perds un peu en pos/neg look ahead/behinds.en utilisant regex pour passer devant tous les caractères jusqu'à ce qu'une séquence spécifique de lettres soit trouvée en utilisant la tête de lecture négative

J'essaie de tirer le id # de ceci:

[keyword stuff=otherstuff id=123 morestuff=stuff]

Il pourrait y avoir des quantités illimitées de « trucs » avant ou après. Je l'ai utilisé le Regex Coach pour aider debug ce que je l'ai essayé, mais je ne suis pas aller de l'avant plus ...

Jusqu'à présent, j'ai ceci:

\[keyword (?:id=([0-9]+))?[^\]]*\] 

qui prend en charge tous les attributs supplémentaires après l'ID, mais je ne peux pas comprendre comment ignorer tout entre mot-clé et ID. Je sais que je ne peux pas y aller [^id]* Je crois que je dois utiliser un lookahead négatif comme celui-ci (?!id)* mais je suppose que, puisque c'est la largeur nulle, il ne va pas de l'avant à partir de là. Cela ne fonctionne pas non plus:

\[keyword[A-z0-9 =]*(?!id)(?:id=([0-9]+))?[^\]]*\] 

Je l'ai cherchée partout des exemples, mais n'ai pas trouvé. Ou peut-être que j'ai, mais ils sont allés si loin sur ma tête que je ne savais même pas ce qu'ils étaient.

Aide! Merci.

EDIT: Il doit également correspondre à [keyword stuff = otherstuff], où id = n'existe pas du tout, donc je dois avoir un 1 ou 0 sur le groupe id #. Il y a aussi d'autres [otherkeywords id = 32] que je ne veux pas faire correspondre. Le document doit correspondre à plusieurs [mot-clé id = 3] dans les documents en utilisant preg_match_all.

+0

Les solutions fournies fonctionnent très bien et se comparent probablement plus rapidement que n'importe quel type de lookahead, je vais certainement le faire de cette façon. Mais pour ma propre curiosité, et peut-être quelqu'un qui répond à cette question avec google dans un avenir lointain, est la méthode que j'ai essayé de rendre possible? Autrement dit, est-ce que les lookaheads peuvent être utilisés pour sauter des trucs jusqu'à ce qu'un mot particulier soit frappé? – phazei

Répondre

2

Aucun test avant/arrière nécessaire:

/\[keyword(?:[^\]]*?\bid=([0-9]+))?[^\]]*?\]/ 

Ajouté la terminaison '[^]] *]' pour vérifier une véritable balise de fin, pourrait être inutile.

Edit: a ajouté le \ b pour id sinon il pourrait correspondre [keyword you-dont-want-this-guid=123123-132123-123 id=123]

$ php -r 'preg_match_all("/\[keyword(?:[^\]]*?\bid=([0-9]+))?[^\]]*?\]/","[keyword stuff=otherstuff morestuff=stuff]",$matches);var_dump($matches);' 
array(2) { 
    [0]=> 
    array(1) { 
    [0]=> 
    string(42) "[keyword stuff=otherstuff morestuff=stuff]" 
    } 
    [1]=> 
    array(1) { 
    [0]=> 
    string(0) "" 
    } 
} 
$ php -r 'var_dump(preg_match_all("/\[keyword(?:[^\]]*?\bid=([0-9]+))?[^\]]*?\]/","[keyword stuff=otherstuff id=123 morestuff=stuff]",$matches),$matches);' 
int(1) 
array(2) { 
    [0]=> 
    array(1) { 
    [0]=> 
    string(49) "[keyword stuff=otherstuff id=123 morestuff=stuff]" 
    } 
    [1]=> 
    array(1) { 
    [0]=> 
    string(3) "123" 
    } 
} 
+0

Je pensais que ça fonctionnait, mais après l'avoir testé, il semble que id n'est pas optionnel et il doit l'être. – phazei

+0

Oh, n'a pas obtenu cela, va corriger, – Wrikken

+0

Correction (dans un sous-masque non-capture) – Wrikken

2

Vous n'avez pas besoin regarder vers l'avenir/derrière.

Depuis que la question est balisée en PHP, utilisez preg_match_all() et enregistrez le match dans $ matches.

Voilà comment:

<?php 

    // Store the string. I single quote, in case there are backslashes I 
    // didn't see. 
$string = 'blah blah[keyword stuff=otherstuff id=123 morestuff=stuff] 
      blah blah[otherkeyword stuff=otherstuff id=555 morestuff=stuff] 
      blah blah[keyword stuff=otherstuff id=444 morestuff=stuff]'; 

    // The pattern is '[keyword' followed by not ']' a space and id 
    // The space before id is important, so you don't catch 'guid', etc. 
    // If '[keyword' is always at the beginning of a line, you can use 
    // '^\[keyword' 
$pattern = '/\[keyword[^\]]* id=([0-9]+)/'; 

    // Find every single $pattern in $string and store it in $matches 
preg_match_all($pattern, $string, $matches); 

    // The only tricky part you have to know is that each entire match is stored in 
    // $matches[0][x], and the part of the match in the parentheses, which is what 
    // you want is stored in $matches[1][x]. The brackets are optional, since it's 
    // only one line. 
foreach($matches[1] as $value) 
{  
    echo $value . "<br/>"; 
} 
?> 

Sortie:

123 
444 

(555 est sautée, comme il devrait être)

PS

Vous pouvez également utiliser \b à la place d'un espace littéral si le re pourrait être un onglet à la place. \b represents a word boundary ... dans ce cas le début d'un mot.

$pattern = '/\[keyword[^\]]*\bid=([0-9]+)/'; 
+0

Cela ne fonctionnera pas, parce que j'utilise preg_match_all sur un document volumineux qui pourrait avoir [otherkeyword id = 324] que je ne peux pas faire correspondre. En outre, je dois correspondre [mot-clé stuff = otherstuff] où il n'y a pas d'ID. – phazei

+0

@phazei J'ai édité ma réponse pour afficher plusieurs réponses et ignorer otherkeyword. –

+0

Cool. Vous avez tout ignoré après l'ID, mais je dois le conserver puisque je l'utilise pour remplacer toute la section [mot-clé x = x], mais ce n'est pas un problème pour moi de changer. Je vois que vous avez résolu le plus gros problème que j'ai eu de la même manière que Wrikken avec [^]] * juste après le mot-clé. Pourquoi cela fonctionne-t-il et ne fait-il pas tout sauter jusqu'au dernier "]"? – phazei

0

Je pense que c'est ce que vous obtenez à:

\[keyword(?:\s+(?!id\b)[A-Za-z]+=[^\]\s]+)*(?:\s+id=([0-9]+))?[^\]]*\] 

(Je suppose des noms d'attributs ne peut contenir que des lettres ASCII, tandis que les valeurs peuvent contenir des caractères non-blancs, sauf les ]

(?:\s+(?!id\b)[A-Za-z]+=[^\]\s]+)* correspond à un nombre quelconque de attribute=value paires (et à l'espace qui les précède), tant que le nom d'attribut n'est pas id. Le \b (limite de mot) est là juste au cas où il y a des noms d'attribut que commencent avec id, comme idiocy. Il n'est pas nécessaire de mettre un devant le le nom de l'attribut cette fois, car vous savez que tout nom correspondant sera précédé d'un espace. Mais, comme vous l'avez appris, l'approche lookahead est excessive dans ce cas.

Maintenant, à ce sujet:

[A-z0-9 =] 

Ce A-z est soit une faute de frappe ou une erreur. Si vous vous attendez à ce qu'il corresponde à toutes les majuscules et minuscules, eh bien, c'est le cas. Mais il correspond également

'[', ']', '^', '_', '`` and '\' 

... parce que leurs points de code se situent entre ceux des majuscules et des minuscules. Lettres ASCII, c'est.