请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364694 新帖:15

在MetaTrader5中创建交互应用来展现RSS订阅

汇市风云榜发表于:4 月 17 日 16:49回复(1)

目录

  • 简介
  • 1. RSS文档资料
  • 2. 应用的总体架构
    • 2.1. 用户界面
    • 2.2. 代码实现
    • 2.3. 简单的XML解析器
    • 2.4. EA代码
    • 2.5. 控件初始化方法
    • 2.6. RSS文档处理方法
      • 2.6.1. LoadDocument()
      • 2.6.2. ItemNodesTotal()
      • 2.6.3. FreeDocumentTree()
    • 2.7. 从文件树中提取信息的方法
      • 2.7.1. getChannelTitle()
      • 2.7.2. getTitle()
    • 2.8. 格式化文本的方法
      • 2.8.1. FormatString()
      • 2.8.2. removeTags()
      • 2.8.3. removeSpecialCharacters()
      • 2.8.4. tagPosition()
    • 2.9. 处理独立控件事件的方法
      • 2.9.1. OnChangeListView()
      • 2.9.2. OnObjectEdit()
      • 2.9.3. OnClickButton1/2()
    • 2.10. CRssReader类的实现
    • 2.11. EA代码
  • 总结

简介

文章"通过MQL4语言读取RSS新闻"介绍了一种基本的脚本,它使用简单的库在终端中来展现RSS订阅,最初是用于解析HTML文档的。

随着MetaTrader 5和MQL5变成语言的出现,我认为创建互动应用来展现RSS内容将成为可能。本文描述如何使用MQL5标准类库以及MQL5社区会员开发的一些工具来制作此应用。


1. RSS文档资料

在我们详细讨论此应用的细节前,我觉得有必要回顾一下RSS文档的总体架构。

为了理解下面的描述,你需要熟悉可扩展标记语言及相关概念。如果你对XML文档不熟悉的话,请参考XML教程。请注意在本文中,node代表XML文档中的一个标签。如上述提到的MQL4文章,RSS文件是带特定标签结构的XML文档。

每个RSS文档都有一个全局容器,即RSS标签。这是RSS文档的共同点。频道标签是RSS标签的直接继承者。它包含网站和源的描述信息。由此,RSS文档可根据其包含的特定标签的不同而不同,当时有一些标签是所有RSS文档必须具备的,用以标识其为RSS文件。

所需的标签有:

  • title - 频道的主题。要包含网站的名称;
  • link - 提供该频道的网站URL;
  • description - 网站简介;
  • item - 至少有一个内容标签。

以上标签必须是频道标签的子节点。项目节点包含同特定内容相关的数据。

每一个项目节点都包含如下标签:

  • title - 内容的主题;
  • link - 内容的URL链接;
  • description - 内容简介;
  • date - 网站发布的数据内容。

所有的RSS文档都包含所述标签并具备相同结果。

一个完整的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. 应用的总体架构

在这里我给出RSS阅读器要展示的信息描述以及该应用程序的用户图形界面。

应用程序首先要展示频道标题,它包含在标题标签中。此信息将用于作为网站源的参照。

这个应用也应能展示源所描述的所有内容的截图。这涉及文档中所有项的标签。对于每一个标签,内容的标题都将展现。最后,我想让RSS阅读器展现内容的描述,即包含在每个标签描述中的内容。


2.1. 用户界面

用户界面是应用程序用于展现信息的函数。

关于用户界面的想法下图能够最好的表达。


应用程序会话框架

图 1. 应用程序会话框架


下图展现了构成银狐界面的不用模块。

  • 首先是标题栏。频道标题在此展现;
  • 输入区域。用户在此输入RSS源的网址;
  • 标题区域。特定内容的标题在此展现;
  • 文本区域。内容的描述在此展现;
  • 列表区域。此滚动列表将显示源所包含的所有内容的标题;
  • 左侧的按钮重置和清空显示在标题,文本和列表区域的文本;
  • 更新当前源的按钮检索当前加载的源的最新更新内容。

RSS阅读器工作原理如下——当程序加载到图表后,空白的程序对话框将显示,用户在输入区域键入想要的RSS源网址,然后按回车。这将加载所有内容标题,例如,加载每个项目标签的标题到列表视图区。列表从1开始,1代表最新发布的内容。

每个列表项都是可以点击的,点击每个列表项,将突出显示并且相应的标题描述内容会显示在文本区域。与此同时,内容标题将更为清晰的显示在标题区域。如果不管什么原因在加载源时发生了错误,报错信息将会显示在文本区域。

重置按钮将能用于清除任何文本区域、列表展示区域及标题区域的内容。

更新当前源将检查源的任何最新更新。


2.2. 代码实现

RSS阅读器将被实现为一个EA,并且使用MQL5标准类库。

代码将被包含在CRssReader类中,它是CAppDialog类的子类。CAppDialog类在Dialog.mqh文件中给出,提供标题栏功能,以及控制最大化、最小化和关闭等应用程序对话实现。这将是用户界面的基础,在此之上其他模块将被加入。增加的部分是:标题区域,文本区域,列表展示区域及按钮。每一个都是一个控件。按钮将实现为控件,如Button.mqh文件中所描述的。

ListViewArea.mqh文件中的列表展示控件将用于构建RSS阅读器的可视列表区域。

编辑控件显然用于构成输入区域,此控件在Edit.mqh文件中定义。

标题和文本区域的实现是一个挑战。问题在于两者都必须支持多行显示的文本。MQL5中的文本对象无法识别换行符。另一个问题是一行文本对象中只能显示一小部分字符。也就意味着如果创建一个长描述的文本对象,那么对象将显示截断的本文,只能显示特定数量的一些字符。通过尝试和报错信息我发现只能显示63个字符,包括空格和标点符号。

为了克服这些困难,我决定这两个地方都使用修正的列表可视化控件。针对标题区域,修正的列表控件将不能滚动,并且固定列表项数量(2)。每个列表项都不能点击或者可选,控件看上去不像列表。这2个列表项展现为两行文本。如果文本太长一行放不下,将会被分割成2行显示。标题区域的控件在TitleArea.mqh文件中定义。

对于文本区域,应用类似的方式。此时列表项的数量将是动态的,修改后的列表控件将是可垂直滚动的。此控件在TextArea.mqh文件中给出。

此处提到的类库都是用于处理用户界面的。在此还有一个程序用到的非常重要的类库需要介绍。就是用于解析XML文档的类库。


2.3. 简单的XML解析器

因为一个RSS文档是一个XML文件,由liquinaut开发的EasyXML - XML 解析器类库能够在代码库中找到,将被用于应用程序。

这个类库非常广泛,涵盖了几乎所有RSS阅读器所需的功能。我在原始类库上做了修改,加了些我认为必要的额外特性。

这些都是次要的补充。第一个额外附加的方法是loadXmlFromUrlWebReq()。这个方法是替代loadXmlFromUrl()方法,loadXmlFromUrl()依赖于WinInet库来处理网页请求,loadXmlFromUrlWebReq()则使用内嵌的WebRequest()函数来从因特网上做下载。

//+------------------------------------------------------------------+
//| 使用MQL5webrequest函数加载给定url的xml                              |
//+------------------------------------------------------------------+
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);
//--- 检查报错
   if(res==-1)
     {
      Err=EASYXML_ERR_WEBREQUEST_URL;
      return(Error());
     }
//-- 下载文件成功
   sStream=CharArrayToString(result,0,-1,CP_UTF8);
//---设置缓存文件
   if(blSaveToCache)
     {
      bool bResult=writeStreamToCacheFile(sStream);
      if(!bResult) Error(-1,false);
     }
//---
   return(loadXmlFromString(sStream));
  }

第二个附加的方法是GetErrorMsg(),当错解析器报错时可以获取报错信息。

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

最后一个附加方法是用于修正一个严重的缺陷,我在测试easyxml解析器时发现的。

我发现这个类库无法识别XML类型的表的声明。代码中弄错了样式表的一个属性声明。这导致程序陷入死循环,代码不断的查找不存在的属性值。

对skipProlog()方法进行下小修改就能更正。

//+------------------------------------------------------------------+
//| 跳过 xml 头                                                       |
//+------------------------------------------------------------------+
bool CEasyXml::skipProlog(string &pText,int &pPos)
  {
//--- 跳过 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);
        }
     }
//--- 跳过样表声明
   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);
        }
     }
//--- 跳过备注
   if(!skipWhitespaceAndComments(pText,pPos,"")) return(false);

//--- 跳过 文档类型
   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);
        }
     }

//--- 跳过备注
   if(!skipWhitespaceAndComments(pText,pPos,"")) return(false);

   return(true);
  }

除了上述问题,liquinaut的代码无需改动,easyxml.mqh是出色的工具。


2.4. EA代码

至此,所有应用程序所需的类库已经准备就绪,是时候将这些组件集成起来定义CRssReader类了。

请注意,RSS阅读器EA代码将从定义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>
//+------------------------------------------------------------------+
//| 定义                                                              |
//+------------------------------------------------------------------+
//--- 缩进和间隔
#define INDENT_LEFT                         (11)      // 左边距(留出边界宽度)
#define INDENT_TOP                          (11)      // 顶边距(留出边界宽度)
#define INDENT_RIGHT                        (11)      // 右边距(留出边界宽度)
#define INDENT_BOTTOM                       (11)      // 底边距(留出边界宽度)
#define CONTROLS_GAP_X                      (5)       // 间距 X轴坐标
#define CONTROLS_GAP_Y                      (5)       // 间距 Y轴坐标

#define EDIT_HEIGHT                         (20)      // Y轴坐标尺寸
#define BUTTON_WIDTH                        (150)     // X轴坐标尺寸
#define BUTTON_HEIGHT                       (20)      // Y轴坐标尺寸
#define TEXTAREA_HEIGHT                     (131)     // Y轴坐标尺寸
#define LIST_HEIGHT                         (93)      // Y轴坐标尺寸

从包含必要的文件开始。该定义指令用于设置控件像素的物理参数。

INDENT_LEFT, INDENT_RIGHT, INDENT_TOP 和 INDENT_DOWN 设置控件和应用程序对话框间的间距。

  • CONTROLS_GAP_Y 是控件间的垂直距离;
  • EDIT_HEIGHT 设置构成输入区域的Edit控件的高度;
  • BUTTON_WIDTH 和 BUTTON_HEIGHT 定义所有按钮控件的高度;
  • TEXTAREA_HEIGHT 是文本区域的高度;
  • LIST_HEIGHT 设置列表控件的高度。

定义完我们开始定义CRssReader类。

//+------------------------------------------------------------------+
//| CRssReader类                                                     | 
//| 用途:RSS应用主类                                                   |
//+------------------------------------------------------------------+
class CRssReader : public CAppDialog
  {
private:
   int               m_shift;                    // 第一个向目标标记的索引
   string            m_rssurl;                   // 最新源的网址拷贝 
   string            m_textareaoutput[];         // 用于输出到文本区域的字符串数组
   string            m_titleareaoutput[];        // 用于输出到标题区域的字符串数组
   CButton           m_button1;                  // 按钮对象
   CButton           m_button2;                  // 按钮对象      
   CEdit             m_edit;                     // 输入区域
   CTitleArea        m_titleview;                // 显示区域对象
   CListViewArea     m_listview;                 // 列表对象
   CTextArea         m_textview;                 // 文本区域对象
   CEasyXml          m_xmldocument;              // xml文档对象
   CEasyXmlNode     *RssNode;                    // 根节点对象
   CEasyXmlNode     *ChannelNode;                // 频道节点对象
   CEasyXmlNode     *ChannelChildNodes[];        // 频道子节点对象数组

如前所述CRssReader继承自CAppDialog类。

这个类有几个私有属性如下:

  • m_shift - 这个整型变量存储ChannelChildnodes数组中第一个项目节点的索引;
  • m_rssurl - 是一个字符串,它保存作为输入参数的最新URL的副本。
  • m_textareaoutput[] - 是一个字符串数组,每个元素对应一行字符数量一定的文本。
  • m_titleareaoutput[] - 也是一个字符串数组,用处同前一个字符串数组类似。
  • m_button1 和 m_button2 是CButton类型的对象;
  • m_listview是列表控件的对象;
  • m_edit 是CEdit对象,实例化输入区域;
  • m_titleview 是标题区域的对象;
  • m_textview - 文本区域对象;
  • m_xmldocument 是xml文档对象;
  • RssNode 是根节点对象;
  • ChannelNode 是频道节点对象;
  • ChannelChildNodes数组是一系列指针,指向Channel标记的直接继承者。

我们的类仅仅有两个公共方法。

public:
                     CRssReader(void);
                    ~CRssReader(void);
   //--- 创建
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- 图表事件处理函数
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

第一个方法Create()设置应用程序对话框的大小和初始位置。

它也初始化所有RSS阅读程序的控件(记住我们的类从 CAppDialog类继承而来,因此父类的公共方法和它的子类能够被实例CRssReader调用)。

//+------------------------------------------------------------------+
//| 创建                                                              |
//+------------------------------------------------------------------+
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);
//--- 创建独立控件
   if(!CreateEdit())
      return(false);
   if(!CreateButton1())
      return(false);
   if(!CreateButton2())
      return(false);
   if(!CreateTitleView())
      return(false);
   if(!CreateListView())
      return(false);
   if(!CreateTextView())
      return(false);
//--- 成功
   return(true);
  }

其次是OnEvent()方法,此函数为控件和处理函数分配相应的特定事件来实现交互。

//+------------------------------------------------------------------+
//| 事件处理                                                          |  
//+------------------------------------------------------------------+
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. 控件初始化方法

CreateEdit(), CreateButton1() ,CreateButton2(), CreateTitleView(), CreateListView() 和 CreateTextView() 保护方法被 Create()调用,用以初始化相应的控件。

protected:
   // --- 创建控件
   bool              CreateEdit(void);
   bool              CreateButton1(void);
   bool              CreateButton2(void);
   bool              CreateTitleView(void);
   bool              CreateListView(void);
   bool              CreateTextView(void);

正是在这些函数中设置控件的大小,位置和属性(例如 字体,字体大小,颜色,边界颜色,边界类型)。

//+------------------------------------------------------------------+
//| 创建展示区域                                                       | 
//+------------------------------------------------------------------+
bool CRssReader::CreateEdit(void)
  {
//--- 坐标
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+EDIT_HEIGHT;
//--- 创建
   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);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建按钮 1                                                        |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton1(void)
  {
//--- 坐标
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=x1+BUTTON_WIDTH;
   int y2=y1+BUTTON_HEIGHT;
//--- 创建
   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);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建按钮 2                                                        |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton2(void)
  {
//--- 坐标
   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;
//--- 创建
   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);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建展示区域                                                       | 
//+------------------------------------------------------------------+
bool CRssReader::CreateTitleView(void)
  {
//--- 坐标
   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();
//--- 创建 
   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);
     }
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建"ListView"组建                                                |
//+------------------------------------------------------------------+
bool CRssReader::CreateListView(void)
  {

//--- 坐标
   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;
//--- 创建
   if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!Add(m_listview))
      return(false);
//--- 用字符填充
   for(int i=0;i<20;i++)
      if(!m_listview.AddItem(" "))
         return(false);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建展示区域                                                       | 
//+------------------------------------------------------------------+
bool CRssReader::CreateTextView(void)
  {
//--- 坐标
   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();
//--- 创建 
   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);
     }
//----成功      
   return(true);
  }


2.6. RSS文档处理方法

// --- rss文档处理
   bool              LoadDocument(string filename);
   int               ItemNodesTotal(void);
   void              FreeDocumentTree(void);

2.6.1. LoadDocument()

此函数有不少重要的用处。主要的一个就是处理网站请求。loadXmlFromUrlWebReq()用于下载RSS文件。

如果成功,函数执行第二步操作初始化RssNode,ChannelNode指针并填充ChannelChildnodes数组。正是在这里设置m_rssurl和m_shift指针。所有这些完成后,函数返回true。

如果RSS文件无法下载,那么标题区域、列表区域以及文本区域中的文本将被清除,并且在标题栏中展示状态信息。随其后的是在文本区域输出的报错信息。函数返回false。

//+------------------------------------------------------------------+
//|   加载文档                                                        |
//+------------------------------------------------------------------+
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()

此函数用于LoadDocument()方法中。它返回继承于频道标签的项目节点数量的整型值。

如果没有项目节点,将为无效的RSS文档并且函数返回0.

//+------------------------------------------------------------------+
//|  统计文档中标签项数量的函数                                          |     
//+------------------------------------------------------------------+
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()

此函数重置所有CEasyXmlNode指针。

首先通过调用CArrayObj类的Shutdown()方法删除ChannelChildnodes数组的元素。然后调用一次ArrayFree(),该数组被释放。

接着频道节点的指针被删除,easyxml解析器的文档树被清除。这些操作导致RssNode和ChannelNode指针变成空指针,这也是他们被分配NULL值的原因。

//+------------------------------------------------------------------+
//|  释放文档树并重置指针值                                             |
//+------------------------------------------------------------------+ 
void CRssReader::FreeDocumentTree(void)
  {
   ChannelNode.Children().Shutdown();
   ArrayFree(ChannelChildNodes);
   RssNode.Children().Shutdown();
   m_xmldocument.Clear();
   m_shift=0;
   RssNode=NULL;
   ChannelNode=NULL;
  }


2.7. 从文档树中提取信息的方法

这些函数用于从RSS文档中获取文本。

//--- 获取函数
   string            getChannelTitle(void);
   string            getTitle(CEasyXmlNode *Node);
   string            getDescription(CEasyXmlNode *Node);
   string            getDate(CEasyXmlNode *Node);

2.7.1. getChannelTitle()

该函数获取RSS文档的当前频道的标题。

它从检查频道节点的指针有效性开始。如果指针有效,它循环搜索所有频道节点的直接继承者,查找主题标签。

for循环使用m_shift属性来限制循环搜索的频道节点数量。如果函数不成功返回NULL。

//+------------------------------------------------------------------+
//| 获取频道标题                                                       |
//+------------------------------------------------------------------+
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()

该函数将输入指针指向一个项目标签,并遍历该标签的继承对象,查找主题标签并返回其值。

getDescription() 和 getDate() 函数有着相同的格式并且运行方式类似。成功调用该函数返回一个字符串,否则返回NULL。

//+------------------------------------------------------------------+
//| 显示标题                                                          |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| 显示描述                                                          |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| 显示日期                                                          |
//+------------------------------------------------------------------+ 
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. 格式化文本的方法

这些函数用于为输出的文本对象准备文本,以便克服一些文本对象所面临的限制。

 //--- 文本格式 
   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()

这个是从RSS文档中提取文档输出给应用程序的主要函数。

它的主要作用是以字符串作为输入参数,将文本分割为"n"个字符一行。"n" 是每一行文本的字符个数。在文本中的每"n"个字符后,代码查找合适的位置来插入新的一行。接着真个字符串都处理完毕,新的一行源字符被插入到初始文本位置。

StringSplit()函数用于创建字符串数组,每个元素不超过"n"个字符。此函数返回一个布尔型值以及一个字符串作为输出。

//+------------------------------------------------------------------+
//|  文本区域面板输出字符格式化                                          |
//+------------------------------------------------------------------+
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);}
// 成功 
   return(true);
  }


2.8.2. removeTags()

当我注意到许多RSS文档在XML节点中包含HTML标记时,此函数变为必要的了。

一些RSS文档以这种方式发布,许多RSS聚合应用在浏览器中运行。

此函数以字符串作为参数,在文本中查找标记。如果在文本中找到任何标记,将标记字符的开始和结束位置保存在2维数组a[][]中。这个数组用于提取标记间的文本,返回被提取的字符串。如果没有找到标记,输入字符串被返回。

//+------------------------------------------------------------------+
//| 移除标签                                                          |
//+------------------------------------------------------------------+
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);
  }

此种文档的部分样例如下。

<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()

此函数将特定的字符串替换为正确的字符。

例如,xml中的ampersand字符串可以表示成"&amp"。当出现这类情况时,此函数使用内置StringReplace()函数来替换。

//+------------------------------------------------------------------+
//| 移除特殊的字符                                                     |
//+------------------------------------------------------------------+ 
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()

此函数是一个辅助函数,在函数removeTags()中被调用。它的输入为一个字符串和一个整型值。

输入的整型值代表字符串中字符的位置,函数从这个位置开始搜索标签的起始字符。例如,"<"。如果起始标记找到,然后函数开始查找结束标记并且u哦为输出返回对饮的结束标记字符 ">"的位置。如果没有找到标记则返回-1.

//+------------------------------------------------------------------+
//| 标签位置                                                          |
//+------------------------------------------------------------------+
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. 处理独立控件事件的方法

这些函数处理特定控件捕获的事件。

//--- 控件事件处理函数
   void              OnChangeListView(void);
   void              OnObjectEdit(void);
   void              OnClickButton1(void);
   void              OnClickButton2(void);
  };

2.9.1. OnChangeListView()

这是一个事件处理函数,当应用程序可视化列表区域的一个列表项被点击时被调用。

此函数用于RSS文档中某些内容摘要的可视化。

此函数清除文本及标题区域的任何文字,从文档树中获取新的数据并输出。所有这些仅在ChannelChildnodes数组不为空时发生。

//+------------------------------------------------------------------+
//| 事件处理                                                          |
//+------------------------------------------------------------------+
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()

当用户在输入区域输入文本结束后,此处理函数被调用。

函数调用LoadDocument()方法。如果下载成功,文本将从整个程序中清除。接下来,标题改变并且新的内容输出到列表区域。

//+------------------------------------------------------------------+
//| 事件处理                                                          |
//+------------------------------------------------------------------+
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()

当用户点击重置或源更新按钮时,这些处理函数被调用。

当EA首次加载时,点击重置按钮更新应用程序对话框。

点击“check for feed update”按钮触发回调LoadDocument()方法,RSS源数据将被下载,更新可视列表区域。

//+------------------------------------------------------------------+
//| 事件处理  更新程序对话框                                            |
//+------------------------------------------------------------------+   
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;
        }
     }
  }
//+------------------------------------------------------------------+
//| 事件处理  更新当前源                                               |
//+------------------------------------------------------------------+ 
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;
     }
  }

至此完成了CRssReader类的定义。


2.10. CRssReader类的实现

//+------------------------------------------------------------------+
//| CRssReader类                                                     | 
//| 用途:RSS应用主类                                                   |
//+------------------------------------------------------------------+
class CRssReader : public CAppDialog
  {
private:
   int               m_shift;                   // 第一个项目标记的索引
   string            m_rssurl;                  // 最新源的网址副本 
   string            m_textareaoutput[];        // 用于输出到文本区域的字符串数组
   string            m_titleareaoutput[];       // 用于输出到标题区域的字符串数组
   CButton           m_button1;                 // 按钮对象
   CButton           m_button2;                 // 按钮对象      
   CEdit             m_edit;                    // 输入区域
   CTitleArea        m_titleview;               // 显示区域对象
   CListViewArea     m_listview;                // 列表对象
   CTextArea         m_textview;                // 文本区域对象
   CEasyXml          m_xmldocument;             // xml文档对象
   CEasyXmlNode     *RssNode;                   // 根节点对象
   CEasyXmlNode     *ChannelNode;               // 频道节点对象
   CEasyXmlNode     *ChannelChildNodes[];       // 频道子节点对象数组

public:
                     CRssReader(void);
                    ~CRssReader(void);
   //--- 创建
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- 图表事件处理函数
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   // --- 创建控件
   bool              CreateEdit(void);
   bool              CreateButton1(void);
   bool              CreateButton2(void);
   bool              CreateTitleView(void);
   bool              CreateListView(void);
   bool              CreateTextView(void);
   // --- rss文档处理
   bool              LoadDocument(string filename);
   int               ItemNodesTotal(void);
   void              FreeDocumentTree(void);
   //--- 获取函数
   string            getChannelTitle(void);
   string            getTitle(CEasyXmlNode *Node);
   string            getDescription(CEasyXmlNode *Node);
   string            getDate(CEasyXmlNode *Node);
   //--- 文本格式 
   bool              FormatString(string v,string &array[],int n);
   string            removeTags(string _string);
   string            removeSpecialCharacters(string s_tring);
   int               tagPosition(string _string,int w);
   //--- 控件事件处理函数
   void              OnChangeListView(void);
   void              OnObjectEdit(void);
   void              OnClickButton1(void);
   void              OnClickButton2(void);
  };
//+------------------------------------------------------------------+
//| 事件处理                                                          |  
//+------------------------------------------------------------------+
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)
//+------------------------------------------------------------------+
//| 构造函数                                                          |
//+------------------------------------------------------------------+
CRssReader::CRssReader(void)
  {

  }
//+------------------------------------------------------------------+
//| 析构函数                                                          |
//+------------------------------------------------------------------+
CRssReader::~CRssReader(void)
  {
  }
//+------------------------------------------------------------------+
//| 创建                                                              |
//+------------------------------------------------------------------+
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);
//--- 创建独立控件
   if(!CreateEdit())
      return(false);
   if(!CreateButton1())
      return(false);
   if(!CreateButton2())
      return(false);
   if(!CreateTitleView())
      return(false);
   if(!CreateListView())
      return(false);
   if(!CreateTextView())
      return(false);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建展示区域                                                       | 
//+------------------------------------------------------------------+
bool CRssReader::CreateEdit(void)
  {
//--- 坐标
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+EDIT_HEIGHT;
//--- 创建
   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);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建按钮 1                                                        |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton1(void)
  {
//--- 坐标
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=x1+BUTTON_WIDTH;
   int y2=y1+BUTTON_HEIGHT;
//--- 创建
   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);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建按钮 2                                                        |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton2(void)
  {
//--- 坐标
   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;
//--- 创建
   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);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建展示区域                                                       | 
//+------------------------------------------------------------------+
bool CRssReader::CreateTitleView(void)
  {
//--- 坐标
   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();
//--- 创建 
   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);
     }
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建"ListView"组建                                                |
//+------------------------------------------------------------------+
bool CRssReader::CreateListView(void)
  {

//--- 坐标
   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;
//--- 创建
   if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!Add(m_listview))
      return(false);
//--- 用字符填充
   for(int i=0;i<20;i++)
      if(!m_listview.AddItem(" "))
         return(false);
//--- 成功
   return(true);
  }
//+------------------------------------------------------------------+
//| 创建展示区域                                                       | 
//+------------------------------------------------------------------+
bool CRssReader::CreateTextView(void)
  {
//--- 坐标
   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();
//--- 创建 
   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);
     }
//----成功      
   return(true);
  }
//+------------------------------------------------------------------+
//|   加载文档                                                        |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//|  统计文档中标签项数量的函数                                          |     
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//|  释放文档树并重置指针值                                             |
//+------------------------------------------------------------------+ 
void CRssReader::FreeDocumentTree(void)
  {
   ChannelNode.Children().Shutdown();
   ArrayFree(ChannelChildNodes);
   RssNode.Children().Shutdown();
   m_xmldocument.Clear();
   m_shift=0;
   RssNode=NULL;
   ChannelNode=NULL;
  }
//+------------------------------------------------------------------+
//| 获取频道标题                                                       |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| 显示标题                                                          |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| 显示描述                                                          |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| 显示日期                                                          |
//+------------------------------------------------------------------+ 
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);
  }
//+------------------------------------------------------------------+
//|  文本区域面板输出字符格式化                                          |
//+------------------------------------------------------------------+
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);}
// 成功 
   return(true);
  }
//+------------------------------------------------------------------+
//| 移除特殊的字符                                                     |
//+------------------------------------------------------------------+ 
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);
  }
//+------------------------------------------------------------------+
//| 移除标签                                                          |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| 标签位置                                                          |
//+------------------------------------------------------------------+
int CRssReader::tagPosition(string _string,int w)
  {
   int iClose=-1;
   if(StringCompare("<",StringSubstr(_string,w,StringLen("<")))==0)
     {
      iClose=StringFind(_string,">",w+StringLen("<"));
     }

   return(iClose);
  }
//+------------------------------------------------------------------+
//| 事件处理                                                           |
//+------------------------------------------------------------------+
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;
        }
     }
  }
//+------------------------------------------------------------------+
//| 事件处理                                                           |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| 事件处理  更新程序对话框                                            |
//+------------------------------------------------------------------+   
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;
        }
     }
  }
//+------------------------------------------------------------------+
//| 事件处理  更新当前源                                               |
//+------------------------------------------------------------------+ 
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;
     }
  }

现在它能被用于EA代码中了。


2.11. EA代码

EA没有输入参数,因为程序设计为完全互动的。

首先我们声明一个全局变量,它是CRssReader类的实例。在OnInit()函数中,我们调用Create()方法来初始化应用程序对话框。如果成功,一个父类的方法Run()将被调用。

在OnDeinit()函数中,父类的Destroy()方法被调用来删除整个应用并且将EA从图表上移除。

OnChartEvent()函数包含对CRssReader类的父类方法的调用,它将处理所有事件。

//EA代码从这里开始
//+------------------------------------------------------------------+
//| 全局变量                                                           |
//+------------------------------------------------------------------+
CRssReader ExtDialog;
//+------------------------------------------------------------------+
//| EA初始化函数                                                       |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 创建程序对话框
   if(!ExtDialog.Create(0,"RSSReader",0,20,20,518,394))
      return(INIT_FAILED);
//--- 运行程序
   ExtDialog.Run();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| EA反初始化函数                                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   ExtDialog.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| ChartEvent函数                                                    |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }

编译代码后程序就可以使用了。

当RssReader.mq5 EA加载到图表上,一个空白的对话框出现:

图 2. RssReader EA程序空白对话框截图

图 2. RssReader EA程序空白对话框截图

输入网址,RSS内容将在应用程序对话框中加载,如下图所示:

图 3. RssReader EA 在终端中运行

图 3. RssReader EA 在终端中运行

我使用大量的RSS源测试此程序。我观察到的仅有问题是,有时会显示出我不想要的字符,绝大多数时候RSS文档包含的字符都能在HTML文档中找到。

我还发现当程序运行时改变图表周期导致EA重新初始化,会使得程序的空间不能正确的绘制。

我没能过改正这个问题,因此我建议在RSS阅读器程序运行时不要改变图表的时间框架。


总结

至此,我们使用面向对象的编程技术,完成了创建完整的MetaTrader 5互动RSS阅读器的程序。

我相信会有更多的特性加入到程序中,并且用户界面也有更多的组织方式。我希望那些更好的应用程序GUI设计技巧的加入来进一步优化此应用程序。

P.S. 请注意这里提供下载的asyxml.mqh文件和Code Base中的不是同一个,它包含了文本已经提及的改动。所有需要的包含文件都在RssReader.zip文件中。


全部回复

0/140

量化课程

    移动端课程