Filmotech ScriptEdit - Trucs et astuces
- 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 Do… Loop 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 Do…Loop 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
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…).
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