Un Wiki stand-alone en PHP (le source)


1 décembre 2007

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));
        }
    }
}

?>
Accueil