<?php // script de test de la grammaire placid spipBlocs.grammar // pour l'utiliser : // php propreBlocs.php -fichier un_fichier_contenant_un_texte_spip // => affiche la structure des blocs de l'article. // ce fichier doit être généré par placid, à partir du fichier // spipBlocs.grammar require("spipBlocs_parser.php"); // on représente l'arbre syntaxique d'un texte spip par une arbo // d'éléments, mais on représente cet arbre dans une liste à plat. // Ainsi, si on a l'arbre suivant : // A-B // +C-D // | +E // | +F-G // +H // // On le représente par A/0, B/1, C/1, D/2, E/2, F/2, G/3, H/1 class Element { var $type; // type de raccourci associé var $niveau; // profondeur de cet élément dans l'arbre var $content; // contenu textuel function Element($type) { global $niveau; //error_log("new Element($type)"); $this->type= $type; $this->niveau= $niveau; $this->content= null; } } function traiterInline($contenu) { return $contenu; // ' '.preg_replace('/\s+/', ' ', $contenu); } /** génère le tag fermant correspondant à un type de bloc liste ou parag */ function fermer($type) { switch($type) { case 'ul': return "\n/UL"; case 'ol': return "\n/OL"; case 'cadre': return "\n/CADRE"; case 'quote': return "\n/QUOTE"; case 'html': return "\n/HTML"; case 'poesie': return "\n/POESIE"; case 'intertitre': return "\n/INTERTITRE"; case '' : return "\n/PARAG"; default: die("Type d'état inconnu : '$type'"); } } /** génère le tag ouvrant correspondant à un type de bloc liste ou parag */ function ouvrir($type) { switch($type) { case 'ul': return "\nUL"; case 'ol': return "\nOL"; case 'cadre': return "\nCADRE"; case 'quote': return "\nQUOTE"; case 'html': return "\nHTML"; case 'poesie': return "\nPOESIE"; case 'intertitre': return "\nINTERTITRE"; case '' : return "\nPARAG"; default: die("Type d'état inconnu : '$type'"); } } /** * permet de fermer les tags d'avant et d'en ouvrir de nouveaux * pour passer d'un état courant à celui imposé par l'élément passé en * paramètre. En clair, si on change de niveau ou de type deliste à * puce, on ferme/ouvre ce qu'il faut, et si on change de paragraphe, * on fait "ce qu'il faut" */ function adapterEtats(&$etats, $elem=null) { $res=''; error_log("adapterEtats".$elem->type.":".$elem->content["niveau"] ."/".$elem->content["type"]." -> ".print_r($etats, true)); if (empty($elem)) { $niveauDst= 0; $typeDst= ''; } elseif($elem->type=="Puce") { $niveauDst= &$elem->content["niveau"]; $typeDst = &$elem->content["type"]; } else { // Parag error_log("adapterEtats Parag".$elem->content); $niveauDst= 1; $typeDst= ''; } // ouvrir ceux qui manquent (si la pile est vide, ou si on // passe de -* à -### par ex.) if(count($etats)<$niveauDst) { while(count($etats)<$niveauDst) { $res.= ouvrir($typeDst); array_unshift($etats, $typeDst); } // auquel cas, on a forcement fini return $res; } // ou fermer les niveaux en trop while(count($etats)>$niveauDst) { $res.= fermer($etats[0]); array_shift($etats); } // ensuite, si ça change de type, ou que c'est un paragraphe, // on ferme l'ancien et on en ouvre un nouveau if($niveauDst!=0 && ($typeDst!=$etats[0] || $elem->type=='Saut')) { $res.= fermer($etats[0]); $etats[0]= &$typeDst; $res.= ouvrir($etats[0]); } return $res; } $debutBlocs=array( 'Tableau' => "\nTABLEAU", 'Ligne' => "\nTR", 'Cellule' => "\nTD", 'Quote' => "\nQUOTE", 'Code' => "\nCODE", 'Cadre' => "\nCADRE", 'Html' => "\nHTML", 'Poesie' => "\nPOESIE", 'Intertitre' => "\nINTERTITRE", 'Puce' => "\nLI" ); $finBlocs=array( 'Tableau' => "\n/TABLEAU", 'Ligne' => "\n/TR", 'Cellule' => "\n/TD", 'Quote' => "\n/QUOTE", 'Code' => "\n/CODE", 'Cadre' => "\n/CADRE", 'Html' => "\n/HTML", 'Poesie' => "\n/POESIE", 'Intertitre' => "\n/INTERTITRE", 'Puce' => "\n/LI" ); /** * Là .. l'algo se complique sur certains points, du fait que les * raccourcis ne définissent pas clairement les limites de certains * blocs (paragraphe et puce surtout). * Le principe consiste à parcourir le tableau en effectuant des appels * récursifs quand on change de niveau dans le tableau. Ainsi, il * "suffit" de dumper chaque niveau et de l'insérer dans le bloc au dessus. * Là où c'est galère, c'est pour les les paragraphes, et les * imbrications de listes à puces dont il faut tenir le compte. Pour * ça, on a une "pile" des états (parag, ol ou ul) pour chaque niveau * d'appel récursif. */ function dump($niveau=0) { global $arbreBlocs, $indexBlocs, $debutBlocs, $finBlocs; $etats= array(); // error_log("DUMP $niveau"); while($indexBlocs < count($arbreBlocs)) { $elem= &$arbreBlocs[$indexBlocs]; // error_log("DUMP elem ".$elem->type.":".$elem->niveau." ($niveau)"); if($elem->niveau > $niveau) { $sousBloc= dump($niveau+1); switch($last->type) { case 'Tableau' : case 'Ligne' : case 'Cellule' : case 'InterTitre' : case 'Quote' : case 'Cadre' : case 'Puce' : $contenu.= adapterEtats($etats, $last); $contenu.= $debutBlocs[$last->type] .$sousBloc.$finBlocs[$last->type]; break; case 'Html' : $contenu.= adapterEtats($etats); $contenu.= $debutBlocs[$last->type].$sousBloc .$finBlocs[$last->type]; break; case 'Code' : $contenu.= adapterEtats($etats); $contenu.= $debutBlocs[$last->type].$sousBloc .$finBlocs[$last->type]; break; case 'Poesie' : $contenu.= adapterEtats($etats); $contenu.= $debutBlocs[$last->type] .str_replace("\n", "<br/>\n", $sousBloc) .$finBlocs[$last->type]; break; default: $contenu.= "\n$last->type : ".$sousBloc."\n"; break; } // error_log("apres dump ".($niveau+1)); } elseif($elem->niveau < $niveau) { // error_log("FIN $niveau -> ".$elem->niveau); break; } else { switch($elem->type) { case '' : $contenu.= traiterInline($elem->content); break; case 'Saut' : switch($elem->content) { case '': $contenu.= adapterEtats($etats, $elem); break; case '_': $contenu.= "<BR/>\n"; break; case '-': $contenu.= "\n<PUCE/>"; break; } break; default: $last= &$elem; break; } $indexBlocs++; } } // finir la pile de trucs en stock $contenu.= adapterEtats($etats, null); return $contenu; } // ajoute un élément dans l'arbre, au niveau courant (élément // unitaire par exemple) function &addElement($type) { global $pile, $arbreBlocs, $niveau, $typeCourant; //error_log("$niveau : addElement($type)"); // créer un nouveau noeud et l'ajouter à l'arbre $courant= &new Element($type); array_push($arbreBlocs, &$courant); return $courant; } // ajoute un élément dans l'arbre, dans une nouvelle branche function &pushElement($type) { global $pile, $arbreBlocs, $niveau, $typeCourant; // empiler le noeud précédant array_push($pile, $typeCourant); $courant= &addElement($type); $typeCourant= $type; $niveau++; return $courant; } function &pushText(&$text) { //error_log("pushText($text)"); global $arbreBlocs; $courant=&new Element(null, true); $courant->content= $text; array_push($arbreBlocs, &$courant); return $courant; } function popElement($type) { //error_log("popElement($type)"); global $pile, $arbreBlocs, $typeCourant, $niveau; if($type!=$typeCourant) { var_dump($arbreBlocsp); die("Mismatch entre '".$courant->type."' et '$type'"); } $niveau--; $typeCourant= array_pop($pile); } if ($_SERVER["argv"][1]=="-fichier") { $fichier=$_SERVER["argv"][2]; } else { $fichier= "zz"; } $f = fopen($fichier, "r") or die("peut pas ouvrir $fichier"); $buffer = fread($f, filesize($fichier)); fclose($f); tic(); $pile=array(); $arbreBlocs=array(); $niveau=0; $courant= &pushElement("PRINCIPAL", false); $typeCourant= "PRINCIPAL"; $parser=new spipBlocs_parser(); $parser->parse($buffer); tac("parsing 1"); var_dump($arbreBlocs); echo "count : ".count($arbreBlocs); $indexBlocs= 0; $contenu= dump(); echo $contenu; ?>