Précédent   Forum des professionnels en informatique > PHP > Langage > Regex
Regex Forum d'entraide sur les expressions rationnelles PHP. Avant de poster -> FAQ regex, Cours de regex et Sources de regex
Partagez cette discussion sur d'autres réseaux sociaux : Viadeo Twitter Google Facebook Digg Delicious MySpace Yahoo
Réponse Proposer ce sujet en actualité
 
Outils de la discussion
Publicité
'
Vieux 25/06/2007, 11h15   #1
Invité de passage
 
Inscription : juin 2007
Messages : 7
Détails du profil
Informations forums :
Inscription : juin 2007
Messages : 7
Points : 1
Points : 1
Par défaut Imbrication de balises

Bonjour.
Je suis en train de créer un parseur de BBCode avec des balises imbriquées.
J'ai vu dans la documentation PHP pour la fonction preg_replace_callback une regex particulière qui permet de gérer l'imbrication.Ccependant j'ai du mal à l'adapter pour une utilisation légèrement plus complexe.

Citation:
Envoyé par manuel php
Exemple 1813. Exemple avec preg_replace_callback() en utilisant une structure récursive pour gérer du BB code
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$input = "plain<blockquote> deep<blockquote> deeper </blockquote>deep </blockquote>plain";
 
function parseTagsRecursive($input)
{
 
  $regex = '#\<blockquote>((?:[^[]|\[(?!/?indent])|(?R))+)\</blockquote>#';
 
  if (is_array($input)) {
    $input = '<div style="margin-left: 10px">'.$input[1].'</div>';
  }
 
  return preg_replace_callback($regex, 'parseTagsRecursive', $input);
}
 
$output = parseTagsRecursive($input);
 
echo $output;
?>
J'ai trouvé dans le manuel PHP la signification de (?R), il s'agit apparemment d'une option pour effectuer une recherche récursive.

Je ne maîtrise pas cette recherche récursive et j'ai un problème car je veux parser une balise BBCode avec un argument (par exemple je veux capturer aussi bien[indent=10] que[indent]), et en laissant la même regex il me capture seulement les tags sans argument.

Merci de m'éclairer.
ben.popeye est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 25/06/2007, 21h44   #2
Membre chevronné
 
Avatar de Korko Fain
 
Étudiant
Inscription : août 2005
Messages : 632
Détails du profil
Informations professionnelles :
Activité : Étudiant

Informations forums :
Inscription : août 2005
Messages : 632
Points : 632
Points : 632
[indent(?:=\d+)?]
te capture[indent=10] mais aussi[indent]

Et merci pour le ?R je ne connaissait pas.
Korko Fain est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 26/06/2007, 10h38   #3
Invité de passage
 
Inscription : juin 2007
Messages : 7
Détails du profil
Informations forums :
Inscription : juin 2007
Messages : 7
Points : 1
Points : 1
Justement non, j'avais essayé et ça ne marche pas, l'imbrication n'est plus supportée.
En gros je veux faire une balise pour créer un tableau. J'ai besoin de pouvoir y ajouter un style éventuellement, pour cela je dois aussi bien capturer [table]...[/table] que [table style="margin:auto;"]...[/table] par exemple...
Voilà ma regex telle qu'elle est maintenant, je précise qu'elle ne supporte pas l'imbrication quand je met l'argument style mais qu'elle supporte l'imbrication lorsque je met simplement [table]...[/table] (elle capture mais pas de façon récursive en fait).
Code :
1
2
3
<?php
$regex = '`(\[table(?: style="(?:[ ()a-zA-Z0-9:;#/\'-]+)")?\](?:[^[]|\[(?!/?table(?: style="(?:[ ()a-zA-Z0-9:;#/\'-]+)")?\])|(?R))+\[/table\])`';
?>
Je pense que je n'arrive pas à régler mon problème parce que je n'ai pas vraiment compris le fonctionnement de ?R mais je n'ai pas trouvé davantage d'explications que sur la page syntaxe des masques du manuel php.

