La sémantique (en construction)


5 avril 2009

Liste des verbes

Pour reprendre (tous) les verbes de la langue française :

wget -q "http://conjf.cactus2000.de/index.fr.php?begin=a&end=zzzzz" -O - | grep '\[' | grep '<a' | php -r '$in=file_get_contents("php://stdin"); preg_match_all("/>([^<]+) \[.*\]<\/a/", $in, $Matches); foreach ($Matches[1] as $item) { echo "INSERT INTO Verbs (id, name) values (0, \"".$item."\");\n";}' | mysql --user root -p testverbs

Ã? suivre

Il faut envoyer des requêtes utilisant les produits précédent à un script qui interrogerait un site comme le conjugueur.com :

for item in `mysql --user root -p 'select name from Verbs;'`; do wget 'http://www.leconjugueur.com/php5/index.php?v=`echo $item | tr A-Z a-z`' | storeConjugue

Représentation graphique de fonctions et exemples d'utilisation


4 août 2008

Représentation graphique de valeurs ou séries de données

A chaque fois que j'avais besoin d'afficher une courbe j'utilisais des logiciels commerciaux dédiés ou des programmes que je faisais sur le pouce mais que j'oubliais bien vite une fois leur office rempli. Fini ! J'ai à ma disposition un script PHP relativement court (ça devrait s'améliorer) qui me permet de générer des images simplement.

<?

/*
Graph de fonctions : voir en bas du source pour des exemples d'utilisations.

Pour utiliser ce module de dessins de graphes ou fonctions la fonction la plus importante est :

MathGraph_create(<données à représenter>, <chemin du fichier image produit>, <paramètres>)
*/

global  $MathGraph_fontPath;        //Le chemin vers le fichier TTF utilisé pour l'affichage des textes sur un graph.
$MathGraph_fontPath = "";

//Convertit des coordonnées logiques (x,y) du repère des fonctions à représenter en coordonnées image/pixel
function    MathGraph_funcToDisplay($x, $y, &$displayX, &$displayY, &$params)
{
    $displayX = $params["LeftMargin"]+($x-$params["LogicalXMin"])*($params["DisplayWidth"]/$params["LogicalWidth"]);
    $displayY = $params["TopMargin"] + $params["DisplayHeight"] - ($y-$params["LogicalYMin"])*($params["DisplayHeight"]/$params["LogicalHeight"]);
}

function    MathGraph_logicalMinMax(&$ValSet, &$XMin, &$XMax, &$YMin, &$YMax)
{
    $XMin = min($LogicalXs = array_keys($ValSet));
    $YMin = min($ValSet);
    $XMax = max($LogicalXs);
    $YMax = max($ValSet);
}

//Nettoie le nombre contenu dans $txt : vire les 0 à la fin de la partie décimale de la virgule
function    MathGraph_cleanNumber($txt)
{
    if (!strlen($txt))      return false;
    $DotPos = strpos($txt, ".");
    if ($DotPos === false)      return $txt;
    //
    $Limit = strlen($txt) - 1;
    while ($txt[$Limit] == "0" && $Limit > $DotPos)     $Limit--;
    if ($txt[$Limit] != ".")        $Limit++;
    $txt = substr($txt, 0, $Limit);
    return $txt;
}

//displayModes peut être une suite de mode (xcenter, ycenter etc) séparés par des '|'. Bien sûr il faut que la combinaison ait un sens.
function    MathGraph_displayText($View, $x, $y, $color, $txt, $displayModes="")
{
global  $MathGraph_fontPath;

    $DisplayX = $x;
    $DisplayY = $y;
    $BBox = imagettfbbox(12, 0, $MathGraph_fontPath, $txt);
    $Modes = explode("|", $displayModes);
    foreach($Modes as $Mode)        {
        switch ($Mode)      {
            //On calcule DisplayX de manière à ce que le centre du texte soit en $x
            case    "xcenter":
                $Width = $BBox[2] - $BBox[0];
                $DisplayX = $DisplayX - $Width/2;
                break;
            //On calcule DisplayX de manière à ce qu'il n'aille pas plus loin que $x (aligné à droite)
            case    "xright":
                $Width = $BBox[2] - $BBox[0];
                $DisplayX = $DisplayX - $Width;
                break;
            case    "ycenter":
                $Height = $BBox[1] - $BBox[7];
                $DisplayY = $DisplayY + $Height/2;
                break;
            default:    break;
        }
    }
    //
    imagettftext($View, 12, 0, $DisplayX, $DisplayY, $color, $MathGraph_fontPath, $txt);
}

//Affiche un nombre sur l'image mais en l'ayant nettoyé au préalable (enlevé les 0 à la fin qui font "sales")
function    MathGraph_displayNumber($View, $x, $y, $color, $txt, $displayMode="")
{   return MathGraph_displayText($View, $x, $y, $color, MathGraph_cleanNumber($txt), $displayMode);     }

/*Taille à représenter : la largeur pour les X ou la hauteur pour les Y, peu importe
On va déterminer un espacement qui permet segmenter la taille en au moins 2 séparations.
Cet espacement est intuitif pour les humains (ceux qui raisonnent en base 10), il ne sera jamais de 7 ou de 3, mais plutôt de 1, 2 ou 5.*/
function    MathGraph_mediumStep($size, $threshold=5)
{
    if (!$size)     $size = 1;
    //
    $SizeLog = floor(log($size, 10));
    $CurPow10 = pow(exp($SizeLog), log(10));            //Puissance de 10 courante (130=>100, 1517=>1000 etc)
    $PrevPow10 = pow(exp($SizeLog-1), log(10));         //Puissance de 10 immédiatement précédente (130=>10, 1517=>100)
    //
    $TryOuts = array($PrevPow10, $PrevPow10*2, $PrevPow10*5, $CurPow10, $CurPow10*2, $CurPow10*5);
    $PrevValue = null;
    foreach($TryOuts as $Value)     {
        //On peut mettre 3 pour les lignes grossières et 5 pour les lignes fines
        if ($size/$Value < $threshold)      return $PrevValue;
        $PrevValue = $Value;
    }
    die("MathGraph_mediumStep : je ne devrais pas être ici, mon algo est faux !");
}

function    Graph_displayValue($value)
{
    $DisplayValue = sprintf("%.08f", $value);
    if ($DisplayValue == "-0")      $DisplayValue = "0";
    return $DisplayValue;
}

//Affiche une grille indiquant les unités X et Y d'un graphique
function    MathGraph_showGrid($View, $xStep, $yStep, $color, &$params, $displayCoords=true)
{
    $XMin = $params["LogicalXMin"];
    $XMax = $params["LogicalXMax"];
    $StartX = $XMin + $xStep * (ceil(($XMax - $XMin)/2)/$xStep);
    $YMin = $params["LogicalYMin"];
    $YMax = $params["LogicalYMax"];
    $StartY = $YMin + $yStep * (ceil(($YMax - $YMin)/2)/$yStep);
    //
    $XLeft = $StartX;       //-$xStep;
    $XRight = $StartX + $xStep;
    $YBottom = $StartY;     //-$yStep;
    $YTop = $StartY + $yStep;
    while (true)        {
        $Clip = 0;
        $TextYPos = $params["height"]-$params["BottomMargin"] + 14;
        $TextXPos = $params["LeftMargin"] - 14;
        MathGraph_funcToDisplay($XLeft, $YTop, $DisplayX, $DisplayY, $params);
        if ($DisplayX < $params["width"]-$params["RightMargin"] && $DisplayX>$params["LeftMargin"])     {
            ImageLine($View, $DisplayX, $params["TopMargin"], $DisplayX, $params["height"]-$params["BottomMargin"], $color);
            if ($displayCoords)     MathGraph_displayNumber($View, $DisplayX, $TextYPos, $StrongGridColor, Graph_displayValue($XLeft), "xcenter");
        }   else    $Clip++;
        if ($DisplayY < $params["height"]-$params["BottomMargin"] && $DisplayY>$params["TopMargin"])    {
            ImageLine($View, $params["LeftMargin"], $DisplayY, $params["width"]-$params["RightMargin"], $DisplayY, $color);
            if ($displayCoords)     MathGraph_displayNumber($View, $TextXPos, $DisplayY, $StrongGridColor, Graph_displayValue($YTop), "xright|ycenter");
        }   else    $Clip++;
        MathGraph_funcToDisplay($XRight, $YBottom, $DisplayX, $DisplayY, $params);
        if ($DisplayX < $params["width"]-$params["RightMargin"] && $DisplayX>$params["LeftMargin"])     {
            ImageLine($View, $DisplayX, $params["TopMargin"], $DisplayX, $params["height"]-$params["BottomMargin"], $color);
            if ($displayCoords)     MathGraph_displayNumber($View, $DisplayX, $TextYPos, $StrongGridColor, Graph_displayValue($XRight), "xcenter");
        }   else    $Clip++;
        if ($DisplayY < $params["height"]-$params["BottomMargin"] && $DisplayY>$params["TopMargin"])    {
            ImageLine($View, $params["LeftMargin"], $DisplayY, $params["width"]-$params["RightMargin"], $DisplayY, $color);
            if ($displayCoords)     MathGraph_displayNumber($View, $TextXPos, $DisplayY, $StrongGridColor, Graph_displayValue($YBottom), "xright|ycenter");
        }   else    $Clip++;
        //
        if ($Clip == 4)     break;  //Si on n'a affiché aucune unité c'est qu'on est sorti du repère, ça ne sert à rien de continuer.
        $XLeft -= $xStep;
        $XRight += $xStep;
        $YTop += $yStep;
        $YBottom -= $yStep;
    }
}

//Transforme une couleur de type "FF00FF" en un tableau array(255, 0, 255).
function    MathGraph_parseColor($strColor)
{
    if (strlen($strColor)!=6)       return false;
    $Red = hexdec(substr($strColor, 0, 2));
    $Green = hexdec(substr($strColor, 2, 2));
    $Blue = hexdec(substr($strColor, 4, 2));
    return array($Red, $Green, $Blue);
}

function    MathGraph_time()
{
    list($usec, $sec) = explode(" ", microtime(true));
    return $sec+$usec;
}

//Calcul des largeur et hauteur logiques
function    Graph_computeExtents(&$params)
{
    $params["LogicalWidth"] = $params["LogicalXMax"] - $params["LogicalXMin"];
    if (!$params["LogicalWidth"])       $params["LogicalWidth"] = 1;
    $params["LogicalHeight"] = $params["LogicalYMax"] - $params["LogicalYMin"];
    if (!$params["LogicalHeight"])      $params["LogicalHeight"] = 1;
}

//TODO : Documenter le tableau params que l'on peut passer à la fonction pour customiser l'apparence du rendu produit.
//$func est un tableau de tableaux, où chaque sous-tableau contient les données à représenter et ce sous deux formes possibles :
//- forme simple : les sous-tableaux contiennent les données uniquement.
//- forme avancée : les sous-tableaux contiennent des paramètres à des indices prédéfinis (pour l'instant "color" et "list") dont les données à représenter à l'indice "list".
function    MathGraph_create($func, $picPath, $params=null)
{
    $Start = MathGraph_time();
    if (!strlen($picPath))      return false;
    //Inits
    if ($params == null)        $params = array();
    if (!isset($params["width"]))       $params["width"] = 500;
    $Width = $params["width"];
    if (!isset($params["height"]))      $params["height"] = 300;
    $Height = $params["height"];
    if (!isset($params["background-color"]))        $params["background-color"] = array(255,255,255);
    if (!isset($params["color"]))                   $params["color"] = array(0,0,255);
    if (!isset($params["origAxisColor"]))           $params["origAxisColor"] = array(255, 0, 0);
    if (!isset($params["origAxisWidth"]))           $params["origAxisWidth"] = 2;
global  $MathGraph_fontPath;
    $MathGraph_fontPath = isset($params["fontPath"]) ? $params["fontPath"] : "fonts/trebucbd.ttf";
    //
    if (!isset($params["LeftMargin"]))      $params["LeftMargin"] = 20;
    if (!isset($params["RightMargin"]))     $params["RightMargin"] = 20;
    if (!isset($params["TopMargin"]))       $params["TopMargin"] = 20;
    if (!isset($params["BottomMargin"]))    $params["BottomMargin"] = 20;
    //###TODO : faire des vérifications de cohérence sur les marges et la hauteur ou la largeur...
    $params["DisplayWidth"] = $Width - $params["LeftMargin"] - $params["RightMargin"];
    $params["DisplayHeight"] = $Height - $params["TopMargin"] - $params["BottomMargin"];
    //On alloue une image de la taille demandée
    $View = ImageCreateTrueColor($Width, $Height);
    //On alloue les couleurs qui vont être utilisées dans la suite du code
    $BackgroundColor = ImageColorAllocate($View, $params["background-color"][0], $params["background-color"][1], $params["background-color"][2]);
    ImageFilledRectangle($View, 0, 0, $Width-1, $Height-1, $BackgroundColor);
    $ForegroundColor = ImageColorAllocate($View, $params["color"][0], $params["color"][1], $params["color"][2]);
    $LightGridColor = ImageColorAllocate($View, 215, 215, 215);
    $MediumGridColor = ImageColorAllocate($View, 150, 150, 150);
    $StrongGridColor = ImageColorAllocate($View, 0, 0, 0);
    $Black = ImageColorAllocate($View, 0, 0, 0);
    $OrigAxesColor = ImageColorAllocate($View, $params["origAxisColor"][0], $params["origAxisColor"][1], $params["origAxisColor"][2]);
    //Détermination des minimas et maximas de la fonction à représenter
    for($n=0; $n<count($func); $n++)        {
        $CurFunc =& $func[$n];
        if (isset($CurFunc["list"]))        $ValSet =& $CurFunc["list"];
        else        $ValSet =& $CurFunc;
        MathGraph_logicalMinMax($ValSet, $XMin, $XMax, $YMin, $YMax);
        $params["LogicalXMin"] = $n ? min($params["LogicalXMin"], $XMin) : $XMin;
        $params["LogicalYMin"] = $n ? min($params["LogicalYMin"], $YMin) : $YMin;
        $params["LogicalXMax"] = $n ? max($params["LogicalXMax"], $XMax) : $XMax;
        $params["LogicalYMax"] = $n ? max($params["LogicalYMax"], $YMax) : $YMax;
    }
    //
    Graph_computeExtents($params);
    //
    if (isset($params["title"]))        MathGraph_displayText($View, $Width / 2, 20, $Black, $params["title"], "xcenter");
    //Les axes "intermédiaires"
    $MediumXStep = MathGraph_mediumStep($params["LogicalWidth"]);
    $MediumYStep = MathGraph_mediumStep($params["LogicalHeight"]);
    $LightXStep = MathGraph_mediumStep($MediumXStep, 3);
    $LightYStep = MathGraph_mediumStep($MediumYStep, 3);
    //Ajout d'une tolérance, d'une marge aux limites du repère logique
    $params["LogicalXMax"] += 2*$LightXStep;
    $params["LogicalXMin"] -= 2*$LightXStep;
    $params["LogicalYMax"] += 2*$LightYStep;
    $params["LogicalYMin"] -= 2*$LightYStep;
    Graph_computeExtents($params);      //Après avoir modifié les limites logiques du repère on doit recalculer son étendue.
    //Dessine la grille du repère
    MathGraph_showGrid($View, $LightXStep, $LightYStep, $LightGridColor, $params, false);
    MathGraph_showGrid($View, $MediumXStep, $MediumYStep, $MediumGridColor, $params, true);
    //Les deux axes passant par (0,0)
    MathGraph_funcToDisplay(0, 0, $X0, $Y0, $params);
    ImageSetThickness($View, $params["origAxisWidth"]);
    $DisplayVerticalOrigin = $params["LogicalXMin"] * $params["LogicalXMax"] < 0;       //Si la multiplication donne un résultat négatif c'est que l'axe des origines est visible.
    if ($DisplayVerticalOrigin)     ImageLine($View, $X0, $params["TopMargin"], $X0, $params["height"]-$params["BottomMargin"], $OrigAxesColor);
    $DisplayHorizontalOrigin = $params["LogicalYMin"] * $params["LogicalYMax"] < 0;     //Ibid.
    if ($DisplayHorizontalOrigin)   ImageLine($View, $params["LeftMargin"], $Y0, $params["width"]-$params["RightMargin"], $Y0, $OrigAxesColor);
    ImageSetThickness($View, 1);
    //Dessine les fonctions une par une sur la grille précédente
    for($n=0; $n<count($func); $n++)        {
        $CurFunc =& $func[$n];
        if (isset($CurFunc["list"]))        $ValSet =& $CurFunc["list"];
        else        $ValSet =& $CurFunc;
        //
        if (isset($CurFunc["color"]))       {
            $ColorComps = MathGraph_parseColor($CurFunc["color"]);
            $LocalForegroundColor = ImageColorAllocate($View, $ColorComps[0], $ColorComps[1], $ColorComps[2]);
        }   else    $LocalForegroundColor = $ForegroundColor;
        //Parcours toutes les valeurs de la fonction courante
        if (isset($CurFunc["width"]))       ImageSetThickness($View, $CurFunc["width"]);
        $PrevX = $PrevY = false;
        foreach($ValSet as $x=>$y)      {
            MathGraph_funcToDisplay($x, $y, $CurX, $CurY, $params);
            ImageLine($View, $PrevX === false ? $CurX : $PrevX, $PrevY === false ? $CurY : $PrevY, $CurX, $CurY, $LocalForegroundColor);
            if ($PrevX !== false)       ImageFilledArc($View, $PrevX, $PrevY, 6, 6, 0, 360, $MediumGridColor, 0);
            $PrevX = $CurX;         $PrevY = $CurY;
        }
        if ($PrevX !== false)       ImageFilledArc($View, $PrevX, $PrevY, 6, 6, 0, 360, $MediumGridColor, 0);
        ImageSetThickness($View, 1);
    }
    //On peut afficher le temps de génération de l'image (en excluant celui de la génération du fichier proprement dit)
    if ($params["showCPUTime"])     {
        $Duration = MathGraph_time() - $Start;
        MathGraph_displayText($View, 0, 15, $MediumGridColor, sprintf("%f", $Duration), "");
    }
    //On crée le fichier image et on détruit la ressource run-time qui la représente
    $PicExt = substr($picPath, strrpos($picPath, ".")+1);
    switch ($PicExt)        {
        case    "jpg":      $Res = imagejpeg($View, $picPath);  break;
        case    "png":      $Res = imagepng($View, $picPath);   break;
        default:            echo "Extension <b>$PicExt</b> inconnue.<br/>\n";   break;
    }
    imagedestroy($View);
    return true;
}

