
Création d'une application interactive pour afficher les flux RSS dans MetaTrader 5
Contenu
Introduction
L'article «Lecture des fils d'actualité RSS au moyen de MQL4» décrit un script plutôt rudimentaire qui pourrait être utilisé pour afficher les flux RSS dans la console du terminal au moyen d'une simple bibliothèque qui a été initialement conçue pour analyser les documents HTML.
Avec l'avènement de MetaTrader 5 et du langage de programmation MQL5, j'ai pensé qu'il était possible de créer une application interactive capable de mieux afficher le contenu RSS. Cet article décrit comment produire cette application à l'aide de la vaste bibliothèque standard MQL5 et d'autres outils développés par les contributeurs de la communauté MQL5.
1. Documents RSS en général
Avant d'aborder les spécificités de l'application, je pense qu'il est nécessaire de donner un aperçu de la structure générale d'un document RSS.
Afin de comprendre la description qui suit, vous devez vous familiariser avec le langage de balisage extensible et les concepts associés. Veuillez vous référer à Xml Tutoriel XML si vous n'êtes pas familier avec les documents XML. Notez que dans cet article, nœud fait référence à une balise dans un document XML. Comme mentionné dans l'article MQL4 référencé ci-dessus, les fichiers RSS sont simplement des documents XML avec une structure de balise spécifique.
Chaque document RSS a un conteneur global, la balise RSS. Ceci est commun à tous les documents RSS. La balise de canal est toujours un descendant direct de la balise RSS. Il contient des informations sur le site Web décrit par le flux. À partir de là, les documents RSS peuvent varier en termes de balises spécifiques qu'ils contiennent, mais il existe des balises que tous les documents doivent contenir pour être vérifiés en tant que fichiers RSS.
Les balises requises sont :
- titre - Le titre de la chaîne. Doit contenir le nom du site Web ;
- lien - URL du site Web qui fournit ce canal ;
- description - Résumé de l'objet du site Web ;
- élément- une balise d'élément au moins, pour le contenu.
Les balises affichées ci-dessus doivent toutes être des nœuds enfants de la balise de canal. Le nœud de l'élément est celui qui contient les données relatives à un contenu spécifique.
Chaque nœud d'élément à son tour doit également contenir les balises suivantes :
- titre - Titre du contenu ;
- lien - Le lien URL vers le contenu ;
- description - Résumé du contenu ;
- date - Date à laquelle le contenu a été publié sur le site Web.
Tous les documents RSS contiennent les balises décrites et suivent la même structure.
Un exemple de document RSS complet est donné ci-dessous.
<?xml version="1.0"?> <rss version="2.0"> <channel> <title>Xul.fr: Tutorials and Applications of the Web 2.0</title> <link>http://www.xul.fr/</link> <description>Ajax, JavaScript, XUL, RSS, PHP and all technologies of the Web 2.0. Building a CMS, tutorial and application.</description> <pubDate>Wed, 07 Feb 2007 14:20:24 GMT</pubDate> <item> <title>News on interfaces of the Web in 2010</title> <link>http://www.xul.fr/en/2010.php</link> <description>Steve Jobs explains why iPad does not support Adobe Flash:<em>At Adobe they are lazy. They have the potential to make interesting things, but they refuse to do so. Apple does not support Flash because it is too buggy. Each time a Mac crashes, most often it is because of Flash. Nobody will use Flash. The world is moving to <a href="http://www.xul.fr/en/html5/" target="_parent">HTML 5</a></description> <pubDate>Sat, 11 Dec 10 09:41:06 +0100</pubDate> </item> <item> <title>Textured Border in CSS</title> <link>http://www.xul.fr/en/css/textured-border.php</link> <description> The border attribute of the style sheets can vary in color and width, but it was not expected to give it a texture. However, only a CSS rule is required to add this graphic effect... The principle is to assign a texture to the whole <em>fieldset</em> and insert into it another <em>fieldset</em> (for rounded edges) or a <em>div</em>, whose background is the same as that of the page</description> <pubDate>Wed, 29 Jul 09 15:56:54 0200</pubDate> </item> <item> <title>Create an RSS feed from SQL, example with Wordpress</title> <link>http://www.xul.fr/feed/rss-sql-wordpress.html</link> <description>Articles contain at least the following items: And possibly, author's name, or an image. This produces the following table: The returned value is true if the database is found, false otherwise. It remains to retrieve the data from the array</description> <pubDate>Wed, 29 Jul 09 15:56:50 0200</pubDate> </item> <item> <title>Firefox 3.5</title> <link>http://www.xul.fr/gecko/firefox35.php</link> <description>Les balises audio et vidéo sont implémentées. Le format de donnée JSON est reconnu nativement par Firefox. L'avantage est d'éviter l'utilisation de la fonction eval() qui n'est pas sûr, ou d'employer des librairies additionnelles, qui est nettement plus lent</description> <pubDate>Wed, 24 Jun 09 15:18:47 0200</pubDate> </item> <item> <title>Contestation about HTML 5</title> <link>http://www.xul.fr/en/html5/contestation.php</link> <description> Nobody seemed to be worried so far, but the definition of HTML 5 that is intended to be the format of billions of Web pages in coming years, is conducted and decided by a single person! <em>Hey, wait! Pay no attention to the multi-billions dollar Internet corporation behind the curtain. It's me Ian Hickson! I am my own man</description> <pubDate>Wed, 24 Jun 09 15:18:29 0200</pubDate> </item> <item> <title>Form Objects in HTML 4</title> <link>http://www.xul.fr/javascript/form-objects.php</link> <description> It is created by the HTML <em>form</em> tag: The name or id attribute can access by script to its content. It is best to use both attributes with the same identifier, for the sake of compatibility. The <em>action</em> attribute indicates the page to which send the form data. If this attribute is empty, the page that contains the form that will be charged the data as parameters</description> <pubDate>Wed, 24 Jun 09 15:17:49 0200</pubDate> </item> <item> <title>DOM Tutorial</title> <link>http://www.xul.fr/en/dom/</link> <description> The Document Object Model describes the structure of an XML or HTML document, a web page and allows access to each individual element.</description> <pubDate>Wed, 06 May 2009 18:30:11 GMT</pubDate> </item> </channel> </rss>
2. Structure globale de l'application
Ici, je vais donner une description des informations que le lecteur RSS doit afficher et une vue d'ensemble de l'interface utilisateur graphique de l'application.
Le premier aspect que l'application doit afficher est le titre de la chaîne, qui est contenu dans la balise titre. Ces informations serviront d'indication du site Web auquel le flux fait référence.
L'application doit également afficher un instantané de tout le contenu décrit par le flux, cela concerne toutes les balises d'élément du document. Pour chaque balise d'élément, le titre du contenu sera affiché. Enfin, je souhaite que le lecteur RSS puisse afficher la description du contenu, ce seront les données contenues dans la balise de description de chaque nœud d'élément.
2.1. L'interface utilisateur
L'interface utilisateur est fonction des informations à afficher par l'application.
L'idée que j'ai eue d'une interface utilisateur est mieux illustrée par le schéma ci-dessous.
Fig. 1. Croquis du dialogue de l'application
Le diagramme montre les différentes sections qui composent l'interface utilisateur.
- La première est la barre de titre. C'est là que le titre de la chaîne sera affiché ;
- Zone de saisie. C'est ici que les utilisateurs saisiront l'adresse Web d'un flux RSS ;
- Zone de titre. Le titre du contenu spécifique sera affiché ici ;
- Zone de texte. La description du contenu est affichée ici;
- Zone d'affichage de la liste. Cette liste déroulante affichera les titres de tout le contenu que contient le flux ;
- Le bouton de gauche réinitialise et efface le texte affiché dans les zones Titre, texte et liste ;
- Le bouton Mettre à jour le flux actuel récupère les nouvelles mises à jour pour un flux actuellement chargé.
Le lecteur RSS fonctionnera de la manière suivante - lorsque le programme est chargé sur la carte, la boîte de dialogue d'application vide s'affiche, l'utilisateur doit alors saisir l'adresse Web d'un flux RSS souhaité dans la zone de saisie, puis appuyez sur Entrée. Cela chargera tous les titres de contenu, c'est-à-dire les valeurs de balise de titre pour chaque balise d'élément dans la zone d'affichage de la liste. La liste sera numérotée à partir de 1, ce qui représente le contenu le plus récemment publié.
Chaque élément de la liste sera cliquable, en cliquant sur un élément de la liste, il sera mis en surbrillance et la description correspondante du contenu du titre sera affichée dans la zone de texte. En même temps, le titre du contenu sera affiché plus clairement dans la section de la zone de titre. Si une erreur se produit lors du chargement du flux pour quelque raison que ce soit, un message d'erreur s'affichera dans la zone de texte.
Le bouton de réinitialisation peut ensuite être utilisé pour effacer n'importe quel texte dans la zone de texte, la zone d'affichage de liste, les sections de la zone de titre.
Mettre à jour le flux actuel vérifie simplement les mises à jour du flux actuel.
2.2. Implémentation du code
Le lecteur RSS sera implémenté en tant qu'Expert Advisor et la bibliothèque standard MQL5 sera utilisée.
Le code sera contenu dans une classe CRssReader qui sera un descendant de la classe CAppDialog. La classe CAppDialog, donnée dans le fichier d'inclusion Dialog.mqh, permettra la mise en œuvre du dialogue d'application, qui fournit des fonctionnalités pour une barre de titre, et des contrôles d'application pour minimiser, maximiser et fermer. Ce sera la base de l'interface utilisateur, sur laquelle d'autres sections seront ajoutées. Pour les sections à ajouter, à savoir : la zone de titre, la zone de texte, la zone d'affichage de liste et les boutons. Chacun sera un contrôle. Les boutons seront implémentés en tant que contrôle de bouton décrit dans le fichier d'inclusion Button.mqh.
Le contrôle d'affichage de liste défini dans le fichier d'inclusion ListViewArea.mqh sera utilisé pour construire la section de zone d'affichage de liste du lecteur RSS.
Le champ de saisie suffira bien évidemment à construire la zone de saisie, ce champ est défini dans le fichier Edit.mqh.
Les sections de zone de titre et de zone de texte constituent un défi unique en ce qui concerne leur mise en œuvre. Le problème est que les deux doivent avoir un support pour le texte qui peut être affiché sur plusieurs lignes. Les objets texte dans MQL5 ne reconnaissent pas les nouveaux caractères de saut de ligne. Un autre problème est que seul un nombre limité de caractères de chaîne peut être affiché sur une ligne d'un objet texte. Cela signifie que si vous créez un objet texte avec une description suffisamment longue, l'objet sera affiché avec le texte coupé, seul un certain nombre de caractères sera affiché. Par essais et erreurs, j'ai découvert que la limite de caractères est de 63, espaces et signes de ponctuation compris.
Afin de surmonter ces problèmes, j'ai décidé d'implémenter les deux sections en tant que contrôles de vue de liste modifiés. Pour la section de zone de titre, le contrôle de vue de liste modifié ne pourra pas faire défiler et aura un nombre fixe d'éléments de liste (2). Chaque élément de liste ne sera pas cliquable ou sélectionnable et l'apparence physique du contrôle sera telle qu'il ne ressemblera pas à une liste. Ces 2 éléments de liste représenteront deux lignes de texte. Si le texte est trop long pour tenir sur une ligne, il sera divisé en conséquence et affiché sur 2 lignes de texte. Le contrôle de la section de zone de titre sera défini dans le fichier TitleArea.mqh.
Pour la section de zone de texte, une approche similaire sera appliquée, mais cette fois le nombre d'éléments de liste sera dynamique et le contrôle de vue de liste modifié défilera verticalement. Ce contrôle sera donné dans le fichier TextArea.mqh.
Les bibliothèques mentionnées jusqu'à présent s'occupent de l'interface utilisateur. Il y a encore une bibliothèque plus importante pour cette application qui doit être discutée. Il s'agit de la bibliothèque utilisée pour analyser les documents XML.
2.3. L'analyseur XML simple
Un document RSS étant un fichier XML, la bibliothèque EasyXML - XML Parser développée par liquinaut et présente dans la Base de Code est appliquée.
La bibliothèque est assez étendue et contient presque toutes les fonctionnalités nécessaires à notre lecteur RSS. J'ai apporté quelques modifications à la bibliothèque d'origine pour ajouter des fonctionnalités supplémentaires que je jugeais nécessaires.
Il s'agissait d'ajouts mineurs. Le premier était l'ajout d'une méthode supplémentaire appelée loadXmlFromUrlWebReq(). Cette méthode fournit simplement une alternative à l'utilisation de loadXmlFromUrl(), qui repose sur la bibliothèque WinInet pour le traitement des requêtes Web, loadXmlFromUrlWebReq() utilise la fonction WebRequest() intégrée pour permettre les téléchargements depuis Internet.
//+------------------------------------------------------------------+ //| load xml by given url using MQL5 webrequest function | //+------------------------------------------------------------------+ bool CEasyXml::loadXmlFromUrlWebReq(string pUrl) { //--- string cookie=NULL,headers; char post[],result[]; int res; //--- string _url=pUrl; string sStream; res=WebRequest("GET",_url,cookie,NULL,5000,post,0,result,headers); //--- check error if(res==-1) { Err=EASYXML_ERR_WEBREQUEST_URL; return(Error()); } //---success downloading file sStream=CharArrayToString(result,0,-1,CP_UTF8); //---set up cach file if(blSaveToCache) { bool bResult=writeStreamToCacheFile(sStream); if(!bResult) Error(-1,false); } //--- return(loadXmlFromString(sStream)); }
Le deuxième ajout était la méthode GetErrorMsg(), cela permet de récupérer le message d'erreur généré par l'analyseur chaque fois qu'une erreur se produit.
string GetErrorMsg(void){ return(ErrMsg);}
Le dernier ajout a été fait pour corriger un défaut assez sérieux que j'ai trouvé lors du test de l'analyseur easyxml.
J'ai découvert que la bibliothèque n'était pas capable de reconnaître les déclarations de feuille de style XML. Le code confond une déclaration de feuille de style avec un attribut. Cela a bloqué le programme dans une boucle infinie, car le code recherchait en permanence la valeur d'attribut correspondante, qui n'a jamais existé.
Cela a été facilement rectifié, avec une petite modification de la méthode skipProlog().
//+------------------------------------------------------------------+ //| skip xml prolog | //+------------------------------------------------------------------+ bool CEasyXml::skipProlog(string &pText,int &pPos) { //--- skip xml declaration if(StringCompare(EASYXML_PROLOG_OPEN,StringSubstr(pText,pPos,StringLen(EASYXML_PROLOG_OPEN)))==0) { int iClose=StringFind(pText,EASYXML_PROLOG_CLOSE,pPos+StringLen(EASYXML_PROLOG_OPEN)); if(blDebug) Print("### Prolog ### ",StringSubstr(pText,pPos,(iClose-pPos)+StringLen(EASYXML_PROLOG_CLOSE))); if(iClose>0) { pPos=iClose+StringLen(EASYXML_PROLOG_CLOSE); } else { Err=EASYXML_INVALID_PROLOG; return(false); } } //--- skip stylesheet declarations if(StringCompare(EASYXML_STYLESHEET_OPEN,StringSubstr(pText,pPos,StringLen(EASYXML_STYLESHEET_OPEN)))==0) { int iClose=StringFind(pText,EASYXML_STYLESHEET_CLOSE,pPos+StringLen(EASYXML_STYLESHEET_OPEN)); if(blDebug) Print("### Prolog ### ",StringSubstr(pText,pPos,(iClose-pPos)+StringLen(EASYXML_STYLESHEET_CLOSE))); if(iClose>0) { pPos=iClose+StringLen(EASYXML_STYLESHEET_CLOSE); } else { Err=EASYXML_INVALID_PROLOG; return(false); } } //--- skip comments if(!skipWhitespaceAndComments(pText,pPos,"")) return(false); //--- skip doctype if(StringCompare(EASYXML_DOCTYPE_OPEN,StringSubstr(pText,pPos,StringLen(EASYXML_DOCTYPE_OPEN)))==0) { int iClose=StringFind(pText,EASYXML_DOCTYPE_CLOSE,pPos+StringLen(EASYXML_DOCTYPE_OPEN)); if(blDebug) Print("### DOCTYPE ### ",StringSubstr(pText,pPos,(iClose-pPos)+StringLen(EASYXML_DOCTYPE_CLOSE))); if(iClose>0) { pPos=iClose+StringLen(EASYXML_DOCTYPE_CLOSE); } else { Err=EASYXML_INVALID_DOCTYPE; return(false); } } //--- skip comments if(!skipWhitespaceAndComments(pText,pPos,"")) return(false); return(true); }
Malgré ce problème, n'enlevez rien à liquinaut, car easyxml.mqh est un excellent outil.
2.4. Code d'Expert Advisor
Maintenant que toutes les bibliothèques nécessaires à l'application ont été décrites, il est temps de rassembler ces composants pour définir la classe CRssReader.
Veuillez noter que le code Lecteur RSS d’Expert Advisor commencera par la définition de la classe CRssReader.
//+------------------------------------------------------------------+ //| RssReader.mq5 | //| Ufranco | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Ufranco" #property link "https://www.mql5.com" #property version "1.00" #include <Controls\Dialog.mqh> #include <Controls\Edit.mqh> #include <Controls\Button.mqh> #include <TitleArea.mqh> #include <TextArea.mqh> #include <ListViewArea.mqh> #include <easyxml.mqh> //+------------------------------------------------------------------+ //| defines | //+------------------------------------------------------------------+ //--- indents and gaps #define INDENT_LEFT (11) // indent from left (with allowance for border width) #define INDENT_TOP (11) // indent from top (with allowance for border width) #define INDENT_RIGHT (11) // indent from right (with allowance for border width) #define INDENT_BOTTOM (11) // indent from bottom (with allowance for border width) #define CONTROLS_GAP_X (5) // gap by X coordinate #define CONTROLS_GAP_Y (5) // gap by Y coordinate #define EDIT_HEIGHT (20) // size by Y coordinate #define BUTTON_WIDTH (150) // size by X coordinate #define BUTTON_HEIGHT (20) // size by Y coordinate #define TEXTAREA_HEIGHT (131) // size by Y coordinate #define LIST_HEIGHT (93) // size by Y coordinate
Nous commençons par inclure les fichiers nécessaires. Les directives définir sont utilisées pour définir les paramètres physiques des contrôles en pixels.
INDENT_LEFT, INDENT_RIGHT, INDENT_TOP et INDENT_DOWN définissent la distance entre un contrôle et le bord du dialogue de l'application.
- CONTROLS_GAP_Y est la distance verticale entre deux contrôles ;
- EDIT_HEIGHT définit la hauteur du champ Editer qui constitue la zone de saisie ;
- BUTTON_WIDTH et BUTTON_HEIGHT définissent la largeur et la hauteur de tous les contrôles de bouton ;
- TEXTAREA_HEIGHT est la hauteur de la section de la zone de texte ;
- LIST_HEIGHT définit la hauteur du contrôle d'affichage de liste.
Après cette définition, nous définissons la classe CRssReader.
//+------------------------------------------------------------------+ //| Class CRssReader | //| Usage: main class for the RSS application | //+------------------------------------------------------------------+ class CRssReader : public CAppDialog { private: int m_shift; // index of first item tag string m_rssurl; // copy of web address of last feed string m_textareaoutput[]; // array of strings prepared for output to the text area panel string m_titleareaoutput[]; // array of strings prepared for output to title area panel CButton m_button1; // the button object CButton m_button2; // the button object CEdit m_edit; // input panel CTitleArea m_titleview; // the display field object CListViewArea m_listview; // the list object CTextArea m_textview; // text area object CEasyXml m_xmldocument; // xml document object CEasyXmlNode *RssNode; // root node object CEasyXmlNode *ChannelNode; // channel node object CEasyXmlNode *ChannelChildNodes[]; // array of channel child node objects
Comme mentionné précédemment, CRssReader hérite de la classe CAppDialog.
La classe a plusieurs propriétés privées indiquées ci-dessous :
- m_shift - cette variable de type entier stocke l'index du premier nœud d'élément, dans le tableau ChannelChildnodes ;
- m_rssurl - est une valeur de chaîne qui conserve une copie de la dernière URL saisie ;
- m_textareaoutput[] -est un tableau de chaînes, chaque élément correspond à une ligne de texte avec un certain nombre de caractères ;
- m_titleareaoutput[] - il s'agit également d'un tableau de chaînes ayant le même objectif que le tableau de chaînes précédent ;
- m_button1 et m_button2 sont des objets de type CButton;
- m_listview est un objet représentant un contrôle de liste ;
- propriété m_edit un objet CEdit et implémente la zone de saisie ;
- m_titleview est un objet pour le champ d'affichage de la zone de titre ;
- m_textview - l'objet de la section de zone de texte ;
- m_xmldocument est un objet document XML ;
- RssNode est l'objet nœud racine ;
- ChannelNode est l'objet du nœud de canal ;
- Le tableau ChannelChildNodes est un ensemble de pointeurs vers les descendants directs de la balise Chaine.
Notre classe n'aura que deux méthodes exposées publiquement.
public: CRssReader(void); ~CRssReader(void); //--- create virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); //--- chart event handler virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
La première méthode Create() définit la taille et la position initiale du dialogue de l'application.
Il initialise également tous les contrôles de l'application de lecteur RSS (rappelez-vous que notre classe hérite de la classe CAppDialog et donc les méthodes publiques de la classe parent et ses ancêtres peuvent être appelées par des instances de CRssReader).
//+------------------------------------------------------------------+ //| Create | //+------------------------------------------------------------------+ bool CRssReader::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); //--- create dependent controls if(!CreateEdit()) return(false); if(!CreateButton1()) return(false); if(!CreateButton2()) return(false); if(!CreateTitleView()) return(false); if(!CreateListView()) return(false); if(!CreateTextView()) return(false); //--- succeed return(true); }
La seconde est la méthode OnEvent(), la fonction permet l'interactivité en attribuant des événements spécifiques à un contrôle correspondant et à une fonction de gestionnaire.
//+------------------------------------------------------------------+ //| Event Handling | //+------------------------------------------------------------------+ EVENT_MAP_BEGIN(CRssReader) ON_EVENT(ON_CHANGE,m_listview,OnChangeListView) ON_EVENT(ON_END_EDIT,m_edit,OnObjectEdit) ON_EVENT(ON_CLICK,m_button1,OnClickButton1) ON_EVENT(ON_CLICK,m_button2,OnClickButton2) EVENT_MAP_END(CAppDialog)
2.5. Méthodes d'initialisation des champs
Les méthodes protégées CreateEdit(), CreateButton1() ,CreateButton2(), CreateTitleView(), CreateListView() et CreateTextView() sont appelées par la fonction principale Create() pour initialiser un contrôle correspondant.
protected: // --- creating controls bool CreateEdit(void); bool CreateButton1(void); bool CreateButton2(void); bool CreateTitleView(void); bool CreateListView(void); bool CreateTextView(void);
C'est dans chacune de ces fonctions que la taille, la position et les propriétés (c'est-à-dire la police, la taille de la police, la couleur, la couleur de bordure, le type de bordure) d'un contrôle sont définies.
//+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateEdit(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+EDIT_HEIGHT; //--- create if(!m_edit.Create(m_chart_id,m_name+"Edit",m_subwin,x1,y1,x2,y2)) return(false); if(!m_edit.Text("Please enter the web address of an Rss feed")) return(false); if(!m_edit.ReadOnly(false)) return(false); if(!Add(m_edit)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create button 1 | //+------------------------------------------------------------------+ bool CRssReader::CreateButton1(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y); int x2=x1+BUTTON_WIDTH; int y2=y1+BUTTON_HEIGHT; //--- create if(!m_button1.Create(m_chart_id,m_name+"Button1",m_subwin,x1,y1,x2,y2)) return(false); if(!m_button1.Text("Reset")) return(false); if(!m_button1.Font("Comic Sans MS")) return(false); if(!m_button1.FontSize(8)) return(false); if(!m_button1.Color(clrWhite)) return(false); if(!m_button1.ColorBackground(clrBlack)) return(false); if(!m_button1.ColorBorder(clrBlack)) return(false); if(!m_button1.Pressed(true)) return(false); if(!Add(m_button1)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create button 2 | //+------------------------------------------------------------------+ bool CRssReader::CreateButton2(void) { //--- coordinates int x1=(ClientAreaWidth()-INDENT_RIGHT)-BUTTON_WIDTH; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y); int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+BUTTON_HEIGHT; //--- create if(!m_button2.Create(m_chart_id,m_name+"Button2",m_subwin,x1,y1,x2,y2)) return(false); if(!m_button2.Text("Update current feed")) return(false); if(!m_button2.Font("Comic Sans MS")) return(false); if(!m_button2.FontSize(8)) return(false); if(!m_button2.Color(clrWhite)) return(false); if(!m_button2.ColorBackground(clrBlack)) return(false); if(!m_button2.ColorBorder(clrBlack)) return(false); if(!m_button2.Pressed(true)) return(false); if(!Add(m_button2)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateTitleView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y)+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+(EDIT_HEIGHT*2); m_titleview.Current(); //--- create if(!m_titleview.Create(m_chart_id,m_name+"TitleView",m_subwin,x1,y1,x2,y2)) { Print("error creating title view"); return(false); } else { for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } } if(!Add(m_titleview)) { Print("error adding title view"); return(false); } //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the "ListView" element | //+------------------------------------------------------------------+ bool CRssReader::CreateListView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+TEXTAREA_HEIGHT+CONTROLS_GAP_Y+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+LIST_HEIGHT; //--- create if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2)) return(false); if(!Add(m_listview)) return(false); //--- fill out with strings for(int i=0;i<20;i++) if(!m_listview.AddItem(" ")) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateTextView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+TEXTAREA_HEIGHT; m_textview.Current(); //--- create if(!m_textview.Create(m_chart_id,m_name+"TextArea",m_subwin,x1,y1,x2,y2)) { Print("error creating text area view"); return(false); } else { for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); ChartRedraw(); } if(!Add(m_textview)) { Print("error adding text area view"); return(false); } //----success return(true); }
2.6. Méthodes de traitement des documents RSS
// --- rss document processing bool LoadDocument(string filename); int ItemNodesTotal(void); void FreeDocumentTree(void);
2.6.1. LoadDocument()
Cette fonction a plusieurs rôles importants à jouer. Le principal étant le traitement des requêtes web. Le loadXmlFromUrlWebReq() est appelé pour télécharger le fichier RSS.
Si cela est terminé avec succès, la fonction passe à sa deuxième tâche d'initialiser les pointeurs RssNode, ChannelNode et également de remplir le tableau ChannelChildnodes. C'est ici que les propriétés m_rssurl et m_shift sont définies. Une fois tout cela fait, la fonction retourne vraie.
Si le fichier RSS ne peut pas être téléchargé, la zone de titre, la zone d'affichage de liste et les sections de zone de texte sont effacées de tout texte et un message d'état s'affiche dans la barre de titre. Ceci est suivi de la sortie d'un message d'erreur dans la section de zone de texte. Ensuite, la fonction renvoie faux.
//+------------------------------------------------------------------+ //| load document | //+------------------------------------------------------------------+ bool CRssReader::LoadDocument(string filename) { if(!m_xmldocument.loadXmlFromUrlWebReq(filename)) { m_textview.ItemsClear(); m_listview.ItemsClear(); m_titleview.ItemsClear(); CDialog::Caption("Failed to load Feed"); if(!m_textview.AddItem(m_xmldocument.GetErrorMsg())) Print("error displaying error message"); return(false); } else { m_rssurl=filename; RssNode=m_xmldocument.getDocumentRoot(); ChannelNode=RssNode.FirstChild(); if(CheckPointer(RssNode)==POINTER_INVALID || CheckPointer(ChannelNode)==POINTER_INVALID) return(false); } ArrayResize(ChannelChildNodes,ChannelNode.Children().Total()); for(int i=0;i<ChannelNode.Children().Total();i++) { ChannelChildNodes[i]=ChannelNode.Children().At(i); } m_shift=ChannelNode.Children().Total()-ItemNodesTotal(); return(true); }
2.6.2. ItemNodesTotal()
Cette fonction d'assistance est utilisée dans la méthode LoadDocument(). Il renvoie une valeur entière qui correspond au nombre de nœuds d'élément descendants de la balise de canal.
S'il n'y a pas de nœuds d'élément, le document sera un document RSS non valide et la fonction renverra 0.
//+------------------------------------------------------------------+ //| function counts the number of item tags in document | //+------------------------------------------------------------------+ int CRssReader::ItemNodesTotal(void) { int t=0; for(int i=0;i<ChannelNode.Children().Total();i++) { if(ChannelChildNodes[i].getName()=="item") { t++; } else continue; } return(t); }
2.6.3. FreeDocumentTree()
Cette fonction réinitialise tous les pointeurs CEasyXmlNode.
Tout d'abord, les éléments du tableau ChannelChildnodes sont supprimés en appelant la méthode Shutdown() de la classe CArrayObj. Le tableau est ensuite libéré avec un seul appel de ArrayFree().
Ensuite, le pointeur vers le nœud de canal est supprimé et l'arborescence de documents de l'analyseur easyxml est effacée. Ces actions font que les pointeurs RssNode et ChannelNode deviennent de mauvais pointeurs, c'est pourquoi ils reçoivent tous deux la valeur NULL.
//+------------------------------------------------------------------+ //| free document tree and reset pointer values | //+------------------------------------------------------------------+ void CRssReader::FreeDocumentTree(void) { ChannelNode.Children().Shutdown(); ArrayFree(ChannelChildNodes); RssNode.Children().Shutdown(); m_xmldocument.Clear(); m_shift=0; RssNode=NULL; ChannelNode=NULL; }
2.7. Méthodes d'extraction d'informations de l'arborescence du document
Ces fonctions permettent d'obtenir du texte à partir d'un document RSS.
//--- getters string getChannelTitle(void); string getTitle(CEasyXmlNode *Node); string getDescription(CEasyXmlNode *Node); string getDate(CEasyXmlNode *Node);
2.7.1. getChannelTitle()
Cette fonction récupère le titre de canal actuel du document RSS.
Il commence par vérifier la validité du pointeur de nœud de canal. Si le pointeur est valide, il parcourt tous les descendants directs du nœud de canal à la recherche de la balise titre.
La boucle for utilise la propriété m_shift pour limiter le nombre de descendants de nœuds de canal à rechercher. Si la fonction échoue, elle renvoie NULL.
//+------------------------------------------------------------------+ //| get channel title | //+------------------------------------------------------------------+ string CRssReader::getChannelTitle(void) { string ret=NULL; if(!CheckPointer(ChannelNode)==POINTER_INVALID) { for(int i=0;i<m_shift;i++) { if(ChannelChildNodes[i].getName()=="title") { ret=ChannelChildNodes[i].getValue(); break; } else continue; } } //---return value return(ret); }
2.7.2. getTitle()
La fonction prend en entrée un pointeur vers une balise d'élément, parcourt les descendants de cette balise à la recherche d'une balise de titre et renvoie sa valeur.
Les fonctions getDescription() et getDate() suivent le même format et fonctionnent de manière similaire. Un appel réussi de la fonction renvoie une valeur de chaîne, sinon NULL est renvoyé en sortie.
//+------------------------------------------------------------------+ //| display title | //+------------------------------------------------------------------+ string CRssReader::getTitle(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="title") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| display description | //+------------------------------------------------------------------+ string CRssReader::getDescription(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="description") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| display date | //+------------------------------------------------------------------+ string CRssReader::getDate(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="pubDate") { n=subNode.getValue(); break; } else continue; } return(n); }
2.8. Méthodes de mise en forme du texte
Ces fonctions servent à préparer le texte pour la sortie en tant qu'objets texte afin de surmonter certaines des limitations des objets texte.
//--- text formating bool FormatString(string v,string &array[],int n); string removeTags(string _string); string removeSpecialCharacters(string s_tring); int tagPosition(string _string,int w);
2.8.1. FormatString()
Il s'agit de la fonction principale qui prépare le texte extrait d'un document RSS pour la sortie vers l'application.
Fondamentalement, il prend une valeur d'entrée de chaîne et divise le texte en lignes de "n" caractères. "n" étant une valeur entière du nombre de caractères dans une seule ligne de texte. Après chaque «n» caractères dans le texte, le code recherche un endroit approprié pour insérer un nouveau caractère de saut de ligne. Ensuite, toute la valeur de la chaîne est traitée et les nouveaux caractères de saut de ligne sont insérés dans le texte d'origine.
La fonction StringSplit() est utilisée pour créer un tableau de chaînes, chacune ne dépassant pas «n» caractères. La fonction renvoie une valeur booléenne ainsi qu'un tableau de valeurs de chaîne prêtes à être sorties.
//+------------------------------------------------------------------+ //| format string for output to text area panel | //+------------------------------------------------------------------+ bool CRssReader::FormatString(string v,string &array[],int n) { ushort ch[],space,fullstop,comma,semicolon,newlinefeed; string _s,_k; space=StringGetCharacter(" ",0); fullstop=StringGetCharacter(".",0); comma=StringGetCharacter(",",0); semicolon=StringGetCharacter(";",0); newlinefeed=StringGetCharacter("\n",0); _k=removeTags(v); _s=removeSpecialCharacters(_k); int p=StringLen(_s); ArrayResize(ch,p+1); int d=StringToShortArray(_s,ch,0,-1); for(int i=1;i<d;i++) { int t=i%n; if(!t== 0)continue; else { if(ch[(i/n)*n]==fullstop || ch[(i/n)*n]==semicolon || ch[(i/n)*n]==comma) { ArrayFill(ch,((i/n)*n)+1,1,newlinefeed); } else { for(int k=i;k>=0;k--) { if(ch[k]==space) { ArrayFill(ch,k,1,newlinefeed); break; } else continue; } } } } _s=ShortArrayToString(ch,0,-1); int s=StringSplit(_s,newlinefeed,array); if(!s>0) {return(false);} // success return(true); }
2.8.2. removeTags()
Cette fonction est devenue une nécessité après avoir remarqué qu'un bon nombre de documents RSS contiennent des balises HTML dans les nœuds XML.
Certains documents RSS sont publiés de cette manière, car de nombreuses applications d'agrégation RSS fonctionnent dans le navigateur.
La fonction prend une valeur de chaîne et recherche les balises dans le texte. Si des balises sont trouvées, la fonction parcourt chaque caractère du texte et stocke la position de chaque caractère de balise d'ouverture et de fermeture dans le tableau à 2 dimensions a[][]. Ce tableau est utilisé pour extraire le texte entre les balises et la chaîne extraite est renvoyée. Si aucune balise n'est trouvée, la chaîne d'entrée est renvoyée telle quelle.
//+------------------------------------------------------------------+ //| remove tags | //+------------------------------------------------------------------+ string CRssReader::removeTags(string _string) { string now=NULL; if(StringFind(_string,"<",0)>-1) { int v=0,a[][2]; ArrayResize(a,2024); for(int i=0;i<StringLen(_string);i++) { int t=tagPosition(_string,i); if(t>0) { v++; a[v-1][0]=i; a[v-1][1]=t; } else continue; } ArrayResize(a,v); for(int i=0;i<v-1;i++) { now+=StringSubstr(_string,(a[i][1]+1),(a[i+1][0]-(a[i][1]+1))); } } else { now=_string; } return(now); }
Un exemple partiel d'un tel document est présenté ci-dessous.
<item> <title>GIGABYTE X99-Gaming G1 WIFI Motherboard Review</title> <author>Ian Cutress</author> <description><![CDATA[ <p>The gaming motherboard range from a manufacturer is one with a lot of focus in terms of design and function due to the increase in gaming related PC sales. On the Haswell-E side of gaming, GIGABYTE is putting forward the X99-Gaming G1 WIFI at the top of its stack, and this is what we are reviewing today. </p> <p align="center"><a href='http://dynamic1.anandtech.com/www/delivery/ck.php?n=a1f2f01f&cb=582254849' target='_blank'><img src='http://dynamic1.anandtech.com/www/delivery/avw.php?zoneid=24&cb=582254849&n=a1f2f01f' border='0' alt='' /></a><img src="http://toptenreviews.122.2o7.net/b/ss/tmn-test/1/H.27.3--NS/0" height="1" width="1" border="0" alt="" /></p>]]></description> <link>http://www.anandtech.com/show/8788/gigabyte-x99-gaming-g1-wifi-motherboard-review</link> <pubDate>Thu, 18 Dec 2014 10:00:00 EDT</pubDate> <guid isPermaLink="false">tag:www.anandtech.com,8788:news</guid> <category><![CDATA[ Motherboards]]></category> </item>
2.8.3. removeSpecialCharacters()
Cette fonction remplace simplement certaines constantes de chaîne par le caractère correct.
Par exemple, le caractère esperluette dans certains documents XML peut être représenté par «&». Cette fonction utilise la fonction StringReplace() intégrée pour remplacer ces types d'occurrences.
//+------------------------------------------------------------------+ //| remove special characters | //+------------------------------------------------------------------+ string CRssReader::removeSpecialCharacters(string s_tring) { string n=s_tring; StringReplace(n,"&","&"); StringReplace(n,"'","'"); StringReplace(n," "," "); StringReplace(n,"“","\'"); StringReplace(n,"”","\'"); StringReplace(n,""","\""); StringReplace(n,"–","-"); StringReplace(n,"’","'"); StringReplace(n,">",""); return(n); }
2.8.4. tagPosition()
Il s'agit d'une fonction d'assistance appelée dans la fonction removeTags(). Il prend en entrée une chaîne et une valeur entière.
La valeur entière d'entrée représente la position d'un caractère dans la chaîne, à partir de laquelle la fonction commencera à rechercher un caractère de balise ouvrante, c'est-à-dire «<». Si une balise ouvrante est trouvée, la fonction commence à rechercher une balise fermante et renvoie en sortie la position de la balise de fermeture correspondante «>». Si aucune balise n'est trouvée, la fonction renvoie -1.
//+------------------------------------------------------------------+ //| tag positions | //+------------------------------------------------------------------+ int CRssReader::tagPosition(string _string,int w) { int iClose=-1; if(StringCompare("<",StringSubstr(_string,w,StringLen("<")))==0) { iClose=StringFind(_string,">",w+StringLen("<")); } return(iClose); }
2.9. Méthodes de gestion des événements de contrôles indépendants
Ces fonctions gèrent les événements capturés d'un contrôle spécifique.
//--- handlers of the dependent controls events void OnChangeListView(void); void OnObjectEdit(void); void OnClickButton1(void); void OnClickButton2(void); };
2.9.1. OnChangeListView()
Il s'agit d'une fonction de gestionnaire d'événements qui est appelée à chaque fois que l'on clique sur l'un des éléments de liste dans la section de zone d'affichage de liste de l'application.
La fonction est chargée de permettre la visualisation du résumé de description de certains contenus référencés dans le document RSS.
La fonction efface les sections de zone de texte et de zone de titre de tout texte, récupère les nouvelles données de l'arborescence du document et les prépare pour la sortie. Tout cela ne se produit que si le tableau ChannelChildnodes n'est pas vide.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CRssReader::OnChangeListView(void) { int a=0,k=0,l=0; a=m_listview.Current()+m_shift; if(ArraySize(ChannelChildNodes)>a) { if(m_titleview.ItemsClear()) { if(!FormatString(getTitle(ChannelChildNodes[a]),m_titleareaoutput,55)) { return; } else if(ArraySize(m_titleareaoutput)>0) { for(l=0;l<ArraySize(m_titleareaoutput);l++) { m_titleview.AddItem(removeSpecialCharacters(m_titleareaoutput[l])); } } } if(m_textview.ItemsClear()) { if(!FormatString(getDescription(ChannelChildNodes[a]),m_textareaoutput,35)) return; else if(ArraySize(m_textareaoutput)>0) { for(k=0;k<ArraySize(m_textareaoutput);k++) { m_textview.AddItem(m_textareaoutput[k]); } m_textview.AddItem(" "); m_textview.AddItem("Date|"+getDate(ChannelChildNodes[a])); } else return; } } }
2.9.2. OnObjectEdit()
La fonction de gestion est appelée chaque fois qu'un utilisateur termine de saisir du texte dans la zone de saisie.
La fonction appelle la méthode LoadDocument(). Si le téléchargement réussit, le texte est effacé de l'ensemble de l'application. Ensuite, la légende est modifiée et le nouveau contenu est affiché dans la section de zone d'affichage de liste.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CRssReader::OnObjectEdit(void) { string f=m_edit.Text(); if(StringLen(f)>0) { if(ArraySize(ChannelChildNodes)<1) { CDialog::Caption("Loading..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } else { FreeDocumentTree(); CDialog::Caption("Loading new RSS Feed..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } } else return; }
2.9.3. OnClickButton1/2()
Ces gestionnaires sont appelés chaque fois qu'un utilisateur clique sur les boutons de réinitialisation ou de recherche de mises à jour de flux.
Cliquer sur le bouton de réinitialisation actualise la boîte de dialogue de l'application à l'état dans lequel elle se trouvait lors du premier lancement de l'Expert advisor.
Cliquer sur le bouton «vérifier la mise à jour du flux» provoque un rappel de la méthode charger LoadDocument() et les données du flux RSS seront téléchargées, actualisant la section de la zone d'affichage de la liste.
//+------------------------------------------------------------------+ //| Event handler refresh the app dialogue | //+------------------------------------------------------------------+ void CRssReader::OnClickButton1(void) { if(ArraySize(ChannelChildNodes)<1) { if(!m_edit.Text("Enter the web address of an Rss feed")) Print("error changing edit text"); if(!CDialog::Caption("RSSReader")) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("error adding to listview"); } m_listview.VScrolled(true); for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } return; } } else { FreeDocumentTree(); if(!m_edit.Text("Enter the web address of an Rss feed")) Print("error changing edit text"); if(!CDialog::Caption("RSSReader")) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("error adding to listview"); } m_listview.VScrolled(true); for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } return; } } } //+------------------------------------------------------------------+ //| Event handler update current feed | //+------------------------------------------------------------------+ void CRssReader::OnClickButton2(void) { string f=m_rssurl; if(ArraySize(ChannelChildNodes)<1) return; else { FreeDocumentTree(); CDialog::Caption("Checking for RSS Feed update..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } }
Cela conclut la définition de la classe CRssReader.
2.10. L'implémentation de la classe CRssReader
//+------------------------------------------------------------------+ //| Class CRssReader | //| Usage: main class for the RSS application | //+------------------------------------------------------------------+ class CRssReader : public CAppDialog { private: int m_shift; // index of first item tag string m_rssurl; // copy of web address of last feed string m_textareaoutput[]; // array of strings prepared for output to the text area panel string m_titleareaoutput[]; // array of strings prepared for output to title area panel CButton m_button1; // the button object CButton m_button2; // the button object CEdit m_edit; // input panel CTitleArea m_titleview; // the display field object CListViewArea m_listview; // the list object CTextArea m_textview; // text area object CEasyXml m_xmldocument; // xml document object CEasyXmlNode *RssNode; // root node object CEasyXmlNode *ChannelNode; // channel node object CEasyXmlNode *ChannelChildNodes[]; // array of channel child node objects public: CRssReader(void); ~CRssReader(void); //--- create virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); //--- chart event handler virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); protected: // --- creating controls bool CreateEdit(void); bool CreateButton1(void); bool CreateButton2(void); bool CreateTitleView(void); bool CreateListView(void); bool CreateTextView(void); // --- rss document processing bool LoadDocument(string filename); int ItemNodesTotal(void); void FreeDocumentTree(void); //--- getters string getChannelTitle(void); string getTitle(CEasyXmlNode *Node); string getDescription(CEasyXmlNode *Node); string getDate(CEasyXmlNode *Node); //--- text formating bool FormatString(string v,string &array[],int n); string removeTags(string _string); string removeSpecialCharacters(string s_tring); int tagPosition(string _string,int w); //--- handlers of the dependent controls events void OnChangeListView(void); void OnObjectEdit(void); void OnClickButton1(void); void OnClickButton2(void); }; //+------------------------------------------------------------------+ //| Event Handling | //+------------------------------------------------------------------+ EVENT_MAP_BEGIN(CRssReader) ON_EVENT(ON_CHANGE,m_listview,OnChangeListView) ON_EVENT(ON_END_EDIT,m_edit,OnObjectEdit) ON_EVENT(ON_CLICK,m_button1,OnClickButton1) ON_EVENT(ON_CLICK,m_button2,OnClickButton2) EVENT_MAP_END(CAppDialog) //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRssReader::CRssReader(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CRssReader::~CRssReader(void) { } //+------------------------------------------------------------------+ //| Create | //+------------------------------------------------------------------+ bool CRssReader::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); //--- create dependent controls if(!CreateEdit()) return(false); if(!CreateButton1()) return(false); if(!CreateButton2()) return(false); if(!CreateTitleView()) return(false); if(!CreateListView()) return(false); if(!CreateTextView()) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateEdit(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+EDIT_HEIGHT; //--- create if(!m_edit.Create(m_chart_id,m_name+"Edit",m_subwin,x1,y1,x2,y2)) return(false); if(!m_edit.Text("Please enter the web address of an Rss feed")) return(false); if(!m_edit.ReadOnly(false)) return(false); if(!Add(m_edit)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create button 1 | //+------------------------------------------------------------------+ bool CRssReader::CreateButton1(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y); int x2=x1+BUTTON_WIDTH; int y2=y1+BUTTON_HEIGHT; //--- create if(!m_button1.Create(m_chart_id,m_name+"Button1",m_subwin,x1,y1,x2,y2)) return(false); if(!m_button1.Text("Reset")) return(false); if(!m_button1.Font("Comic Sans MS")) return(false); if(!m_button1.FontSize(8)) return(false); if(!m_button1.Color(clrWhite)) return(false); if(!m_button1.ColorBackground(clrBlack)) return(false); if(!m_button1.ColorBorder(clrBlack)) return(false); if(!m_button1.Pressed(true)) return(false); if(!Add(m_button1)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create button 2 | //+------------------------------------------------------------------+ bool CRssReader::CreateButton2(void) { //--- coordinates int x1=(ClientAreaWidth()-INDENT_RIGHT)-BUTTON_WIDTH; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y); int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+BUTTON_HEIGHT; //--- create if(!m_button2.Create(m_chart_id,m_name+"Button2",m_subwin,x1,y1,x2,y2)) return(false); if(!m_button2.Text("Update current feed")) return(false); if(!m_button2.Font("Comic Sans MS")) return(false); if(!m_button2.FontSize(8)) return(false); if(!m_button2.Color(clrWhite)) return(false); if(!m_button2.ColorBackground(clrBlack)) return(false); if(!m_button2.ColorBorder(clrBlack)) return(false); if(!m_button2.Pressed(true)) return(false); if(!Add(m_button2)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateTitleView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y)+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+(EDIT_HEIGHT*2); m_titleview.Current(); //--- create if(!m_titleview.Create(m_chart_id,m_name+"TitleView",m_subwin,x1,y1,x2,y2)) { Print("error creating title view"); return(false); } else { for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } } if(!Add(m_titleview)) { Print("error adding title view"); return(false); } //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the "ListView" element | //+------------------------------------------------------------------+ bool CRssReader::CreateListView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+TEXTAREA_HEIGHT+CONTROLS_GAP_Y+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+LIST_HEIGHT; //--- create if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2)) return(false); if(!Add(m_listview)) return(false); //--- fill out with strings for(int i=0;i<20;i++) if(!m_listview.AddItem(" ")) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateTextView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+TEXTAREA_HEIGHT; m_textview.Current(); //--- create if(!m_textview.Create(m_chart_id,m_name+"TextArea",m_subwin,x1,y1,x2,y2)) { Print("error creating text area view"); return(false); } else { for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); ChartRedraw(); } if(!Add(m_textview)) { Print("error adding text area view"); return(false); } //----success return(true); } //+------------------------------------------------------------------+ //| load document | //+------------------------------------------------------------------+ bool CRssReader::LoadDocument(string filename) { if(!m_xmldocument.loadXmlFromUrlWebReq(filename)) { m_textview.ItemsClear(); m_listview.ItemsClear(); m_titleview.ItemsClear(); CDialog::Caption("Failed to load Feed"); if(!m_textview.AddItem(m_xmldocument.GetErrorMsg())) Print("error displaying error message"); return(false); } else { m_rssurl=filename; RssNode=m_xmldocument.getDocumentRoot(); ChannelNode=RssNode.FirstChild(); if(CheckPointer(RssNode)==POINTER_INVALID || CheckPointer(ChannelNode)==POINTER_INVALID) return(false); } ArrayResize(ChannelChildNodes,ChannelNode.Children().Total()); for(int i=0;i<ChannelNode.Children().Total();i++) { ChannelChildNodes[i]=ChannelNode.Children().At(i); } m_shift=ChannelNode.Children().Total()-ItemNodesTotal(); return(true); } //+------------------------------------------------------------------+ //| function counts the number of item tags in document | //+------------------------------------------------------------------+ int CRssReader::ItemNodesTotal(void) { int t=0; for(int i=0;i<ChannelNode.Children().Total();i++) { if(ChannelChildNodes[i].getName()=="item") { t++; } else continue; } return(t); } //+------------------------------------------------------------------+ //| free document tree and reset pointer values | //+------------------------------------------------------------------+ void CRssReader::FreeDocumentTree(void) { ChannelNode.Children().Shutdown(); ArrayFree(ChannelChildNodes); RssNode.Children().Shutdown(); m_xmldocument.Clear(); m_shift=0; RssNode=NULL; ChannelNode=NULL; } //+------------------------------------------------------------------+ //| get channel title | //+------------------------------------------------------------------+ string CRssReader::getChannelTitle(void) { string ret=NULL; if(!CheckPointer(ChannelNode)==POINTER_INVALID) { for(int i=0;i<m_shift;i++) { if(ChannelChildNodes[i].getName()=="title") { ret=ChannelChildNodes[i].getValue(); break; } else continue; } } //---return value return(ret); } //+------------------------------------------------------------------+ //| display title | //+------------------------------------------------------------------+ string CRssReader::getTitle(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="title") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| display description | //+------------------------------------------------------------------+ string CRssReader::getDescription(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="description") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| display date | //+------------------------------------------------------------------+ string CRssReader::getDate(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="pubDate") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| format string for output to text area panel | //+------------------------------------------------------------------+ bool CRssReader::FormatString(string v,string &array[],int n) { ushort ch[],space,fullstop,comma,semicolon,newlinefeed; string _s,_k; space=StringGetCharacter(" ",0); fullstop=StringGetCharacter(".",0); comma=StringGetCharacter(",",0); semicolon=StringGetCharacter(";",0); newlinefeed=StringGetCharacter("\n",0); _k=removeTags(v); _s=removeSpecialCharacters(_k); int p=StringLen(_s); ArrayResize(ch,p+1); int d=StringToShortArray(_s,ch,0,-1); for(int i=1;i<d;i++) { int t=i%n; if(!t== 0)continue; else { if(ch[(i/n)*n]==fullstop || ch[(i/n)*n]==semicolon || ch[(i/n)*n]==comma) { ArrayFill(ch,((i/n)*n)+1,1,newlinefeed); } else { for(int k=i;k>=0;k--) { if(ch[k]==space) { ArrayFill(ch,k,1,newlinefeed); break; } else continue; } } } } _s=ShortArrayToString(ch,0,-1); int s=StringSplit(_s,newlinefeed,array); if(!s>0) {return(false);} // success return(true); } //+------------------------------------------------------------------+ //| remove special characters | //+------------------------------------------------------------------+ string CRssReader::removeSpecialCharacters(string s_tring) { string n=s_tring; StringReplace(n,"&","&"); StringReplace(n,"'","'"); StringReplace(n," "," "); StringReplace(n,"“","\'"); StringReplace(n,"”","\'"); StringReplace(n,""","\""); StringReplace(n,"–","-"); StringReplace(n,"’","'"); StringReplace(n,">",""); return(n); } //+------------------------------------------------------------------+ //| remove tags | //+------------------------------------------------------------------+ string CRssReader::removeTags(string _string) { string now=NULL; if(StringFind(_string,"<",0)>-1) { int v=0,a[][2]; ArrayResize(a,2024); for(int i=0;i<StringLen(_string);i++) { int t=tagPosition(_string,i); if(t>0) { v++; a[v-1][0]=i; a[v-1][1]=t; } else continue; } ArrayResize(a,v); for(int i=0;i<v-1;i++) { now+=StringSubstr(_string,(a[i][1]+1),(a[i+1][0]-(a[i][1]+1))); } } else { now=_string; } return(now); } //+------------------------------------------------------------------+ //| tag positions | //+------------------------------------------------------------------+ int CRssReader::tagPosition(string _string,int w) { int iClose=-1; if(StringCompare("<",StringSubstr(_string,w,StringLen("<")))==0) { iClose=StringFind(_string,">",w+StringLen("<")); } return(iClose); } //+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CRssReader::OnChangeListView(void) { int a=0,k=0,l=0; a=m_listview.Current()+m_shift; if(ArraySize(ChannelChildNodes)>a) { if(m_titleview.ItemsClear()) { if(!FormatString(getTitle(ChannelChildNodes[a]),m_titleareaoutput,55)) { return; } else if(ArraySize(m_titleareaoutput)>0) { for(l=0;l<ArraySize(m_titleareaoutput);l++) { m_titleview.AddItem(removeSpecialCharacters(m_titleareaoutput[l])); } } } if(m_textview.ItemsClear()) { if(!FormatString(getDescription(ChannelChildNodes[a]),m_textareaoutput,35)) return; else if(ArraySize(m_textareaoutput)>0) { for(k=0;k<ArraySize(m_textareaoutput);k++) { m_textview.AddItem(m_textareaoutput[k]); } m_textview.AddItem(" "); m_textview.AddItem("Date|"+getDate(ChannelChildNodes[a])); } else return; } } } //+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CRssReader::OnObjectEdit(void) { string f=m_edit.Text(); if(StringLen(f)>0) { if(ArraySize(ChannelChildNodes)<1) { CDialog::Caption("Loading..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } else { FreeDocumentTree(); CDialog::Caption("Loading new RSS Feed..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } } else return; } //+------------------------------------------------------------------+ //| Event handler refresh the app dialogue | //+------------------------------------------------------------------+ void CRssReader::OnClickButton1(void) { if(ArraySize(ChannelChildNodes)<1) { if(!m_edit.Text("Enter the web address of an Rss feed")) Print("error changing edit text"); if(!CDialog::Caption("RSSReader")) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("error adding to listview"); } m_listview.VScrolled(true); for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } return; } } else { FreeDocumentTree(); if(!m_edit.Text("Enter the web address of an Rss feed")) Print("error changing edit text"); if(!CDialog::Caption("RSSReader")) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("error adding to listview"); } m_listview.VScrolled(true); for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } return; } } } //+------------------------------------------------------------------+ //| Event handler update current feed | //+------------------------------------------------------------------+ void CRssReader::OnClickButton2(void) { string f=m_rssurl; if(ArraySize(ChannelChildNodes)<1) return; else { FreeDocumentTree(); CDialog::Caption("Checking for RSS Feed update..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } }
Il peut maintenant être utilisé dans le code de l'Expert Advisor.
2.11. Le code de l'Expert Advisor
L'Expert Advisor n'a pas de variables d'entrée puisque l'application est censée être entièrement interactive.
Nous déclarons d'abord une variable globale qui est une instance de la classe CRssReader. Dans la fonction OnInit(), nous initialisons le dialogue de l'application avec un appel à la méthode principale Create(). Si cela réussit, la méthode Run() d'une classe ancêtre est appelée.
Dans la fonction OOnDeinit()), la méthode Destroy() de la classe parent est appelée pour supprimer l'intégralité de l'application et retirer l'Expert Advisor du graphique.
La fonction OnChartEvent() contient un appel à une méthode ancêtre de la classe CRssReader, qui gérera le traitement de tous les événements.
//Expert Advisor code begins here //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CRssReader ExtDialog; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create application dialog if(!ExtDialog.Create(0,"RSSReader",0,20,20,518,394)) return(INIT_FAILED); //--- run application ExtDialog.Run(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- ExtDialog.Destroy(reason); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- ExtDialog.ChartEvent(id,lparam,dparam,sparam); }
Le code doit ensuite être compilé et le programme sera prêt à être utilisé.
Lorsque l'Expert Advisor RssReader.mq5 est chargé sur un graphique, une boîte de dialogue d'application vide apparaît comme suit :
Fig. 2. Capture d'écran du dialogue d'application vide du RssReader de l’Expert Advisor
Entrez une adresse Web et le contenu RSS sera chargé dans la boîte de dialogue de l'application, comme illustré par l'image ci-dessous :
Fig. 3. RssReader EA fonctionnant dans le terminal
J'ai testé le programme avec une large gamme de flux RSS. Le seul problème que j'ai observé était lié à l'affichage de certains caractères indésirables, principalement le résultat de documents RSS contenant des caractères généralement trouvés dans les documents HTML.
J'ai également remarqué que la modification de la période d'un graphique pendant que l'application est en cours d'exécution entraîne la réinitialisation de l'EA et peut entraîner un mauvais dessin des contrôles de l'application.
Je n'ai pas pu corriger ce comportement, mon conseil est donc d'éviter de modifier la période du graphique lorsque le programme RSS Reader est en cours d'exécution.
Conclusion
Nous avons terminé la création d'une application de lecteur RSS entièrement interactive pour MetaTrader 5, en utilisant des techniques de programmation orientées objet.
Il y a beaucoup plus de fonctionnalités qui pourraient être ajoutées à l'application et je suis sûr qu'il existe de nombreuses autres façons d'organiser l'interface utilisateur. J'espère que ceux qui ont peut-être de meilleures compétences en conception d'interface graphique d'application amélioreront l'application et partageront leurs créations.
P.S. Veuillez noter que le fichier easyxml.mqh disponible en téléchargement ici n'est pas le même que celui disponible dans la base de code, il contient des modifications déjà mentionnées dans l'article. Toutes les inclusions nécessaires se trouvent dans le fichier RssReader.zip.
Traduit de l’anglais par MetaQuotes Ltd.
Article original : https://www.mql5.com/en/articles/1589





- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation