Yep, I'm a geek !


14 juillet 2007

Extraction automatique de .torrents

Deux programmes (PHP et Bash) pour parcourir tout ThePirateBay et choper tous ses torrents sans s'embèter. Le principe étant que le script Shell/Bash commande un script PHP qui lui, se charge de tout le travail de reconnaissance de chaînes et d'extraction d'URLs. Ces deux scripts sont censés s'exécuter depuis la ligne de commande, un navigateur ne nous sert à rien ici.

#!/bin/bash

#"The Pirate Bay"-specific
BaseURL="http://thepiratebay.org"
ListPagePattern='/<a[ \n\r\t]*href="(.*tor\/[^">]*)[^>]*>(.*)<\/a>/'
TorrentPagePattern='/<a href="(.*\.torrent)">.*<\/a>/'
OtherPagePattern='/<a href="(\/brwsearch[^>"]*page=[0-9]+)"/'
FirstPage=brwsearch.php\?b\=1\&c\=501

./extract.php $BaseURL $FirstPage --get-pages "$OtherPagePattern" | sort | uniq > pages.list

pageIndex=0
for page in `cat pages.list`; do
    PageURL=page$pageIndex.html
    wget $BaseURL$page -O $PageURL      #Download de la page du site en question
    ./extract.php $BaseURL $PageURL --extract-torrents "$ListPagePattern" "$TorrentPagePattern" > page$pageIndex.list
    #
    for url in `cat page$pageIndex.list`; do
        grep $url torrents.list.done > /dev/null
        [[ $? == 0 ]] && echo "$url est déjà mis à télécharger" && continue;
        echo "On doit télécharger $url"
        dcop ktorrent KTorrent openTorrentSilently "$url";
        echo $url >> torrents.list.done
    done
    exit 12
    #
    ((pageIndex++))
done

Script shell qui appelle le PHP pour déterminer ce qui reste à télécharger
#!/usr/local/bin/php -q
<?
//Extrait d'une page toutes les sous-pages contenant un torrent et pour chaque sous-page, stocke l'URL du torrent dans un fichier local.
//
//Syntaxe :
//
//./extract.php <BaseURL> <FirstListPage> <ListPagePattern> <TorrentPagePattern> <OtherPagePattern>
//
//1 : BaseURL, l'URL de base du site visé, préfixe de toutes les adresses internes.
//2 : adresse d'une des pages contenant les liste d'éléments à extraire.
//3 : pattern repérant l'URL d'une page descriptive d'un torrent sur une page-liste.
//4 : pattern repérant l'URL d'un fichier torrent sur une page descriptive.
//5 : pattern repérant les URL des autres pages-listes à partir de celle passée en paramètre 2.
//
//Exemples d'utilisation :
//
//The Pirate Bay :
//./extract.php http://www.thepiratebay.org brwsearch.php\?b\=1\&c\=501 '/<a[ \n\r\t]*href="(.*tor\/[^">]*)[^>]*>(.*)<\/a>/' '/<a href="(.*\.torrent)">.*<\/a>/' '/<a href="(\/brwsearch[^>"]*page=[0-9]+)"/'

//TODO : penser à l'option --debug pour afficher des choses...
//DONE : faire deux options, --get-pages et --extract-torrents pour différencier les deux types d'appels de ce script

function    displayArray(&$arr)
{
    $Output = "<ul>\n";
    foreach($arr as $key=>$val)
        $Output .= "<li>$key : ".(is_array($val) ? displayArray($val) : $val)."</li>\n";
    return $Output."</ul>\n";
}
    
    $Args =& $_SERVER["argv"];
    if (count($Args) == 1)      die("Rien...");
    $BaseURL = isset($Args[1]) ? $Args[1] : "";
    $SrcFile = isset($Args[2]) ? $Args[2] : "";
    if (!strlen($SrcFile))      die("Pas d'URL de fichier source à analyser.\n");
    if (!file_exists($SrcFile))     die("Pas de fichier source à analyser.\n");
    $Contents = file_get_contents($SrcFile);
    if ($Debug) echo "Taille du fichier : ".strlen($Contents)."\n";
    //
    $Action = isset($Args[3]) ? $Args[3] : "";
    if (!strlen($Action))       die("Aucune action passée en paramètre.\n");
    switch ($Action)        {
        case    "--get-pages":
            $PatternPages = isset($Args[4]) ? $Args[4] : "";
            break;
        case    "--extract-torrents":
            $Pattern = isset($Args[4]) ? $Args[4] : "";
            $Pattern2 = isset($Args[5]) ? $Args[5] : "";
            if (!strlen($Pattern) || !strlen($Pattern2))    die("Extraction de torrents : il manque des patterns...\n");
            break;
        default:    die("Action inconnue : $Action.\n");
    }
    //Sous-pages
    if (strlen($PatternPages))      {
        $SubPages = array();
        $NbSubPages = preg_match_all($PatternPages, $Contents, $SubPages);
        //echo "Nb de matches : $NbSubPages\n";
        foreach($SubPages[1] as $PageURL)       echo $PageURL."\n";
        //echo displayArray($SubPages);
        die();
    }
    //Extraction de torrents
    $Matches = array();
    if ($Debug)     echo "Pattern = $Pattern\n";
    $NbMatches = preg_match_all($Pattern, $Contents, $Matches);
    if ($Debug)     echo "NbMatches = $NbMatches\n";
    //echo displayArray($Matches);
    $SubMatches = array();
    for($i=0; $i<$NbMatches; $i++)      {
//      echo $Matches[1][$i]."\n";
        if ($Debug) echo $Matches[2][$i]."\n";
        $DownloadPage = file_get_contents($BaseURL.$Matches[1][$i]);
        if (!$DownloadPage)     echo "Problème !\n";
        //Tentative de détection de pages erronées...
    if (strlen($DownloadPage) < 100)    {
        continue;
        echo "$DownloadPage\n";
    }
//      echo "Taille de la page téléchargée : ".strlen($DownloadPage)." octets\n";
        $Ret = preg_match($Pattern2, $DownloadPage, $SubMatches);
        $TorrentURL = $SubMatches[1];
        if ($Debug) echo "Torrent : ";
        echo (strlen($TorrentURL) ? $TorrentURL : "FALSE")."\n";
//      echo displayArray($SubMatches);
    }

?>
Moteur d'analyse de page qui extrait la hiérarchie liste-page-torrent

Aspirateur de site utilisant Fusker

Au départ était Fusker, qui agrégeait des sites épars et permettait leur consultation depuis un lieu unique. A partir d'un site de ce type on peut très facilement, en couplant Bash et PHP, rapatrier ces images localement. Voici :

#!/bin/bash