Merci d'avance.
ben.popeye est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 26/06/2007, 16h56   #4
Rédacteur
 
Avatar de Yogui
 
Homme Guillaume Rossolini
Directeur technique
Inscription : février 2004
Messages : 13 720
Détails du profil
Informations personnelles :
Nom : Homme Guillaume Rossolini
Localisation : France

Informations professionnelles :
Activité : Directeur technique

Informations forums :
Inscription : février 2004
Messages : 13 720
Points : 17 355
Points : 17 355
Je viens d'essayer :
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function recursive($input)
{
    $nbMatches = preg_match_all('#\[([^\]]+)(?:="[^"]*")?\] ((?: (?:[^\[\]]) | ((?R)) )*) \[/\1\]#x', $input, $matches, PREG_SET_ORDER);
    if($nbMatches)
    {
        foreach($matches as $match)
        {
            if(!empty($match[2]))
            {
                recursive($match[2]);
            }
        }
        print_r($matches);
    }
}
 
recursive('some [it]italics [bd]bolded [un]underlined [address="uri"]link[/address][/un][/bd] italics[/it], [bd]bold[/bd] raw');
Sortie :
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Array
(
    [0] => Array
        (
            [0] => [address="uri"]link[/address]
            [1] => address
            [2] => link
        )
 
)
Array
(
    [0] => Array
        (
            [0] => [un]underlined [address="uri"]link[/address][/un]
            [1] => un
            [2] => underlined [address="uri"]link[/address]
            [3] => [address="uri"]link[/address]
        )
 
)
Array
(
    [0] => Array
        (
            [0] => [bd]bolded [un]underlined [address="uri"]link[/address][/un][/bd]
            [1] => bd
            [2] => bolded [un]underlined [address="uri"]link[/address][/un]
            [3] => [un]underlined [address="uri"]link[/address][/un]
        )
 
)
Array
(
    [0] => Array
        (
            [0] => [it]italics [bd]bolded [un]underlined [address="uri"]link[/address][/un][/bd] italics[/it]
            [1] => it
            [2] => italics [bd]bolded [un]underlined [address="uri"]link[/address][/un][/bd] italics
            [3] => [bd]bolded [un]underlined [address="uri"]link[/address][/un][/bd]
        )
 
    [1] => Array
        (
            [0] => [bd]bold[/bd]
            [1] => bd
            [2] => bold
        )
 
)
__________________
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework
Yogui est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 01/07/2007, 22h56   #5
Invité de passage
 
Inscription : juin 2007
Messages : 7
Détails du profil
Informations forums :
Inscription : juin 2007
Messages : 7
Points : 1
Points : 1
En fait j'utilise une preg_split pour décomposer un texte en séparant les tableaux et le texte entre les tableaux.

La regex que j'avais, à savoir
Code :
1
2
3
<?php
$contents = preg_split('`\[table\]((?:[^[]|\[(?!/?table\])|(?R))+)\[/table\]`', $contents, -1, PREG_SPLIT_DELIM_CAPTURE);
?>
me traite seulement les tableaux de niveau 1 et en l'appelant récursivement j'arrive à faire ce que je veux, par exemple avec le code suivant :
Citation:
[table]contenu d'un tableau[table]tableau de niveau 2[/table][/table]
Mais si je prends en compte l'argument style, la récursivité ne fonctionne plus, il s'arrête à la première occurrence trouvée.
J'ai mis cette regex
Code :
1
2
3
<?php
$contents = preg_split('`\[table(?: style="(?:[ ()a-zA-Z0-9:;#/\'-]+)")?\]((?:[^[]|\[(?!/?table\])|(?R))+)\[/table\]`', $contents, -1, PREG_SPLIT_DELIM_CAPTURE);
?>
et la récursivité ne fonctionne pas lorsque je rajoute un argument, par exemple :
Citation:
[table style="margin:auto;"]contenu du tableau[/table]
Je ne comprends pas pourquoi la récursivité ne fonctionne pas dans cette regex, si quelqu'un pouvait m'expliquer le fonctionnement exact de l'option ?R je pense que j'arriverais à régler le problème tout seul par la suite.

