Un Wiki stand-alone en PHP (le source)
Composant Wiki en PHP
Voici de manière brute mon composant Wiki écrit en PHP que je décris sur une autre page.
<?
//###TODO : les antislash à la fin d'une ligne c'est facile, et c'est en préprocess
global $Markers;
$Markers = array(
"=="=>array("end"=>"\n", "replace"=>"<h2>$0</h2>", "separator"=>false, "recursive"=>true, "TOCLevel"=>1),
"==="=>array("end"=>"\n", "replace"=>"<h3>$0</h3>", "separator"=>false, "recursive"=>true, "TOCLevel"=>2),
"===="=>array("end"=>"\n", "replace"=>"<h4>$0</h4>", "separator"=>false, "recursive"=>true, "TOCLevel"=>3),
"====="=>array("end"=>"\n", "replace"=>"<h5>$0</h5>", "separator"=>false, "recursive"=>true, "TOCLevel"=>4),
"http://"=>array("end"=>array(" ", "\n"), "replace"=>"<a href='http://$0'>$0</a>", "separator"=>false, "recursive"=>false, "replaceEndMarker"=>false),
"//"=>array("end"=>"//", "replace"=>"<i>$0</i>", "separator"=>false, "recursive"=>true),
"**"=>array("end"=>"**", "replace"=>"<b>$0</b>", "separator"=>false, "recursive"=>true),
"["=>array("end"=>"]", "replace"=>"<a href='$0'>$1+</a>", "separator"=>" ", "recursive"=>false),
"[{"=>array("end"=>"}]", "replace"=>"<img src='$0' title='$1+' border='0'/>", "separator"=>" ", "recursive"=>false),
"----"=>array("end"=>false, "replace"=>"<hr/>", "separator"=>false, "recursive"=>false),
"\n\n"=>array("end"=>false, "replace"=>"<p>", "separator"=>false, "recursive"=>false),
"\n"=>array("end"=>false, "replace"=>"<br/>", "separator"=>false, "recursive"=>false),
"`"=>array("end"=>"`", "replace"=>"$0", "separator"=>false, "recursive"=>false),
"> "=>array("end"=>"\n", "replace"=>"$0<br/>\n", "separator"=>false, "recursive"=>true, "groupBy"=>"<blockquote>$0</blockquote>"),
"- "=>array("end"=>"\n", "replace"=>"<li>$0</li>", "separator"=>false, "recursive"=>true, "groupBy"=>"<ul>$0</ul>"),
"# "=>array("end"=>"\n", "replace"=>"<li>$0</li>", "separator"=>false, "recursive"=>true, "groupBy"=>"<ol>$0</ol>"),
//
"[[refs]]"=>array("end"=>false, "replacePlugin"=>"WikiPlug_displayRefs"),
"[[toc]]"=>array("end"=>false, "replacePlugin"=>"WikiPlug_displayTOC", "deferred"=>true),
"<math>"=>array("end"=>"</math>", "replacePlugin"=>"WikiPlug_displayFormula", "recursive"=>false),
);
global $WikiTOC; $WikiTOC = array();
global $Refs; $Refs = array();
global $Debug; $Debug = false;
global $Deferred; $Deferred = array();
function WikiPlug_displayRefs($src)
{
global $Refs;
if (!count($Refs)) return "";
$Output = "Notes et références :<ul>";
$RefIndex = 1;
foreach($Refs as $Ref) $Output .= "<li>Référence ".$RefIndex++." : <a href='$Ref'>$Ref</a></li>\n";
$Output .= "</ul>\n";
return $Output;
}
function WikiPlug_displayTOC($src)
{
global $WikiTOC;
$Output = "";
foreach($WikiTOC as $Row) {
$Output .= "<div style='padding-left:".($Row["level"]*2+1)."em;'><a href='#".$Row["name"]."'>".$Row["title"]."</a></div>";
}
return "<div style='padding:0 1em 1em 1em;'><table style='border:1px solid black;'><tr><td><h3>Table des matières</h3>\n$Output</td></tr></table></div>";
}
//Ce script nécessite d'installer le package texgd et la présence de plusieurs répertoires ayant les privilèges d'écriture règlés pour le serveur web (l'utilisateur sous lequel tourne ce script).
function WikiPlug_displayFormula($exp)
{
$Formula = str_replace("\\\\", "\\", rawurldecode($exp));
$PicName = md5($Formula).".png";
$CacheDir = "mathcache/";
$PicFilePath = "$CacheDir$PicName";
$TmpPath = $CacheDir."tmp/";
//On ne crée l'image que si elle n'existe pas déjà dans le cache
if (!file_exists($PicFilePath)) {
$TexGDCommand = "export texgd_src='$Formula'; export texgd_tmpdir=$TmpPath; export texgd_fontdir=".$CacheDir."mathfonts; export texgd_outfile=$PicFilePath; export texgd_texheader=".$CacheDir."header.tex; export texgd_style='\$\$'; export texgd_density=10; export texgd_compressratio='3'; texgd";
$Ret = exec($TexGDCommand, $Output);
}
//On envoie un tag HTML contenant la référence de l'image générée.
return "<img style='vertical-align:middle;' src='$PicFilePath' border='0' title='$Formula'/>";
}
function WikiCallPlugin($plugIn, $src)
{
if (!function_exists($plugIn)) die("Le plug-in de remplacement <b>$plugIn</b> n'existe pas !");
return call_user_func_array($plugIn, array($src));
}
function Wiki_flushPreviousBlock($previousMarker, $curBlock, &$output)
{
global $Markers;
if (!isset($Markers[$previousMarker]["groupBy"])) return;
$BlockPattern = $Markers[$previousMarker]["groupBy"];
$BlockPattern = str_replace("\$0", $curBlock, $BlockPattern);
$output .= $BlockPattern;
}
//$level positif indique qu'on est en train de processer le texte Wiki
//$level nul indique qu'on a terminé mais qu'on fait la passe des éléments mis en attente (la TOC par exemple)
function WikiProcess(&$wikiText, &$output, $level=1)
{
global $Markers, $Debug, $Refs;
global $WikiTOC, $Deferred;
if ($Debug) echo "Entrée dans WikiProcess, level = $level, avec le texte :<blockquote style='border:1px solid red'>".str_replace("\n", "<br/>", $wikiText)."</blockquote>\n";
//
$Cursor = 0;
$StartSegment = 0;
$WikiTextLn = strlen($wikiText);
$Marker = "";
$PreviousMarker = "";
$MarkerBlock = "";
while ($Cursor < $WikiTextLn) {
//Tout d'abord on détermine le marqueur concerné, si c'en est un
$MarkerFound = null;
do {
$Char = $wikiText[$Cursor++];
if ($Debug) echo "on rencontre <b>$Char</b> alors que le marqueur était $Marker.<br/>\n";
$Marker .= $Char;
$MarkerLn = strlen($Marker);
$MarkersFound = array();
foreach($Markers as $Key=>$Params) {
$SubKey = substr($Key, 0, $MarkerLn);
if ($SubKey == $Marker) array_push($MarkersFound, $Key);
if ($Key == $Marker) $MarkerFound = $Key;
}
if ($Debug) { print_r($MarkersFound); echo "<br/>\n"; }
} while($Cursor < $WikiTextLn && count($MarkersFound));
if ($Debug) if ($Cursor == $WikiTextLn) echo "Le marqueur ? $MarkerFound, compteur ? ".count($MarkersFound)."<br/>\n";
if ($MarkerFound) { //On revient au marqueur précédent
if ($Cursor < $WikiTextLn) $Cursor--; //On recule d'un caractère
$SegmentLn = $Cursor-strlen($MarkerFound)-$StartSegment;
if ($Cursor == $WikiTextLn) $SegmentLn--;
$Segment = $SegmentLn > 0 ? substr($wikiText, $StartSegment, $SegmentLn) : "";
if ($Debug) echo "On a trouvé un marqueur (Taille = ".strlen($MarkerFound).", Cursor = $Cursor, StartSegment = $StartSegment), on émet donc le segment précédent : ".(strlen($Segment) ? htmlentities($Segment) : "<i>Segment vide</i>")."<br/>\n";
$output .= $Segment;
//
if ($Debug) echo "On a trouvé le marqueur <b>".str_replace("\n", "\\n", $MarkerFound)."</b> (Taille : ".strlen($MarkerFound).").<br/>\n";
$Target =& $Markers[$MarkerFound];
$EndMarker = $Target["end"];
if ($EndMarker !== false) {
//Cas d'un marqueur qui définit un début et une fin (via un marqueur de fin)
if (is_array($EndMarker)) {
$EndPos = false;
foreach($EndMarker as $CurEndMarker) {
$CurEndPos = strpos($wikiText, $CurEndMarker, $Cursor);
if ($EndPos === false || $EndPos > $CurEndPos) {
$EndPos = $CurEndPos;
$RealEndMarker = $CurEndMarker;
}
}
$EndMarker = $RealEndMarker;
} else $EndPos = strpos($wikiText, $EndMarker, $Cursor);
if ($Debug) echo "On a trouvé le marqueur de fin (<b>".$Target["end"]."</b>) à la position ".($EndPos === false ? "<i>false</i>" : $EndPos).".<br/>\n";
$TagContent = substr($wikiText, $Cursor, $EndPos-$Cursor);
if ($Target["recursive"]) { //On traite ce marqueur de manière récursive (le contenu marqué peut lui-même être marqué)
$TagProcessed = "";
WikiProcess($TagContent, $TagProcessed, $level+1);
} else $TagProcessed = $TagContent;
$Sep = isset($Target["separator"]) ? $Target["separator"] : false;
if ($Debug) echo "Le séparateur de <b>$MarkerFound</b> est ".($Sep === false ? "<i>false</i>" : $Sep)."<br/>\n";
if ($Sep === false) {
if ($Debug) echo "On va remplacer \$0 par $TagProcessed dans ".htmlentities($Target["replace"])."<br/>\n";
if (isset($Target["replacePlugin"]))
$Pattern = WikiCallPlugin($Target["replacePlugin"], $TagProcessed);
else
$Pattern = str_replace("$0", $TagProcessed, $Target["replace"]);
if ($Debug) echo "<blockquote>$output</blockquote>\n";
} else {
$TagProcessed = trim($TagProcessed);
$TagItems = explode($Sep, $TagProcessed);
$Pattern = $Target["replace"];
$Base = $TagItems[0];
$Pattern = str_replace('$0', $Base, $Pattern);
$Label = "";
for($i=1; $i<count($TagItems); $i++) $Label .= (strlen($Label) ? " " : "").$TagItems[$i];
if (!strlen($Label)) {
$Refs[] = $Base;
$Label = "<sup><span style='font-size:20%;'>[".count($Refs)."]</span></sup>";
}
$Pattern = str_replace('$1+', $Label, $Pattern);
}
//
if (isset($Target["TOCLevel"])) {
$TOCLevel = $Target["TOCLevel"];
$TOCIndex = count($WikiTOC);
$SectionName = "section-".md5($TOCIndex);
array_push($WikiTOC, array("name"=>$SectionName, "level"=>$TOCLevel, "title"=>$TagProcessed));
$Pattern = "<a name='$SectionName'>$Pattern</a>";
}
//
if ($PreviousMarker != $MarkerFound) {
Wiki_flushPreviousBlock($PreviousMarker, $MarkerBlock, $output);
$MarkerBlock = "";
$BlockPattern = "";
}
if (isset($Target["groupBy"])) $MarkerBlock .= $Pattern;
else {
$MarkerBlock = "";
$output .= $Pattern;
}
//
$Cursor += strlen($TagContent);
$Cursor += strlen($EndMarker);
//
$PreviousMarker = $MarkerFound;
} else {
//Cas d'un marqueur ponctuel, remplacé simplement
if ($Debug) echo "On remplace un marqueur ponctuel<br/>\n";
if (isset($Target["deferred"]) && $Target["deferred"] === true && $level) {
$DeferredIndex = md5(date("c").($level + $Cursor));
$Deferred[] = $DeferredIndex;
$output .= "{{"."$DeferredIndex:".strlen($MarkerFound).":$MarkerFound"."}}";
} else {
if (isset($Target["replacePlugin"])) $output .= WikiCallPlugin($Target["replacePlugin"], null);
else {
$output .= $Target["replace"];
if ($Debug) echo "Contexte (Cursor = $Cursor) : ".str_replace("\n", "\\n", substr($wikiText, $Cursor, 4))."<br/>\n";
}
}
$Marker = "";
}
if (isset($Target["replaceEndMarker"]) && $Target["replaceEndMarker"] === false)
$Cursor -= strlen($RealEndMarker);
$StartSegment = $Cursor;
$Marker = "";
} else if (!count($MarkersFound)) $Marker = "";
}
//
Wiki_flushPreviousBlock($PreviousMarker, $MarkerBlock, $output);
$output .= substr($wikiText, $StartSegment);
if ($Debug) echo "<h3>On est arrivé au bout et le résultat est...</h3><blockquote style='border:1px solid black;'>".htmlentities($output)."</blockquote>\n";
//On termine le niveau 1, le premier de la récursion, on peut donc passer au niveau 0, celui qui concrétise les éléments en attente.
if ($level == 1 && count($Deferred)) {
foreach($Deferred as $Key=>$Index) {
$Marker = "{{".$Index.":";
$EndMarker = "}}";
$MarkPos = strpos($output, $Marker, 0);
$ElementPos = strpos($output, ":", $MarkPos+strlen($Marker)) + 1;
$LnPos = $MarkPos+strlen($Marker);
$Ln = substr($output, $LnPos, $ElementPos-$LnPos-1);
$DeferredTxt = substr($output, $ElementPos, $Ln);
$DeferredProcessed = "";
WikiProcess($DeferredTxt, $DeferredProcessed, 0);
$output = substr($output, 0, $MarkPos) . $DeferredProcessed . substr($output, $ElementPos+$Ln+strlen($EndMarker));
}
}
}
?>