BaseURL="http://kladblog.funwithbabes.com/"
IndexURL=$BaseURL"index.php?offset=0"
for page in `wget -q -O- $IndexURL | grep "special=preview" | awk 'BEGIN{FS="<a href=\"[^\"]*\">.*</a>"} {URL=substr($0, index($0, $1)+length($1)+9); print substr(URL, 1, index(URL, "\"")-1)}'`; do
    page=$BaseURL$page
    pixList=$(wget -q -O- $page | php -r '$Contents=file_get_contents("php://stdin"); preg_match_all("@You are looking at <b>(http://[^<]*)</b>@", $Contents, $Matches); $Nb=count($Matches[1]); if ($Nb != 1) die("Oups"); $ToFusk=$Matches[1][0]; if (!preg_match("@(http://[^\[]*)\[[^\]]*\](.*)@", $ToFusk, $Matches)) die("Oups2\n"); $FP=$Matches[1]; $SP=$Matches[2]; $NbPix=preg_match_all("@(".$FP."[^<:]*).*<img src=\"\\1\"@", $Contents, $Matches); if (!$NbPix) die("Oups3"); $DirName=str_replace("/","",str_replace("http://","",$FP)); mkdir($DirName); echo $DirName."\n";  foreach($Matches[1] as $Item) echo $Item."\n";' 2>/dev/null)
    DirName=""
    for pix in $pixList; do
        if [[ ${#DirName} -eq 0 ]]; then
            DirName=$pix
            echo "Dossier : $DirName"
        else
            echo "Image : "$pix
            wget -P $DirName -nd -q $pix
        fi
     done
done

En changeant la déclaration de la variable IndexURL on peut rapatrier toutes les pages restantes. Ceci peut se faire automatiquement, mais je me suis arrêté là :).

C'est sûr que ça n'est pas ni du grand code ni du code propre mais ça fait le boulot. Il ne faut pas avoir envie de revenir dessus au bout de 6 mois c'est tout...

Les exécutables réduits - C'est possible !


10 juin 2007

Petite page temporaire de prise de notes pour un futur point complet sur l'analyse et la réduction des exécutables.

#include <stdio.h>
#include <string.h>

int pgcd(int u, int v, int verbose)
{
    int M = 1;
    while (u && v)      {
        if (verbose)        printf("pgcd(%d, %d)\n", u, v);
        if (!(u&1) && !(v&1))       { M <<= 1; u>>=1, v>>=1; }
        else    if (!(u&1) && v&1)      u>>=1;
        else    if (u&1 && !(v&1))      v>>=1;
        else    {
            //Les deux nombres sont impairs
            if (u>v)    u=(u-v)>>1;
            else        v=(v-u)>>1;
        }
    }
    return M * (u ? u : v);
}

int printSyntax(char* progName, int retVal)
{
    printf("Syntaxe pour calculer le PGCD de u et v :\n\n\t%s [--verbose] <u> <v>\n\n", progName);
    return retVal;
}

int main(int argc, char** argv)
{
    int Verbose;
    int N[2], i, j;
    for(i=1, j=0; i<argc && j<2; i++)    {
        if (!strcmp(argv[i], "--verbose"))  Verbose = 1;
        if (!strcmp(argv[i], "--help"))     return printSyntax(*argv, 0);
        if (!sscanf(argv[i], "%d", &N[j++]))    j--;
    }
    if (argc<=2 || j<2) {
        printf("J'ai besoin de deux pour calculer leur PGCD.\n");
        return printSyntax(argv[0], 1);
    }
    //
    printf("%d\n", pgcd(N[0], N[1], Verbose));
    return 0;
}

Si on compile le source ci-dessus avec les commandes suivantes on arrive à un exécutable de 2764 octets (sur ma machine) :

gcc -Os -Wall -L`gcc -print-file-name=` /usr/lib/crt1.o /usr/lib/crti.o -nostdlib -o pgcd pgcd.c /usr/lib/crtn.o -lc
strip --strip-unneeded -R .comment -R .gnu.version -R .note.ABI-tag pgcd

Questions :

  • le code (le segment .text) ne fait que 557 octets et les segments .init et .fini ensembles 36 octets. Total = 593 octets. Pourquoi 2171 octets en plus ?
  • Optimiser le fichier ELF résultant.
  • Combler le fossé (500 octets je crois) entre les versions C et C++.
  • Déduire les meilleures pratiques (code et compil) pour avoir un code le plus petit possible.

AJAX dans Konqueror / Safari


22 mai 2007

Du XML dans Konqueror

Pour charger un document XML dans Konqueror et le manipuler avec le DOM, il faut au préalable créer le document et charger le texte XML. Comme suit dans cet exemple JS :

    var Doc = "<"."?xml version='1.0' encoding='utf-8'?".">\
    <root2>\
        <child1>Contenu1</child1>\
        <child2>Contenu2\
            <child21>Contenu du 21</child21>\
        Fin du contenu 2\
        </child2>\
        <child3>Contenu3</child3>\
        <child4>Contenu4</child4>\
    </root2>"
    //
    var XMLDoc = document.implementation.createDocument();
    XMLDoc.loadXML(Doc);
    var Root = XMLDoc.documentElement;</code>

            <p>A partir de là on a un objet JS (Root) qui représente la version DOM de notre document XML. Si vous ne me croyez pas, essayez d'utiliser la fonction suivante qui parse le DOM en question :</p>
            <code legend='A appeler comme suit : getNodeHierarchy(Root, 0)'>function    getNodeHierarchy(node, level)
{
    var Output = typeof node+"-"+level+" : "+node.nodeName+", "+NodeTypesDisplay[node.nodeType]+", "+node.nodeValue+"\n";
    if (!node.childNodes.length)        return Output
    node = node.firstChild
    do { Output += getNodeHierarchy(node, level+1) } while (node = node.nextSibling);
    return Output
}

La fonction précédente avec notre XML d'exemple affichera :

object-0 : root, Node, null
object-1 : #text, Texte,

object-1 : child1, Node, null
object-2 : #text, Texte, On
object-1 : #text, Texte,

object-1 : child2, Node, null
object-2 : #text, Texte, Contenu2

object-2 : child21, Node, null
object-3 : #text, Texte, Contenu du 21
object-2 : #text, Texte,
Fin du contenu 2

object-1 : #text, Texte,

object-1 : child3, Node, null
object-2 : #text, Texte, Con
object-2 : b, Node, null
object-3 : #text, Texte, tenu3
object-1 : #text, Texte,

object-1 : child4, Node, null
object-2 : #text, Texte, Contenu4
object-1 : #text, Texte,

Jusque là tout va bien, mais comment nourrir un script avec un XML dynamique, i.e. que l'on ne peut pas hardcoder et que l'on peut même recharger plusieurs fois au cours du déroulement de l'application ? C'est très simple : on intègre une iframe dans la page web, on règle l'attribut src de cette iframe à une URL correspondant au fichier et en utilisant l'évènement onLoad de l'iframe on peut recopier peu ou prou les trois lignes qui crée le document XML et qui charge les données. Seul problème, si on essaye, Konqueror va transformer le XML en document HTML pour le rendre (c'est une IFrame après tout) alors plutôt que d'essayer tout de suite de faire des XSL pour pallier à ça, on construit une petite passerelle PHP qui va servir un fichier en transformant les < en &lt; et les > en &gt;. J'ai presque honte de faire ça mais ça fonctionne très bien, vous allez voir. Voici déjà la passerelle PHP qui sert les fichiers XML transformés :

<?
    if (!isset($_GET["file"]))      die("Pas de paramètre qui indique un fichier à servir");
    $FilePath = $_GET["file"];
    while (true)        {
        if (strpos("/", \$FilePath) !== false)      die("Interdit");
        if (file_exists($FilePath))     break;
        $Temp = rawurldecode($FilePath);
        if ($Temp == $FilePath)     die("Fichier introuvable");
    }
    $Contents = file_get_contents($FilePath);
    echo str_replace("<", "&lt;", str_replace(">", "&gt;", $Contents));
?"."></code>
            <p>Ainsi, en utilisant cette passerelle et en interceptant l'évènement onLoad de l'iframe avec la fonction JS suivante on obtient le même résultat que précédemment mais sans avoir hardcodé le XML :</p>
            <code>function ReceiveDatas()
{
    var Input = document.getElementById("inputXML")
    if (!Input.src.length)      return
    var XMLContent = Input.contentDocument.body.innerHTML
    XMLContent = XMLContent.replace(/&amp;lt;/g, "&lt;").replace(/&amp;gt;/g, "&gt;")
    //
    var XMLDoc = document.implementation.createDocument();
    XMLDoc.loadXML(XMLContent)
    var Root = XMLDoc.documentElement
    alert(getNodeHierarchy(Root, 0))
}?>

Et ça y est, Konqueror peut charger et manipuler du XML comme un grand. Maintenant qu'on a trouvé une méthode pour charger un XML "à la main", pour les navigateurs équipés passons à...

L'utilisation des xmlHttpRequest

OpenGL : la base


9 avril 2007

OpengL - généralités

Effacement d'un écran

glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);

