Outils pour utilisateurs

Outils du site


issue115:freepascal

In this series of articles, I will be building a text-based application with Free Pascal, using its text-based interface for user interaction. This will be combined with other, more modern, technologies such as database access using SQL and Web access with HTTP. The final aim of the project is to demonstrate how Pascal can be used to build a modern application, while avoiding the overhead associated with a graphical interface that uses a widget set such as GTK or Qt. In the previous part of the series, we deviated a bit from Free Vision and went into the technical details on how to use the CURL library from Pascal to connect to an RSS feed on FCM’s content management system. At this stage, we know how to connect to the server and download an XML file containing a list of recent articles published on the web page. In this part, we will see how to parse the XML code to retrieve the information we are aiming for: issue numbers and download URLs. We will then put it all together, and update the database our application uses with fresh data from the Web.

Dans cette série d'articles, je construirai une application en mode texte avec Free Pascal, en utilisant son interface en mode texte pour l'interaction avec l'utilisateur. Ceci sera combiné avec d'autres technologies, plus modernes, telles que l'accès à une base de données en utilisant SQL et l'accès au Web avec HTTP. Le but final du projet est de démontrer comment Pascal peut être utilisé pour construire une application moderne, tout en évitant le surpoids associé à une interface graphique qui utilise un jeu de gadgets comme GTK ou Qt.

Dans l'article précédent de la série, nous avons un peu dévié de Free Vision en allant dans les détails techniques de l'utilisation de la bibliothèque CURL pour se connecter à un lien RSS sur le CMS (Content Management System - Système de gestion du contenu) du FCM. Maintenant, nous savons comment nous connecter au serveur et télécharger un fichier XML contenant une liste des articles récents, publiés sur la page Web. Dans ce numéro, nous verrons comment analyser le code XML pour retrouver l'information ciblée : le numéro de publication et les URL de téléchargement. Nous les assemblerons et mettrons à jour la base de données qu'utilise notre application avec des données fraîches issues du Web.

Understanding the XML language EXtended Meta Language (XML) is a simple text-based language that aims to structure data as a tree: each data element may have none, or several child nodes. On the other hand, each element must have a single parent element - except for a single node in the entire tree, which is then called the root element. Each element should open with a start tag, such as <element>. The corresponding ending tag would be </element>. Perhaps an example may help. If we wish to codify a library, for instance, our root element will be the library itself. This library may then contain an element defining its owner, and perhaps another giving the date on which the data set was compiled. Finally, we will need to create an element for each book in the library, giving its title and author. The beauty of this scheme is that it may easily be adapted for different purposes in a flexible manner. For instance, in the above example, one of the books has been marked with a genre, while another has not.

Comprendre le langage XML

Le XML (EXtended Meta Language - Méta-langage étendu) est un langage simple en mode texte dont le but est de structurer les données sous forme d'arbre : chaque élément de données peut avoir aucun ou plusieurs nœuds enfant. En revanche, chaque élément doit avoir un unique parent, sauf pour un seul nœud dans tout l'arbre, qui est donc appelé l'élément racine. Chaque élément doit s'ouvrir avec une étiquette de début, tel que <élément>. L'étiquette de fin correspondante sera </élément>.

Peut-être qu'un exemple peut aider. Si nous souhaitons codifier une bibliothèque, par exemple, notre élément racine sera la bibliothèque elle-même. Cette bibliothèque peut ensuite contenir un élément définissant son propriétaire et peut-être un autre donnant la date à laquelle le jeu de données a été compilé. Enfin, nous créerons un élément pour chaque livre de la bibliothèque, en donnant ses titre et auteur.

La beauté de ce schéma est qu'il peut être facilement adapté à différents sujets de manière flexible. Par exemple, dans le cas ci-dessus, un des livres a été marqué avec un genre et l'autre pas.

Writing a program to transverse a data set in XML has its own challenges; luckily for us, the Free Pascal project has foreseen the use of a set of standard classes that may be implemented for our own purposes. Let us write a simple program that reads in an XML file, and outputs on screen each element name in sequence, or its value if it is a text element that simply contains text. To begin with, let us include the standard and XML classes and define our variables (previous page, bottom right). The TFileStream will be used to access the XML file on our local disk, and make it available to a TXMLReader through an adaptor class, TXMLInputSource. The TXMLReaderSettings object is needed to pass parameters to the reader. We begin by configuring the settings, basically telling the reader to ignore supplementary whitespace (actual spaces, but also line breaks and tabulations), and to use XML namespaces if available - though we will not need them here: settings := TXMLReaderSettings.Create; settings.PreserveWhiteSpace := false; settings.Namespaces := true;

L'écriture d'un programme pour transposer un jeu de données en XML a ses difficultés spécifiques ; heureusement pour nous, le projet Free Pascal a prévu l'usage d'un ensemble de classes standard, qui peuvent être utilisées pour nos propres besoins. Écrivons un programme simple qui lit un fichier XML et sort à l'écran le nom de chaque élément en séquence, ou sa valeur si c'est un élément de texte qui ne contient que du texte. Pour commencer, incluons les classes standard et XML et définissons nos variables (page précédente, en bas à droite).

TFileStream sera utilisé pour accéder au fichier XML sur le disque local et le rendre disponible à TXMLReader à travers une classe d'adaptation, TXMLInputSource. L'objet TXMLReaderSettings est nécessaire pour passer les paramètres au lecteur.

Nous commençons par configurer les paramètres, essentiellement en disant au lecteur d'ignorer les espaces blancs supplémentaires (les vrais blancs, mais aussi les changements de lignes et les tabulations) et d'utiliser les noms d'espace XML si disponibles, bien que nous n'en ayons pas besoin ici :

settings := TXMLReaderSettings.Create;

settings.PreserveWhiteSpace := false;

settings.Namespaces := true;

We access the file, and create a TXMLInputSource from the resulting stream: f := TFileStream.Create('test.xml', fmOpenRead); input := TXMLInputSource.Create(f); Now (shown above) we can create our TXMLTextReader, and have it parse each element encountered. Finally, let’s not forget to close the file stream neatly: f.Free; The code for the complete program is available at this link: http://pastebin.com/PtciSAQb . Parsing our RSS feed in XML In the last part of this series, we obtained the RSS for Full Circle Magazine using the CURL library. This is a piece of XML data, with the following structure. It has been cleaned up a bit to showcase relevant elements (shown below).

Nous accédons au fichier pour créer une TXMLInputSource du flux résultant :

f := TFileStream.Create('test.xml', fmOpenRead);

input := TXMLInputSource.Create(f);

Maintenant (voir ci-dessus), nous pouvons créer notre TXMLTextReader et lui faire analyser chaque élément rencontré.

Enfin, n'oublions pas de fermer le flux du fichier proprement :

f.Free;

Le code complet du programme est disponible ici : http://pastebin.com/PtciSAQb

Analyser notre flux RSS en XML

Dans la partie précédente de cette série, nous avions obtenu le RSS du Full Circle Magazine en utilisant la bibliothèque CURL. Ce sont des données XML, avec la structure suivante. Il a été un peu nettoyé pour mieux souligner les éléments intéressants (voir ci-dessous).

So, what we want to do is isolate individual <title> elements, and, within each element, the corresponding <link>. We have on the one hand a routine from CURL that fetches the contents of a URL, and produces a readable Stream. On the other hand, we have an XML parser that can parse a writable Stream. The link is obvious: we now need a mechanism to pipe data from the first stream into the second, and, in Free Pascal, this mechanism is a piped stream. Let’s do it. First, we will need a double set of variables (next page, top right). The first set are those used for the CURL library, the second will be the input and output streams to be parsed together, and the third set is for the XML parser. Finally, the two strings and associated boolean variables will be needed to link each element (of type ntElement) with its associated value (type ntText) - which is not the element itself, but a sub-element inserted inside the parent element. Unfortunately, this textual element is not always in the first position among the element’s children, so a rather convoluted set of flags (the boolean variables) must be used to detect them. We will not go over either the use of the CURL library, that has been described in the previous part of this series, or over the XML parser. We will concentrate instead on the use of the piped streams. We will create the two streams together: CreatePipeStreams (inPipe, outPipe);

Aussi, nous voulons isoler les éléments individuels <title> (titre), et, dans chaque élément, le <link> (lien) correspondant. D'un côté, nous avons une routine en CURL qui récupère le contenu d'une URL et produit un Stream lisible. De l'autre côté, nous avons un analyseur XML qui peut analyser un Stream scriptible. Le lien est évident : nous avons besoin maintenant d'un mécanisme pour « tuber » le premier Stream vers le second ; dans Free Pascal, le mécanisme est un « piped stream » (flux en tube). Faisons-le. D'abord, nous avons besoin d'un double jeu de variables (page suivante, en haut à droite).

Le premier jeu est celui utilisé pour la bibliothèque CURL, le second sera pour les flux d'entrée et de sortie qui seront analysés ensemble, et le troisième jeu sera pour l'analyseur XML. Enfin, les deux chaînes et les variables booléennes associées seront nécessaires pour relier chaque élément (de type ntElement) à sa valeur associée (type ntText), qui n'est pas l'élément lui-même, mais un sous-élément inséré dans l'élément parent. Malheureusement, l'élément textuel n'est pas toujours en première position dans les éléments enfants ; aussi, un jeu plutôt alambiqué d'indicateurs (les variables booléennes) doit être utilisé pour les détecter.

Nous n'irons pas plus loin, que ce soit dans la description de la bibliothèque CURL, que nous avons décrite dans l'article précédent de cette série, ni à propos de l'analyseur XML. Nous nous concentrerons plutôt sur l'utilisation des flux en tube. Nous créerons les deux flux ensemble :

CreatePipeStreams (inPipe, outPipe);

The CURL library can then be created using the outPipe section, to which it will write the data obtained from the Internet: curl_easy_setopt(hCurl,CURLOPT_WRITEDATA,[Pointer(outPipe)]); The XML reader’s input will be connected to the inPipe section, from which it will read back the bytes: input := TXMLInputSource.Create(inPipe); Finally, the XML reader’s main loop can be configured to detect title/link pairs. For the time being, they will simply be output on screen (shown bottom right). The complete program can be found at this link: http://pastebin.com/ciVGXvy6 . Integrating XML parsing into our Free Vision application At this stage, we have on the one hand a working Free Vision application, that consults its internal SQLite database of FCM issues and gives the result in a scrolling list on screen. On the other hand, we have a mechanism to connect to the Internet and update the database. Now, we need to connect the two, so that the database is updated before the data is shown to the user.

La bibliothèque CURL peut ensuite être créée en utilisant la section outPipe, vers laquelle il écrira les données obtenues d'Internet :

curl_easy_setopt(hCurl,CURLOPT_WRITEDATA,[Pointer(outPipe)]);

L'entrée du lecteur XML sera connectée à la section inPipe, de laquelle il lira les mots :

input := TXMLInputSource.Create(inPipe);

Enfin, la boucle principale du lecteur XML peut être configurée pour détecter les paires titre/lien. Pour l'instant, ils seront uniquement sortis sur écran (voir en bas à droite).

Le programme complet peut être trouvé ici : http://pastebin.com/ciVGXvy6

Intégrer l'analyse XML dans notre application Free Vision

À ce stade, d'une part, nous avons une application Free Vision fonctionnelle, qui consulte sa base de données SQLite des numéros du FCM et qui donne son résultat dans une liste déroulante à l'écran. D'autre part, nous avons un mécanisme pour nous connecter à Internet et rafraîchir notre base de données. Maintenant, nous avons besoin de connecter les deux, de sorte que la base de données soit mise à jour avant de montrer les données à l'utilisateur.

Perhaps the most elegant way of doing this - and the least expensive in terms of writing code - is to create a new Dialog type. Called TUpDateDialog, it will be shown on screen just before TDisplaySQLDialog is created. So, in the main application’s HandleEvent procedure, we have what's shown on the next page, top right. The TUpdateDialog will need no outside inputs, since it will always be using the same target URL to connect to the Internet, and local database filename to append any data found on new issues of FCM. This Dialog will just need a constructor that builds it, and sets off the process: TUpdateDialog = object(TDialog) constructor Init (FileName : String); end; PUpdateDialog = ^TUpdateDialog; This constructor procedure will need a whole lot of variables, but they can be classified into separate categories. We will need: • A TRect and PLabel to set up this Dialog on screen; this is the Free Vision part. • A URL and PCurl to get to the Internet and retrieve a stream accessing FCM’s feed. • Two pipes, to set up the connection between the incoming stream from the Internet, and an outgoing stream towards the XML reader. • The XML reader itself, associated settings, and several variables to identify each new issue’s identification code (e.g. ‘111’), title (‘Full Circle Magazine #111’) and download link. • A handler for the SQLite connection to the local database.

La façon peut-être la plus élégante de le faire - et la moins chère en écriture de code - est de créer un nouveau type Dialog. Appelé TUpDateDialog, il sera montré à l'écran juste avant la création de TDisplaySQLDialog. Ainsi, dans la procédure HandleEvent de l'application principale, nous avons ce qui est montré sur la page suivante, en haut à droite.

TUpDateDialog n'a besoin d'aucune entrée externe - car il utilisera toujours la même URL cible pour se connecter à Internet -, mais seulement du nom de fichier de la base de données locale pour ajouter chaque donnée trouvée dans les nouveaux numéros du FCM. Pour ce Dialog il ne faudra qu'un « constructor » (constructeur) qui le bâtira et déclenchera le processus :

TUpdateDialog = object(TDialog)
constructor Init (FileName : String);
end;
PUpdateDialog = ^TUpdateDialog;

Cette procédure de constructeur aura besoin d'un bon nombre de variables, mais elles peuvent être classées en catégories séparées. Il nous faudra : • Un TRect et un PLabel pour paramétrer ce Dialog à l'écran ; ce sera la partie Free Vision. • Une URL et un PCurl pour aller sur Internet et récupérer un flux accédant aux informations du FCM. • Deux tubes, pour préparer la connexion entre le flux d'entrée venant d'Internet et le flux de sortie vers le lecteur XML. • Le lecteur XML lui-même, ses paramètres associés et plusieurs variables pour identifier le code d'identification de chaque nouveau numéro (par ex. 111), le titre (par ex. ‘Full Circle Magazine #111’) et le lien de téléchargement. • Un gestionnaire pour la connexion de SQLite à la base de données locale.

