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.
Bibliothèque standard, par domaine :
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.
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;.
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 :
Begin … End. ;__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.
ScriptUn 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.
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.
| Forme | Exemple | Remarque |
|---|---|---|
| Nombre | 42, 3.14 | un 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, #9 | concaténable : #13#10 |
S := 'Ligne 1' + #13#10 + 'Ligne 2';
S := 'Aujourd''hui';
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 //.
Les directives se placent dans un commentaire :
{$define MODE_DEBUG}
(*$define MODE_DEBUG*)
Var et Define sont synonymes. Les déclarations se font avant le Begin du bloc.
var Total;
var Nom, Prenom, Age;
Define Compteur;
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 }
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.
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.
Par priorité décroissante :
| Niveau | Opérateurs | Remarque |
|---|---|---|
| 1 | ( ) | groupement |
| 2 | Mod | reste de division entière |
| 3 | * / | |
| 4 | + - | - unaire admis en tête d'expression |
| 5 | = <> < > <= >= | rendent '1' ou '0' |
| 6 | Not | |
| 7 | And | |
| 8 | Or |
++ 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.
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.
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 Not Eof(F) Do
Begin
ReadLn(F, Ligne);
EchoChar(Ligne + '<br>');
End;
Repeat
I := I + 1;
EchoChar(I);
Until I >= 10;
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 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(Compteur);
Dec(Stock);
procedure Trace(Message);
Begin
Console('[trace] ' + Message);
End;
function Tva(Montant, Taux);
Begin
Result := Montant * Taux / 100;
End;
Points à retenir :
Result, initialisée à chaîne vide.Deux procédures ont un rôle particulier si elles existent :
| Nom | Rôle |
|---|---|
__Init | appelée automatiquement au début du bloc principal |
__Finalization | appelée automatiquement avant Halt et ChainToPage |
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.
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.
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.
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.
Object() doit précéder tout usage de la variable : compilation en une passe.D := C ne crée pas d'alias, un objet ne se range pas dans un tableau et ne se renvoie pas depuis une fonction.ObjectCopy.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 :
| Fichier | Point d'insertion | Usage |
|---|---|---|
__top.inc | tout au début de la page, avant son premier HTML | en-tête commun : balises <head>, barre de navigation, ouverture de la mise en page |
__bottom.inc | juste avant le bloc principal | procé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.
Dans les zones HTML (hors <% %>), le moteur remplace certains marqueurs à l'exécution.
| Marqueur | Remplacé par |
|---|---|
$$v:nom | la variable nom de la liste Vars |
$$p:nom | le paramètre de requête nom (GET/POST) |
$$e:nom | la variable d'environnement nom |
$$c:nom | le cookie nom |
$$s:page | le nom du fichier de la page courante |
$$s:uri | l'URI de la requête, sans la query string |
$$s:sessiondir | le répertoire de session |
$$s:date | la date du jour |
$$s:date_ansi | la date du jour au format yyyy-mm-dd |
$$s:time | l'heure courante |
$$s:version | la version du compilateur |
$$s:engineversion | la 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).
292 procédures et fonctions. Notation employée ci-dessous :
P = procédure, ne rend rien. F** = fonction, rend une valeur.var X doit être un nom de variable : la fonction y écrit son résultat, ou s'en sert comme poignée (fichier, connexion SQL, table, socket). Tous les autres paramètres sont des expressions passées par valeur.'1' en cas de succès et '0' en cas d'échec.| Signature | Type | Rôle |
|---|---|---|
Echo(texte) | P | affiche, suivi d'un saut de ligne ; substitue les $$ |
EchoChar(texte) | P | affiche sans saut de ligne ; substitue les $$ |
CrudeEcho(texte) | P | comme Echo, sans substitution des $$ |
CrudeEchoChar(texte) | P | comme EchoChar, sans substitution des $$ |
Console(texte) | P | écrit sur la sortie console du serveur |
DebugMessage(texte) | P | trace de débogage |
ResetOutput() | P | vide tout ce qui a déjà été produit pour la page |
SendStream(nom, contenu) | P | envoie un flux binaire au client |
SendFile(fichier, nomAffiche) | F | envoie un fichier en téléchargement |
Halt() | P | arrête la page (déclenche __Finalization) |
ChainToPage(page) | P | passe la main à une autre page (déclenche __Finalization) |
ChildScript(page) | F | exécute une autre page comme sous-programme |
CurrentPage() | F | nom de la page en cours |
Sleep(millisecondes) | P | met la page en pause |
SetTimeout(secondes) | P | change le délai maximum d'exécution |
SetProfiler(actif) | P | active (1) ou coupe (0) le profileur |
EngineVersion() | F | version du moteur |
EngineVersionDetail() | F | version détaillée du moteur |
| Signature | Type | Rôle |
|---|---|---|
HttpHeader(type) | P | pose le Content-Type de la réponse |
HttpCustomHeaders(entetes) | P | ajoute des en-têtes de réponse |
HttpGetCustomHeaders(entetes) | P | fixe les en-têtes des appels HTTP sortants |
HttpExpires(date) | P | pose l'en-tête Expires |
HttpGetContentType(type) | P | fixe le Content-Type des appels sortants |
HttpGetAccept(accept) | P | fixe l'en-tête Accept des appels sortants |
SetStatusCode(code) | P | code de statut HTTP de la réponse |
SetWwwAuthenticate(valeur) | P | pose l'en-tête WWW-Authenticate |
HttpAuthentication(user, motDePasse) | P | authentification des appels sortants |
RequestContent() | F | corps brut de la requête entrante |
ReplaceInclusion(texte) | F | résout les inclusions de paramètres dans texte |
Quatre dictionnaires de chaînes : Vars (variables de session applicative), Parms (paramètres de requête GET/POST), Env (environnement CGI), Cookie.
| Signature | Type | Rôle |
|---|---|---|
SetVars(nom, valeur) | P | écrit dans Vars ; une valeur vide supprime la clé |
DeleteVars(nom) | P | supprime une entrée |
Vars(nom) | F | lit une valeur |
VarsCount() | F | nombre d'entrées |
VarsName(index) | F | nom de la n-ième entrée |
InVars(nom) | F | '1' si la clé existe |
SetParms(nom, valeur) | P | idem pour Parms |
DeleteParms(nom) | P | |
Parms(nom) | F | |
ParmsCount() | F | |
ParmsName(index) | F | |
ParmsIndex(index) | F | valeur de la n-ième entrée |
InParms(nom) | F | |
SetEnv(nom, valeur) | P | idem pour Env |
DeleteEnv(nom) | P | |
Env(nom) | F | |
EnvCount() | F | |
EnvName(index) | F | |
InEnv(nom) | F | |
SetCookie(nom, valeur, expiration, chemin) | P | pose un cookie |
DeleteCookie(nom) | P | |
Cookie(nom) | F | |
CookieCount() | F | |
CookieName(index) | F | |
InCookie(nom) | F | |
SessionId() | F | identifiant de session |
SessionDir() | F | répertoire de travail de la session |
GetEnvironmentVariable(nom) | F | variable 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>');
| Signature | Type | Rôle |
|---|---|---|
Length(s) | F | longueur |
Copy(s, debut, nb) | F | sous-chaîne, position 1 = premier caractère |
Pos(cherche, dans) | F | position, 0 si absent |
PosFrom(cherche, dans, depuis) | F | recherche à partir d'une position |
Insert(quoi, var S, position) | P | insère dans S |
Delete(var S, position, nb) | P | supprime dans S |
StringReplace(s, cherche, remplace) | F | remplace toutes les occurrences |
StringOfChar(caractere, nb) | F | répète un caractère |
Trim(s) | F | ôte les blancs de début et de fin |
UpperCase(s) / LowerCase(s) | F | changement de casse |
Utf8UpperCase(s) / Utf8LowerCase(s) | F | idem, en tenant compte de l'UTF-8 |
Utf8DiacriticToLetter(s) | F | ôte les accents |
QuotedStr(s) | F | met entre apostrophes en doublant celles du contenu |
QuotedBinary(s) | F | échappe une valeur binaire pour SQL |
Ord(caractere) | F | code du caractère |
Chr(code) | F | caractère de code donné |
Format(masque, …) | F | formatage à la Format de Delphi |
Concat(a, b, …) | F | concaténation, nombre libre d'arguments |
FastConcat(a, b, …) | F | idem, optimisé pour les longues accumulations |
StrToArray(s, separateur, var A) | P | découpe en tableau |
StrToObject(s, separateur, var O) | P | découpe en propriétés |
StringTranslate(s, source, cible) | F | translittération caractère à caractère |
StringTranslation(texte, langue) | F | traduction par le dictionnaire |
SetLocaleInfo(info, valeur) | P | réglages régionaux |
SetUtf8Mode(actif) | P | bascule le moteur en mode UTF-8 |
StrUtf8ToIso(s) / StrIsoToUtf8(s) | F | conversion d'encodage |
HtmlEncode(s) / StrToHtml(s) | F | échappement HTML |
UrlEncode(s) / UrlDecode(s) | F | échappement d'URL |
StrToUrl(s) / UrlToStr(s) | F | idem, variantes |
StrToJson(s) / JsonToStr(s) | F | échappement JSON |
MimeEncoder(s) / MimeDecoder(s) | F | base64 |
HexStrToStr(hexa, s) | F | conversion 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>');
| Signature | Type | Rôle |
|---|---|---|
Abs(x) | F | valeur absolue |
Trunc(x) | F | partie entière (troncature) |
Round(x) | F | arrondi |
Odd(x) | F | '1' si impair |
Random(max) | F | entier aléatoire |
IsInteger(s) / IsFloat(s) / IsDate(s) | F | tests de format |
FormatFloat(masque, x) | F | formatage numérique |
Now() | F | date et heure courantes |
Date() / Time() | F | date seule / heure seule |
DateToStr(d) / TimeToStr(h) | F | conversion en texte |
StrToDate(s) / StrToTime(s) | F | conversion depuis le texte |
FormatDateTime(masque, d) | F | formatage de date |
DayOfWeek(d) | F | jour de la semaine |
StartOfTheMonth(d) / EndOfTheMonth(d) | F | premier / dernier jour du mois |
LocalTimeToUniversal(d) | F | heure locale vers UTC |
DateToSql(d) / TimeToSql(h) / FloatToSql(x) | F | littéraux pour une requête SQL |
SqlToDate(s) | F | date SQL vers date Joodo |
GetTickCount() | F | compteur de millisecondes, pour chronométrer |
EchoChar(FormatDateTime('dd/mm/yyyy', Now));
Debut := GetTickCount;
{ … traitement … }
Console('durée : ' + (GetTickCount - Debut) + ' ms');
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.
| Signature | Type | Rôle |
|---|---|---|
AssignFile(var F, chemin) | P | associe la poignée F à un chemin |
FreeFile(var F) | P | libère la poignée |
Reset(var F) | F | ouvre en lecture |
Rewrite(var F) | F | crée / écrase, ouvre en écriture |
Append(var F) | F | ouvre en ajout |
Read(var F, var S) | F | lit une valeur |
ReadLn(var F, var S) | F | lit 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) | F | ferme |
LoadFromFile(chemin, var S) | F | charge 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) | F | date de dernière modification |
SetFileAge(chemin, date) | F | force la date de modification |
MkDir(chemin) / RmDir(chemin) | F | crée / supprime un répertoire |
DirExists(chemin) | F | |
DiskSize(unite) / DiskFree(unite) | F | taille / espace libre |
ExtractFilePath(chemin) | F | répertoire |
ExtractFileName(chemin) | F | nom du fichier |
ExtractFileExt(chemin) | F | extension |
ChangeFileExt(chemin, ext) | F | remplace l'extension |
ExpandFileName(chemin) | F | chemin absolu |
ExpandUNCFileName(chemin) | F | chemin UNC |
DirectorySeparator() | F | séparateur du système (/ ou \) |
IniRead(fichier, section, cle) | F | lit une clé de fichier .ini |
IniWrite(fichier, section, cle, valeur) | P | écrit une clé |
Zip(archive, source, options) | F | compresse |
Unzip(archive, destination) | F | décompresse |
Parcours de répertoire — FindFirst / FindNext partagent une poignée, et les attributs de recherche sont donnés par les fonctions Fa… :
| Signature | Type | Rôle |
|---|---|---|
FindFirst(masque, attributs, var H) | F | démarre la recherche |
FindNext(var H) | F | élément suivant, '0' quand c'est fini |
FindClose(var H) | P | libère la poignée |
FindFileName(var H) | F | nom de l'élément courant |
FindFileSize(var H) | F | taille |
FindDateTime(var H) | F | date |
FaAnyFile() FaDirectory() FaArchive() FaReadOnly() FaHidden() FaSysFile() FaVolumeId() | F | constantes 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);
| Signature | Type | Rôle |
|---|---|---|
SqlConnection(hote, base, user, …) | P | définit la connexion courante |
SqlFlushConnection() | P | ferme les connexions du pool |
SqlAssignDb(var Q, base) | P | crée une requête sur une base donnée — la forme à employer |
SqlAssignDbReadOnly(var Q, base) | P | idem, en lecture seule |
SqlAssign(var Q) | P | crée une requête sur la connexion courante |
SqlFree(var Q) | P | libère la requête |
SqlQuery(var Q, texte) | P | pose le texte de la requête |
SqlParam(var Q, nom, valeur) | P | valorise un paramètre |
SqlParamLoadFromFile(var Q, nom, fichier) | P | paramètre depuis un fichier (blob) |
SqlExec(var Q) | F | exécute |
SqlFirst(var Q) SqlLast(var Q) SqlNext(var Q) SqlPrior(var Q) | F | navigation ; rendent '0' quand il n'y a plus de ligne |
SqlRecordCount(var Q) | F | nombre de lignes |
SqlField(var Q, champ) | F | valeur d'un champ de la ligne courante |
SqlFieldCount(var Q) | F | nombre de colonnes |
SqlFieldName(var Q, index) | F | nom de la n-ième colonne |
SqlFieldType(var Q, index) | F | type de la n-ième colonne |
SqlToStr(var Q) | F | résultat sérialisé en texte |
SqlToJson(var Q) | F | résultat sérialisé en JSON |
SqlToTextFile(var Q, fichier, options) | P | export texte |
SqlToCsvFile(var Q, fichier, options) | P | export CSV |
SqlSaveToTable(var Q, table) | P | recopie du résultat dans une table |
SqlStartTransaction(var Q) / SqlCommit(var Q) / SqlRollback(var Q) | P | transaction |
SqlError() | F | dernier message d'erreur |
SqlExecTime(var Q) | F | durée de la dernière exécution |
SqlDateFormat(masque, valeur) | P | format de date de la connexion |
SqlPrefix(prefixe) | P | préfixe appliqué aux noms de tables |
SqlGoogleChart(…) | F | rend un graphique à partir du résultat |
SqlJpegImg(var Q, champ) | F | image 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);
| Signature | Type | Rôle |
|---|---|---|
jsonToObject(json, var O) | F | aplatit un objet ou une collection JSON dans O ; rend '1' si l'analyse réussit |
ConvertJsonToText(json, var S) | F | rend le JSON sous forme lisible |
ObjectCopy(var source, var destination) | P | copie un sous-arbre de clés |
FreeObject(var O) | P | supprime 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.
| Signature | Type | Rôle |
|---|---|---|
HttpGet(url) | F | requête GET, rend le corps de la réponse |
HttpGetTimeout(url, secondes) | F | GET avec délai maximum |
HttpPost(url) | F | requête POST |
HttpPostTimeout(url, secondes) | F | POST avec délai |
HttpPostFile(url, champ, fichier) | F | POST d'un fichier |
HttpCall(methode, url, corps, type) | F | appel générique |
HttpCallUploadFile(methode, url, corps, type, var F) | F | appel générique avec envoi de fichier |
HttpDownload(url, fichier) | F | télécharge vers un fichier |
HttpPut(url) | F | requête PUT |
HttpPutFile(url, champ, fichier) | F | PUT d'un fichier |
HttpError() | F | dernière erreur |
HttpErrorDetail() | F | détail de la dernière erreur |
InternetConnection() | F | '1' si la machine a un accès Internet |
DnsLookup(nom) | F | résolution de nom |
ReverseDnsLookup(ip) | F | résolution inverse |
| Signature | Type | Rôle |
|---|---|---|
TcpAssign(var S) | P | crée une socket |
TcpAssignSsl(var S) | P | crée une socket TLS |
TcpFree(var S) | P | libère la socket |
TcpConnect(var S, adresse) | F | connexion |
TcpConnected(var S) | F | état de la connexion |
TcpWrite(var S, donnees) | F | émission |
TcpReadLn(var S, var R, delai, fin) | F | lecture d'une ligne |
TcpReadString(var S, var R, delai, taille) | F | lecture d'un bloc |
TcpDisconnect(var S) | F | déconnexion |
TcpError() | F | dernière erreur |
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.
| Signature | Type | Rôle |
|---|---|---|
FtpConnect(hote, user, mdp) | F | teste la connexion |
FtpDisconnect(hote, user, mdp) | F | ferme la connexion |
FtpList(hote, user, mdp, chemin, var L) | F | liste un répertoire dans L |
FtpGet(hote, user, mdp, distant, local) | F | téléchargement |
FtpPut(hote, user, mdp, local, distant) | F | envoi |
FtpDelete(hote, user, mdp, fichier) | F | suppression |
FtpRename(hote, user, mdp, ancien, nouveau) | F | renommage |
FtpMakeDir(hote, user, mdp, chemin) | F | création de répertoire |
FtpRemoveDir(hote, user, mdp, chemin) | F | suppression de répertoire |
FtpError() | F | dernière erreur |
FtpTransferTypeBinary() / FtpTransferTypeAscii() | P | mode de transfert |
FtpsList FtpsGet FtpsPut FtpsDelete FtpsRename FtpsMakeDir FtpsRemoveDir FtpsError | F | équivalents FTPS |
SftpList SftpGet SftpPut SftpDelete SftpError | F | équivalents SFTP |
| Signature | Type | Rôle |
|---|---|---|
SendMail(serveur, de, a, sujet, corps, copie, copieCachee, piecesJointes) | F | envoi en texte |
SendMailWithLogin(… + user, mdp) | F | envoi authentifié |
SendMailHtml(… ) | F | envoi en HTML |
SendMailHtmlWithLogin(… ) | F | envoi HTML authentifié |
AddEmailHeaders(entetes) | P | en-têtes supplémentaires du prochain envoi |
SetEmailContentType(type) | P | type de contenu du prochain envoi |
| Signature | Type | Rôle |
|---|---|---|
ImageResize(source, destination, largeur, hauteur) | F | redimensionne en respectant les proportions |
ImageFixedResize(source, destination, largeur, hauteur) | F | redimensionne aux dimensions imposées |
JpegResize(…) / JpegFixedResize(…) | F | équivalents dédiés au JPEG |
CropImage(source, destination, x, y, largeur, hauteur) | P | recadrage |
BitmapToJpeg(source, destination) | F | conversion |
GetImageSize(fichier, var Largeur, var Hauteur) | F | dimensions |
| Signature | Type | Rôle |
|---|---|---|
PrintPdfReport(modele, sortie, …) | F | génère un PDF depuis un modèle |
PrintReportToPdf(modele, sortie, …) | F | variante |
PrintReportToArchive(modele, sortie, …) | F | génération vers archive |
HtmlReport(modele, sortie, …) | F | rapport HTML |
HtmlSubReport(modele, sortie, …) | F | sous-rapport HTML |
| Signature | Type | Rôle |
|---|---|---|
StrToMd5(s) / StrToSha1(s) | F | empreinte d'une chaîne |
HashMd5(s, cle) HashSha1(s, cle) HashSha256(s, cle) HashSha512(s, cle) | F | empreinte avec clé (HMAC) |
CreateGuid() | F | identifiant unique |
BiometricEnabled() | F | disponibilité du module biométrique |
BiometricEnrolment(…) / BiometricVerify(…) | F | enrôlement / vérification |
| Signature | Type | Rôle |
|---|---|---|
Exec(commande) | F | lance une commande sans attendre |
ExecAndWait(commande) | F | lance et attend la fin |
SetSemaphore(nom) | F | prend un verrou nommé, '0' s'il est déjà pris |
ReleaseSemaphore(nom) | P | libère le verrou |
If SetSemaphore('import_nuit') Then
Begin
{ … traitement exclusif … }
ReleaseSemaphore('import_nuit');
End
Else EchoChar('import déjà en cours');
<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>.
<%
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.
<%
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.
%>
<%
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.
%>
| Piège | Ce qui se passe | Remède |
|---|---|---|
EchoChar dans le bloc principal | sort avant tout le HTML de la page | afficher depuis un bloc Script |
A + B sur deux variables | addition, pas concaténation | Concat(A, B) ou '' + A + B |
B := A sur une structure | copie la racine seulement | ObjectCopy(A, B) |
SetVars(v, '') | supprime la clé | tester avec InVars |
EchoChar sur du JSON | les $$ sont substitués | CrudeEchoChar |
| Utiliser avant de déclarer | erreur de compilation | compilation en une passe : déclarer d'abord |
; avant Else | erreur de syntaxe | pas de ; avant Else |
End. vs End; | le point ne clôt que le bloc principal | |
| Deux points dans un nom de méthode | refusé | un seul point autorisé |
| Passer un objet en paramètre | seule la racine scalaire est transmise | ObjectCopy |