?>
Le source PHP du module destiné à générer des images à partir de données séquentielles.

Exemples d'utilisation

Sinus

Sinus+Cosinus

Données générées

Les fichiers MHTML décodés en Bash


16 mai 2008

Fichiers MHT sous Bash

J'ai été confronté à un fichier MHTML (d'extension .mht) et j'avais donc deux options pour consulter son contenu (32 fichiers...) : soit je faisais du copier-coller et du décodage (quoted-printable - RFC - et base64 - RFC) \"à la main\" et j'avais mon contenu en à peine 5 minutes, soit je faisais un script pour automatiser tout ça. Devinez ce que j'ai choisi...

�a me permet de parler d'une petite astuce de décodage de contenu en base64 que j'avais trouvé je ne sais plus trop où :

openssl base64 -d -in inputFile -out outputFile
openssl base64 -d < inputFile > outputFile

Pour faire court, à partir du fichier .mht, on le découpe (avec csplit), on traite les parties suivant qui sont encodées en base64 (fichiers binaires) ou quoted-printable (pour les autres pages web dans le cas d'un frameset). Et hop !

#!/bin/bash

#Explode a .mht archive into its original files.
#Syntax : explodemht MHTFile DestFolder

#Needed : php (pour le rawurldecode), openssl (pour le base64 decode) et "recode" pour le Quoted-Printable

[[ ${#} -lt 2 ]] && echo "Pas assez d'arguments : le fichier MHT et le dossier de destination" && exit 1;

MHTFile=${1}
[[ ! -f $MHTFile ]] && echo "Le fichier <$MHTFile> n'existe pas." && exit 2;

mainFolder=${2}
[[ ! -d $mainFolder ]] && mkdir "$mainFolder";

#Passe 0 : on fait le split du fichier .mht
SplitPrefix="mht_exploding_"
csplit -f $SplitPrefix "$MHTFile" /^\-\-\-\-\-\-=_NextPart/ {*} > /dev/null

#Première passe : on extrait tous les fichiers de l'archive MIME et on les décode correctement
echo "On crée les fichiers :"
j=0
NewNames=()
OriginalName=()
Encodings=()
for item in $SplitPrefix*; do
    FileName=`cat $item| grep -m 1 "Content-Location:" | awk '{print $2}' | sed 's/\r//g'`
    OriginalNames[$j]=$FileName
    FileName=`php -q -r 'echo (isset($_SERVER["argv"][1]) ? rawurldecode($_SERVER["argv"][1]) : "");' ${FileName}`
    FileName=`echo ${FileName} | sed 's/\\\/\//g'`
    FileName=`basename "${FileName}" | sed 's/\r//g'`
    Encoding=`cat $item| grep -m 1 "Content-Transfer-Encoding:" | sed 's/\r//g' | awk '{print $2}'`
    Encodings[$j]=$Encoding
    FirstEmptyLine=`cat $item | sed 's/\r//g' | grep -m 1 -n '^$' | awk 'BEGIN{FS=":"}{print $1}'`
    ((FirstEmptyLine++))
    if [[ ${#FileName} -ne 0 ]]; then
        echo "Fichier $j : ${FileName} - ($Encoding)"
        NewNames[$j]=${FileName}
        [[ ${Encoding} == "base64" ]] && sed -n $FirstEmptyLine',$p' $item | openssl base64 -d -out "$mainFolder/${FileName}";
        if [[ ${Encoding} == "quoted-printable" ]]; then
            sed -n $FirstEmptyLine',$p' $item | recode windows-1252/CRLF.. | recode /qp.. > $mainFolder/${FileName}

        fi
    fi
    ((j++))
done

#Deuxième passe : on fixe les liens HTML qui peuvent être invalides
i=0
while [[ $i -lt $j ]]; do
    if [[ ${Encodings[$i]} == "quoted-printable" ]]; then
        k=0
        echo "On traite les liens du fichier ${NewNames[$i]}."
        while [[ $k -lt $j ]]; do
            [[ ${#NewNames[$k]} == 0 ]] && ((k++)) && continue;
            OriginalName=`echo -n ${OriginalNames[$k]} | sed 's@\\\@\\\\@g;s@&@&amp;@g;s@%26@&amp;@g'`
            cat $mainFolder/${NewNames[$i]} | sed 's@'$OriginalName'@'${NewNames[$k]}'@g;s@'${OriginalNames[$k]}'@'${NewNames[$k]}'@g' > tempfile.tmp
            cp tempfile.tmp $mainFolder/${NewNames[$i]}
            ((k++))
        done
    fi
    ((i++))
done

#On nettoie
rm -f tempfile.tmp
rm -f $SplitPrefix*

En fait c'est énervant à écrire. Parce que je suis trop bête et que je n'ai pas trouvé d'équivalent à rawurldecode sous Bash. Parce que le code n'est pas beau. Du tout. Et parce que ça marche, rageant...

Il faut donc inclure un programme C qui implémente le rawurldecode et coder tout ça un peu plus proprement...

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

?>

Rendu web de formules mathématiques


1 décembre 2007
Cette page date un peu et le paquet texgd n'étant pas utilisable sur mon serveur actuel, j'ai modifié le processus de transformation de formules LaTeX en PNG que j'ai documenté sur une nouvelle page.

Introduction

J'utilise le langage TeX pour afficher toutes les formules mathématiques sur ce site et cette page explique la mise en oeuvre de cette fonctionnalité.

Tex2Png : texgd + PHP

Le coeur de la solution c'est l'exécutable texgd, une application qui s'installe sur le serveur envisagé. Utilisant Ubuntu je n'ai eu besoin que d'exécuter la commande suivante pour disposer de cette application sur ma machine :

apt-get install texgd

La doc ("man texgd") est courte mais suffit pour utiliser cette application, plutôt spartiate dans son genre. En gros : on exporte des variables d'environnement Bash pour passer des paramètres à l'application, que l'on lance directement, sans arguments. Elle génère une image selon les variables d'environnement exportées qui sont la formule, le chemin du fichier image généré etc.

La passerelle web

Il ne reste maintenant qu'à décrire la partie PHP : c'est un script qui gère un dossier de cache d'images pour appeler tex2gd uniquement quand il y en a besoin et économiser beaucoup de temps machine qu'un serveur web doit économiser par-dessus tout :

<?
    //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).
    $Formula = str_replace("\\\\", "\\", rawurldecode($_SERVER["QUERY_STRING"]));
    $PicName = md5($Formula).".png";
    $CacheDir = "mathcache/";
    $PicFilePath = "http://www.flubb.net/"."$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=$CacheDir$PicName
            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.
    echo "<img style='vertical-align:middle;' src='$PicFilePath' border='0' title='$Formula'/>";
?>

Ce script est une page à part entière. L'intérêt ? Et bien si on écrit l'URL suivante dans la barre d'adresse de son navigateur :

http://www.flubb.net/tex2png.php?I_2=\frac{x-a}{2}f^{\prime\prime}(a)+\int_{a}^x\frac{x-t}{2}f^{(3)}(t)dt

et bien vous verrez l'image suivante apparaître dans votre navigateur :

Exemples

En cours...

Liens externes

J'aurais pu utiliser d'autres applications/scripts pour faire des conversions de formules TeX en PNG, comme ceux-ci : Charles' Latex Page : une page intéressante qui propose des infos sur TeX mais également des schémas de conversion. TeX2png.php : un code qui fait la conversion TeX en PNG mais en partant de bien plus bas, et plus intéressant, que texgd. Là, on appelle TeX directement. Note : c'est issu du projet PHPWiki. Des vidéos de Knuth : ça n'a presque rien à voir avec ce qui précède, si ce n'est qu'il cause de TeX :)

Accueil1 2 3 4 5 6 7 8