Manuel du langage Joodo

Guide de formation et de référence pour les développeurs — Joolan Smart Retail

Ce manuel décrit le langage Joodo dans lequel sont écrites les pages .do : la syntaxe, les structures de contrôle, le modèle de données, et les 292 procédures et fonctions de la bibliothèque standard avec leurs paramètres et leur résultat.


Sommaire

  1. Ce qu'est une page Joodo
  2. Lexique
  3. Variables
  4. Opérateurs
  5. Structures de contrôle
  6. Procédures et fonctions
  7. Le mode objet
  8. Includes
  9. Substitutions dans le HTML
  10. Bibliothèque standard
  11. Exemples complets
  12. Pièges fréquents

Bibliothèque standard, par domaine :


1. Ce qu'est une page Joodo

Une page .do est un fichier HTML dans lequel on ouvre des zones de code avec <%%>, à la manière de PHP ou ASP. Tout ce qui est hors de ces balises est envoyé tel quel au navigateur ; tout ce qui est dedans est compilé et exécuté.

<h1>Bonjour</h1>

<%
Begin
EchoChar('Nous sommes le ' + DateToStr(Now));
End.
%>

<p>Fin de page</p>

Le moteur compile la page en un code intermédiaire (pile + opcodes) qu'il exécute immédiatement. Il n'y a pas de phase de link séparée ni de binaire à déployer.

Structure d'une page

Une page comporte un bloc principal et, optionnellement, des procédures, des fonctions et des blocs Script.

<%
{ 1. déclarations : includes, variables, procédures, fonctions }
include 'outils.inc';

var Total;

function Tva(Montant);
Begin
Result := Montant * 20 / 100;
End;

{ 2. bloc principal, terminé par un point }
Begin
Total := '100';
SetVars('Tva', Tva(Total));
End.
%>

Le bloc principal se termine par End. (avec un point). Toutes les autres constructions se terminent par End;.

L'ordre d'exécution

C'est le point le plus important à comprendre, et le moins intuitif : le bloc principal s'exécute en premier, avant que la moindre ligne de HTML ne soit envoyée, quel que soit l'endroit du fichier où il est écrit.

L'ordre réel est :

  1. le bloc principal BeginEnd. ;
  2. puis __top.inc s'il existe, puis le HTML de la page et les blocs Script, dans l'ordre du fichier.

Autrement dit, une page se lit comme un contrôleur suivi d'une vue. Le bloc principal est l'endroit où l'on décide : lire les paramètres de la requête, rediriger, poser le titre, préparer les données. La mise en page vient ensuite et se contente d'afficher.

<h1>$$v:Titre</h1>
<p>Solde : $$v:Solde</p>

<%
Begin
If InParms('FermerBtn') Then ChainToPage('accueil.do');

SetVars('Titre', 'Fiche client');
SetVars('Solde', '1250,00');
End.
%>

Ici le <h1> est écrit avant le bloc principal dans le fichier, et pourtant les variables qu'il affiche sont bien renseignées : le bloc principal est déjà passé. De même, un ChainToPage place dans le bloc principal détourne la page avant qu'un seul octet de HTML ne soit produit.

La conséquence pratique : n'affichez pas depuis le bloc principal. Un EchoChar qui s'y trouve sortira avant tout le reste de la page, y compris avant la balise ouvrante <html>. Pour afficher au milieu de la mise en page, utilisez un bloc Script.

Les blocs Script

Un bloc Script est un morceau de code exécuté là où il est écrit, au fil du HTML, avec ses propres variables locales. Contrairement à une procédure, il n'est pas appelable : il n'est pas sauté, il s'exécute en passant.

<div class="encadre">
  <%
  Script;
  var S;
  Begin
  LoadFromFile('sync.log', S);
  S := StringReplace(S, #10, '<br>');
  EchoChar(S);
  End;
  %>
</div>

C'est la forme idéale pour intercaler un calcul au milieu d'une mise en page, sans polluer l'espace de noms du bloc principal.


2. Lexique

Identifiants

Un identifiant commence par une lettre ou _, et continue par des lettres, des chiffres ou _. Le langage est insensible à la casse : Total, TOTAL et total désignent la même chose (le compilateur met tout en majuscules). La longueur maximale est de 80 caractères.

Littéraux

FormeExempleRemarque
Nombre42, 3.14un seul point décimal
Chaîne'Bonjour'apostrophes simples
Apostrophe dans une chaîne'L''heure'doubler l'apostrophe
Caractère par code#13, #10, #9concaténable : #13#10
S := 'Ligne 1' + #13#10 + 'Ligne 2';
S := 'Aujourd''hui';

Commentaires

Deux formes, toutes deux imbriquables dans le flux de code :

{ commentaire entre accolades }
(* commentaire style Delphi *)

Il n'y a pas de commentaire de fin de ligne //.

Compilation conditionnelle

Les directives se placent dans un commentaire :

{$define MODE_DEBUG}
(*$define MODE_DEBUG*)

3. Variables

Déclaration

Var et Define sont synonymes. Les déclarations se font avant le Begin du bloc.

var Total;
var Nom, Prenom, Age;
Define Compteur;

Typage

Joodo n'a qu'un seul type : la chaîne de caractères. Les nombres, les dates et les booléens sont des chaînes interprétées selon le contexte de l'opérateur ou de la fonction. Un booléen vaut '1' (vrai) ou '0' (faux).

Total := '100';
Total := Total + 50;        { arithmétique : 150 }
If Total > 100 Then ...     { comparaison numérique }

Portée

Les variables déclarées dans une procédure, une fonction ou un Script sont locales à ce bloc et disparaissent à sa sortie. Celles du bloc principal sont visibles partout ensuite.

Tableaux et objets : l'aplatissement de clés

Joodo n'a pas de type tableau ni de type enregistrement. A[3] et C.nom sont du sucre syntaxique pour des clés composées dans le dictionnaire de variables : A[3] désigne la clé A:3, et C.nom la clé C:nom.

A[0] := 'premier';
A[1] := 'second';
A[2,5] := 'matrice';        { clé A:2:5 }

C.nom := 'Dupont';
C.adresse.ville := 'Lyon';  { clé C:adresse:ville }

Conséquence importante : **B := A ne copie pas la structure**, seulement la valeur scalaire de la clé racine. Pour dupliquer un sous-arbre entier, il faut ObjectCopy.

ObjectCopy(A, B);           { copie tout le sous-arbre }
ObjectCopy(A[0], B);        { un élément vers une racine }
ObjectCopy(A[0], B[3]);     { insertion dans un tableau }
ObjectCopy(A[0], C.client); { greffe sur une propriété }

Les fonctions qui produisent une structure (jsonToObject, StrToArray, StrToObject) posent la même convention : la racine reçoit le nombre d'éléments, et les éléments vivent dans les sous-clés.

jsonToObject('[{"nom":"Durant"},{"nom":"Martin"}]', A);
{ A vaut '2', A[0].nom vaut 'Durant', A[1].nom vaut 'Martin' }

For I := 0 To A - 1 Do EchoChar(A[I].nom + '<br>');

jsonToObject charge indifféremment un objet ou une collection JSON : c'est la seule fonction à employer pour l'un comme pour l'autre.


4. Opérateurs

Par priorité décroissante :

NiveauOpérateursRemarque
1( )groupement
2Modreste de division entière
3* /
4+ -- unaire admis en tête d'expression
5= <> < > <= >=rendent '1' ou '0'
6Not
7And
8Or

Le piège du +

+ est polymorphe : le compilateur émet une concaténation si l'un des deux opérandes est un littéral chaîne, et une addition sinon. C'est décidé à la compilation, sur la forme du texte, pas sur le contenu à l'exécution.

EchoChar('Total : ' + 42);   { concaténation -> "Total : 42" }
X := 40 + 2;                 { addition      -> 42 }

A := '40';
B := '2';
X := A + B;                  { addition      -> 42, et non "402" }

Pour concaténer deux variables sans ambiguïté, passez par Concat ou par un littéral vide :

X := Concat(A, B);           { "402" }
X := '' + A + B;             { "402" }

Concat et FastConcat acceptent un nombre quelconque d'arguments ; FastConcat est optimisé pour les très longues accumulations.


5. Structures de contrôle

If / Then / Else

If Total > 1000 Then
  EchoChar('gros client')
Else
  EchoChar('client standard');

If (Age >= 18) And (Pays = 'FR') Then
  Begin
  EchoChar('majeur');
  Compteur := Compteur + 1;
  End;

Pas de ; avant Else.

Case / Of / Else / End

Le sélecteur est une expression quelconque, et les étiquettes aussi — ce ne sont pas forcément des constantes.

Case Statut Of
  'A' : EchoChar('actif');
  'S' : EchoChar('suspendu');
  'R' : Begin
        EchoChar('résilié');
        Compteur := Compteur + 1;
        End;
  Else  EchoChar('inconnu');
  End;

While / Do

While Not Eof(F) Do
  Begin
  ReadLn(F, Ligne);
  EchoChar(Ligne + '<br>');
  End;

Repeat / Until

Repeat
  I := I + 1;
  EchoChar(I);
Until I >= 10;

For / To / DownTo / Do

For I := 0 To 10 Do EchoChar(I);
For I := 10 DownTo 0 Do EchoChar(I);

La borne est évaluée une seule fois, avant l'entrée dans la boucle.

Exit

Exit quitte immédiatement la procédure ou la fonction en cours — y compris depuis l'intérieur d'un For, d'un Case ou d'un While.

function Cherche(Cible);
var I;
Begin
Result := '-1';
For I := 0 To Liste - 1 Do
  If Liste[I] = Cible Then
    Begin
    Result := I;
    Exit;
    End;
End;

Inc / Dec

Inc(Compteur);
Dec(Stock);

6. Procédures et fonctions

procedure Trace(Message);
Begin
Console('[trace] ' + Message);
End;

function Tva(Montant, Taux);
Begin
Result := Montant * Taux / 100;
End;

Points à retenir :

Deux procédures ont un rôle particulier si elles existent :

NomRôle
__Initappelée automatiquement au début du bloc principal
__Finalizationappelée automatiquement avant Halt et ChainToPage

7. Le mode objet

Joodo n'a pas de vrai système objet : ce qui suit est du sucre syntaxique résolu à la compilation, bâti sur l'aplatissement de clés.

Déclarer des méthodes

Le nom d'une procédure ou d'une fonction peut contenir un point, et un seul. La partie avant le point joue le rôle de classe.

procedure TClient.Ajoute_facture(Montant, Libelle);
Begin
Self.Total := Self.Total + Montant;
Self.Dernier := Libelle;
End;

function TClient.Solde;
Begin
Result := Self.Total;
End;

Self est un paramètre implicite ajouté en tête. Il contient la clé de l'instance appelante, ce qui donne un passage par adresse : Self.Total := … écrit dans la même clé que C.Total chez l'appelant.

Déclarer une variable objet

Object(C, TClient);

Object associe une classe à une variable, ce qui permet au compilateur de résoudre les appels de méthode. Il émet une seule instruction : C.__Object := 'TClient', pour que la classe soit lisible à l'exécution.

Utiliser

Begin
Object(C, TClient);
C.Nom := 'Dupont';                     { propriété : simple clé }
C.Ajoute_facture(100, 'facture A');    { méthode : appel avec Self = C }
EchoChar(C.Solde);
EchoChar(C.__Object);                  { -> TClient }
End.

La résolution se fait sur le premier point seulement. Dans C.adresse.ville, ville est une sous-clé, jamais une méthode — même si TClient.Ville existe.

Limites à connaître


8. Includes

include 'outils.inc';

Le fichier est inséré textuellement à l'endroit de la directive, au moment de la compilation.

Deux fichiers sont inclus automatiquement s'ils existent dans le répertoire de la page, sans qu'il faille les déclarer :

FichierPoint d'insertionUsage
__top.inctout au début de la page, avant son premier HTMLen-tête commun : balises <head>, barre de navigation, ouverture de la mise en page
__bottom.incjuste avant le bloc principalprocédures et fonctions utilitaires partagées

__top.inc s'insère dans le flux de la vue : son contenu est émis en premier, juste après l'exécution du bloc principal. Il peut donc utiliser les variables que celui-ci a préparées.

__bottom.inc s'insère dans la zone des déclarations : c'est l'emplacement habituel de la bibliothèque de fonctions commune à toutes les pages d'un répertoire. Comme la compilation se fait en une passe, y placer les fonctions les rend disponibles au bloc principal.

Les projets se constituent ainsi leur propre bibliothèque : des fonctions comme ifstr ou Abort, très présentes dans les scripts, ne sont pas des primitives du langage mais des fonctions Joodo définies dans des includes.


9. Substitutions dans le HTML

Dans les zones HTML (hors <% %>), le moteur remplace certains marqueurs à l'exécution.

MarqueurRemplacé par
$$v:nomla variable nom de la liste Vars
$$p:nomle paramètre de requête nom (GET/POST)
$$e:nomla variable d'environnement nom
$$c:nomle cookie nom
$$s:pagele nom du fichier de la page courante
$$s:uril'URI de la requête, sans la query string
$$s:sessiondirle répertoire de session
$$s:datela date du jour
$$s:date_ansila date du jour au format yyyy-mm-dd
$$s:timel'heure courante
$$s:versionla version du compilateur
$$s:engineversionla version du moteur
$${texte}la traduction de texte
§§{texte}la traduction de texte, sans encodage HTML
<form action="$$e:page" method="post">
  <button type="submit">$${Valider}</button>
</form>
<p>Bonjour $$v:prenom, nous sommes le $$s:date.</p>

Echo et EchoChar appliquent aussi ces substitutions sur ce qu'ils affichent. CrudeEcho et CrudeEchoChar ne les appliquent pas — à utiliser quand la donnée peut contenir $$ (JSON, contenu utilisateur).


10. Bibliothèque standard

292 procédures et fonctions. Notation employée ci-dessous :

10.1 Sortie et flux de page

SignatureTypeRôle
Echo(texte)Paffiche, suivi d'un saut de ligne ; substitue les $$
EchoChar(texte)Paffiche sans saut de ligne ; substitue les $$
CrudeEcho(texte)Pcomme Echo, sans substitution des $$
CrudeEchoChar(texte)Pcomme EchoChar, sans substitution des $$
Console(texte)Pécrit sur la sortie console du serveur
DebugMessage(texte)Ptrace de débogage
ResetOutput()Pvide tout ce qui a déjà été produit pour la page
SendStream(nom, contenu)Penvoie un flux binaire au client
SendFile(fichier, nomAffiche)Fenvoie un fichier en téléchargement
Halt()Parrête la page (déclenche __Finalization)
ChainToPage(page)Ppasse la main à une autre page (déclenche __Finalization)
ChildScript(page)Fexécute une autre page comme sous-programme
CurrentPage()Fnom de la page en cours
Sleep(millisecondes)Pmet la page en pause
SetTimeout(secondes)Pchange le délai maximum d'exécution
SetProfiler(actif)Pactive (1) ou coupe (0) le profileur
EngineVersion()Fversion du moteur
EngineVersionDetail()Fversion détaillée du moteur

10.2 En-têtes HTTP et requête

SignatureTypeRôle
HttpHeader(type)Ppose le Content-Type de la réponse
HttpCustomHeaders(entetes)Pajoute des en-têtes de réponse
HttpGetCustomHeaders(entetes)Pfixe les en-têtes des appels HTTP sortants
HttpExpires(date)Ppose l'en-tête Expires
HttpGetContentType(type)Pfixe le Content-Type des appels sortants
HttpGetAccept(accept)Pfixe l'en-tête Accept des appels sortants
SetStatusCode(code)Pcode de statut HTTP de la réponse
SetWwwAuthenticate(valeur)Ppose l'en-tête WWW-Authenticate
HttpAuthentication(user, motDePasse)Pauthentification des appels sortants
RequestContent()Fcorps brut de la requête entrante
ReplaceInclusion(texte)Frésout les inclusions de paramètres dans texte

10.3 Listes Vars, Parms, Env, Cookies

Quatre dictionnaires de chaînes : Vars (variables de session applicative), Parms (paramètres de requête GET/POST), Env (environnement CGI), Cookie.

SignatureTypeRôle
SetVars(nom, valeur)Pécrit dans Vars ; une valeur vide supprime la clé
DeleteVars(nom)Psupprime une entrée
Vars(nom)Flit une valeur
VarsCount()Fnombre d'entrées
VarsName(index)Fnom de la n-ième entrée
InVars(nom)F'1' si la clé existe
SetParms(nom, valeur)Pidem pour Parms
DeleteParms(nom)P
Parms(nom)F
ParmsCount()F
ParmsName(index)F
ParmsIndex(index)Fvaleur de la n-ième entrée
InParms(nom)F
SetEnv(nom, valeur)Pidem pour Env
DeleteEnv(nom)P
Env(nom)F
EnvCount()F
EnvName(index)F
InEnv(nom)F
SetCookie(nom, valeur, expiration, chemin)Ppose un cookie
DeleteCookie(nom)P
Cookie(nom)F
CookieCount()F
CookieName(index)F
InCookie(nom)F
SessionId()Fidentifiant de session
SessionDir()Frépertoire de travail de la session
GetEnvironmentVariable(nom)Fvariable d'environnement du système
If InParms('FermerBtn') Then ChainToPage('accueil.do');

SetVars('Utilisateur', Parms('login'));
EchoChar('Bonjour ' + Vars('Utilisateur'));

For I := 0 To ParmsCount - 1 Do
  EchoChar(ParmsName(I) + ' = ' + ParmsIndex(I) + '<br>');

10.4 Chaînes de caractères

SignatureTypeRôle
Length(s)Flongueur
Copy(s, debut, nb)Fsous-chaîne, position 1 = premier caractère
Pos(cherche, dans)Fposition, 0 si absent
PosFrom(cherche, dans, depuis)Frecherche à partir d'une position
Insert(quoi, var S, position)Pinsère dans S
Delete(var S, position, nb)Psupprime dans S
StringReplace(s, cherche, remplace)Fremplace toutes les occurrences
StringOfChar(caractere, nb)Frépète un caractère
Trim(s)Fôte les blancs de début et de fin
UpperCase(s) / LowerCase(s)Fchangement de casse
Utf8UpperCase(s) / Utf8LowerCase(s)Fidem, en tenant compte de l'UTF-8
Utf8DiacriticToLetter(s)Fôte les accents
QuotedStr(s)Fmet entre apostrophes en doublant celles du contenu
QuotedBinary(s)Féchappe une valeur binaire pour SQL
Ord(caractere)Fcode du caractère
Chr(code)Fcaractère de code donné
Format(masque, …)Fformatage à la Format de Delphi
Concat(a, b, …)Fconcaténation, nombre libre d'arguments
FastConcat(a, b, …)Fidem, optimisé pour les longues accumulations
StrToArray(s, separateur, var A)Pdécoupe en tableau
StrToObject(s, separateur, var O)Pdécoupe en propriétés
StringTranslate(s, source, cible)Ftranslittération caractère à caractère
StringTranslation(texte, langue)Ftraduction par le dictionnaire
SetLocaleInfo(info, valeur)Préglages régionaux
SetUtf8Mode(actif)Pbascule le moteur en mode UTF-8
StrUtf8ToIso(s) / StrIsoToUtf8(s)Fconversion d'encodage
HtmlEncode(s) / StrToHtml(s)Féchappement HTML
UrlEncode(s) / UrlDecode(s)Féchappement d'URL
StrToUrl(s) / UrlToStr(s)Fidem, variantes
StrToJson(s) / JsonToStr(s)Féchappement JSON
MimeEncoder(s) / MimeDecoder(s)Fbase64
HexStrToStr(hexa, s)Fconversion hexadécimale
Nom := Trim(Parms('nom'));
If Pos('@', Email) = 0 Then EchoChar('adresse invalide');

StrToArray('rouge;vert;bleu', ';', Couleurs);
For I := 0 To Couleurs - 1 Do EchoChar(Couleurs[I] + '<br>');

10.5 Nombres, tests et dates

SignatureTypeRôle
Abs(x)Fvaleur absolue
Trunc(x)Fpartie entière (troncature)
Round(x)Farrondi
Odd(x)F'1' si impair
Random(max)Fentier aléatoire
IsInteger(s) / IsFloat(s) / IsDate(s)Ftests de format
FormatFloat(masque, x)Fformatage numérique
Now()Fdate et heure courantes
Date() / Time()Fdate seule / heure seule
DateToStr(d) / TimeToStr(h)Fconversion en texte
StrToDate(s) / StrToTime(s)Fconversion depuis le texte
FormatDateTime(masque, d)Fformatage de date
DayOfWeek(d)Fjour de la semaine
StartOfTheMonth(d) / EndOfTheMonth(d)Fpremier / dernier jour du mois
LocalTimeToUniversal(d)Fheure locale vers UTC
DateToSql(d) / TimeToSql(h) / FloatToSql(x)Flittéraux pour une requête SQL
SqlToDate(s)Fdate SQL vers date Joodo
GetTickCount()Fcompteur de millisecondes, pour chronométrer
EchoChar(FormatDateTime('dd/mm/yyyy', Now));
Debut := GetTickCount;
{ … traitement … }
Console('durée : ' + (GetTickCount - Debut) + ' ms');

10.6 Fichiers et répertoires

Les fonctions de lecture/écriture ligne à ligne travaillent sur une poignée de fichier : une variable passée par nom, obtenue par AssignFile et libérée par FreeFile.

SignatureTypeRôle
AssignFile(var F, chemin)Passocie la poignée F à un chemin
FreeFile(var F)Plibère la poignée
Reset(var F)Fouvre en lecture
Rewrite(var F)Fcrée / écrase, ouvre en écriture
Append(var F)Fouvre en ajout
Read(var F, var S)Flit une valeur
ReadLn(var F, var S)Flit une ligne
Write(var F, texte)Fécrit sans saut de ligne
WriteLn(var F, texte)Fécrit une ligne
Eof(var F)F'1' en fin de fichier
CloseFile(var F)Fferme
LoadFromFile(chemin, var S)Fcharge tout le fichier dans S
SaveToFile(chemin, var S)Fécrit S dans le fichier
FileExists(chemin)F
DeleteFile(chemin)F
RenameFile(ancien, nouveau)F
CopyFile(source, destination)F
FileAge(chemin)Fdate de dernière modification
SetFileAge(chemin, date)Fforce la date de modification
MkDir(chemin) / RmDir(chemin)Fcrée / supprime un répertoire
DirExists(chemin)F
DiskSize(unite) / DiskFree(unite)Ftaille / espace libre
ExtractFilePath(chemin)Frépertoire
ExtractFileName(chemin)Fnom du fichier
ExtractFileExt(chemin)Fextension
ChangeFileExt(chemin, ext)Fremplace l'extension
ExpandFileName(chemin)Fchemin absolu
ExpandUNCFileName(chemin)Fchemin UNC
DirectorySeparator()Fséparateur du système (/ ou \)
IniRead(fichier, section, cle)Flit une clé de fichier .ini
IniWrite(fichier, section, cle, valeur)Pécrit une clé
Zip(archive, source, options)Fcompresse
Unzip(archive, destination)Fdécompresse

Parcours de répertoire — FindFirst / FindNext partagent une poignée, et les attributs de recherche sont donnés par les fonctions Fa… :

SignatureTypeRôle
FindFirst(masque, attributs, var H)Fdémarre la recherche
FindNext(var H)Félément suivant, '0' quand c'est fini
FindClose(var H)Plibère la poignée
FindFileName(var H)Fnom de l'élément courant
FindFileSize(var H)Ftaille
FindDateTime(var H)Fdate
FaAnyFile() FaDirectory() FaArchive() FaReadOnly() FaHidden() FaSysFile() FaVolumeId()Fconstantes d'attributs
If FindFirst('*.csv', FaAnyFile, H) Then
  Repeat
    EchoChar(FindFileName(H) + ' — ' + FindFileSize(H) + ' octets<br>');
  Until Not FindNext(H);
FindClose(H);
AssignFile(F, 'journal.txt');
Append(F);
WriteLn(F, DateToStr(Now) + ' — connexion de ' + Vars('Utilisateur'));
CloseFile(F);
FreeFile(F);

10.7 Base de données — SQL

SignatureTypeRôle
SqlConnection(hote, base, user, …)Pdéfinit la connexion courante
SqlFlushConnection()Pferme les connexions du pool
SqlAssignDb(var Q, base)Pcrée une requête sur une base donnée — la forme à employer
SqlAssignDbReadOnly(var Q, base)Pidem, en lecture seule
SqlAssign(var Q)Pcrée une requête sur la connexion courante
SqlFree(var Q)Plibère la requête
SqlQuery(var Q, texte)Ppose le texte de la requête
SqlParam(var Q, nom, valeur)Pvalorise un paramètre
SqlParamLoadFromFile(var Q, nom, fichier)Pparamètre depuis un fichier (blob)
SqlExec(var Q)Fexécute
SqlFirst(var Q) SqlLast(var Q) SqlNext(var Q) SqlPrior(var Q)Fnavigation ; rendent '0' quand il n'y a plus de ligne
SqlRecordCount(var Q)Fnombre de lignes
SqlField(var Q, champ)Fvaleur d'un champ de la ligne courante
SqlFieldCount(var Q)Fnombre de colonnes
SqlFieldName(var Q, index)Fnom de la n-ième colonne
SqlFieldType(var Q, index)Ftype de la n-ième colonne
SqlToStr(var Q)Frésultat sérialisé en texte
SqlToJson(var Q)Frésultat sérialisé en JSON
SqlToTextFile(var Q, fichier, options)Pexport texte
SqlToCsvFile(var Q, fichier, options)Pexport CSV
SqlSaveToTable(var Q, table)Precopie du résultat dans une table
SqlStartTransaction(var Q) / SqlCommit(var Q) / SqlRollback(var Q)Ptransaction
SqlError()Fdernier message d'erreur
SqlExecTime(var Q)Fdurée de la dernière exécution
SqlDateFormat(masque, valeur)Pformat de date de la connexion
SqlPrefix(prefixe)Ppréfixe appliqué aux noms de tables
SqlGoogleChart(…)Frend un graphique à partir du résultat
SqlJpegImg(var Q, champ)Fimage JPEG issue d'un champ blob

Une requête s'ouvre par SqlAssignDb, qui indique explicitement sur quelle base elle porte. La base est presque toujours celle de la session courante, rangée dans la variable Db :

SqlAssignDb(Q, Vars('Db'));

L'idiome de parcours est **If SqlFirst(Q) Then Repeat … Until Not SqlNext(Q)** : SqlFirst indique s'il y a au moins une ligne, SqlNext rend '0' sur la dernière. Eof ne s'applique pas aux requêtes SQL, seulement aux fichiers.

SqlAssignDb(Q, Vars('Db'));
SqlQuery(Q, 'select code, nom, solde from clients where solde > :seuil');
SqlParam(Q, 'seuil', '1000');
If SqlExec(Q) Then
  Begin
  If SqlFirst(Q) Then
    Repeat
      EchoChar(SqlField(Q, 'nom') + ' : ' + SqlField(Q, 'solde') + '<br>');
    Until Not SqlNext(Q)
  Else EchoChar('aucun résultat');
  End
Else EchoChar('erreur SQL : ' + SqlError);
SqlFree(Q);

10.8 JSON et structures

SignatureTypeRôle
jsonToObject(json, var O)Faplatit un objet ou une collection JSON dans O ; rend '1' si l'analyse réussit
ConvertJsonToText(json, var S)Frend le JSON sous forme lisible
ObjectCopy(var source, var destination)Pcopie un sous-arbre de clés
FreeObject(var O)Psupprime tout le sous-arbre
StrToJson(s) / JsonToStr(s)Féchappement JSON

jsonToObject est la seule fonction de chargement JSON : elle traite aussi bien {"nom":"Durant"} qu'une collection [{…},{…}]. Dans le cas d'une collection, la racine reçoit le nombre d'éléments, ce qui donne directement la borne de la boucle.

Son résultat s'utilise couramment comme test, pour se prémunir d'un JSON malformé :

If not jsonToObject(HttpGet('https://api.exemple.fr/clients'), Liste) Then
  EchoChar('réponse illisible')
Else
  Begin
  For I := 0 To Liste - 1 Do
    EchoChar(Liste[I].nom + ' (' + Liste[I].ville + ')<br>');
  FreeObject(Liste);
  End;

Attention : SetVars(v, '') supprime la clé. Une sérialisation retour perdrait donc les chaînes vides et les null, et le typage nombre/chaîne/booléen n'est pas conservé par l'aplatissement.

10.9 HTTP client

SignatureTypeRôle
HttpGet(url)Frequête GET, rend le corps de la réponse
HttpGetTimeout(url, secondes)FGET avec délai maximum
HttpPost(url)Frequête POST
HttpPostTimeout(url, secondes)FPOST avec délai
HttpPostFile(url, champ, fichier)FPOST d'un fichier
HttpCall(methode, url, corps, type)Fappel générique
HttpCallUploadFile(methode, url, corps, type, var F)Fappel générique avec envoi de fichier
HttpDownload(url, fichier)Ftélécharge vers un fichier
HttpPut(url)Frequête PUT
HttpPutFile(url, champ, fichier)FPUT d'un fichier
HttpError()Fdernière erreur
HttpErrorDetail()Fdétail de la dernière erreur
InternetConnection()F'1' si la machine a un accès Internet
DnsLookup(nom)Frésolution de nom
ReverseDnsLookup(ip)Frésolution inverse

10.10 TCP

SignatureTypeRôle
TcpAssign(var S)Pcrée une socket
TcpAssignSsl(var S)Pcrée une socket TLS
TcpFree(var S)Plibère la socket
TcpConnect(var S, adresse)Fconnexion
TcpConnected(var S)Fétat de la connexion
TcpWrite(var S, donnees)Fémission
TcpReadLn(var S, var R, delai, fin)Flecture d'une ligne
TcpReadString(var S, var R, delai, taille)Flecture d'un bloc
TcpDisconnect(var S)Fdéconnexion
TcpError()Fdernière erreur

10.11 FTP, FTPS et SFTP

Les trois familles ont la même forme. Les paramètres de connexion sont répétés à chaque appel : hôte, utilisateur, mot de passe, puis les arguments propres.

SignatureTypeRôle
FtpConnect(hote, user, mdp)Fteste la connexion
FtpDisconnect(hote, user, mdp)Fferme la connexion
FtpList(hote, user, mdp, chemin, var L)Fliste un répertoire dans L
FtpGet(hote, user, mdp, distant, local)Ftéléchargement
FtpPut(hote, user, mdp, local, distant)Fenvoi
FtpDelete(hote, user, mdp, fichier)Fsuppression
FtpRename(hote, user, mdp, ancien, nouveau)Frenommage
FtpMakeDir(hote, user, mdp, chemin)Fcréation de répertoire
FtpRemoveDir(hote, user, mdp, chemin)Fsuppression de répertoire
FtpError()Fdernière erreur
FtpTransferTypeBinary() / FtpTransferTypeAscii()Pmode de transfert
FtpsList FtpsGet FtpsPut FtpsDelete FtpsRename FtpsMakeDir FtpsRemoveDir FtpsErrorFéquivalents FTPS
SftpList SftpGet SftpPut SftpDelete SftpErrorFéquivalents SFTP

10.12 Courrier électronique

SignatureTypeRôle
SendMail(serveur, de, a, sujet, corps, copie, copieCachee, piecesJointes)Fenvoi en texte
SendMailWithLogin(… + user, mdp)Fenvoi authentifié
SendMailHtml(… )Fenvoi en HTML
SendMailHtmlWithLogin(… )Fenvoi HTML authentifié
AddEmailHeaders(entetes)Pen-têtes supplémentaires du prochain envoi
SetEmailContentType(type)Ptype de contenu du prochain envoi

10.13 Images

SignatureTypeRôle
ImageResize(source, destination, largeur, hauteur)Fredimensionne en respectant les proportions
ImageFixedResize(source, destination, largeur, hauteur)Fredimensionne aux dimensions imposées
JpegResize(…) / JpegFixedResize(…)Féquivalents dédiés au JPEG
CropImage(source, destination, x, y, largeur, hauteur)Precadrage
BitmapToJpeg(source, destination)Fconversion
GetImageSize(fichier, var Largeur, var Hauteur)Fdimensions

10.14 Rapports et PDF

SignatureTypeRôle
PrintPdfReport(modele, sortie, …)Fgénère un PDF depuis un modèle
PrintReportToPdf(modele, sortie, …)Fvariante
PrintReportToArchive(modele, sortie, …)Fgénération vers archive
HtmlReport(modele, sortie, …)Frapport HTML
HtmlSubReport(modele, sortie, …)Fsous-rapport HTML

10.15 Empreintes et sécurité

SignatureTypeRôle
StrToMd5(s) / StrToSha1(s)Fempreinte d'une chaîne
HashMd5(s, cle) HashSha1(s, cle) HashSha256(s, cle) HashSha512(s, cle)Fempreinte avec clé (HMAC)
CreateGuid()Fidentifiant unique
BiometricEnabled()Fdisponibilité du module biométrique
BiometricEnrolment(…) / BiometricVerify(…)Fenrôlement / vérification

10.16 Système et concurrence

SignatureTypeRôle
Exec(commande)Flance une commande sans attendre
ExecAndWait(commande)Flance et attend la fin
SetSemaphore(nom)Fprend un verrou nommé, '0' s'il est déjà pris
ReleaseSemaphore(nom)Plibère le verrou
If SetSemaphore('import_nuit') Then
  Begin
  { … traitement exclusif … }
  ReleaseSemaphore('import_nuit');
  End
Else EchoChar('import déjà en cours');

11. Exemples complets

Formulaire avec traitement

<h2>$${Recherche client}</h2>

<form method="post" action="$$e:page">
  <input type="text" name="code" value="$$p:code">
  <button type="submit" name="ChercherBtn">$${Chercher}</button>
</form>

<%
Script;
var Q, Trouve;
Begin
If InParms('ChercherBtn') Then
  Begin
  SqlAssignDb(Q, Vars('Db'));
  SqlQuery(Q, 'select nom, ville, solde from clients where code = :c');
  SqlParam(Q, 'c', Parms('code'));
  Trouve := '0';
  If SqlExec(Q) Then
    If SqlFirst(Q) Then
      Repeat
        Trouve := '1';
        EchoChar('<p>' + HtmlEncode(SqlField(Q, 'nom')));
        EchoChar(' — ' + HtmlEncode(SqlField(Q, 'ville')));
        EchoChar(' — ' + FormatFloat('#,##0.00', SqlField(Q, 'solde')) + '</p>');
      Until Not SqlNext(Q);
  If Trouve = '0' Then EchoChar('<p>$${Aucun résultat}</p>');
  SqlFree(Q);
  End;
End;
%>

L'affichage est confié à un bloc Script, placé là où le résultat doit apparaître. Un EchoChar dans le bloc principal serait sorti avant le <h2>.

Point d'entrée d'API JSON

<%
var Corps, Reponse, I;
Begin
HttpHeader('application/json');

jsonToObject(RequestContent, Corps);

If Corps.action = 'ping' Then
  CrudeEchoChar('{"statut":"ok","heure":"' + FormatDateTime('hh:nn:ss', Now) + '"}')
Else
  Begin
  SetStatusCode('400');
  CrudeEchoChar('{"erreur":"action inconnue"}');
  End;

FreeObject(Corps);
Halt;
End.
%>

Une page d'API est entièrement du code, sans HTML autour : le bloc principal est donc le bon endroit pour produire la réponse, et Halt clôt la page proprement.

CrudeEchoChar est préféré ici à EchoChar parce que du JSON peut contenir $$, qu'EchoChar tenterait de substituer. Le risque est réel mais rare, et beaucoup de pages existantes utilisent EchoChar sans dommage ; CrudeEchoChar le supprime par construction.

Style objet

<%
procedure TPanier.Ajoute(Reference, Quantite, Prix);
Begin
Self.Lignes := Self.Lignes + 1;
Self.Total  := Self.Total + Quantite * Prix;
End;

function TPanier.Resume;
Begin
Result := Self.Lignes + ' article(s) — ' + FormatFloat('#,##0.00', Self.Total) + ' €';
End;

procedure TPanier.Vide;
Begin
Self.Lignes := '0';
Self.Total  := '0';
End;

Begin
Object(Panier, TPanier);
Panier.Vide;

Panier.Ajoute('REF001', 2, 19.90);
Panier.Ajoute('REF002', 1, 45.00);

EchoChar(Panier.Resume);
EchoChar('<br>classe : ' + Panier.__Object);
End.
%>

Traitement de fichier ligne à ligne

<%
var F, Ligne, Champs, Nb;
Begin
Nb := '0';
AssignFile(F, 'import/clients.csv');
If Reset(F) Then
  Begin
  While Not Eof(F) Do
    Begin
    ReadLn(F, Ligne);
    If Trim(Ligne) <> '' Then
      Begin
      StrToArray(Ligne, ';', Champs);
      EchoChar(Champs[0] + ' / ' + Champs[1] + '<br>');
      Nb := Nb + 1;
      End;
    End;
  CloseFile(F);
  End
Else EchoChar('fichier introuvable');
FreeFile(F);
EchoChar('<p>' + Nb + ' ligne(s) traitée(s)</p>');
End.
%>

12. Pièges fréquents

PiègeCe qui se passeRemède
EchoChar dans le bloc principalsort avant tout le HTML de la pageafficher depuis un bloc Script
A + B sur deux variablesaddition, pas concaténationConcat(A, B) ou '' + A + B
B := A sur une structurecopie la racine seulementObjectCopy(A, B)
SetVars(v, '')supprime la clétester avec InVars
EchoChar sur du JSONles $$ sont substituésCrudeEchoChar
Utiliser avant de déclarererreur de compilationcompilation en une passe : déclarer d'abord
; avant Elseerreur de syntaxepas de ; avant Else
End. vs End;le point ne clôt que le bloc principal
Deux points dans un nom de méthoderefuséun seul point autorisé
Passer un objet en paramètreseule la racine scalaire est transmiseObjectCopy