Filmotech ScriptEdit - Trucs et astuces

Ce document vise à aider à l'écriture et la lecture des scripts ; il est centré sur la programmation Xojo, et aborde :
  • Les notions de base : variables et tableaux - boucles.
  • L'utilisation de ScriptEdit - dont Debug pour tester de petits programmes et mettre au point un script.
  • La notion de fonction.

Les variables

Toutes les variables doivent être déclarées par Dim nom as Type

Dim i, indice as Integer
Dim chaine, texte as String
Dim condition as Boolean

Noms

Pas de différence entre majuscules et minuscules (noms “insensibles à la case”); ainsi, Debut et debut représentent la même variable

Cette règle vaut également pour les noms réservés comme dim, integer, …

Les lettres accentuées sont autorisées dans Xojo. Mais attention : début et debut sont 2 variables différentes. L'habitude est cependant d'éviter les lettres accentuées.

Types

Principaux types utilisés dans les scripts : Integer, String, Boolean

Integer

Cf. type entier. Type entier “historique” dit “signé” (valeurs positives et négatives de -2 147 483 648 à 2 147 483647), codés sur 4 octets. Correspond à Int32 sur les machines 32 bits. Sur les machine 64 bits Integer correspond à Int64 ; il est codé sur 8 octets (valeurs de -2^63 à 2^63-1 - ^ est le symbole puissance).
Valeur par défaut : 0
Remarque : le type entier UInt16 non signé, codé sur 2 octets, (valeurs positives entre 0 et 65 535) serait largement suffisant pour analyser les lignes des pages HTML dans les scripts - mais l'habitude est d'utiliser le type historique, même s'il prend plus de place en mémoire.

String

Cf. Type chaine de caractères. Type chaine de caractères : suite de caractères tels lettres, chiffres, signes de ponctuation, etc. La longueur de la chaine n'est limitée que par la mémoire disponible.
Les valeurs doivent être mises entre guillemets. Si la chaine contient des guillemets, ils doivent être doublés.

' ex pris dans le script Allociné
chaine = "<a href='/film/fichefilm_gen_cfilm="
bannee = "<span class=""fs11"">"

Valeur par défaut : “”
Remarque : Retour charriot (carriage return (CR)) est un caractère, de code 13 dans le code “historique” ASCII. Pour le rendre actif dans une chaine il faut écrire CHR(13) - “écrire” le caractère dont le code ASCCII est 13, donc passer à la ligne suivante.
Dans les scripts CR doit être le séparateur dans la liste des acteurs ou des réalisateurs.

' ex pris dans le script Filmstarts concernant les acteurs : variable text
chaine = "<span title="
if instr(fmt_ValeurLigne(k), chaine) <> 0  then
  if text <> "" then
    text = text + chr(13)
  end if
  text = text + trim(fmt_ValeurLigne(k+1) )
end if

text = text + chr(13) : la nouvelle valeur de text est égale à la valeur précédente de text à laquelle on ajoute le caractère CR ; + est l'opérateur de concaténation entre 2 (ou plusieurs) chaines.

NB : Dans la documentation Xojo, à l'intitulé String, on a See Also qui renvoie aux opérateurs et fonctions relatifs aux chaines - comme Trim ci-dessus qui supprime les espaces en début et fin d'une chaine, ou inStr qui donne la position d'une chaine dans une autre.

Boolean

Cf. Type booléen. Type booléen (en hommage au mathématicien George Boole), ne prend que 2 valeurs True (vrai) ou False (faux).
Valeur par défaut : False

' script Allociné
do
  i=i+1
  condition = left( fmt_ValeurLigne( i ) , 1 ) <> "<" AND trim(fmt_ValeurLigne( i )) <> ""
loop until condition

Remarques :

  • until condition, cad jusqu'à ce que la variable condition ait la valeur True (inutile d'écrire until condition = True, car on teste toujours si une expression ou une variable booléenne est vraie).
  • 2 valeurs booléennes peuvent être reliées par OR (ou “inclusif”) ou AND.
    • condition1 OR condition2 est VRAI si et seulement si l'une au moins des 2 conditions est VRAIE (les 2 peuvent être VRAIES : OU inclusif).
    • condition1 AND condition2 est VRAI si et seulement si les 2 conditions sont VRAIES.

Les tableaux

Cf. Tableau (Array). Ensemble de valeurs numérotées à partir de 0, ayant toutes le même type.
NB : on se limite ici à ce qui est utilisé dans les scripts : déclaration, append, join, redim.

' script Allociné, recherche des acteurs
dim Tableau(), letexte as string 
' ....
redim tableau(-1)  ' on réinitialise le tableau (au cas où il contiendrait déjà des valeurs) 
letexte=""
i=debut+1
Do
  condition = left( fmt_ValeurLigne( i ) , 1 ) <> "<" AND trim(fmt_ValeurLigne( i )) <> "")
  if condition then
    tableau.append trim(fmt_ValeurLigne( i )) 'on ajoute nom acteur au tableau
    i=i+4  'ligne du rôle  : on ne fait rien
  end if
  i = i + 1
loop until i >= fin  
letexte= join(tableau, chr(13))  'les acteurs sont recopiés dans letexte et séparés par CR

Remarque : Les parenthèses (seules) après le nom de la variable indique qu'il s'agit d'un tableau “dynamique”, cad dont le nombre d'éléments (la dimension) n'est pas précisé.
Si l'on voulait déclarer un tableau de 10 valeurs de type chaine, on écrirait par ex : Dim MonTableau(9) as String (ne pas oublier que le premier élément à l'indice 0).

NB : Pour la structure DoLoop until cf. ci-dessous Boucle Repeter

Tester un programme dans ScriptEdit

Il est possible, pour tester des fonctions telles Trim, NthField, Instr… ou voir comment fonctionne une boucle, etc., d'écrire de petits programmes dans SciptEdit, non nécessairement liés à l'analyse d'un site.
Le programme doit être écrit dans Requete des titres.

Exécuter : Tester/ Lancer la recherche ; revenir à l'éditeur : OK (ou Arreter).

Tester les tableaux dans SciptEdit

Test sur un tableau dynamique de append, join et redim ; et du caractère CHR(13).

' Exécuter : cliquer sur lancer la recherche  -  cliquer sur OK pour sortir.

dim acteurs, tableau() as string
dim a, b, c as string
 
a= "Fabrice Luchini"
b= "Emmanuelle Seigner"
c = "Yolande Moreau"
 
tableau.append a
tableau.append b
tableau.append c
 
fmt_AfficheMessage(tableau(0))  'affiche la première valeur du tableau

acteurs = join(tableau, chr(13))
fmt_AfficheMessage(acteurs)
' fmt_DebugMsg(acteurs)  n'affiche que la valeur de la chaine avant le 1er CR  (pour fmt_DebugMsg il n'y a qu'une ligne disponible)
redim tableau(-1)  ' réinitialise le tableau
acteurs = join(tableau, chr(13))
fmt_AfficheMessage(acteurs)

La structure de boucle

C'est une notion qui, au début, peut être délicate à bien comprendre.

Il y a deux principales structures de boucle : la boucle Pour (For…next) et la boucle Répéter (Do…loop).

Boucle Pour

syntaxe générale :

For i = debut to fin
 [instructions]
 [Exit] 'facultatif
 [instructions)]
Next

on retiendra tout d'abord :

For i = 1 to n   'n entier quelconque
 [instructions]
Next

Les instructions entre For et Next seront exécutées n fois (n “passages” dans la boucle). Lors du premier passage i (i est un compteur) aura la valeur 1; à la fin du premier passage le compteur i est augmenté d'une unité : il prend donc la valeur 2 ; à l'issue du 2 eme passage il prend la valeur 2+1=3, etc. jusqu'à l'issue du n eme passage où il prend la valeur n+1 ; il est temps de sortir de la boucle.

NB : la gestion du compteur est automatique

Tester la boucle Pour dans Scriptedit

NB :

Pour fmt_DebugMsg et fmt_AfficheMessage, le paramètre peut être mis ou non entre parenthèses.
On peut donc écrire fmt_DebugMsg(“Bonjour”) ou fmt_DebugMsg “Bonjour” (il doit y avoir un espace entre fmt_DebugMsg et le paramètre)

Exemple 1

L'idée est de faire afficher la valeur du compteur i, qui représente le i eme passage dans la boucle (avec un test, car on a un affichage particulier pour i = 1).
NB : i représente le i eme passage dans la boucle que si le compteur varie de 1 à n. S'il varie par ex de 100 à 110, il faut faire une petite modif dans le programme (par ex gérer une variable k qui vaut 1 lors du 1er passage, 2 lors du second, etc.)

  • Cet exemple montre bien qu'on sort de la boucle avec i = n+ 1
' NB : str(nombre), fonction qui convertit un nombre en chaine de caractères
' str(i) obligatoire, car le paramètre de fmt_DebugMsg doit être de type chaine

Dim i, n as Integer
' NB : si n=0, la boucle ne sera pas exécutée 
' car la valeur finale n est strictement inférieure à la valeur initiale 1
n= 5
For i = 1 to n
  if  i = 1 then
    fmt_DebugMsg "premier passage dans la boucle" + "   " +  "i = " + str(i)
  else
    fmt_DebugMsg str(i) + " eme passage dans la boucle" + "   " +  "i = " + str(i)
  end if
Next
fmt_DebugMsg "i = " +str(i) + " en sortie de boucle"

Exemple 2

Proche de ce qui peut être écrit dans un script.
Allociné : La belle et le bête (Christophe Gans - 2014)
On reproduit quelques lignes de la page HTML générée, stockées dans un tableau de nom essai. On veut obtenir l'adresse de l'affiche : http://fr.web.img6.acsta.net/r_160_240/b_1_d6d6d6/pictures/13/12/18/10/01/110035.jpg (sur la ligne 4 du tableau).
On parcourt les lignes du tableau de l'indice i=0 à l'indice n=Ubound(essai) - donc on fait varier le compteur i de la boucle For comme l'indice du tableau, cad de 0 à n. Si on trouve la chaine “<img src=” à la ligne i, on peut alors extraire l'adresse de l'affiche sur cette même ligne. Si on ne trouve jamais “<img src=”, on sort de la boucle avec i = n+1 (cf. exemple 1).

Remarquer :

  • l'instruction exit qui interrompt l'exécution de la boucle si “<img src=” est trouvé
  • Ubound qui renvoie l'indice de la dernière valeur ajoutée par append.
  • Ubound peut faire penser à la fonction fmt_NombreLignes fmt_NombreLignes - qui renvoie le nombre de lignes de la page HTML analysée par ScriptEdit.
    • Attention, l'indice de la dernière ligne est fmt_NombreLignes - 1, puisque les lignes des pages HTML sont numérotées à partir de 0.
    • Par contre les pages générées par Scriptedit sont numérotées à partir de 1 - donc il y a un décalage d'une unité entre les 2 numérotations.
    • C'est pourquoi on touve dans les scripts des boucles (cf. Exemple de script) :
      • For i = 0 to fmt_NombreLignes - 1 ou For i = indice to fmt_NombreLignes - 1

NB : Pour les instructions Instr et NthField, se reporter à documentation Xojo

Dim essai(), chaine, image as string
Dim i, n  as integer
 
essai.append "<div class=""poster"">"  'indice 0 du tableau
essai.append ""   'indice 1 du tableau ,etc.
essai.append  "<span class=""acLnk 1FC4464B434F1FC0484AC643C2C1945CB4E48>"
essai.append  ""
essai.append  "<img src='http://fr.web.img6.acsta.net/r_160_240/b_1_d6d6d6/pictures/13/12/18/10/01/110035.jpg' alt='La Belle et La Bête' >"
essai.append  ""
essai.append  "<img class=""OverlayPlay"" width=""0"" height=""0"" alt="" "" src='http://fr.web.img4.acsta.net/commons/empty.gif'/ >"
essai.append  ""
essai.append " </span>"  
 
n = Ubound(essai)  ' renvoie le dernier indice du tableau (ici 8)
chaine = "<img src="
for i = 0 to n
  if  instr(essai(i) , chaine ) <> 0 then  
    'si on trouve "<img src=" sur la ligne i, Instr renvoie une valeur différente de 0
    image = NthField( essai(i) , "'" , 2 ) 
    'sur la ligne trouvée les infos sont séparées par '; on retient la 2eme valeur entre 2 séparations
    fmt_DebugMsg image 
    exit  'on sort de la boucle si la condition instr(essai(i),chaine ) <> 0 est vraie
  end if
next  'i vaut n+1 si la condition instr(essai(i),chaine ) <> 0 n'est jamais vraie (cf. ex précédent)

fmt_DebugMsg "i = " +str(i) + "  (<= à 8 si chaine trouvée,  8+1 = 9 sinon)"
 
if i = n+1 then  
  'on peut modifier légèrement la valeur de la ligne 4 pour que "<img src=" ne soit pas trouvé
  fmt_DebugMsg "affiche non trouvée"
end if

NB : Une boucle Pour For i = debut to fin n'est pas exécutées si debut > fin ; on sort de la boucle avec i = debut, Ex :

dim i, debut, fin as integer
 
debut = 20
fin = 10
For i = debut to fin
  fmt_DebugMsg str(i) 'non exécuté
next
fmt_AfficheMessage str(i)  'affiche 20

Boucle Repeter

syntaxe :

test en fin de boucle
la boucle est exécutée au moins une fois
test en début de boucle
la boucle ne sera pas exécutée si condition = true
Do
[instructions]
loop until condition
Do until condition
[instructions]
loop

La différence entre une boucle Pour et une boucle Répéter tient essentiellement au fait que : dans une boucle Pour le nombre de passage(s) est déterminé à l'avance (géré par un compteur) - même si la boucle peut être interrompue par exit ; dans une boucle Répéter, il est indéterminé : on ne sait pas a priori au bout de combien de passage(s) condition sera vraie.

  • NB : Il faut bien gérer la condition d'arrêt d'une boucle Répéter, car si elle n'est jamais vraie, la boucle est “infinie”; le programme ne s'arrête jamais et il faut l'interrompre depuis le système d'exploitation (en général Windows ou OS X selon que l'on est sur PC ou mac). Cf. ci-dessous boucle infinie

Tester la boucle Repeter dans Scriptedit

Reprenons l'exemple 2 de la boucle Pour ci-dessus.
Le traitement se fera en 2 temps :

  • Dans une boucle Do..Loop until : recherche de l'indice du tableau pour lequel instr(essai(indice) , chaine ) <> 0 est vrai (chaine = “<img src=”).
  • En sortie de boucle : utilisation de l'indice trouvé précédemment pour continuer le traitement (ici affecter à la variable image la valeur NthField( essai(indice) , “'” , 2 ))

On comparera 2 manières d'écrire la boucle. (Et l'on verra que l'écriture des boucles peut être subtile :-))

NB : On se limitera à la syntaxe DoLoop Until condition (test en fin de boucle).

On initialise indice à - 1 avant d'entrer dans la boucle

remarques :
  • Au niveau until, condition or indice = n est vraie si l'une au moins des valeurs booléennes condition ou indice = n est vraie. Donc les 2 conditions peuvent être vraies en même temps.
  • Dès lors instr(essai(indice) , chaine ) <> 0 peut être vraie sur la dernière ligne (indice=n). C'est pourquoi il vaut mieux tester en sortie de boucle If condition (plutôt que If indice <> n).
  • Mais en pratique, dans les scripts, compte tenu de l'amplitude de la plage de recherche, condition n'est jamais vraie sur la dernière ligne, et l'on teste souvent If indice <> n.
  • Attention, la manière dont on a géré indice fait qu'on ne sort pas de la boucle (si chaine non trouvé) avec i = n+1, comme dans une boucle For - ou dans l'initialisation suivante indice = 0, mais bien avec indice = n.
Dim essai(), chaine, image as string
Dim indice, n  as integer
Dim condition as Boolean
 
essai.append "<div class=""poster"">"  'indice 0 du tableau
essai.append ""   'indice 1 du tableau ,etc.
essai.append  "<span class=""acLnk 1FC4464B434F1FC0484AC643C2C1945CB4E48>"
essai.append  ""
essai.append  "<img src='http://fr.web.img6.acsta.net/r_160_240/b_1_d6d6d6/pictures/13/12/18/10/01/110035.jpg' alt='La Belle et La Bête' >"
essai.append  ""
essai.append  "<img class=""OverlayPlay"" width=""0"" height=""0"" alt="" "" src='http://fr.web.img4.acsta.net/commons/empty.gif'/ >"
essai.append  ""
essai.append " </span>"  
 
n = Ubound(essai)  ' renvoie le dernier indice du tableau (ici 8)