Effacement de l'écran et du z-buffer

glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearDepth(1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Efface l'écran en noir et met chaque pixel du z-buffer à la valeur 1.0.

Initialisation d'une fenêtre en GLUT

glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowPosition(100, 100);
glutInitWindowSize(400, 400);
glutCreateWindow("Titre de ma fenêtre");

Exemple de callback appelée par glutReshapeFunc

glutReshapeFunc(animReshape);

void animReshape(int w, int h)
{
    glViewport(0, 0, (GLsizei) w, (GLsizei) h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-50, 50, -50, 50, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

Comment dessiner un carré à l'écran ?

//Première méthode : on utilise un glRect pour faire simple
glColor3f(1,0,1);
glBegin(GL_POLYGON);
glRectf(0.25, 0.25, 0.75, 0.75);
glEnd();
glFlush();

//Deuxième méthode : on utilise plusieurs appels à glVertex3f pour décomposer le polygone
glColor3f(1,0,1);
glBegin(GL_POLYGON);
    glVertex3f(0.25, 0.25, 0.0);
    glVertex3f(0.75, 0.25, 0.0);
    glVertex3f(0.75, 0.75, 0.0);
    glVertex3f(0.25, 0.75, 0.0);
glEnd();
    glFlush();

Exemples d'appels valides à glVertex*

glVertex2s(2, 3);
glVertex3d(0.0, 0.0, 3.1415926535898);
glVertex4f(2.3, 1.0, -2.2, 2.0);

GLdouble    dvect[3] = {5.0, 9.0, 1992.0};
glVertex3dv(dvect);
Vérifier si il y a un avantage en temps machine à utiliser la version pointeur de glVertex*() ou pas.

Affichage d'un cercle généré

glBegin(GL_POLYGON);
//Le cercle est blanc
glColor3d(1,1,1);
//Début des déclarations des points composant le cercle
#define     PI      3.141592654f
int i = 0, NbPoints = 30;
float   Radius = 25.0f;
for(i=0; i>NbPoints; i++)       {
    float   Angle = 2.0f*PI*i/NbPoints;
    glVertex2f(Radius*cos(Angle), Radius*sin(Angle));
}
//Tous les points ont été calculés, on peut provoquer le rendu
glEnd();
Adapter le vieux code des meta-balls.

Exemple d'utilisation de glPolygonMode

glPolygonMode(GL_FRONT, GL_FILL);   //Faces avant remplies
        glPolygonMode(GL_BACK, GL_LINE);    //Faces arrières outlined
Faire des routines de générations d'objets procéduraux. Ajouter les caps (de l'appendice B du bouquin) et toutes les fonctions de requêtes. Ajouter au Snippets la possibilités de faire des liens avec des fichiers externes, sous la forme de dock OSX ça serait pas mal : ça s'ouvre au bas du bloc orangé et contient les icônes des images ou des fichiers. Bien sûr ça peut se refermer.

Réglages préliminaires : viewport, projection & caméra

    //Réglages de la projection
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, 1.0, 0.5, 100.0);      //Un glFrustum bien paramétré est équivalent à cet appel GLU
    //Réglages du système objet
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    //Définition de l'espace de dessin
    glViewport(0, 0, (GLsizei) 400, (GLsizei) 400);
    //Position de la caméra, point de visée, vecteur Up de la caméra
    gluLookAt(0.0, 0.0, 0.0,  0.0, 0.0, 100.0,  0.0, 1.0, 0.0);

Les différentes transformations

gluLookAt(pos, target, up) : visualisation
glScalef(scalev) : modélisation
glMatrixMode(GL_PROJECTION) puis glFrustum() : projection

Première application

On affiche ici un cube en fil de fer face à la caméra en rafraîchissant la fenètre et la redimensionnant au besoin.

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

void    display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1,1,1);
    glLoadIdentity();
    gluLookAt(0,0,5,0,0,0,0,1,0);
    glScalef(1,2,1);
    glutWireCube(1);
    glFlush();
}

void reshape(int w, int h)
{
    glViewport(0,0,(GLsizei)w,(GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-1,1,-1,1,1.5,20);
    glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(400, 400);
    glutCreateWindow(\"Titre\");
    //
    glClearColor(0,0,0,0);
    glShadeModel(GL_FLAT);
    //
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();

    return EXIT_SUCCESS;
        }
Fichier first.cpp

A compiler avec la commande suivante :

g++ -Wall -lglut first.cpp -o first

Convertisseur HTML vers PDF


8 avril 2007

Convertisseur de fichier HTML en PDF

Objectif : réaliser une combinaison logicielle permettant, quelque soit la page HTML, d'en déduire un fichier PDF le plus fidèle possible. Piste : l'ensemble contiendra une application C++ (aka binaire) rapide qu'on nourrira de la page HTML qui, utilisant PDFLib, générera le fichier PDF. Note : il ne faut pas oublier la gestion des fontes à insérer dans les PDFs produits. Gabarit :

//On récupère le flux d'entrée
    HTMLFile = stdin();
    //On vérifie que l'entrée suit bien les specs XHTML
    if (!(ParsedDatas = parseXML(HTMLFile)))        return EXIT_FAILURE;
    //On crée le document PDF
    Doc = new PDFDocument();
    if (!Doc->generatePDF(ParsedDatas))     return EXIT_FAILURE;
    //On envoie le PDF produit sur la sortie standard
    Doc->echo();
    return EXIT_SUCCESS;
        

Bien sûr les fonctions/méthodes utilisées (stdin(), parseXML() etc) n'existent pas encore, mais le but de ce document est d'y remédier. Le choix est de recevoir des données depuis stdin et d'émettre le produit sur stdout pour rendre l'usage de l'application multiple : aujourd'hui, nous avons besoin de transformer des pages web depuis une application serveur PHP, mais demain les spécifications peuvent évoluer (Java, transformation PDF en batch etc) il faut donc qu'elles puissent s'adapter à ce genre de situations. Je veux pouvoir être capable d'écrire les lignes de scripts suivantes :

    cat test.html | pdfconverter > test.pdf
    wget "http://new.google.com/" | pdfconverter > new.google.com.pdf
    wget "http://new.google.com/" | pdfconverter > test.pdf && echo "Texte du mail" | mutt -a test.pdf -t "Titre" 42@flubb.net
    pdfconverter --input-file test.html > test.pdf
    pdfconverter --input-file test.html --output-file test.pdf', "Insertion du convertisseur de HTML en PDF dans n'importe quelle chaîne de production

Il faut à présent détailler toutes ces étapes : 1- interception du flux d'entrée depuis l'entrée standard 2- parsing XML utilisant libxml2 3- implémentation d'une classe PDFDocument 4- émission sur la sortie standard Le point 3 est le plus problématique mais les trois autres ne sont pas à négliger : elles peuvent en effet poser problèmes plus tard si elles ne sont pas bien affutées maintenant. On va donc les traiter dans le désordre :

1- interception du flux d'entrée depuis l'entrée standard

�a peut être fait de façon simpl(e|iste) mais si on veut essayer de minimiser les new (en gros n'en faire qu'un seul), la façon ci-dessous correspond :

void    getSTDIn(char** ptr, int& nbLines, int& totalLength)
{
    int     LineLength = 1000;
    nbLines = 0;
    totalLength = 0;
    char*   Line = new char[LineLength];
    char*   FGetsStatus = NULL;
    FILE*   STDInCopy = tmpfile();
    //
    do  if (FGetsStatus = fgets(Line, LineLength, stdin))       {
        int Ln = strlen(Line);
        fwrite(Line, Ln, 1, STDInCopy);     //###CHECK
        nbLines++;
        totalLength += Ln;
    } while(FGetsStatus != NULL);
    delete []Line;
    //
    rewind(STDInCopy);
    //
    *ptr = new char[totalLength+1];
    (*ptr)[totalLength] = 0;
    fread((*ptr), totalLength, 1, STDInCopy);       //###CHECK
    fclose(STDInCopy);      //On efface le fichier temporaire
        }
Lire stdin en deux passes pour minimiser les new

2- parsing XML utilisant libxml2

void    displayNode(xmlNodePtr node, int level)
{
    if (!node)      return;
    while (node)        {
        int i;
        for(i=0; i<level; i++)      printf(\"  \");
        printf(\"%s, type %d, Contenu : %s\\n\", node->name, node->type, node->content);
        if (node->children)     displayNode(node->children, level+1);
        node = node->next;
    }
}

//Affiche l'arbre de parsing de libXML2
void showXMLTree(xmlDocPtr doc)
{
    xmlNodePtr  HTMLNode;
    printf(\"Document : %s\\n\", doc->name);
    xmlNodePtr  CurNode = doc->children;
    displayNode(CurNode, 1);
}


int main(int argc, char *argv[])
{
    // Buffer contient le texte à parser
    xmlDocPtr   HTMLDoc;
    HTMLDoc = xmlParseDoc((xmlChar*)Buffer);
    showXMLTree(HTMLDoc);
    //...
        }

Ce qui produit :

Pour l'entrée HTML suivante on obtient l'output au-dessous :

<html>
    <head>
        <title>Titre de ma page</title>
    </head>
    <body>
        <p>Premier paragraphe</p>
    </body>
</html>

-------------------------------------

Document : (null)
    html, type 1, Contenu : (null)
        text, type 3, Contenu :

        head, type 1, Contenu : (null)
            text, type 3, Contenu :

            title, type 1, Contenu : (null)
                text, type 3, Contenu : Titre de ma page
            text, type 3, Contenu :

        text, type 3, Contenu :

        body, type 1, Contenu : (null)
            text, type 3, Contenu :

            p, type 1, Contenu : (null)
                text, type 3, Contenu : Premier paragraphe
            text, type 3, Contenu :

   text, type 3, Contenu :

4- émission sur la sortie standard

On utilise simplement la fonction puts pour envoyer un contenu sur le terminal (ou même un - encore plus simple - printf). On peut donc passer aux choses sérieuses...

3- implémentation d'une classe PDFDocument

On reprend le code de PDFDocument.php et html2pdf.php pour commencer le travail. Question de debug : comment débugguer un programme que l'on veut nourrir par un pipe ? Je ne vois pas comment faire ça sous KDevelop. C'est ça qui m'empêchait de debugguer le fgets qui était pending (les io étaient bloquantes). Deux choses : essayer stdio sans lock et voir comment envoyer des données à un programme qui tourne (un pipe est activé une fois que l'applicatino est lancée donc).

Le programme de développement

Pour la version 0.1 : - générer un PDF simple pour tester les bindings de PDFLib6. Pour la version 0.2 : - Gérer plusieurs formats de pages (a4, a3 etc). - Récupérer le code du texteur PHP pour afficher du texte multi-ligne. Pour la version 0.3 : - faire que les scripts du tableau 2 fonctionnent. - gérer les éléments HTML suivants : p, b, i, u, a, div. Pour la version 0.4 : - gérer les styles suivants : font-style, color, background-color Pour la version 0.5 : - gérer les images (essentiel mais très utile également pour le débug des tableaux de la phase suivante). Pour la version 0.6 : - gérer les tableaux Pour la version 0.7 : - gérer la pagination - gérer les headers et les footers - ajouter la gestion d'autres styles. Pour la version 0.8 : - afficher correctement la fiche contact de Linda - ajouter la gestion d'autres styles. Pour la version 0.9 : - afficher correctement un rapport NCA de Linda - ajouter la gestion d'autres styles. Pour la version 1.0 : - afficher correctement deux synthereports de GED-Pro : hopital-couple-enfant et chu-rennes. - finir la gestion des styles.

1 2 3..... 9 10 11 12.. 15 16 17 18 19 20