Page 1 sur 1

Découper un "string" avec balise HTML définies

Posté : 21 mars 2009, 16:09
par Invité
Bonjour,

J'ai dans un table (Mysql) l'enregistrement suivant (enregistré via TinyMCE) :
<?php
$chaine = '<p>l'&eacute;toile du parc ! Integer ac dolor vel sem adipiscing venenatis. 
Donec magna. Etiam <em>congue arcu</em> et felis. Sed molestie elit ac odio. 
Ut ac sem? Donec at eros. 
Nam porta elit a eros! Nam pharetra. Fusce facilisis ante nec diam. 
Ut lobortis eros in risus? Etiam ultricies leo nec velit. 
Fusce convallis scelerisque lorem. Phasellus in ipsum.
 <strong>Vestibulum ante ipsum</strong> primis in faucibus orci luctus et ultrices posuere cubilia Curae; Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
Mauris consectetur. 
Sed ultrices felis vel leo. Fusce vel ante eget augue commodo luctus. Sed molestie felis eu lorem.</p>

<p>Ut eleifend nibh <span style="background-color: #99cc00;">et erat. 
Curabitur auctor risus id diam. Suspendisse ut libero! Sed scelerisque. 
In tellus lectus, ultrices</span> vel, convallis vel, porttitor in, est. Nullam mauris. 
Suspendisse luctus. Morbi posuere felis ac risus consectetur pretium. 
Pellentesque vitae nibh a nibh tincidunt tincidunt. 
Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. 
In et arcu. Aenean a turpis sit amet est blandit <span style="color: #888888;">ornare. 
Vestibulum</span> faucibus? Suspendisse pulvinar. 
Morbi auctor adipiscing mi. Mauris bibendum. Donec tortor magna, euismod eu, vestibulum eu, sagittis eu, tortor. 
Nullam vehicula lacus vitae elit.</p>

<p>Etiam facilisis consectetur turpis? Morbi turpis. Mauris lacinia gravida dolor. 
Nam porttitor, nisl at vestibulum placerat, massa libero vehicula nunc, aliquam volutpat risus nisl eget orci. 
Sed lobortis gravida libero. Donec auctor scelerisque magna. 
Proin luctus ultrices metus. Proin ac eros id augue convallis pharetra. 
Praesent ipsum lorem, adipiscing et, porta eget, tempus non, sem. 
Pellentesque arcu est, fringilla quis, bibendum eget; fringilla vitae, diam. 
Praesent ut diam nec tortor adipiscing lacinia.</p>';
?>
Seule les balise suivantes sont admises :
<?php
$allowedTags='<p><strong><em><u><h1><h2><h3><h4><h5><h6>';
$allowedTags.='<li><ol><ul><span><div><br><ins><del><a>';
?>
Je voudrais découper cette chaine si le texte seul est > ou = à 250 caractères.
Donc ce que je fais d'abord c'est enlever toutes les balises HTML pour compter le nombre de caractères avec ceci :
<?php
$nohtml = strip_tags(stripslashes($chaine),'');
if(strlen($nohtml) >= 250) {
        //Afficher les 250 caractères . '...'
}
?>
Je sais afficher les 250 caractères mais je voudrais qu'un mot ne soit pas coupé, tout en respectant les ' (ex : l'étoile) mais aussi les accent (ex : &eacute; = é) car l'encodage utilisé est UTF-8

J'ai cherché sur google (qui ne semble pas être mon meilleur ami aujourd'hui) et j'ai trouvé ceci : HTML Parser for PHP5 mais je ne comprend pas comment l'utiliser.

Pourriez-vous m'apporter de l'aide ?

D'avance merci.

Posté : 21 mars 2009, 16:54
par julian
J'ai pris pour exemple une coupure à 277 caractères. Une coupe franche (on se croirait au tarot :p) aurait donné :
l'étoile du parc ! Integer ac dolor vel sem adipiscing venenatis. Donec magna. Etiam congue arcu et felis. Sed molestie elit ac odio. Ut ac sem? Donec at eros. Nam porta elit a eros! Nam pharetra. Fusce facilisis ante nec diam. Ut lobortis eros in risus? Etiam ultri...
alors que le dernier mot est coupé. J'ai fais une petite expression régulière qui permet d'afficher au maximum 277 caractères suivis d'un espace ou d'un saut de ligne. Ce qui donne
l'étoile du parc ! Integer ac dolor vel sem adipiscing venenatis. Donec magna. Etiam congue arcu et felis. Sed molestie elit ac odio. Ut ac sem? Donec at eros. Nam porta elit a eros! Nam pharetra. Fusce facilisis ante nec diam. Ut lobortis eros in risus? Etiam...

Et le code, j'ai pas remis la variable chaine mais c'est la même que celle que tu as proposé :
$nbMax = 75;

$nohtml = strip_tags(stripslashes($chaine),'');
echo $nohtml."<hr />";
if(strlen($nohtml) >= $nbMax) {
	preg_match("`^(.|\n){0,".$nbMax."}( |\n)`", $nohtml, $result);
	echo trim($result[0])."...";
}

Posté : 21 mars 2009, 18:21
par Invité
Merci d'avoir répondu.

Cela fonctionne correctement cependant les balise HTML sont définitivement perdues, après les avoir supprimées je dois les ré-intégrer afin de respecter la "chaine"

Voila comment je verrais la chose :
<?php
$nb = 32; // Nombre de caractères à afficher
$chaine = '<p>l\'&eacute;toile du parc ! <a href="test.html">un lien</a> j\'ai plein de caractères au total beaucoup</p>';

// Je supprime les balise HTML
// donc je ne garde que :  l'étoile du parc ! un lien j'ai plein de caractères au total beaucoup
$nohtml = strip_tags(stripslashes($chaine),'');

// Je compte le nombre de caractère (sans les balises HTML)
if(strlen($nohtml) > $nb) {
        //C'est ici ou tout ce complique
        //car le résultat final de la chaine attendu est :

        // <p>l'étoile du parc ! <a href="test.html">un lien</a> j'ai ...</p>

}else{
        echo stripslashes($chaine);
}
?>
En fait il ne faudrait compter que les caractères interprétés par le navigateur en respectant la mise en forme, notamment pour ne pas avoir d'erreur HTML (je pense au validateur W3C)[/php]

Posté : 21 mars 2009, 19:12
par julian
Là ça devient plus complexe.
Il faut à la fois garder les balises, mais aussi vérifier que toutes sont bien fermées.

Déjà je pense qu'il faut reprendre ta chaine de départ, et essayer de la couper au même endroit que celle retournée par la regexp.
Ensuite, tu parcours ta chaine en enregistrant les balises ouvrantes et fermantes, ainsi que leur ordre. Après tu fermes dans l'ordre inverse les balises restantes (Un peu comme une pile).

Par exemple, si tu parcours ta chaine exemple, tu obtiens de la regexp :
l'étoile du parc ! un lien j'ai ...
Ensuite tu recoupe la chaine de départ, tu obtiens :
<p>l'étoile du parc ! <a href="test.html">un lien</a> j'ai ...
Après j'ai fais un petit truc mais il faut le tester sur quelques exemples pour voir si ça fonctionne bien :
$chaine = '<p>l\'étoile du parc ! <strong><a href="test.html">un lien</a> j\'ai ...';
// On recherche toutes les balises html
preg_match_all("`<([/A-Za-z]*)(>| )`", $chaine, $result);
$pile = $result[1];
// Tant qu'on a trouvé des balises à supprimer
$fin = false;
while (!$fin) {
	$fin = true;
	// On parcours le tableau
	for ($i = 0; $i < count($pile) - 1; $i++) {
		// Si on trouve une balise ouvrante à côté d'une fermante, on les supprime
		if ("/".$pile[$i] == $pile[$i + 1]) {
			array_splice($pile, $i, 2);
			$fin = false;
		}
	}
}
// On parcours le tableau des balises restantes dans le sens inverse
for ($i = count($pile) - 1; $i >= 0; $i--) {
	$chaine .= "</".$pile[$i].">";
}

Posté : 21 mars 2009, 20:35
par Invité
Je saisi pas ou il et la délimitation, ex : $NB = XX;

J'ai plusieurs exemples à tester mais pour le moment il ne coupe rien et affiche normalement.

Posté : 21 mars 2009, 22:21
par Invité
en fait j'ai compris, pour obtenir $chaine dans ton dernier exemple il faut que je coupe la source à $NB + les balises HTML

avec un regex je peux faire cela ?

Posté : 22 mars 2009, 12:17
par Invité
Bonjour,

Par avance je tient à m'excuser (peur des représailles éventuelles des chevronnés), j'ai lu pas mal d'article sur les regexp et fait pas mal de test, fort sympathique d'ailleurs grasse à mon ami google qui à été plus clémént avec moi en fin de journée.

Je suis ensuite passé à la pratique dans le but toujours d'extraire le nombre de caractères voulu par $nb + balise HTML (dans la limite de $nb), je n'y suis pas arrivé. :(

Alors je me suis accoudé au comptoir et j'me suis dit, pourquoi faire simple (c'est incontestable, en utilisant regexp on peut faire des "miracles") quand on peut faire compliqué ! Ce qui par définition plaira au serveur qui hébergera ce bout de code.

J'ai mis moins de temps à faire ce qui suit qu'à comprendre les regexp.

Voila le résultat (les excuses c'est pour ce qui suit) :
<?php
	$chaine = '<p><strong>l\'&eacute;toile du </strong>&<strong> parc ! Integer</strong> ac <span style="color: #ff6600;">do</span>autre pour faire plus</p>';
	
	$chaine = html_entity_decode($chaine); // Pour transformer les accents
	$nbcar=34; // Nombre de caractères a afficher (sans balise html, ni ')			
	$i=0; // Pour la boucle WHILE
	$carchaine = strlen($chaine); // Nombres total de caractère dans $chaine sans balise HTML
	$cntbal=0; // Initialise le compteur de balise
	$cntcar=0; // Initialise le compteur de caractère
	$opencar=0; // Initialise la variable qui détermine si la balise en cours de lecture est ouverte ou non

	while($i != $carchaine){
		if(substr($chaine,$i+0,1) == '<' AND $cntcar!=$nbcar){
			$cntbal++;
			$opencar=1;
		}
						
		if(substr($chaine,$i+0,1) == '>' AND $cntcar!=$nbcar){
			$cntbal++;
			$opencar=0;
		}

		if(substr($chaine,$i+0,1) != '<' AND $opencar==1 AND $cntcar!=$nbcar){
			$cntbal++;
		}

		if(substr($chaine,$i+0,1) != '>' AND $opencar==0 AND $cntcar!=$nbcar){
			$cntcar++;
		}

		$i++;
	}
?>
Pour visualiser le résultat je me suis servi de ceci :
<?php
echo '<br />';
echo 'Total Caractere : '.$carchaine;
echo '<br />';
echo 'Total boucle (verification) : '.$i;
echo '<br />';
echo 'Compteur balise : '.$cntbal;
echo '<br />';
echo 'Compteur caractere : '.$cntcar;
echo '<br />';
echo 'TOTAL : (balise+caractere) : '.($cntbal+$cntcar);
echo '<br />';
echo substr($chaine,0,($cntbal+$nbcar)).'...';
?>
A priori cela semble fonctionner. Par contre le dernier mot est malheureusement tranché, brutalement (ex : $nb=5; $chaine='<p>j'<strong>é<strong>cris un test</p>' $resultat='j'écr...';

Voila, je crois que tout est la.

Je vais faire des tests avec le bout de code donné hier, je reste cependant ouvert à tout commentaire.

Merci.

Posté : 22 mars 2009, 12:51
par julian
Ah ok, en fait, le second code que j'ai mis est la suite du premier, mais j'ai pas fais la découpe de la chaine d'origine pour garder les balises.

Mais si tu comprends ton code c'est le principal. Après pour les regexp, c'est pas facile au premier abord, mais au final c'est pas si complexe.

Par exemple :
preg_match("`^(.|\n){0,".$nbMax."}( |\n)`", $nohtml, $result);
Si on prend $nbMax = 250, la regexp sera : `^(.|\n){0,250}( |\n)`
Déjà, on encadre par un délimiteur `, ensuite le symbole ^ signifie qu'il faut que ce soit le début de la chaine.
Ensuite on peut découper en 2 :
(.|\n){0,250}
Les accolades permettent de spécifier une plage pour le nombre d'occurences à chercher. Là on va chercher ce qu'il y a entre parenthèse au moins 0 fois, mais pas plus de 250 fois. Et entre parenthèse on précise qu'on cherche tous les caractères (Symbole point) OU un retour chariot (\n).

( |\n)
Cette partie permet juste de ne pas couper un mot au milieu. On va chercher à ce que la chaine se termine par un espace OU un retour chariot.

En fait on va chercher dans la chaine source, une chaine qui fait au plus 250 caractères, se terminant par un espace ou un retour chariot.

Posté : 23 mars 2009, 11:17
par Invité
Bonjour,

Merci Julian pour ton aide. J'ai tout repris de zéro pour arriver au résultat que j'attendais.

J'ai encore une petite amélioration à apporter pour ajouter les '...' car si la dernière balise est un </p> les '...' se retrouve plus bas (logique).

Il faut que je regarde comment fonctionne TinyMCE mais je crois que par défaut tout les enregistrement se termine par la balise </p>, ce qui me permettra de compter les 4 dernièrs caractères de la chaine final et d'y rajouter mes '...'.

Encore merci.

Posté : 23 mars 2009, 13:08
par julian
Bon courage alors ;)