chaine = "<img src="
indice = -1  'de manière à rentrer dans la boucle avec l'indice 0
do 
  indice = indice +1
  condition = instr(essai(indice) , chaine ) <> 0
loop until condition or indice= n
 
if condition then 
  'ou if indice <> n then si l'on a aucune chance de trouver chaine sur la dernière ligne
  image = NthField( essai(indice) , "'" , 2 ) 
  fmt_DebugMsg image + "   " + "ligne = " + str(indice)
else
  fmt_DebugMsg "affiche non trouvée"
end if  

On initialise indice à 0 avant d'entrer dans la boucle

L'écriture est ici plus délicate du fait qu'on doit augmenter la valeur de indice en fin de boucle (juste avant Loop) - contrairement à l'exemple précédent où indice = indice + 1 était fait en début de boucle (juste après Do)

Ci-dessous exemple commenté (uniquement pour ce qui diffère par rapport à l'exemple précédent).

indice = 0
do 
  condition = instr(essai(indice) , chaine ) <> 0 
     ' à ce niveau indice = 0 lors du premier passage
  indice = indice +1 
     ' à ce niveau indice = 1 lors du premier passage
loop until  condition  or indice = n+1
     ' si on parcout les 8 indices de essai, on sort avec indice = 8+1
     
if condition then
  image = NthField(essai(indice-1) , "'" , 2)
    ' attention indice - 1 puisque indice a été augmenté de 1 après condition 
  fmt_DebugMsg image + "   " + "sur ligne = " + str(indice-1)
else
  fmt_DebugMsg "affiche non trouvée"
end if

Mettre au point un script avec Debug : Allociné Requete des titres - recherche des pages

On ouvre dans ScriptEdit une copie de Allocine.XML (version 1.2.6).
On peut télécharger le script (réduit à requete des titres) : allocine_1.2.6_titres.xml.zip

Comme on peut voir sur le site www.allocine.fr, allociné affiche 20 titres par page; il peut y avoir une ou plusieurs pages.

On s'intéressera uniquement dans Analyse des titres à la recherche des pages (au-delà de la première page). On a ajouté, afin d'utiliser Debug :
fmt_DebugMsg fmt_ValeurLigne( j+2)
fmt_DebugMsg NthField( fmt_ValeurLigne(j+2) , “”“” , 2 )+ “ ” + str(page)

Si la chaine “<li class=”“navnextbtn”“>” existe dans la page courante, on cherche le n° de la page suivante.
On arrête la recherche si chemin = “”. En fait on verra que cette condition pose problème.

'Recherche du n° de page suivante dans la page HTML courante
chaine = "<li class=""navnextbtn"">"
chemin=""
for j = 0 to fmt_NombreLignes-1
  k = instr( fmt_ValeurLigne( j ) , chaine )
  if k<>0 then
    chemin = "http://www.allocine.fr" + NthField( fmt_ValeurLigne( j+2) , """" , 2 )
    page = val( NthField(fmt_ValeurLigne( j+2),"=", 3 ) )
    fmt_DebugMsg fmt_ValeurLigne( j+2) 
    fmt_DebugMsg NthField( fmt_ValeurLigne( j+2) , """" , 2 )+ "  " + str(page)
  exit
  end if
next
 
if chemin<>"" then
  fmt_RequetePageSuivanteListe( "GET", chemin , str(page) )
  'cette instruction relance l'exécution de Analyse des titres sur la nouvelle page
end if
page HTML renvoyée

Recherche sur chambre

commentaires
  • Sur la copie d'écran nous sommes sur la page 3, la page suivante est la page 4. On doit extraire /recherche/1/?p=4&q=chambre de la ligne j+2 <a href=“/recherche/1/?p=4&q=chambre”> (ligne 855 de la page générée - donc 854 de la page HTML originale analysée) afin d'obtenir l'adresse de la page 4 sur le site Allociné http://www.allocine.fr/recherche/1/?p=4&q=chambre
  • Cette adresse est obtenue par NthField(fmt_ValeurLigne( j+2) , “”“” , 2) - cf. ligne 856 ci-dessus ; le séparateur sur cette ligne est “, donc il faut le mettre entre ”“, mais il faut aussi le doubler (puisque c'est un ”) :-)
  • L'instruction NthField(fmt_ValeurLigne( j+2),“=”, 3 ) renvoie en fait la chaine 3&q. Mais Val(…) convertit cette chaine en nombre, et ignore les valeurs non numériques &q, pour ne garder que page = 3 déclaré de type Integer.
  • Xojo envoie un avertissement (dans précompiler) : Converting from Double to Int32 causes a possible loss of precision, which can lead to unexpected results. Double est le type réel double précision. Donc Val renvoie un réel de type Double. Ici Integer est de type Int32 (cf. ci-dessus Types). En fait la conversion ne pose pas problème.
objectif

Utiliser debug pour analyser la condition d'arrêt if chemin<>“” then, l'améliorer et éventuellement limiter le nb de pages analysées.

On testera chambre qui renvoie 117 titres (6 pages)

NB : Les lignes bleues précédées d'une flèche sont les requêtes envoyées à Allociné : http….
Les autres sont les affichages de fmt_DebugMsg.

On voit que :

La condition d'arrêt n'est pas excellente car chemin n'est jamais vide quand il y a plusieurs pages. Par contre il vide s'il n'y a qu'une page (c'est sa valeur initialisée par chemin = “” - qui est aussi la valeur par défaut de toute chaine).

En conséquence il y a 7 requêtes (dont la dernière assez fantaisiste pourrait être à l'origine d'un bug).

La bonne condition d'arrêt est page = 0 (au bout de la 6 eme requête)

NB :

  • La condition d'arrêt page = 0 est également valable quand il n'y a qu'une page. Page étant de type Integer, sa valeur pas défaut est 0.
  • La question ne se pose en fait que parce que le test if chemin<>“”… ou if page <> 0… est fait en dehors de la boucle de recherche de “<li class=”“navnextbtn”“>”. Si on intègre le test à cette boucle, le problème de savoir combien vaut la variable page quand il n'y a qu'une page sur Allociné ne se pose plus (cette intégration est faite ci-dessous : la notion de fonction).

Si l'on veut au maximum 40 titres, donc s'arrêter à la page 2, il faut avoir page < = 2

D'où le script corrigé (affichage limité à 2 pages)

if page <>0  and page <=2 then
  ' if page <> 0 then ' si l'on veut toutes les pages
  fmt_RequetePageSuivanteListe( "GET", chemin , str(page) )
  'cette instruction relance l'exécution de Analyse des titres, donc y compris la partie déclaration des variables, sur la nouvelle page
end if

La notion de fonction

Une fonction est un sous-programme - un programme dans le programme dit “principal” (par ex : requete des titres).

Elle a un nom, un ou plusieurs paramètre(s) (d'un type donné) ; elle détermine, on dit “renvoie” une valeur (d'un type donné).

Elle s'utilise comme les fonctions prédéfinies du langage Xojo telle par ex : NthField qui a 3 paramètres:
chaine = NthField( fmt_ValeurLigne( i+2) , “”“” , 2 )

Une variable est déclarée par Dim, une fonction par Function…End function. Syntaxe, Ex :
Function BaliseExacte(chaine as string, idebut as integer) as integer
instruction(s)
return variable
End function

Interêt : Lorsqu'on écrit souvent le même "code" (code : instructions écrites dans un programme), 
mais avec des valeurs - des paramètres - différents, il peut être utile à la place de faire appel à une 
fonction. C'est le cas dans les script où l'on recherche souvent des chaines (des "balises") dans 
l'ensemble des lignes de la page analysée (surtout dans Analyse du détail pour rechercher l'affiche,
le genre, le pays, la date, etc.)

Ecrire une fonction dans un script

On reprendra la recherche des pages dans le script Allociné (analyse des titres). cf. Mettre au point un script
Mais on remplacera la boucle For par une boucle Do Loop. Cf. Boucle Repeter
On a également remplacé instr(fmt_ValeurLigne(i) , chaine) <> 0 par trim(fmt_ValeurLigne(i)) = chaine (recherche “exacte” - Trim est toujours plus prudent, s'il y a des espaces…)

NB : Il est possible de charger le fichier (le script est réduit à l'analyse des titres) : allocine_fonction_titres.zip

'Recherche du n° de page suivante dans la page HTML courante
' recherche de la "balise" - de la chaine "<li class=""navnextbtn"">"
chaine = "<li class=""navnextbtn"">"
debut = 0
 
i = debut - 1
do
  i = i+1
loop until trim(fmt_ValeurLigne(i)) = chaine or i = nblignes
' nblignes défini plus haut, nblignes = fmt_NombreLignes - 1 

' traitement à partir de la valeur de i
if i <> nblignes then  ' donc trim(fmt_ValeurLigne(i)) = chaine est VRAI, en effet :
  ' chaine n'ayant aucune chance d'être trouvé sur la dernière ligne de la page
  ' trim(fmt_ValeurLigne(i)) = chaine et i = nblignes ne seront pas VRAI en même temps
  chemin = "http://www.allocine.fr" + NthField( fmt_ValeurLigne(i+2) , """" , 2 )
  page = val(NthField(fmt_ValeurLigne(i+2),"=", 3 ))
  if page<>0  and page <=2 then
    fmt_RequetePageSuivanteListe("GET", chemin , str(page))
  end if
end if

Peuvent varier dans la recherche de la “balise” : la chaine cherchée, la ligne debut de la recherche → ce seront les 2 paramètres de la fonction.

Le code suivant sera grosso modo les instructions de la fonction.

i = debut - 1
do
  i = i+1
loop until trim(fmt_ValeurLigne(i)) = chaine or i = nblignes

D'où la déclaration de la fonction BaliseExacte (à écrire après la déclaration de la fonction existante Balise) :

function BaliseExacte(chaine as string, debut as integer) as integer
  dim i as integer  ' NbLignes var globale
  i = debut -1
  do
   i = i +1
  loop until  trim(fmt_ValeurLigne( i)) = chaine  or i = nblignes
  return i ' valeur de la ligne égale à la balise ou NbLignes si non trouvée
end function

Instruction return i : i est la valeur renvoyée par la fonction.

Et dans le script Allociné :

'Recherche du n° de page suivante dans la page HTML courante
chaine = "<li class=""navnextbtn"">"
k = 200  'on ne craint pas grand chose...

i = BaliseExacte(chaine, k)
 
if i <> nblignes then 
  chemin = "http://www.allocine.fr" + NthField( fmt_ValeurLigne( i+2) , """" , 2 )
  page = val( NthField(fmt_ValeurLigne( i+2),"=", 3 ) )
  if page<>0  and page <=2 then
    fmt_RequetePageSuivanteListe( "GET", chemin , str(page) )
  end if
end if

Attention : La variable i dans

i = BaliseExacte(chaine, k)

n'est pas la même que celle déclarée dans la fonction BaliseExacte

function BaliseExacte(chaine as string, debut as integer) as integer 
dim i as integer 
....

Il s'agit de la variable déclarée par

dim i, j, k, l as integer

au début de requete des titres. Cf. ci-dessous.

Variables globales et locales ; paramètres formels et effectifs

Variables globales et locales
  • Les variables du programme “principal” sont dites “globales”. Elles peuvent utilisées dans tout le programme, y compris dans les fonctions (cf. variable NbLignes).
  • Les variables d'une fonction ne peuvent être utilisées que dans cette fonction. Elles sont dites “locales” (à la fonction).
  • Les paramètres d'une fonction sont des variables locales.
  • Le programme principal et une fonction, étant des programmes indépendants, on peut avoir des variables de même noms (par ex. une variable i dans le programme principal et une variable i dans la fonction).
  • Le nom d'une fonction ne peut être le même que le nom d'une variable globale.
Paramètres formels, paramètres effectifs
  • Quand on déclare une fonction, ses paramètres sont simplement des paramètres servant à écrire les instructions de la fonction : ce sont des paramètres formels (comme x, y, z… en algèbre).
  • Quand on utilise une fonction il faut donner aux paramètres formels des valeurs effectives (on dit aussi “réelles”). Ce peut être des variables ou des constantes.
  • Il importe de ne pas confondre les 2 types de paramètres.
    • Dans la déclaration : function BaliseExacte(chaine as string, debut as integer) as integer, chaine et debut sont les paramètres formels.
    • Dans l'utilisation (on dit “l'appel” à la fonction), les paramètres chaine et k sont les paramètres effectifs (ou réels) :
chaine = "<li class=""navnextbtn"">" 
k = 200 
i = BaliseExacte(chaine, k)
  • Attention : La variable i ci-dessus dans i = BaliseExacte(chaine, k) est naturellement la variable du programme principal, ce ne peut être la même que celle déclarée dans la fonction (qui est une variable locale) (i était pratique pour ne pas modifier le reste du code if i <> nblignes then…).

Boucle infinie, attention à la condition d'arrêt

Une boucle infinie est une boucle dans laquelle la condition d'arrêt n'est jamais vraie. 
Cela concerne donc les boucles Répéter.

On n'envisagera des boucles dans lesquelles on gère un compteur (en général de nom i).

Exemple 1 : la condition d'arrêt doit impérativement inclure le compteur

Recherche d'une ligne dans un page HTML sur laquelle existe la chaine “<img src=”, mais on limite la condition d'arrêt au fait de trouver la chaine.

chaine = "<img src="
i = -1 
do 
  i = i +1
loop until instr(essai(indice), chaine ) <> 0

Or, dans les page HTML on ne trouve pas toujours la chaine cherchée, donc la condition instr(essai(indice) , chaine ) <> 0 risque de n'être jamais vraie, et la boucle d'être infinie.

  • Il est donc impératif d'inclure une condition sur i ; en général i = fin (la plupart du temps fin = fmt_NombreLignes-1).
  • La condition d'arrêt est alors : until instr(essai(indice) , chaine ) <> 0 OR i = fin

Exemple 2 : mais la condition d'arrêt sur le compteur peut s'avérer délicate

Le Script CineMotions (version 1.5.0), partie analyse des titres, contient une fonction, qui a été corrigée, mais qui au départ pouvait générer une boucle infinie (et c'est arrivé sur certains films…8-O).

Fonction non corrigée :

function Balise(chaine as string, idebut as integer, ifin as integer) as integer
  dim i as integer
  i = idebut -1
  do
    i = i +1
  loop until instr(fmt_ValeurLigne(i), chaine) <> 0 or i = ifin 
  return i 'valeur de la ligne contenant la balise ou ifin si non trouvée
end function
  • Ça ressemble à la boucle précédente, mais que se passe-t-il si ifin < idebut et que instr(fmt_ValeurLigne(i), chaine) <> 0 = False avec i = idebut+1 (premier passage) ? i dépasse alors ifin dès le premier passage, et i = ifin ne sera jamais vrai. Et en général instr(fmt_ValeurLigne(i), chaine) <> 0 ne sera pas non plus vrai sur les lignes suivantes : la boucle sera infinie.

D'où la version corrigée :

function Balise(chaine as string, idebut as integer, ifin as integer) as integer
  dim i as integer
  if idebut < ifin then
    ' idebut <= ifin est aussi correct pour s'arrêter sur i = ifin, mais on ne souhaite pas
    ' avoir true en même temps pour instr(fmt_ValeurLigne( i), chaine ) <> 0 et i = ifin
    i = idebut -1
    do
      i = i +1
    loop until instr(fmt_ValeurLigne( i), chaine ) <> 0 or i = ifin 
  else
    i = ifin
  end if 
  return i 'valeur de la ligne contenant la balise ou ifin si non trouvée
end function

Pourquoi un paramètre ifin supplémentaire, alors que les fonctions Balise (ou BaliseExacte) ne comportent en général qu'un paramètre idebut (en plus du paramètre chaine) ? cf. fonction BaliseExacte ci-dessus.

Parce que la recherche des balises HTML déterminant chaque film : “<td class=”“noirpetit”“>” et “</td>” ne doit pas aller au-delà de la balise “<td width=”“100%”“>” (dont la ligne pourra être la valeur effective de ifin).

Or les balises “<td class=”“noirpetit”“>” et “</td>” sont très courantes dans la page HTML contenant les titres ; dès lors leur recherche ne peut être faite - comme dans les fonctions Balise habituelles - jusqu'à fmt_NombreLignes - 1.

NB : Analyse des titres a été réécrit de manière à ce que (en principe) on ne risque plus d'avoir idebut > ifin. Mais deux précautions…

On pourrait le programmer autrement, mais cette fonction est assez pratique pour la recherche des titres dans CinEmotions.

On peut télécharger les script (limité à analyse des titres) cinemotions_1.5.0_titres.xml.zip