Bienvenue sur la nouvelle version de PHPfrance ! Nouveau design, nouvelles fonctionnalités : En savoir +

Remplacement sélectif, hors ou dans balises html

ViPHP
ViPHP | 1380 Messages

13 Fév 2005, 14:16

J'ai souvent vu des questions concernant le remplacement dans une page html sans toucher à ce qui est encadré par des balises.

Exemple: comment remplacer le mot texte dans la chaîne suivante:
Texte 1 < 2 hors balises<div id="texte">suite du texte<a href="http://texte.com">texte hors balise</a></div> texte final'
sans toucher aux id="texte" et autres href="http://texte.com"? (la présence de 1 < 2 n'est pas innocente!)

Ou l'inverse, modifier le contenu des balises html sans toucher au reste.

J'ai essayé de synthétiser ce genre de traitement. Le groupe de fonctions suivant permet de faire, au choix, un remplacement hors balises html ou dans les balises html (flag 1 ou -1).

La fonction split_balise() accepte de travailler avec :
    str_replace
    ereg_replace
    eregi_replace
    preg_replace

[php]/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FONCTIONS POUR REMPLACEMENT SELECTIF, HORS OU DANS BALISE HTML
pour str_replace ou preg_replace :
string split_balise(mixed pattern, mixed replacement, string subject, string function [, int flag])

pour ereg et eregi :
string split_balise(string pattern, string replacement, string subject, string function [, int flag])

flag [option]: flag = 1: remplacement HORS balises (défaut)
flag INT autre que 1: remplacement DANS balises

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
PREMIERE VERSION (3 fois plus lente)

function mon_rplc($capture1, $capture2, &$de, &$par, $fct, $flag){
return ($flag == 1)
? $fct($de, $par, stripslashes($capture1)).stripslashes($capture2)
: stripslashes($capture1).$fct($de, $par, stripslashes($capture2));
}

function split_balise($de, $par, $txt, $fct, $flag = 1){
$eval_fct = "mon_rplc('\$1', '\$2', \$de, \$par, '$fct', '$flag')";
return preg_replace('#((?:(?!<[/a-z]).)*)([^>]*>|$)#ies', $eval_fct, $txt);
}
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

function mon_rplc_callback($capture){
global $arg;
return ($arg['flag'] == 1)
? $arg['fct']($arg['de'], $arg['par'], $capture[1]).$capture[2]
: $capture[1].$arg['fct']($arg['de'], $arg['par'], $capture[2]);
}


function split_balise($de, $par, $txt, $fct, $flag = 1){
global $arg;
$arg = compact('de', 'par', 'fct', 'flag');
return preg_replace_callback('#((?:(?!<[/a-z]).)*)([^>]*>|$)#si', "mon_rplc_callback", $txt);
}

[/php]

Pour illustrer son fonctionnement, voici quelques exemples:
[php]$txt = 'Texte 1 < 2 hors balises<div id="texte">suite du texte<a href="http://texte.com">texte hors balise</a></div> texte final';

// exemple 1
// str_replace ou ereg_replace
// cet exemple remplace 'texte' par 'TEXTE' hors balises (option 1)
echo split_balise('texte', 'TEXTE', $txt, 'str_replace', 1);
echo split_balise('#texte#', 'TEXTE', $txt, 'preg_replace', 1);[/php]
Texte 1 < 2 hors balises<div id="texte">suite du TEXTE<a href="http://texte.com">TEXTE hors balise</a></div> TEXTE final


[php]// exemple 2
// str_replace ou ereg_replace
// cet exemple remplace 'texte' par 'TEXTE' dans les balises (option -1)
echo split_balise('texte', 'TEXTE', $txt, 'str_replace', -1);[/php]
Texte 1 < 2 hors balises<div id="TEXTE">suite du texte<a href="http://TEXTE.com">texte hors balise</a></div> texte final


[php]// exemple 3
// eregi_replace (insensible à la casse)
// cet exemple remplace 'texte' ou 'Texte' par 'TEXTE' hors balises
echo split_balise('texte', 'TEXTE', $txt, 'eregi_replace', 1);[/php]
TEXTE 1 < 2 hors balises<div id="texte">suite du TEXTE<a href="http://texte.com">TEXTE hors balise</a></div> TEXTE final


[php]// exemple 4
// str_replace avec des tableaux en argument
// remplace d'abord 'texte' par 'TEXTE' ensuite tous les 'e' par 'Z'
$in = array('texte','e');
$rpl = array('TEXTE','Z');
echo split_balise($in, $rpl, $txt, 'str_replace', 1);[/php]
TZxtZ 1 < 2 hors balisZs<div id="texte">suitZ du TEXTE<a href="http://texte.com">TEXTE hors balisZ</a></div> TEXTE final


[php]// exemple 5
// preg_replace avec remplacement de références arrières
// remplacement hors balises (option 1)
echo split_balise('#(h)(.+?)(s)#s', '$1ORS$3', $txt, 'preg_replace', 1);[/php]
Texte 1 < 2 hORSs balises<div id="texte">suite du texte<a href="http://texte.com">texte hORSs balise</a></div> texte final


[php]// exemple 6
// preg_replace avec remplacement de références arrières
// remplacement dans balises (option -1)
echo split_balise('#(id=")(.+?)(")#s', '$1$2_plus_long$3', $txt, 'preg_replace', -1);[/php]
Texte 1 < 2 hors balises<div id="texte_plus_long">suite du texte<a href="http://texte.com">texte hors balise</a></div> texte final

Voilà, j'espère que cela pourra vous être utile.

Si vous trouvez des erreurs ou incompatibilités, j'essayerai de les corriger.
Dernière édition par Ripat le 21 Août 2005, 09:23, édité 1 fois.
ripat

ViPHP
ViPHP | 1380 Messages

16 Août 2005, 14:02

Un petit dépoussiérage et dopage s’imposaient.
Et puis, ce post était le dernier de la liste, alors un p’ti up…

On sait que preg_replace_callback est presque toujours plus rapide que preg_replace + option e. Le problème qu’il est plus difficile de passer des arguments pour la fonction invoquée (voir discussion ici).

Un petite gymnastique sur la portée des variables a donc été nécessaire.

Gain : près de 3 fois plus rapide. Comme on peut imaginer que cette fonction sera utilisée sur des pages entières, parfois très lourdes, ce gain est appréciable.


[php]
function mon_rplc_callback($capture){
global $arg;
return ($arg['flag'] == 1)
? $arg['fct']($arg['de'], $arg['par'], $capture[1]).$capture[2]
: $capture[1].$arg['fct']($arg['de'], $arg['par'], $capture[2]);
}


// Même prototype que pour la fonction du premier post.
function split_balise2($de, $par, $txt, $fct, $flag = 1){
global $arg;
$arg = compact('de', 'par', 'fct', 'flag');
return preg_replace_callback('#((?:(?!<[/a-z]).)*)([^>]*>|$)#si', "mon_rplc_callback", $txt);
}
[/php]
Il reste peut-être à optimiser le motif du split, mais je ne vois pas trop comment. S'il y a des amateurs...

Edit: Le premier post modifié avec cette nouvelle version
Dernière édition par Ripat le 21 Août 2005, 09:18, édité 1 fois.
ripat

Eléphanteau du PHP | 332 Messages

12 Juin 2006, 17:23

Merci pour ce code. J'ai posé la question dans un autre post et on m'a fait remarqué que la réponse se trouvait ici.

J'avais pour ma part ce code
[php]
$mot = "t[aàäâ]bl[eéèêë]"; //exemple
$replace = "<span class=surligne>\\0</span>";
$texte = eregi_replace ($mot, $replace, $texte);
[/php]
qui me permet de mettre un surlignage par exemple sur le mot table avec toutes les combinaisons possibles de lettres accentuées (sur table, c'est un peu débile, mais sur des noms de sociétés, ce n'est pas inutile). Mais ce remplacement primaire me mettait le souk dans mes tags tableaux ou dans les tags images ...

Je l'ai donc remplacé par
[php]
$mot = "t[aàäâ]bl[eéèêë]"; //exemple
$replace = "<span class=surligne>\\0</span>";
$texte = split_balise2($mot, $replace, $texte, "eregi_replace", 1);
[/php]

Sur un court exemple, c'est OK. Mais je viens de le lancer sur une page typique de mon site (environ 100 Ko de texte) avec un sacré paquet de tableau et ... rien ! Sauf une stackoverflow de PHP au bout de 3 à 4 minutes.

Pour l'instant, la seule solution que j'ai trouvé, c'est de découper ma page sur les retours-chariots (heureusement assez nombreux) de la page et de faire une boucle.

ViPHP
ViPHP | 1380 Messages

12 Juin 2006, 17:31

Essaye avec preg_replace, moins gourmand et plus rapide.
ripat

Eléphanteau du PHP | 332 Messages

12 Juin 2006, 18:06

Correction :
Il semblerait que la limite du stack overflow se situe aux environs de 430 caractères.

Ca fonctionne avec 430 caractères
[php]
$texte = "Lorsque vous utilisez SSL, le serveur IIS de Microsoft violera le protocole en fermant
la connexion sans envoyer l'indicateur close_notify. PHP le reportera en tant que
\"SSL: Fatal Protocol Error\" quand vous arrivez à la fin des données. L'astuce est
de baisser le niveau de la directive error_reporting pour ne pas inclure les warnings.
À partir de PHP 4.3.7, le bogue est détecté automatiquement lors de l'ouverture du flux ";
echo strlen($texte)."<br />";

$mot = "PHP"; //exemple
$replace = "<span class=surligne>\\0</span>";
$texte2 = split_balise2($mot, $replace, $texte, "eregi_replace", 1);
echo $texte2;
[/php]

La même chose en mettant une lettre de plus à la fin du texte :
PHP has encountered a Stack overflow431
[php]
$texte = "Lorsque vous utilisez SSL, le serveur IIS de Microsoft violera le protocole en fermant
la connexion sans envoyer l'indicateur close_notify. PHP le reportera en tant que
\"SSL: Fatal Protocol Error\" quand vous arrivez à la fin des données. L'astuce est
de baisser le niveau de la directive error_reporting pour ne pas inclure les warnings.
À partir de PHP 4.3.7, le bogue est détecté automatiquement lors de l'ouverture du flux x";

echo strlen($texte)."<br />";

$mot = "PHP"; //exemple
$replace = "<span class=surligne>\\0</span>";
$texte2 = split_balise2($mot, $replace, $texte, "eregi_replace", 1);
echo $texte2;
[/php]
Dernière édition par Henri le 13 Juin 2006, 11:09, édité 1 fois.

Eléphanteau du PHP | 332 Messages

12 Juin 2006, 18:30

Ripat a écrit :Essaye avec preg_replace, moins gourmand et plus rapide.


Ouais ... bon ... enfin ... Bref, c'est déjà bien la galère pour les quelques notions de syntaxe ereg, alors la syntaxe preg, comment dire ? j'y suis pas encore. :)

comment traduirais-tu le eregi_replace
[php]
$mot = "t[aàäâ]bl[eéèêë]";
$replace = "<span class=surligne>\\0</span>";
[/php]
en syntaxe preg ?

ViPHP
ViPHP | 1380 Messages

12 Juin 2006, 19:04

Tout simplement:

[php]$mot = "#\bt[aàäâ]bl[eéèêë]\b#"; [/php]

J'ai rajouté l'assertion \b pour marquer la séparation de mots. Sinon tu risque de remplacer "table" dans "étable".
ripat

Petit nouveau ! | 1 Messages

24 Juil 2009, 12:15

Vraiment bien!

Merci, c'est justement ce que je cherchais

Merci beaucoup
:oops: :lol: :roll:


devis comparatif assurance voiture - Comparatif assurance voiture. Devis immédiat. Bas prix et adaptée. Devis assurance voiture.

Eléphanteau du PHP | 111 Messages

24 Juil 2009, 14:12

Quelqu'un a prévu de ré-ouvrir ce post dans trois ans ?

Avatar de l’utilisateur
Administrateur PHPfrance
Administrateur PHPfrance | 13179 Messages

24 Juil 2009, 16:43

C'est sympa de venir dire que ce message a répondu à son besoin.

Lachez du lest les gars ;)
Connaître son ignorance est la meilleure part de la connaissance
Pour un code lisible : n'hésitez pas à sauter des lignes et indenter