So, have a look at the code shown bottom right. Most of the code will not be reproduced here, since it is in essence a mashup of that written in our previous part and the beginning of this one. Salient points would include the use of a Regular Expression parser (regexp) in order to parse the titles from the XML stream, identifying which contain the text identifying a new issue of FCM. We are looking for something such as ‘#109’, ‘#110’, ‘#111’…, so basically a pound sign ‘#’ followed by a series of digits. This can be made systematic with the following code: re := TRegExpr.Create; re.Expression := '#[0-9]*'; We can now use ‘re’ as a regular expression reader in the following way, to identify if the next value found by the XML reader contains the expression we are looking for. If so, it can be isolated and used to prime the issue code for insertion into the database (shown bottm left). Now, all we need to do is determine, for each issue announcement found in the XML stream, if this issue is already inside our database. To so do, we will need to get back to the SQLite driver, and search the existing issues with the same identifying code. If a match is not found, this issue is a new one and needs to be appended to the existing table (shown top right).

Aussi, jetez un œil au code présenté ci-dessous.

La plupart du code ne sera pas reproduit ici , car c'est, au fond, un rabâchage de ce qui a été écrit dans notre article précédent et au début de celui-ci. Les points marquants sont l'utilisation d'un analyseur d'expressions régulières (regexp) de façon à analyser les titres dans le flux XML, en identifiant ceux qui contiennent le texte signalant un nouveau numéro du FCM. Nous cherchons quelque chose comme « #109 », « #110 », « #111 », … ; soit, en gros, un signe « # » suivi d'une série de chiffres. Ceci peut être réalisé systématiquement avec le code suivant :

re := TRegExpr.Create;

re.Expression := '#[0-9]*';

Nous pouvons utiliser maintenant « re » comme un lecteur d'expression régulière de la manière suivante, pour identifier si la valeur suivante trouvée par le lecteur XML contient l'expression que nous cherchons. Si c'est le cas, elle peut être isolée et utilisée comme départ du code du numéro à insérer dans la base de données (voir en bas à gauche).

Maintenant, tout ce que nous avons à faire est de déterminer, pour chaque annonce d'un numéro trouvé dans le flux XML, si ce numéro est déjà dans notre base de données. Pour le faire, nous devons revenir au pilote SQLite et chercher les numéros existants avec le même code d'identification. S'il n'y a pas de correspondance, ce numéro est nouveau et doit être ajouté à la table existante (présentée en haut à droite).

Once the XML code has been completely parsed, we can alter the label on the Dialog to notify the user of how many new issues of FCM have been found. In my case, my database had been initialized by hand with issues 108, 109 and 110. I launched the application, and several new issues were detected from the XML feed: previous issue 107, and newer issue 111. This last one was identified twice, since two different posts on FCM’s feed referenced this issue, but was inserted only once into the database. msgLabel^.Text := NewStr('Found ' + IntToStr(newItems) + ' new issues…'); DrawView; The finalized application’s code can be found here: http://pastebin.com/H422xg3V . In this part of the series, we put our complete application together using Free Vision for the user interface, SQLite to create a local data, and CURL and XML to retrieve fresh data from an RSS feed from the Web to update our database. In the next part, we will see various ways in which our application can run on a Raspberry Pi.

Une fois le code XML complètement analysé, nous pouvons modifier l'étiquette dans le Dialog pour signifier à l'utilisateur combien de nouveaux numéros du FCM ont été trouvés. Dans mon cas, ma base de données a été initialisée à la main avec les numéros 108, 109 et 110. J'ai lancé l'application et plusieurs nouveaux numéros ont été détectés dans le flux XML : le numéro antérieur 107 et le récent 111. Ce dernier a été identifié deux fois, car deux messages différents du flux FCM faisaient référence à ce numéro, mais il n'a été ajouté qu'une fois dans la base de données.

msgLabel^.Text := NewStr('Found ' + IntToStr(newItems) + ' new issues...');

DrawView;

Le code final de l'application peut être trouvé ici : http://pastebin.com/H422xg3V

Dans cet article de la série, nous avons assemblé l'application entière en utilisant Free Vision pour l'interface utilisateur, SQLite pour créer une donnée locale et CURL et XML pour retrouver des données fraîches dans un flux RSS venant du Web afin de mettre à jour notre base de données. Dans la prochaine partie, nous verrons différentes façons de faire tourner notre application sur un Raspberry Pi.

issue115/freepascal.txt · Dernière modification : 2016/12/11 17:14 de andre_domenech