Merci à ceux qui me liront.
ben.popeye est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 01/07/2007, 23h11   #6
Rédacteur
 
Avatar de Yogui
 
Homme Guillaume Rossolini
Directeur technique
Inscription : février 2004
Messages : 13 720
Détails du profil
Informations personnelles :
Nom : Homme Guillaume Rossolini
Localisation : France

Informations professionnelles :
Activité : Directeur technique

Informations forums :
Inscription : février 2004
Messages : 13 720
Points : 17 355
Points : 17 355
Ta regex est trop complexe

Tel que je te l'ai proposé ci-dessus, ceci :
Code :
style="(?:[ ()a-zA-Z0-9:;#/\'-]+)"
Peut être abrégé en :
Bon, c'est pas exactement ça mais l'idée est là : ta regex est trop complexe. Tu gagnerais beaucoup à utiliser des classes négatives [^...] plutôt qu'énumératives [azdv;,è_çà876], et en plus c'est + flexible. Par exemple entre des guillemets, le seul caractère interdit est... Le guillemet
C'est plus simple de dire "tout sauf un guillemet" que d'énumérer toutes les solutions possibles.
__________________
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework
Yogui est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 02/07/2007, 10h13   #7
Invité de passage
 
Inscription : juin 2007
Messages : 7
Détails du profil
Informations forums :
Inscription : juin 2007
Messages : 7
Points : 1
Points : 1
Bon à ce niveau je suis d'accord avec ta remarque, bien que je me sois embêté à lister tous les caractères autorisés pour des raisons de sécurité (pour éviter le plus possible du css invalide et aussi du javascript).
Mais cette remarque ne règle en aucun cas mon problème, elle permet d'optimiser le fonctionnement de la regex certes, mais le problème reste inchangé. Ce qui ne fonctionne plus en revanche lorsque je rajoute un argument optionnel c'est la récursivité et là je ne sais plus comment faire, je n'arrive pas à comprendre comment fonctionne ?R et surtout pourquoi il se trouve dans un "ou" (| en regex).

Merci.
ben.popeye est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 02/07/2007, 11h38   #8
Rédacteur
 
Avatar de Yogui
 
Homme Guillaume Rossolini
Directeur technique
Inscription : février 2004
Messages : 13 720
Détails du profil
Informations personnelles :
Nom : Homme Guillaume Rossolini
Localisation : France

Informations professionnelles :
Activité : Directeur technique

Informations forums :
Inscription : février 2004
Messages : 13 720
Points : 17 355
Points : 17 355
C'est que... La récursivité pour un preg_replace, je ne sais pas si c'est possible

As-tu essayé d'adapter le code que j'ai proposé ci-dessus ? Il fonctionne avec match, l'as-tu essayé avec replace ? J'ai peu que cela donne des résultats complètement erratiques.

Je pense que dans ton cas, il est plus intéressant de définir une fonction récursive perso, utilisant preg_match (appel récursif à ta fonction perso) et str_replace (gain de perfs).
__________________
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework
Yogui est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 02/07/2007, 14h05   #9
Invité de passage
 
Inscription : juin 2007
Messages : 7
Détails du profil
Informations forums :
Inscription : juin 2007
Messages : 7
Points : 1
Points : 1
C'est preg_split que j'utilise et pas preg_replace.
J'ai réussi à régler le problème, j'avais fait une énorme étourderie dans la condition pour rappeler la fonction récursivement, je faisais un strpos('[table'],..), c'est donc normal que ça ne fonctionnait pas.
Merci quand même pour l'aide.
ben.popeye est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 02/07/2007, 14h08   #10
Rédacteur
 
Avatar de Yogui
 
Homme Guillaume Rossolini
Directeur technique
Inscription : février 2004
Messages : 13 720
Détails du profil
Informations personnelles :
Nom : Homme Guillaume Rossolini
Localisation : France

Informations professionnelles :
Activité : Directeur technique

Informations forums :
Inscription : février 2004
Messages : 13 720
Points : 17 355
Points : 17 355
Euh oui

Peux-tu proposer ici la solution finale ? J'aimerais le mettre dans la FAQ.

__________________
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework
Yogui est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 02/07/2007, 15h43   #11
Invité de passage
 
Inscription : juin 2007
Messages : 7
Détails du profil
Informations forums :
Inscription : juin 2007
Messages : 7
Points : 1
Points : 1
D'accord j'enverrai ma fonction.
Il y en a en tout pour une bonne cinquantaine de lignes, elle est presque terminée mais il me reste encore quelques détails à régler (je dois la combiner avec un htmlentities). Dès qu'elle est finie et testé je vous la donne, c'est promis
ben.popeye est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 03/07/2007, 22h39   #12
Invité de passage
 
Inscription : juin 2007
Messages : 7
Détails du profil
Informations forums :
Inscription : juin 2007
Messages : 7
Points : 1
Points : 1
Bonjour, me revoilà avec la balise tableau qui supporte l'imbrication.
Comme vous me l'avez demandé, je publie le code source de mes fonctions récursives. J'ai essayé de bien les commenter pour qu'elles soient plus compréhensibles mais j'avoue que ça reste très complexe.
Voici les fonctions (je les ai élaborées dans le cadre du développement du CMS PHPBoost).

Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//Fonction pour éclater la chaîne selon les tableaux (gestion de l'imbrication infinie)
function split_imbricated_table(&$contents)
{
	$contents = preg_split('`\[table( style="[^"]+")?\]((?:[^[]|\[(?!/?table(?: style="[^"]+")?\])|(?R))+)\[/table\]`', $contents, -1, PREG_SPLIT_DELIM_CAPTURE);
	//1 élément représente les inter tableaux, un le style du tableau et l'autre le contenu
	$nbr_occur = count($contents);
	for( $i = 0; $i < $nbr_occur; $i++)
	{
		if( $i % 3 === 2 && preg_match('`\[table(?: style="[^"]+")?\].+\[/table\]`s', $contents[$i]) )
		{
			//C'est le contenu d'un tableau, il contient un sous tableau donc on éclate
			split_imbricated_table($contents[$i]);
		}
	}
}
 
//Fonction qui parse les tableaux dans l'ordre inverse à l'ordre hiérarchique
function parse_imbricated_table(&$contents)
{
	if( is_array($contents) )
	{
		$string_contents = '';
		$nbr_occur = count($contents);
		for($i = 0; $i < $nbr_occur; $i++)
		{
			//Si c'est le contenu d'un tableau on le parse
			if( $i % 3 === 2 )
			{
				//On parse d'abord les sous tableaux éventuels
				parse_imbricated_table($contents[$i]);
				//On parse le tableau concerné (il doit commencer par [row] puis [col] ou [head] et se fermer pareil moyennant espaces et retours à la ligne sinon il n'est pas valide)
				if( preg_match('`^(?:\s|<br />)*\[row(?: rowspan="[0-9]+")?\](?:\s|<br />)*\[(?:col|head)(?: colspan="[0-9]+")?(?: style="[^"]+")?\].*\[/col|head\](?:\s|<br />)*\[/row\](?:\s|<br />)*$`s', $contents[$i]) )
				{
					//On nettoie les caractères éventuels (espaces ou retours à la ligne) entre les différentes cellules du tableau pour éviter les erreurs xhtml
					$contents[$i] = preg_replace('`^(?:\s|<br />)*(\[row\].*\[/row\])(?:\s|<br />)*$`s', '$1', $contents[$i]);
					$contents[$i] = preg_replace('`(\[/?(?:col|row|head)(?: colspan="[0-9]+")?(?: style="[^"]+")?\])(\s|<br />)+(\[/?(?:col|row|head)(?: colspan="[0-9]+")?(?: style="[^"]+")?\])`U', '$1$3', $contents[$i]);
					//Parsage de row, col et head
					$contents[$i] = preg_replace('`\[row\](.*)\[/row\]`sU', '<tr>$1</tr>', $contents[$i]);
					$contents[$i] = preg_replace('`\[col((?: rowspan="[0-9]+")?(?: colspan="[0-9]+")?(?: style="[^"]+")?)?\](.*)\[/col\]`sU', '<td class="bb_table_col"$1>$2</td>', $contents[$i]);
					$contents[$i] = preg_replace('`\[head((?: colspan="[0-9]+")?(?: style="[^"]+")?)?\](.*)\[/head\]`sU', '<th class="bb_table_head"$1>$2</th>', $contents[$i]);
					//parsage réussi (tableau valide), on rajoute le tableau devant
					$contents[$i] = '<table class="bb_table"' . $contents[$i - 1] . '>' . $contents[$i] . '</table>';
				}
				else
				{
					//le tableau n'est pas valide, on met des balises temporaires afin qu'elles ne soient pas parsées au niveau inférieur
					$contents[$i] = str_replace(array('[col', '[row', '[/col', '[/row', '[head', '[/head'), array('[\col', '[\row', '[\/col', '[\/row', '[\head', '[\/head'), $contents[$i]);
					$contents[$i] = '[table' . $contents[$i - 1] . ']' . $contents[$i] . '[table]';
				}
			}
			//On concatène la chaîne finale si ce n'est pas le style du tableau
			if( $i % 3 !== 1 )
				$string_contents .= $contents[$i];
		}
		$contents = $string_contents;
	}
}
 
function parse_table(&$contents)
{
	//Cette ligne n'est à mettre que si vous avez passé un htmlentities à votre code au préalable
	$contents = preg_replace_callback('`\[(?:table|col|row|head)(?: colspan="[0-9]+")?(?: rowspan="[0-9]+")?( style="(?:[^&]+)")?\]`U', create_function('$matches', 'return str_replace(\'"\', \'"\', $matches[0]);'), $contents);
	split_imbricated_table($contents);
	parse_imbricated_table($contents);
	//On remet les tableaux invalides tels qu'ils étaient avant
	$contents = str_replace(array('[\col', '[\row', '[\/col', '[\/row', '[\head', '[\/head'), array('[col', '[row', '[/col', '[/row', '[head', '[/head'), $contents);
}
L'appel se fait comme ceci :
Code :
1
2
3
//Parsage de la balise table.
	if( strpos($contents, '[table') !== false )
		parse_table($contents);
A noter que toutes ces fonctions contiennent des passages par référence pour les arguments, il ne faut donc pas mettre
Code :
 $contents = parse_table($contents);
L'utilisation est semblable à celle en html :
[table] ou [table style="css du tableau']
Ensuite [row]
Puis [col] ou [head] (pour les td ou th) avec comme arguments optionnels (il faut respecter l'ordre) rowspan="entier" colspan="entier" style="css de la cellule", par exemple [col rowspan="2" colspan="5" style="text-align:center;"].
Et puis ensuite on referme comme en html ([/col][/row][/table]).

Il y a aussi la fonction pour repasser du html au BBCode, si certains sont intéressés qu'ils me fassent signe !
ben.popeye est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 03/07/2007, 22h48   #13
Rédacteur
 
Avatar de Yogui
 
Homme Guillaume Rossolini
Directeur technique
Inscription : février 2004
Messages : 13 720
Détails du profil
Informations personnelles :
Nom : Homme Guillaume Rossolini
Localisation : France

Informations professionnelles :
Activité : Directeur technique

Informations forums :
Inscription : février 2004
Messages : 13 720
Points : 17 355
Points : 17 355
Ok, merci bien !
__________________
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework
Yogui est déconnecté   Envoyer un message privé Réponse avec citation 00
Réponse Proposer ce sujet en actualité Cette discussion est résolue.
Outils de la discussion



Fuseau horaire GMT +2. Il est actuellement 12h26.


 
 
 
 
Partenaires

Hébergement Web