English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Crear aplicación interactiva para visualizar los canales RSS en MetaTrader 5

Crear aplicación interactiva para visualizar los canales RSS en MetaTrader 5

MetaTrader 5Asesores Expertos | 12 agosto 2015, 14:58
889 0
Francis Dube
Francis Dube

Contenido


Introducción

En el artículo “Leer noticias en el formato RSS usando los medios de MQL4” se describe un script bastante sencillo que puede mostrar los canales RSS en la consola del terminal a través de una simple biblioteca, construida originalmente para analizar sintácticamente (parsing) los documentos HTML.

Con la aparición de MetaTrader 5 y lenguaje de programación MQL5, según nuestra opinión, ha sido posible crear una aplicación interactiva que podría mostrar mejor el contenido RSS. En este artículo se muestra cómo crear dicha aplicación usando la Biblioteca estándar MQL5 extendida y otras herramientas desarrolladas por los usuarios de MQL5.community.


1. Características generales de los documentos RSS

Antes de empezar a hablar de las particularidades de la aplicación, hay que considerar la estructura general de un documento RSS.

Para comprender mejor la descripción que sigue a continuación, es necesario estar familiarizado con el lenguaje de marcas extensible y con los conceptos relacionados con él. Por favor, consulte XML Tutorial si no está familiarizado con los documentos XML. Preste atención, en este artículo un nodo se refiere a una etiqueta en el documento XML. Como ya se ha dicho antes en el articulo mencionado sobre MQL4, los archivos RSS representan los documentos XML con una estructura de etiquetas especial.

Cada documento RSS tiene un repositorio global- la etiqueta RSS. Esto es común para todos los documentos RSS. La etiqueta channel siempre se desciende directamente de la etiqueta RSS. Contiene la información sobre el sitio web descrito por el canal. Desde este momento, los documentos RSS diferentes pueden contener las etiquetas específicas distintas. Pero la presencia de algunas etiquetas es obligatoria para todos los documentos. Precisamente estas etiquetas hacen del documento un archivo RSS verificado.

Etiquetas necesarias:

  • title - título del canal. Debe contener el nombre del sitio web;
  • link - URL del sitio web que representa este canal;
  • description - descripción breve del sitio web;
  • item - por lo menos una etiqueta item para el contenido.

Las etiquetas mencionadas son nodos hijos de la etiqueta channel. El nodo item contiene la información sobre el contenido determinado.

A su vez, cada nodo item también debe contener las etiquetas siguientes:

  • title - título del contenido;
  • link - enlace URL al contenido;
  • description - descripción breve del contenido;
  • date - fecha de publicación del contenido en el sitio web.

Todos los documento RSS incluyen las etiquetas listadas y guardan la misma estructura.

A continuación, se muestra el código completo del documento RSS.

<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:&lt;em&gt;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 &lt;a href="http://www.xul.fr/en/html5/" target="_parent"&gt;HTML 5&lt;/a&gt;</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 &lt;em&gt;fieldset&lt;/em&gt; and insert into it another &lt;em&gt;fieldset&lt;/em&gt; (for rounded edges) or a &lt;em&gt;div&lt;/em&gt;, 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&#xE9;o sont impl&#xE9;ment&#xE9;es. Le format de donn&#xE9;e JSON est reconnu nativement par Firefox. L'avantage est d'&#xE9;viter l'utilisation de la fonction eval() qui n'est pas s&#xFB;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! &lt;em&gt;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 &lt;em&gt;form&lt;/em&gt; 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 &lt;em&gt;action&lt;/em&gt; 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. Estructura general de la aplicación

Este apartado contiene la descripción de la información que debe mostrar RSS Reader y el resumen de la interfaz gráfica de usuario de la aplicación.

Lo primero que tiene que mostrar la aplicación es el título del canal especificado en la etiqueta “title”. Esta información indica el sitio web al que se refiere el canal.

Además, la aplicación tiene que mostrar el resumen del contenido completo que describe el canal. Esto se refiere a todas las etiquetas item incluidas en el documento. Para cada etiqueta item se muestra el título del contenido. Por último, es necesario que RSS Reader muestre la descripción del contenido. La descripción es la información que figura en la etiqueta “description” de cada nodo item.


2.1. Interfaz de usuario

La interfaz de usuario representa el medio de visualización de la información por la aplicación.

El esquema de abajo refleja perfectamente nuestra concepción de la interfaz de usuario.


Diseño aproximado de la ventana de diálogo de la aplicación

Fig. 1. Diseño aproximado de la ventana de diálogo de la aplicación


En el esquema se muestran varias secciones que componen la interfaz de usuario.

  • Primero va la barra del título que muestra el nombre del canal;
  • Área de entrada. Aquí el usuario introduce la dirección web del canal RSS;
  • Área del título. Aquí se muestra el título del contenido determinado;
  • Área del texto. Aquí se muestra la descripción del contenido;
  • Área de la lista. En esta lista desplazable se muestran los títulos del contenido entero del canal;
  • El botón de la izquierda vacía las áreas del título, texto y la lista;
  • El botón “Update Feed”  obtiene nuevas actualizaciones para el canal actual.

RSS Reader funciona de la siguiente manera: cuando el programa está cargado, se muestra la ventana vacía de la aplicación, luego el usuario introduce en el área de entrada la dirección web del canal RSS “Enter”. De esta manera, se cargan los títulos del contenido en el área de la lista, es decir las etiquetas “title” para cada etiqueta “item”. La numeración de la lista se empieza desde uno (1) representando el contenido publicado can más frecuencia.

Cada entrada de la lista es interactiva. Al hacer clic en la entrada de la lista, se resalta con el color y en el área del texto aparece la descripción correspondiente del contenido del título. Al mismo tiempo, en el área del título se muestra con más claridad el título del contenido. Si por alguna razón ocurre un fallo durante la carga del canal, veremos un mensaje del error en el área del texto.

Podemos borrar cualquier texto en las áreas del texto, listas o título usando el botón Reset.

Con el botón “Update Feed” se buscan nuevas actualizaciones para el canal actual.


2.2. Implementación del código

RSS Reader está implementado como Asesor Experto (EA). Se utiliza la Biblioteca estándar MQL5.

El código se encuentra en la clase CRssReader que, a su vez, es la heredera de la clase CAppDialog. La clase CAppDialog del archivo de inclusión Dialog.mqh implementa la ventana de diálogo de la aplicación que asegura las capacidades funcionales de la barra de título y los elementos del control que minimizan, maximizan y cierran la aplicación. Es el fundamento de la interfaz de usuario por encima del cual van a agregarse otras secciones. Se planea agregar las siguientes secciones: área del título, área del texto, área de la lista y los botones. Cada una será un elemento del control. Los botones serán implementados como los elementos del control que se describen en el archivo de inclusión Button.mqh.

El elemento del control de la lista descrito en el archivo de inclusión ListViewArea.mqh se utiliza para crear el área de la lista de RSS Reader.

El elemento de edición es suficiente para crear el área de entrada. Este elemento del control se describe en el archivo Edit.mqh.

Lo que se refiere a las áreas del título y texto, su implementación es una tarea complicada pero interesante. Resulta que ambas áreas tienen que soportar el texto con capacidad de visualización en varias líneas. En MQL5 los objetos de texto no reconocen los signos del avance de línea. Otro problema es que en una línea del objeto de texto puede caber una limitada cantidad de caracteres. Eso significa que el objeto de texto con una descripción bastante larga va a mostrarse cortado y se verá sólo una determinada cantidad de caracteres. Con el método de tanteos y errores se ha descubierto que la limitación en cuanto a los signos es de 63 caracteres, incluyendo los espacios y signos de puntuación.

Para resolver este problema, he decidido implementar ambas secciones en forma de los elementos modificados del control de la lista. En el área del título, el elemento modificado del control de la lista no será desplazable, y la misma lista estará limitada con dos renglones. No se podrá desplazar o seleccionar los renglones de la lista, el elemento del control no va a parecer a una lista. Estos dos renglones de la lista van a perecer a dos líneas del texto. Si el texto no cabe en una línea, se divide y se visualiza en dos líneas. El elemento del control del área del título se describe en el archivo TitleArea.mqh.

Para el área del texto se aplica el mismo enfoque, sólo en este caso se puede cambiar el número de renglones en la lista, y el elemento modificado del control de la lista se desplaza horizontalmente. Este elemento del control se describe en el archivo TextArea.mqh.

La interfaz de usuario se implementa a través de las bibliotecas mencionadas. Pero existe otra biblioteca clave para la aplicación, hay que contar de ella. Es la biblioteca para el análisis sintáctico de los documentos XML.


2.3. Analizador sintáctico simple XML (parser)

Debido a que el documento RSS es un archivo XML, usamos la biblioteca EasyXML - XML Parser publicada en Code Base y desarrollada por liquinaut.

Esta biblioteca es bastante extensa e incluye todos las funciones necesarias para nuestro RSS Reader. Hemos modificado la biblioteca original y hemos añadido algunas propiedades que nos parecen necesarias.

Pues, hablaremos de estas pequeñas adiciones. Primero, se ha añadido el método llamado loadXmlFromUrlWebReq(). Este método es una alternativa al uso de loadXmlFromUrl() que cuenta con la biblioteca WinInet durante del procesamiento de las solicitudes web. loadXmlFromUrlWebReq() utiliza la función incorporada WebRequest() para hacer downloads de Internet.

//+----------------------------------------------------------------------+
//| descarga xml a través de url usando la función solicitud web de MQL5 |
//+----------------------------------------------------------------------+

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);
//--- chequeo de errores
   if(res==-1)
     {
      Err=EASYXML_ERR_WEBREQUEST_URL;
      return(Error());
     }
//--- cargar del archivo con éxito
   sStream=CharArrayToString(result,0,-1,CP_UTF8);
//--- configuración de la cache
   if(blSaveToCache)
     {
      bool bResult=writeStreamToCacheFile(sStream);
      if(!bResult) Error(-1,false);
     }
//---
   return(loadXmlFromString(sStream));
  }

La segunda adición ha sido el método GetErrorMsg() que permite recuperar los mensajes del error a través del analizador sintáctico siempre cuando occurra.

string            GetErrorMsg(void){   return(ErrMsg);}

La última modificación se debe a la corrección de un defecto bastante importante que ha sido detectado durante la prueba del analizados sintáctico easyxml.

Descubrí que la biblioteca no era capaz de reconocer las declaraciones XML. El código tomaba equivocadamente esta declaración por un atributo. Debido a eso el programa se quedaba colgado en un ciclo infinito porque el código buscaba el valor correspondiente del atributo que en realidad nunca existía.

Una pequeña modificación de skipProlog() ha solucionado este problema.

//+------------------------------------------------------------------+
//| omitir prólogo xml                                               |
//+------------------------------------------------------------------+
bool CEasyXml::skipProlog(string &pText,int &pPos)
  {
//--- omitir declaración xml
   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);
        }
     }
//--- omitir declaraciones de la tabla de estilos
   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);
        }
     }
//--- omitir comentarios
   if(!skipWhitespaceAndComments(pText,pPos,"")) return(false);

//--- omitir tipo del documento
   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);
        }
     }

//--- omitir comentarios
   if(!skipWhitespaceAndComments(pText,pPos,"")) return(false);

   return(true);
  }
El resto de la biblioteca de liquinaut se ha quedado inalterado. Easyxml.mqh es una excelente herramienta.


2.4. Código del Asesor Experto

Ahora, cuando ya tenemos descritas todas las bibliotecas necesarias para nuestra aplicación, ha llegado el momento para combinarlas y definir la clase CRssReader.

Preste atención en que el código del RSS Reader empieza con la definición de la clase 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>
//+------------------------------------------------------------------+
//| definición                                                       |
//+------------------------------------------------------------------+
//--- sangrías y lagunas
#define INDENT_LEFT                         (11)      // sangría izquierda (tomando en cuenta el grosor del borde)
#define INDENT_TOP                          (11)      // sangría superior (tomando en cuenta el grosor del borde)
#define INDENT_RIGHT                        (11)      // sangría derecha (tomando en cuenta el grosor del borde)
#define INDENT_BOTTOM                       (11)      // sangría inferior (tomando en cuenta el grosor del borde)
#define CONTROLS_GAP_X                      (5)       // laguna por la coordenada X
#define CONTROLS_GAP_Y                      (5)       // laguna por la coordenada Y

#define EDIT_HEIGHT                         (20)      // tamaño por la coordenada Y
#define BUTTON_WIDTH                        (150)     // tamaño por la coordenada X
#define BUTTON_HEIGHT                       (20)      // tamaño por la coordenada Y
#define TEXTAREA_HEIGHT                     (131)     // tamaño por la coordenada Y
#define LIST_HEIGHT                         (93)      // tamaño por la coordenada Y
Vamos a incluir los archivos necesarios. Para configurar los parámetros físicos de los elementos del control en píxeles, hemos usado las directivas define.

INDENT_LEFT, INDENT_RIGHT, INDENT_TOP и INDENT_DOWN establecen la distancia entre un elemento del control y el borde de la ventana de la aplicación.

  • CONTROLS_GAP_Y - distancia vertical entre dos elementos del control;
  • EDIT_HEIGHT establece el alto del elemento de edición que constituye el área de entrada;
  • BUTTON_WIDTH y BUTTON_HEIGHT determina el ancho y el alto de todos los botones de control;
  • TEXTAREA_HEIGHT - el alto del área del texto;
  • LIST_HEIGHT establece el alto del elemento del control de la lista.

Después de las directivas define pasamos a la definición de la clase CRssReader.

//+------------------------------------------------------------------+
//| Clase CRssReader                                                 |
//| Aplicación: clase principal para la aplicación RSS               |
//+------------------------------------------------------------------+
class CRssReader : public CAppDialog
  {
private:
   int               m_shift;                    // índice de la primera etiqueta item
   string            m_rssurl;                   // copiado de la dirección web del último canal 
   string            m_textareaoutput[];         // array de las cadenas a mostrar en el área del texto
   string            m_titleareaoutput[];        // array de las cadenas a mostrar en el área del título
   CButton           m_button1;                  // objeto “botón”
   CButton           m_button2;                  // objeto “botón”      
   CEdit             m_edit;                     // panel de entrada
   CTitleArea        m_titleview;                // objeto “campo de visualización”
   CListViewArea     m_listview;                 // objeto “lista”
   CTextArea         m_textview;                 // objeto “área del texto”
   CEasyXml          m_xmldocument;              // objeto “documento xml”
   CEasyXmlNode     *RssNode;                    // objeto “nodo raíz”
   CEasyXmlNode     *ChannelNode;                // objeto “nodo del canal”
   CEasyXmlNode     *ChannelChildNodes[];        // array de los objetos “nodo hijo del canal”

Como se ha mencionado anteriormente, CRssReader es el heredero de la clase CAppDialog.

A continuación, se muestran algunas propiedades de la clase:

  • m_shift - en estas variables del tipo integer se guarda el índice del primer nodo item en el array ChannelChildnodes;
  • m_rssurl - valor string que almacena la copia de la última URL introducida;
  • m_textareaoutput[] - array de cadenas, cada uno de sus elementos corresponde a una cadena de texto con una determinada cantidad de caracteres;
  • m_titleareaoutput[] - también array de cadenas. Sirve para los mismos propósitos que el anterior array de cadenas
  • m_button1 y m_button2 son objetos del tipo CButton;
  • m_listview - objeto que representa un elemento del control de la lista;
  • m_edit representa un objeto CEdit e implementa el área de entrada;
  • m_titleview - objeto para visualizar el área del título;
  • m_textview - objeto para el área del texto;
  • m_xmldocument - objeto del documento xml;
  • RssNode - objeto “nodo raíz”;
  • ChannelNode - objeto “nodo del canal”;
  • Массив ChannelChildNodes - conjunto de punteros a los herederos directos de la etiqueta channel.

Sólo dos métodos estarán disponibles en nuestra clase.

public:
                     CRssReader(void);
                    ~CRssReader(void);
   //--- crear
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- manejador del evento del gráfico
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

El primer método Create() determina el tamaño y la posición inicial de la ventana de la aplicación.

También inicializa todos los elementos del control de la aplicación RSS Reader (no olvide que nuestra clase se hereda de la clase CAppDialog, por tanto los métodos públicos de la clase padre y sus herederos pueden ser llamados por todas las instancias de CRssReader).

//+------------------------------------------------------------------+
//| Crear                                                            |
//+------------------------------------------------------------------+
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);
//--- crear controles dependientes
   if(!CreateEdit())
      return(false);
   if(!CreateButton1())
      return(false);
   if(!CreateButton2())
      return(false);
   if(!CreateTitleView())
      return(false);
   if(!CreateListView())
      return(false);
   if(!CreateTextView())
      return(false);
//--- con éxito
   return(true);
  }
El segundo método OnEvent() asegura la interactividad a través de la atribución de determinados eventos al elemento del control correspondiente y a la función manejadora.
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
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étodos de inicialización de los elementos del control

Para inicializar el elemento del control correspondiente, la función principal Create() llama a los métodos protegidos CreateEdit(), CreateButton1() ,CreateButton2(), CreateTitleView(), CreateListView() y CreateTextView().

protected:
   //--- crear elementos de control
   bool              CreateEdit(void);
   bool              CreateButton1(void);
   bool              CreateButton2(void);
   bool              CreateTitleView(void);
   bool              CreateListView(void);
   bool              CreateTextView(void);
Son las funciones para ajustar el tamaño, posición y las propiedades (por ejemplo, el tipo, tamaño y color de la fuente, color y tipo de los borden) del elemento del control.
//+------------------------------------------------------------------+
//| Crear el campo de visualización                                  |
//+------------------------------------------------------------------+
bool CRssReader::CreateEdit(void)
  {
//--- coordenadas
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+EDIT_HEIGHT;
//--- crear
   if(!m_edit.Create(m_chart_id,m_name+"Edit",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_edit.Text(“Introduzca la dirección web del canal Rss”))
      return(false);
   if(!m_edit.ReadOnly(false))
      return(false);
   if(!Add(m_edit))
      return(false);
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el botón 1                                                 |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton1(void)
  {
//--- coordenadas
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=x1+BUTTON_WIDTH;
  int pConnection
//--- crear
   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);
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el botón 2                                                 |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton2(void)
  {
//--- coordenadas
   int x1=(ClientAreaWidth()-INDENT_RIGHT)-BUTTON_WIDTH;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=ClientAreaWidth()-INDENT_RIGHT;
  int pConnection
//--- crear
   if(!m_button2.Create(m_chart_id,m_name+"Button2",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_button2.Text(“Actualizar el canal actual”))
      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);
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el campo de visualización                                  |
//+------------------------------------------------------------------+
bool CRssReader::CreateTitleView(void)
  {
//--- coordenadas
   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();
//--- crear
   if(!m_titleview.Create(m_chart_id,m_name+"TitleView",m_subwin,x1,y1,x2,y2))
     {
      Print(“Error al crear el título”);
      return(false);
     }
   else
     {
      for(int i=0;i<2;i++)
        {
         m_titleview.AddItem(" ");
        }
     }
   if(!Add(m_titleview))
     {
      Print(“error al añadir el título”);
      return(false);
     }
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el elemento "ListView"                                     |
//+------------------------------------------------------------------+
bool CRssReader::CreateListView(void)
  {
//--- coordenadas
   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;
//--- crear
   if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!Add(m_listview))
      return(false);
//--- llenamos con cadenas
   for(int i=0;i<20;i++)
      if(!m_listview.AddItem(" "))
         return(false);
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el campo de visualización                                  |
//+------------------------------------------------------------------+
bool CRssReader::CreateTextView(void)
  {
//--- coordenadas
   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();
//--- crear
   if(!m_textview.Create(m_chart_id,m_name+"TextArea",m_subwin,x1,y1,x2,y2))
     {
      Print(“Error al crear el área del texto”);
      return(false);
     }
   else
     {
      for(int i=0;i<1;i++)
        {
         m_textview.AddItem(" ");
        }
      m_textview.VScrolled(true);
      ChartRedraw();
     }
   if(!Add(m_textview))
     {
      Print(“error al añadir el área del texto”);
      return(false);
     }
//--- con éxito      
   return(true);
  }


2.6. Métodos de procesamiento de los documentos RSS

//--- procesamiento del documento RSS
   bool              LoadDocument(string filename);
   int               ItemNodesTotal(void);
   void              FreeDocumentTree(void);

2.6.1. LoadDocument()

Esta función desempeña varios papeles importantes. Uno de ellos consiste en procesar las solicitudes web. loadXmlFromUrlWebReq() se invoca para cargar el archivo RSS.

Si la carga se concluye con éxito, la función pasa a su segunda tarea: es decir, inicializa los punteros RssNode y ChannelNode, así como rellena el array ChannelChildnodes. Precisamente aquí se ajustan las propiedades m_rssurl y m_shift. Después de eso, la función devuelve el valor true.

Si el archivo RSS no se ha cargado con éxito, el texto en el área del título, de la lista y del texto se limpia, y en la barra del título aparece el mensaje sobre el estado. Luego en el área del texto aparece el mensaje del error. La función devuelve el valor false.

//+------------------------------------------------------------------+
//|   cargar documento                                               |
//+------------------------------------------------------------------+
bool CRssReader::LoadDocument(string filename)
  {
   if(!m_xmldocument.loadXmlFromUrlWebReq(filename))
     {
      m_textview.ItemsClear();
      m_listview.ItemsClear();
      m_titleview.ItemsClear();
      CDialog::Caption(“Fallo al cargar el canal”);
      if(!m_textview.AddItem(m_xmldocument.GetErrorMsg()))
         Print(“no se puede mostrar el mensaje del error”);
      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()

Esta función auxiliar se usa en el método LoadDocument(). Devuelve el valor integer, que en realidad representa el número de nodos item que se descienden del elemento channel.

Si no hay ningún nodo item, el documento RSS será inválido y la función devolverá 0.

//+--------------------------------------------------------------------+
//| la función calcula el número de las etiquetas item en el documento |
//+--------------------------------------------------------------------+
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()

Esta función resetea todos los punteros CEasyXmlNode.

Primero se eliminan todos los elementos del array ChannelChildnodes llamando al método Shutdown() de la clase CArrayObj. Luego el array se libera a través de una sola llamada de ArrayFree().

Luego se elimina el puntero al nodo channel y se limpia el árbol del documento del analizador easyxml. Estas acciones hacen que los punteros RssNode y ChannelNode pierden sus propiedades y en consecuencia se les asigna el valor NULL.

//+-----------------------------------------------------------------------+
//| liberar el árbol del documento y resetear los valores de los punteros |
//+-----------------------------------------------------------------------+ 
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étodos para extraer la información del árbol de documentos

Estas funciones sirven para obtener el texto del documento RSS.

//--- métodos de obtención
   string            getChannelTitle(void);
   string            getTitle(CEasyXmlNode *Node);
   string            getDescription(CEasyXmlNode *Node);
   string            getDate(CEasyXmlNode *Node);

2.7.1. getChannelTitle()

Esta función extrae el título del canal actual del documento RSS.

Primero, comprueba si el puntero del nodo channel es correcto. Si el puntero es inválido, recorre todos los herederos directos del nodo channel buscando la etiqueta title.

Usando la propiedad m_shift, la búsqueda se realiza entre una cantidad determinada de los herederos del nodo channel. En caso del fallo, la función devuelve el valor NULL.

//+------------------------------------------------------------------+
//| obtener el título del canal                                      |
//+------------------------------------------------------------------+
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;
        }
     }
//--- valor devuelto
   return(ret);
  }


2.7.2. getTitle()

Esta función recibe el puntero a la etiqueta item como parámetro de entrada, revisa las etiquetas que contiene buscando la etiqueta title y devuelve su valor.

Las funciones getDescription() y getDate() tienen el mismo formato funcionando de forma semejante. En caso del éxito, devuelve el valor de cadena; de lo contrario devuelve NULL.

//+------------------------------------------------------------------+
//| visualización del título                                         |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| visualización de la descripción                                  |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| visualización de la fecha                                        |
//+------------------------------------------------------------------+ 
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étodos de formateo del texto

Estas funciones sirven para preparar el texto para la salida como objetos de texto. Permiten quitar algunas limitaciones que tienen los objetos del texto.

 //--- formateo del texto 
   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()

Es la función principal que se encarga de la preparación del texto extraído del documento RSS para mostrarlo en la aplicación.

Básicamente, coge el valor del parámetro tipo string y divide el texto en líneas. Cada línea contiene la cantidad “n” de caracteres. “n” es el valor integer del número de caracteres en una línea del texto. Después de cada cantidad “n” de caracteres en el texto, el código empieza a buscar un sitio apropiado para insertar el nuevo signo del avance de línea. Luego, se procesa el valor string entero y se insertan nuevos signos del avance de línea en el original del texto.

La función StringSplit() se utiliza para crear el array de cadenas con una cantidad “n” de caracteres en cada una de ellas. La función devuelve el valor booleano y también el array de los valores string listos para la visualización.

//+------------------------------------------------------------------+
//| formateo de la cadena a mostrar en el área del texto             |
//+------------------------------------------------------------------+
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);}
// con éxito
   return(true);
  }


2.8.2. removeTags()

Hemos comprendido la necesidad en esta función tras notar que una gran cantidad de los documentos RSS contiene las etiquetas HTML en los nodos XML.

Algunos documentos RSS se publican de esta manera porque muchos lectores RSS trabajan en el navegador.

La función toma el valor string y realiza la búsqueda de las etiquetas en el texto. Si se encuentra alguna etiqueta, la función todos los caracteres del texto y almacena la posición de cada carácter de la etiqueta de la apertura y del cierre en un array bidimensional a[][]. Este array se usa para extraer el texto entre las etiquetas y devolver la cadena extraída. Si las etiquetas no han sido encontradas, la cadena de entrada se devuelve en su forma original.

//+------------------------------------------------------------------+
//| eliminar etiquetas                                               |
//+------------------------------------------------------------------+
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);
  }

A continuación, se muestra el ejemplo de una parte de este docuemento.

<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.&nbsp;</p>
<p align="center"><a href='http://dynamic1.anandtech.com/www/delivery/ck.php?n=a1f2f01f&amp;cb=582254849' target='_blank'><img src='http://dynamic1.anandtech.com/www/delivery/avw.php?zoneid=24&amp;cb=582254849&amp;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()

Esta función sustituye ciertas constantes string por los caracteres correctos.

Por ejemplo, en algunos documentos XML el símbolo de “ampersand” se muestra como "&amp". En esos casos se aplica la función incorporada StringReplace().

//+------------------------------------------------------------------+
//| eliminar signos especiales                                       |
//+------------------------------------------------------------------+ 
string CRssReader::removeSpecialCharacters(string s_tring)
  {
   string n=s_tring;
   StringReplace(n,"&amp;","&");
   StringReplace(n,"&#39;","'");
   StringReplace(n,"&nbsp;"," ");
   StringReplace(n,"&ldquo;","\'");
   StringReplace(n,"&rdquo;","\'");
   StringReplace(n,"&quot;","\"");
   StringReplace(n,"&ndash;","-");
   StringReplace(n,"&rsquo;","'");
   StringReplace(n,"&gt;","");
   return(n);
  }

2.8.4. tagPosition()

Esta función auxiliar se invoca en la función removeTags(). Como valores de entrada se cogen los valores string y integer.

El valor de entrada integer representa la posición del carácter en la cadena a partir del cual la función empieza la búsqueda del signo de la etiqueta de apertura, es decir, "<". Si la etiqueta de apertura ha sido encontrada, la función empieza a buscar la etiqueta de cierre y devuelve la posición del signo correspondiente ">". La función devuelve el valor -1 si no hay etiquetas.

//+------------------------------------------------------------------+
//| posiciones de etiquetas                                          |
//+------------------------------------------------------------------+
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étodos de procesamiento de eventos de los elementos de control independientes

Estas funciones manejan los eventos capturados de un determinado elemento del control.

//--- manejadores de eventos de los elementos del control dependientes
   void              OnChangeListView(void);
   void              OnObjectEdit(void);
   void              OnClickButton1(void);
   void              OnClickButton2(void);
  };

2.9.1. OnChangeListView()

Esta función de procesamiento de eventos se invoca al hacer clic en una de las entradas de la lista en el área correspondiente.

La función permite ver la descripción breve de algún contenido en el documento RSS.

La función quita el texto de las áreas del texto y del título, realiza la recopilación de nuevos datos del árbol de la documentación y los prepara para la visualización. Todo eso ocurre si el array ChannelChildnodes no está vacío.

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
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()

Esta función auxiliar se invoca cuando el usuario termina de introducir el texto en el área correspondiente.

La función llama al método LoadDocument(). Si la carga ha sido con éxito, el texto se elimina de la aplicación. Luego se cambia el título y el nuevo contenido se muestra en el área de la lista.

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CRssReader::OnObjectEdit(void)
  {
   string f=m_edit.Text();
   if(StringLen(f)>0)
     {
      if(ArraySize(ChannelChildNodes)<1)
        {
         CDialog::Caption(“Cargando...”);
         if(LoadDocument(f))
           {
            if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
               Print(“error al cambiar el título”);
            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(“no se puede añadir la entrada en el área de la lista”);
                     return;
                    }
                 }
              }
            else
              {
               Print(“el área del texto/lista no está vaciada”);
               return;
              }
           }
         else return;
        }
      else
        {
         FreeDocumentTree();
         CDialog::Caption(“Cargando el nuevo canal RSS...”);
         if(LoadDocument(f))
           {
            if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
               Print(“error al cambiar el título”);
            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(“no se puede añadir la entrada en el área de la lista”);
                     return;
                    }
                 }
              }
            else
              {
               Print(“el área del texto/lista no está vaciada”);
               return;
              }
           }
         else return;
        }
     }
   else return;
  }

2.9.3. OnClickButton1/2()

Esta función auxiliar se invoca cuando el usuario pulsa el botón del reseteo o comprobación de actualizaciones.

El clic en el botón del reseteo devuelve la ventana de la aplicación en su estado inicial antes del primer inicio del EA.

El clic en el botón de comprobación de actualizaciones vuelve a llamar al método LoadDocument(), los datos del canal RSS se cargan actualizando el área de la lista.

//+------------------------------------------------------------------+
//| El manejador de eventos  actualiza la ventana de la aplicación   |
//+------------------------------------------------------------------+   
void CRssReader::OnClickButton1(void)
  {
   if(ArraySize(ChannelChildNodes)<1)
     {
      if(!m_edit.Text(“Introduzca la dirección web del canal Rss”))
         Print(“error al editar el texto”);
      if(!CDialog::Caption("RSSReader"))
         Print(“error al cambiar el título”);
      if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
        {
         for(int i=0;i<20;i++)
           {
            if(!m_listview.AddItem(" "))
               Print(“error al añadir la lista”);
           }
         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(“Introduzca la dirección web del canal Rss”))
         Print(“error al editar el texto”);
      if(!CDialog::Caption("RSSReader"))
         Print(“error al cambiar el título”);
      if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
        {
         for(int i=0;i<20;i++)
           {
            if(!m_listview.AddItem(" "))
               Print(“error al añadir la lista”);
           }
         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;
        }
     }
  }
//+------------------------------------------------------------------+
//| El manejador de eventos  actualiza el canal actual               |
//+------------------------------------------------------------------+ 
void CRssReader::OnClickButton2(void)
  {
   string f=m_rssurl;
   if(ArraySize(ChannelChildNodes)<1)
      return;
   else
     {
      FreeDocumentTree();
      CDialog::Caption(“Comprobando las actualizaciones del canal RSS...”);
      if(LoadDocument(f))
        {
         if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
            Print(“error al cambiar el título”);
         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(“no se puede añadir la entrada en el área de la lista”);
                  return;
                 }
              }
           }
         else
           {
            Print(“el área del texto/lista no está vaciada”);
            return;
           }
        }
      else return;
     }
  }
Aquí terminamos con la definición de la clase CRssReader.


2.10. Implementación de la clase CRssReader

//+------------------------------------------------------------------+
//| Clase CRssReader                                                 |
//| Aplicación: clase principal para la aplicación RSS               |
//+------------------------------------------------------------------+
class CRssReader : public CAppDialog
  {
private:
   int               m_shift;                   // índice de la primera etiqueta item
   string            m_rssurl;                  // copiar la dirección web del último canal 
   string            m_textareaoutput[];        // array de las cadenas a mostrar en el área del texto
   string            m_titleareaoutput[];       // array de las cadenas a mostrar en el área del título
   CButton           m_button1;                 // objeto "Botón"
   CButton           m_button2;                 // objeto "Botón"      
   CEdit             m_edit;                    // panel de entrada
   CTitleArea        m_titleview;               // objeto “campo de visualización”
   CListViewArea     m_listview;                // objeto “lista”
   CTextArea         m_textview;                // objeto “área del texto”
   CEasyXml          m_xmldocument;             // objeto “documento xml”
   CEasyXmlNode     *RssNode;                   // objeto “nodo raíz”
   CEasyXmlNode     *ChannelNode;               // objeto “nodo del canal”
   CEasyXmlNode     *ChannelChildNodes[];       // array de objetos “nodo hijo del canal”

public:
                     CRssReader(void);
                    ~CRssReader(void);
   //--- crear
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- manejador del evento del gráfico
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   //--- crear elementos de control
   bool              CreateEdit(void);
   bool              CreateButton1(void);
   bool              CreateButton2(void);
   bool              CreateTitleView(void);
   bool              CreateListView(void);
   bool              CreateTextView(void);
   // --- procesamiento del documento rss
   bool              LoadDocument(string filename);
   int               ItemNodesTotal(void);
   void              FreeDocumentTree(void);
   //--- métodos de obtención
   string            getChannelTitle(void);
   string            getTitle(CEasyXmlNode *Node);
   string            getDescription(CEasyXmlNode *Node);
   string            getDate(CEasyXmlNode *Node);
   //--- formateo del texto
   bool              FormatString(string v,string &array[],int n);
   string            removeTags(string _string);
   string            removeSpecialCharacters(string s_tring);
   int               tagPosition(string _string,int w);
   //--- manejadores de eventos de los elementos del control dependientes
   void              OnChangeListView(void);
   void              OnObjectEdit(void);
   void              OnClickButton1(void);
   void              OnClickButton2(void);
  };
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
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)
  {
  }
//+------------------------------------------------------------------+
//| Crear                                                            |
//+------------------------------------------------------------------+
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);
//--- crear controles dependientes
   if(!CreateEdit())
      return(false);
   if(!CreateButton1())
      return(false);
   if(!CreateButton2())
      return(false);
   if(!CreateTitleView())
      return(false);
   if(!CreateListView())
      return(false);
   if(!CreateTextView())
      return(false);
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el campo de visualización                                  |
//+------------------------------------------------------------------+
bool CRssReader::CreateEdit(void)
  {
//--- coordenadas
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+EDIT_HEIGHT;
//--- crear
   if(!m_edit.Create(m_chart_id,m_name+"Edit",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_edit.Text(“Introduzca la dirección web del canal Rss”))
      return(false);
   if(!m_edit.ReadOnly(false))
      return(false);
   if(!Add(m_edit))
      return(false);
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el botón 1                                                 |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton1(void)
  {
//--- coordenadas
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=x1+BUTTON_WIDTH;
  int pConnection
//--- crear
   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);
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el botón 2                                                 |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton2(void)
  {
//--- coordenadas
   int x1=(ClientAreaWidth()-INDENT_RIGHT)-BUTTON_WIDTH;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=ClientAreaWidth()-INDENT_RIGHT;
  int pConnection
//--- crear
   if(!m_button2.Create(m_chart_id,m_name+"Button2",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_button2.Text(“Actualizar el canal actual”))
      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);
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el campo de visualización                                  |
//+------------------------------------------------------------------+
bool CRssReader::CreateTitleView(void)
  {
//--- coordenadas
   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();
//--- crear
   if(!m_titleview.Create(m_chart_id,m_name+"TitleView",m_subwin,x1,y1,x2,y2))
     {
      Print(“Error al crear el título”);
      return(false);
     }
   else
     {
      for(int i=0;i<2;i++)
        {
         m_titleview.AddItem(" ");
        }
     }
   if(!Add(m_titleview))
     {
      Print(“error al añadir el título”);
      return(false);
     }
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el elemento "ListView"                                     |
//+------------------------------------------------------------------+
bool CRssReader::CreateListView(void)
  {
//--- coordenadas
   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;
//--- crear
   if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!Add(m_listview))
      return(false);
//--- llenamos con cadenas
   for(int i=0;i<20;i++)
      if(!m_listview.AddItem(" "))
         return(false);
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| Crear el campo de visualización                                  |
//+------------------------------------------------------------------+
bool CRssReader::CreateTextView(void)
  {
//--- coordenadas
   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();
//--- crear
   if(!m_textview.Create(m_chart_id,m_name+"TextArea",m_subwin,x1,y1,x2,y2))
     {
      Print(“Error al crear el área del texto”);
      return(false);
     }
   else
     {
      for(int i=0;i<1;i++)
        {
         m_textview.AddItem(" ");
        }
      m_textview.VScrolled(true);
      ChartRedraw();
     }
   if(!Add(m_textview))
     {
      Print(“error al añadir el área del texto”);
      return(false);
     }
//--- con éxito      
   return(true);
  }
//+------------------------------------------------------------------+
//|   cargar documento                                               |
//+------------------------------------------------------------------+
bool CRssReader::LoadDocument(string filename)
  {
   if(!m_xmldocument.loadXmlFromUrlWebReq(filename))
     {
      m_textview.ItemsClear();
      m_listview.ItemsClear();
      m_titleview.ItemsClear();
      CDialog::Caption(“Fallo al cargar el canal”);
      if(!m_textview.AddItem(m_xmldocument.GetErrorMsg()))
         Print(“no se puede mostrar el mensaje del error”);
      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);
  }
//+--------------------------------------------------------------------+
//| la función calcula el número de las etiquetas item en el documento |
//+--------------------------------------------------------------------+
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);
  }
//+-----------------------------------------------------------------------+
//| liberar el árbol del documento y resetear los valores de los punteros |
//+-----------------------------------------------------------------------+ 
void CRssReader::FreeDocumentTree(void)
  {
   ChannelNode.Children().Shutdown();
   ArrayFree(ChannelChildNodes);
   RssNode.Children().Shutdown();
   m_xmldocument.Clear();
   m_shift=0;
   RssNode=NULL;
   ChannelNode=NULL;
  }
//+------------------------------------------------------------------+
//| obtener el título del canal                                      |
//+------------------------------------------------------------------+
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;
        }
     }
//--- valor devuelto
   return(ret);
  }
//+------------------------------------------------------------------+
//| visualización del título                                         |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| visualización de la descripción                                  |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| visualización de la fecha                                        |
//+------------------------------------------------------------------+ 
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);
  }
//+------------------------------------------------------------------+
//| formateo de la cadena a mostrar en el área del texto             |
//+------------------------------------------------------------------+
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);}
//--- con éxito
   return(true);
  }
//+------------------------------------------------------------------+
//| eliminar signos especiales                                       |
//+------------------------------------------------------------------+ 
string CRssReader::removeSpecialCharacters(string s_tring)
  {
   string n=s_tring;
   StringReplace(n,"&amp;","&");
   StringReplace(n,"&#39;","'");
   StringReplace(n,"&nbsp;"," ");
   StringReplace(n,"&ldquo;","\'");
   StringReplace(n,"&rdquo;","\'");
   StringReplace(n,"&quot;","\"");
   StringReplace(n,"&ndash;","-");
   StringReplace(n,"&rsquo;","'");
   StringReplace(n,"&gt;","");
   return(n);
  }
//+------------------------------------------------------------------+
//| eliminar etiquetas                                               |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| posiciones de etiquetas                                          |
//+------------------------------------------------------------------+
int CRssReader::tagPosition(string _string,int w)
  {
   int iClose=-1;
   if(StringCompare("<",StringSubstr(_string,w,StringLen("<")))==0)
     {
      iClose=StringFind(_string,">",w+StringLen("<"));
     }
   return(iClose);
  }
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
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;
        }
     }
  }
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CRssReader::OnObjectEdit(void)
  {
   string f=m_edit.Text();
   if(StringLen(f)>0)
     {
      if(ArraySize(ChannelChildNodes)<1)
        {
         CDialog::Caption(“Cargando...”);
         if(LoadDocument(f))
           {
            if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
               Print(“error al cambiar el título”);
            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(“no se puede añadir la entrada en el área de la lista”);
                     return;
                    }
                 }
              }
            else
              {
               Print(“el área del texto/lista no está vaciada”);
               return;
              }
           }
         else return;
        }
      else
        {
         FreeDocumentTree();
         CDialog::Caption(“Cargando el nuevo canal RSS...”);
         if(LoadDocument(f))
           {
            if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
               Print(“error al cambiar el título”);
            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(“no se puede añadir la entrada en el área de la lista”);
                     return;
                    }
                 }
              }
            else
              {
               Print(“el área del texto/lista no está vaciada”);
               return;
              }
           }
         else return;
        }
     }
   else return;
  }
//+------------------------------------------------------------------+
//| El manejador de eventos  actualiza la ventana de la aplicación   |
//+------------------------------------------------------------------+   
void CRssReader::OnClickButton1(void)
  {
   if(ArraySize(ChannelChildNodes)<1)
     {
      if(!m_edit.Text(“Introduzca la dirección web del canal Rss”))
         Print(“error al editar el texto”);
      if(!CDialog::Caption("RSSReader"))
         Print(“error al cambiar el título”);
      if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
        {
         for(int i=0;i<20;i++)
           {
            if(!m_listview.AddItem(" "))
               Print(“error al añadir la lista”);
           }
         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(“Introduzca la dirección web del canal Rss”))
         Print(“error al editar el texto”);
      if(!CDialog::Caption("RSSReader"))
         Print(“error al cambiar el título”);
      if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
        {
         for(int i=0;i<20;i++)
           {
            if(!m_listview.AddItem(" "))
               Print(“error al añadir la lista”);
           }
         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;
        }
     }
  }
//+------------------------------------------------------------------+
//| El manejador de eventos  actualiza el canal actual               |
//+------------------------------------------------------------------+ 
void CRssReader::OnClickButton2(void)
  {
   string f=m_rssurl;
   if(ArraySize(ChannelChildNodes)<1)
      return;
   else
     {
      FreeDocumentTree();
      CDialog::Caption(“Comprobando las actualizaciones del canal RSS...”);
      if(LoadDocument(f))
        {
         if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
            Print(“error al cambiar el título”);
         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(“no se puede añadir la entrada en el área de la lista”);
                  return;
                 }
              }
           }
         else
           {
            Print(“el área del texto/lista no está vaciada”);
            return;
           }
        }
      else return;
     }
  }
Ahora se puede usarlo en el código del EA.


2.11. Código del Asesor Experto

Puesto que la aplicación tiene que ser totalmente interactiva, el EA no tiene parámetros de entrada.

Primero, declaramos una variable global que es una instancia de la clase CRssReader. En la función OnInit() inicializamos la ventana de la aplicación usando el método principal Create(). En caso del éxito, se llama al método Run() de la clase padre.

Para eliminar la aplicación y el EA del gráfico, en la función OnDeinit() se invoca el método Destroy() de la clase padre.

La función OnChartEvent() contiene la llamada al método heredado de la clase CRssReader que procesa todos los eventos.

//Aquí se empieza el código del Asesor Experto
//+------------------------------------------------------------------+
//| Variables globales                                               |
//+------------------------------------------------------------------+
CRssReader ExtDialog;
//+------------------------------------------------------------------+
//| Función de inicialización del Asesor Experto                     |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- crear diálogo de la aplicación
   if(!ExtDialog.Create(0,"RSSReader",0,20,20,518,394))
      return(INIT_FAILED);
//--- trabajo de la aplicación
   ExtDialog.Run();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Función para deinicializar el Asesor Experto                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   ExtDialog.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| Función ChartEvent                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }

A continuación, compilamos el código y el programa está listo para el uso.

Al iniciar el EA RssReader.mq5, en el gráfico aparece la siguiente ventana vacía:

Fig. 2. Captura de pantalla de la ventana de la aplicación del EA RssReader

Fig. 2. Captura de pantalla de la ventana de la aplicación del EA RssReader

Introduzca la dirección del sitio web y en la ventana de diálogo se cargará el contenido RSS (ver fig. de abajo):

Fig. 3. Asesor Experto RssReader funcionando en el terminal

Fig. 3. Asesor Experto RssReader funcionando en el terminal

Durante el testeo del programa se utilizaron diferentes canales RSS. El único problema observado estaba relacionado con la visualización de algunos símbolos no deseados, generalmente debido al hecho de que los documento RSS contenían los caracteres habituales para los documentos HTML.

Además, noté que el cambio del período del gráfico durante el funcionamiento de la aplicación provocaba el reinicio del EA y llevaba al diseño incorrecto de los elementos del control.

No había manera de corregir esta situación. Así que procuren no cambiar el período del gráfico cuando el RSS Reader esté funcionando.


Conclusión

Pues, hemos terminado de crear la aplicación totalmente interactiva RSS Reader para MetaTrader 5 usando las técnicas de la programación orientada a objetos.

Hay muchos otros recursos que se puede añadir en la aplicación, y sin duda existen muchas otras maneras de organizar la interfaz de usuario. Tal vez, el que domina mejor el diseño de las interfaces de usuario pueda mejorar esta aplicación y compartir sus éxitos con los demás.

P.S. Por favor, recuerden que el archivo adjunto easyxml.mqh se diferencia del archivo disponible en Code Base. Nuestro archivo contiene las modificaciones mencionadas en el artículo. Todas las inclusiones necesarias se encuentran en el archivo RssReader.zip.


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/1589

Archivos adjuntos |
easyxml.mqh (25.66 KB)
easyxmlnode.mqh (7.67 KB)
ListViewArea.mqh (19.89 KB)
TextArea.mqh (13.47 KB)
TitleArea.mqh (13.56 KB)
RssReader.mq5 (27.83 KB)
RssReader.zip (20.49 KB)
Pronóstico One-Step-Ahead de la econometría EURUSD Pronóstico One-Step-Ahead de la econometría EURUSD
El artículo se centra en la previsión de step-ahead para EURUSD utilizando software EViews y una evaluación adicional de la predicción de resultados en los programas de EViews. La previsión consiste en modelos de regresión y se evalúa por medio de un Asesor Experto para MetaTrader 4.
Líneas de tendencia basadas en los fractales usando MQL4 y MQL5 Líneas de tendencia basadas en los fractales usando MQL4 y MQL5
En este artículo se describe la solución de automatización del proceso de la construcción de las líneas de tendencia a base del indicador Fractals usando MQL4 y MQL5. La estructura del artículo está representada como la comparación en el marco de la solución del problema planteado desde las posiciones de dos lenguajes. La construcción de las líneas de tendencia se realiza usando dos últimos fractales conocidos.
Técnica (Optimización) de Prueba y algunos criterios para la selección de los parámetros del Asesor Experto Técnica (Optimización) de Prueba y algunos criterios para la selección de los parámetros del Asesor Experto
No hay ningún problema en encontrar el Santo Grial de la prueba, sin embargo es mucho más difícil deshacerse de él. Este artículo aborda la selección de parámetros de funcionamiento del EA un con grupo automatizado de procesos de optimización y prueba de resultados con máxima utilización de las capacidades de rendimiento del Terminal y mínima carga del usuario final.
Recetas de MQL5 - implementamos el array asociativo o el diccionario para el acceso rápido a los datos Recetas de MQL5 - implementamos el array asociativo o el diccionario para el acceso rápido a los datos
En este artículo se describe un algoritmo especial que permite acceder de manera eficaz a los elementos usando su clave única. Como clave se puede utilizar cualquier tipo básico de datos, por ejemplo, las cadenas o variables de números enteros. Este contenedor de datos suelen llamarlo el diccionario o array asociativo. La solución de muchas tareas con su ayuda resulta más simple y eficaz.