twitter - site perso - Github - Zend Certified Engineer

Avatar de l’utilisateur
ViPHP
ViPHP | 2279 Messages

27 Juil 2009, 17:15

C'est surtout sympa que certains prennent la peine de dire merci.
En plus cela prouve que certains prennent la peine d'utiliser la fonction recherche. :wink:
ImageCe que l'on apprend par l'effort reste toujours ancré beaucoup plus longtemps.

Eléphanteau du PHP | 185 Messages

27 Juil 2009, 18:01

Surtout que la fonction présentée est utile. Ceux qui prennent le temps de lire la discussion pourront même suivre les étapes de son optimisation, c'est pas inintéressant non plus !

Petit nouveau ! | 8 Messages

27 Nov 2009, 17:39

Bonjour,

Merci c'est très intéressant, cela peut servir.

Petit nouveau ! | 1 Messages

30 Déc 2010, 20:37

Bonjour,

merci pour ces infos précieuses.
j'ai un probleme :

lorsque la chaine de caractère traitée est trop longue (+ de 5000 caractères),
alors le script ne marche pas, et le navigateur propose d'enregistrer la page php.
:(

que faire ?

ViPHP
ViPHP | 5465 Messages

02 Jan 2011, 18:45

totodonat a écrit :Bonjour,

merci pour ces infos précieuses.
j'ai un probleme :

lorsque la chaine de caractère traitée est trop longue (+ de 5000 caractères),
alors le script ne marche pas, et le navigateur propose d'enregistrer la page php.
:(

que faire ?


il faut utilisé un outils adapter comme DOMDocument, avec un requete xpath pour sélectionner ton texte :wink: