
MetaTrader 5 でRSS フィードを表示するためのインタラクティブアプリケーション構築
コンテンツ
- はじめに
- 1. 一般的 RSS ドキュメント
- 2. アプリケーションの全体的構成
- おわりに
はじめに
記事"Reading RSS News Feeds by Means of MQL4"は、元々HTMLドキュメントをパースするために構築されたシンプルなライブラリによる端末のコンソールにRSS フィードを表示するのに使用できる初歩的なスクリプトを説明しました。
MetaTrader 5 と MQL5 プログラム言語の出現を得て、私は RSS コンテンツをよりよく表示できるインタラクティブアプリケーションを作成できるだろうと考えました。本稿は幅広いMQL5 標準ライブラリと MQL5 コミュニティの貢献者によって開発されたその他ツールを用いてこのアプリケーションを作成する方法を説明します。
1. 一般的 RSS ドキュメント
アプリケーションの特殊性に取り組む前に、RSS ドキュメントの一般的構成を概説する必要があると思います。
以降の説明を理解するために、拡張マークアップ言語および関連する内容についての知識を得る必要があります。ドキュメントの知識がない場合は XML 教本を参照してください。本稿ではノードは XMLドキュメントのタグを参照することに注意が必要です。上記で述べた MQL4 の記事にあるように、RSS ファイルは単に特別なタグ構成を持つ XMLドキュメントです。
RSS ドキュメントはすべてグローバルコンテナ、RSS タグを持ちます。これは全 RSSドキュメントに共通です。チャネルタグはつねに RSS タグの直接の継承者です。これはフィードが記述されているウェブサイトについての情報をおm値ます。ここから RSSドキュメントはそれが持つ特定のタグのについて異なることがありますが、検証された RSSファイルであるためにすべてのドキュメントがもつタグがいくつかあります。
以下が必要なタグです。
- タイトル -チャンネルのタイトルウェブサイト名を持ちます。
- リンク -このチャンネルを提供するウェブサイトの URL
- 説明-何にかんするウェブサイトであるかの概要
- 項目-コンテントに最低1つの項目タグ
上記のタグはすべてチャンネルタグの子ノードです。項目ノードは特定の内容に関連するデータを持つものです。
各項目ノードは今度は以下のタグを持つ必要があります。
- タイトル-内容のタイトル
- リンク-内容に対する URL リンク
- 説明-内容の概要
- 日付-内容がウェブサイトで好評された日付
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:<em>At Adobe they are lazy. They have the potential to make interesting things, but they refuse to do so. Apple does not support Flash because it is too buggy. Each time a Mac crashes, most often it is because of Flash. Nobody will use Flash. The world is moving to <a href="http://www.xul.fr/en/html5/" target="_parent">HTML 5</a></description> <pubDate>Sat, 11 Dec 10 09:41:06 +0100</pubDate> </item> <item> <title>Textured Border in CSS</title> <link>http://www.xul.fr/en/css/textured-border.php</link> <description> The border attribute of the style sheets can vary in color and width, but it was not expected to give it a texture. However, only a CSS rule is required to add this graphic effect... The principle is to assign a texture to the whole <em>fieldset</em> and insert into it another <em>fieldset</em> (for rounded edges) or a <em>div</em>, whose background is the same as that of the page</description> <pubDate>Wed, 29 Jul 09 15:56:54 0200</pubDate> </item> <item> <title>Create an RSS feed from SQL, example with Wordpress</title> <link>http://www.xul.fr/feed/rss-sql-wordpress.html</link> <description>Articles contain at least the following items: And possibly, author's name, or an image. This produces the following table: The returned value is true if the database is found, false otherwise. It remains to retrieve the data from the array</description> <pubDate>Wed, 29 Jul 09 15:56:50 0200</pubDate> </item> <item> <title>Firefox 3.5</title> <link>http://www.xul.fr/gecko/firefox35.php</link> <description>Les balises audio et vidéo sont implémentées. Le format de donnée JSON est reconnu nativement par Firefox. L'avantage est d'éviter l'utilisation de la fonction eval() qui n'est pas sûr, ou d'employer des librairies additionnelles, qui est nettement plus lent</description> <pubDate>Wed, 24 Jun 09 15:18:47 0200</pubDate> </item> <item> <title>Contestation about HTML 5</title> <link>http://www.xul.fr/en/html5/contestation.php</link> <description> Nobody seemed to be worried so far, but the definition of HTML 5 that is intended to be the format of billions of Web pages in coming years, is conducted and decided by a single person!<em>Hey, wait!Pay no attention to the multi-billions dollar Internet corporation behind the curtain. It's me Ian Hickson!I am my own man</description> <pubDate>Wed, 24 Jun 09 15:18:29 0200</pubDate> </item> <item> <title>Form Objects in HTML 4</title> <link>http://www.xul.fr/javascript/form-objects.php</link> <description> It is created by the HTML <em>form</em> tag: The name or id attribute can access by script to its content. It is best to use both attributes with the same identifier, for the sake of compatibility. The <em>action</em> attribute indicates the page to which send the form data. If this attribute is empty, the page that contains the form that will be charged the data as parameters</description> <pubDate>Wed, 24 Jun 09 15:17:49 0200</pubDate> </item> <item> <title>DOM Tutorial</title> <link>http://www.xul.fr/en/dom/</link> <description> The Document Object Model describes the structure of an XML or HTML document, a web page and allows access to each individual element.</description> <pubDate>Wed, 06 May 2009 18:30:11 GMT</pubDate> </item> </channel> </rss>
2. アプリケーションの全体的構成
ここに RSS リーダーが表示する情報とアプリケーションのグラフィカルユーザーインターフェースの記述を提供します。
アプリケーションが表示する最初の事柄はチャンネルタイトルで、それはタイトルタグに含まれます。この情報はフィードが参照するウェブサイトのインディケーションの役目をします。
アプリケーションはフィードが記述する内容すべてのスナップショットも表示します。これはドキュメント内の項目タグすべてに関連しています。各項目タグには内容のタイトルが表示されます。最後に RSS リーダーに内容の記述も表示してもらいたいと思います。これは各アイテムノードの記述タグにあるデータです。
2.1. ユーザーインターフェース
ユーザーインターフェースはアプリケーションにより表示される情報の関数です。
私がユーザーインターフェースに対して持つ考えは以下の図で一番よく表現されています。
図1 アプリケーション図のスケッチ
この図はユーザーインターフェースを作り上げる異なるセクションを示しています。
- 1番目はタイトルバーです。これはチャンネルタイトルが表示される場所です。
- 入力領域ユーザーがRSS フィードのウェブアドレスを入力するのはここです。
- タイトル領域特定の内容のタイトルがここに表示されます。
- テキスト領域内容の説明がここに表示されます。
- リストビュー領域このスクロールできるリストはフィードが持つ内容すべてのタイトルを表示します。
- 左側のボタンはタイトル、テキスト、リストビュー領域に表示されるテキストをリセットおよび消去します。
- 現フィード更新ボタンは今ロードされているフィードの新な更新を検索します。
RSS リーダーは以下のように動作します。-プログラムがチャートにロードされるとき、入力アプリケーションダイアログが表示され、ユーザーはそこから入力領域で必要な RSS フィードのウェブアドレスを入力し、エンターを押します。これは内容タイトルをすべてロードします。すなわち、リストビュー領域の項目タグに対するタイトルタグ値です。リストは1から番号がつけられ、これは直近に公表された内容を表します。
リストの項目はすべてクリックすることができ、クリックすると強調表示され、内容説明に対応しているリスト項目がテキスト領域に表示されます。同時にタイトル領域部で内容タイトルがよりはっきりと表示されます。なんらかの理由でフィードのロード中にエラーがおこれば、エラーメッセージがテキスト領域部に表示されます。
定期スト領域、リストビュー領域、タイトル領域の領域部でテキストを消去するにはリセットボタンを使用します。
現在のフィード更新は単に、現在のフィードについての更新を確認します。
2.2. コード実装
RSS リーダー Expert Advisor はとして実装され、MQL5 標準ライブラリが使用されます。
コードは CAppDialog クラスの継承クラスである CRssReader クラスにあります。ファイルで提供される CAppDialog クラスはアプリケーションダイアログの実装を可能にします。それはタイトルバーの機能性、最小最大化、クローズのアプリケーションコントロール機能を提供するものです。これはユーザーインターフェースの基本で、その上にその他セクションが追加されます。追加されるセクションとはすなわち、タイトル領域、テキスト領域、リストビュー領域、ボタンです。それぞれはコントロールとなります。ボタンはインクルードファイル Button.mqh に記述されるボタンコントロールとして実装されます。
インクルードファイル ListViewArea.mqh 内のリストビュー領域は RSS リーダーのリストビュー領域、を構成するのに使用されます。
エディット制御は入力領域部を作成するには明らかに十分で、この制御は Edit.mqh ファイルで決定されます。
タイトル領域部とテキスト領域部は実装に関してはユニークな課題を投げかけます。問題は両者が複数行に表示されるテキストをサポートする必要があることです。MQL5 のテキストオブジェクトは新しい改行文字を認識しません。もうひとつ別の問題はテキストオブジェクトの1行に限られた文字列の文字数しか表示されないことです。これは長い説明のテキストオブジェクトを作成する場合、テキストがちょん切られた形で表示され、一定数の文字しか出ないということです。試行錯誤の末、文字数制限はスペースや句読点を含む 63 文字であることがわかりました。
こういった問題を解決するため、両セクションを変更したリストビューコントロールとして実装することにしました。タイトル領域部に対しては、変更したリストビューコントロールはスクロールできず、リスト項目数は固定(2)されることとなります。リスト項目はすべてクリックできない、または選択できず、コントロールの物理的外観はリストのように見えません。これら2つのリスト項目はテキストの2行を表します。テキストが長すぎて1行に収まらないと、適切に分割され2行のテキストとして表示されます。タイトル領域部に対するコントロールはTitleArea.mqh ファイルで決定されます。
テキスト領域部に対しては、同様の方法が行われますが、この時はリスト項目数は変えることができ、変更されたリストビューコントロールは上下にスクロールすることができます。このコントロールは TextArea.mqh ファイルで行われます。
前述のライブラリがユーザーインターフェースを管理するものです。もうひとつ、このアプリケーションにとって重要な、お話すべきライブラリがあります。これは XML ドキュメントをパースするのに使用されるライブラリです。
2.3. 簡単な XML パーサー
RSS ドキュメントは XML ファイルであるため、 liquinaut により作成されたEasyXML - XML Parser ライブラリとして「コードべース」にあるものが使用されます。
このライブラリはひじょうに幅広く、 RSS リーダーに必要な機能性がほとんどすべて入っています。私は必要だと感じた機能をいくつか追加し、元のライブラリをすこし変更しました。
たいした追加ではありません。まず追加したのは、loadXmlFromUrlWebReq() と呼ばれるメソッドです。このメソッドは単に loadXmlFromUrl() の代替です。これはウェブリクエストを処理する WinInet ライブラリに依存し、loadXmlFromUrlWebReq() はインターネットからのダウンロードを行うために内蔵の WebRequest() 関数を使用します。
//+------------------------------------------------------------------+ //| load xml by given url using MQL5 webrequest function | //+------------------------------------------------------------------+ bool CEasyXml::loadXmlFromUrlWebReq(string pUrl) { //--- string cookie=NULL,headers; char post[],result[]; int res; //--- string _url=pUrl; string sStream; res=WebRequest("GET",_url,cookie,NULL,5000,post,0,result,headers); //--- check error if(res==-1) { Err=EASYXML_ERR_WEBREQUEST_URL; return(Error()); } //---success downloading file sStream=CharArrayToString(result,0,-1,CP_UTF8); //---set up cach file if(blSaveToCache) { bool bResult=writeStreamToCacheFile(sStream); if(!bResult) Error(-1,false); } //--- return(loadXmlFromString(sStream)); }
次に追加したのは GetErrorMsg() メソッドで、これによりエラー発生時、パーサーによりエラーメッセージを出力することができます。
string GetErrorMsg(void){ return(ErrMsg);}
最後に、easyxml パーサーを検証したときに見つけた深刻な欠点を修正しました。
ライブラリはXML スタイルシート宣言を認識することができないことに気づいたのです。コードは属性に対するスタイルシート宣言を誤っています。これによりプログラムが無限ループにはまりこんでしまいました。コードが存在しない対応する属性値を探しつづけるためです。
ほんの少しskipProlog() メソッドを変更したら、これは簡単に修正されました。
//+------------------------------------------------------------------+ //| skip xml prolog | //+------------------------------------------------------------------+ bool CEasyXml::skipProlog(string &pText,int &pPos) { //--- skip xml declaration if(StringCompare(EASYXML_PROLOG_OPEN,StringSubstr(pText,pPos,StringLen(EASYXML_PROLOG_OPEN)))==0) { int iClose=StringFind(pText,EASYXML_PROLOG_CLOSE,pPos+StringLen(EASYXML_PROLOG_OPEN)); if(blDebug) Print("### Prolog ### ",StringSubstr(pText,pPos,(iClose-pPos)+StringLen(EASYXML_PROLOG_CLOSE))); if(iClose>0) { pPos=iClose+StringLen(EASYXML_PROLOG_CLOSE); } else { Err=EASYXML_INVALID_PROLOG; return(false); } } //--- skip stylesheet declarations if(StringCompare(EASYXML_STYLESHEET_OPEN,StringSubstr(pText,pPos,StringLen(EASYXML_STYLESHEET_OPEN)))==0) { int iClose=StringFind(pText,EASYXML_STYLESHEET_CLOSE,pPos+StringLen(EASYXML_STYLESHEET_OPEN)); if(blDebug) Print("### Prolog ### ",StringSubstr(pText,pPos,(iClose-pPos)+StringLen(EASYXML_STYLESHEET_CLOSE))); if(iClose>0) { pPos=iClose+StringLen(EASYXML_STYLESHEET_CLOSE); } else { Err=EASYXML_INVALID_PROLOG; return(false); } } //--- skip comments if(!skipWhitespaceAndComments(pText,pPos,"")) return(false); //--- skip doctype if(StringCompare(EASYXML_DOCTYPE_OPEN,StringSubstr(pText,pPos,StringLen(EASYXML_DOCTYPE_OPEN)))==0) { int iClose=StringFind(pText,EASYXML_DOCTYPE_CLOSE,pPos+StringLen(EASYXML_DOCTYPE_OPEN)); if(blDebug) Print("### DOCTYPE ### ",StringSubstr(pText,pPos,(iClose-pPos)+StringLen(EASYXML_DOCTYPE_CLOSE))); if(iClose>0) { pPos=iClose+StringLen(EASYXML_DOCTYPE_CLOSE); } else { Err=EASYXML_INVALID_DOCTYPE; return(false); } } //--- skip comments if(!skipWhitespaceAndComments(pText,pPos,"")) return(false); return(true); }
この問題にもかかわらず、liquinaut の効果を損なうことはなく、 easyxml.mqh はすばらしいツールです。
2.4. Expert Advisor コード
アプリケーションに必要なライブラリはすべて説明したので、クラスを定義するためにこれらコンポーネントを取りまとめます。
RSS リーダーの Expert Advisor コードは CRssReader クラスの宣言から始まることに注意してください。
//+------------------------------------------------------------------+ //| RssReader.mq5 | //| Ufranco | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Ufranco" #property link "https://www.mql5.com" #property version "1.00" #include <Controls\Dialog.mqh> #include <Controls\Edit.mqh> #include <Controls\Button.mqh> #include <TitleArea.mqh> #include <TextArea.mqh> #include <ListViewArea.mqh> #include <easyxml.mqh> //+------------------------------------------------------------------+ //| defines | //+------------------------------------------------------------------+ //--- indents and gaps #define INDENT_LEFT (11) // indent from left (with allowance for border width) #define INDENT_TOP (11) // indent from top (with allowance for border width) #define INDENT_RIGHT (11) // indent from right (with allowance for border width) #define INDENT_BOTTOM (11) // indent from bottom (with allowance for border width) #define CONTROLS_GAP_X (5) // gap by X coordinate #define CONTROLS_GAP_Y (5) // gap by Y coordinate #define EDIT_HEIGHT (20) // size by Y coordinate #define BUTTON_WIDTH (150) // size by X coordinate #define BUTTON_HEIGHT (20) // size by Y coordinate #define TEXTAREA_HEIGHT (131) // size by Y coordinate #define LIST_HEIGHT (93) // size by Y coordinate
必要なファイルをインクルードすることから始めます。コントロールをピクセルの物理的パラメータを設定するには定義済み命令が使用されます。
INDENT_LEFT、INDENT_RIGHT、INDENT_TOP 、 INDENT_DOWN がコントロールとアプリケーションダイアログの端との距離を設定します。
- CONTROLS_GAP_Y は2つのコントロール間の縦方向の距離です。
- EDIT_HEIGHT は入力領域を作成する「編集」コントロールの高さを設定します。
- BUTTON_WIDTH and BUTTON_HEIGHT は全ボタンコントロールの幅と高さを決めます。
- TEXTAREA_HEIGHT はテキスト領域部の高さです。
- LIST_HEIGHT はリストビューコントロールの高さを設定します。
その設定後 CRssReader クラスの定義を開始します。
//+------------------------------------------------------------------+ //| Class CRssReader | //| Usage: main class for the RSS application | //+------------------------------------------------------------------+ class CRssReader : public CAppDialog { private: int m_shift; // index of first item tag string m_rssurl; // copy of web address of last feed string m_textareaoutput[]; // array of strings prepared for output to the text area panel string m_titleareaoutput[]; // array of strings prepared for output to title area panel CButton m_button1; // the button object CButton m_button2; // the button object CEdit m_edit; // input panel CTitleArea m_titleview; // the display field object CListViewArea m_listview; // the list object CTextArea m_textview; // text area object CEasyXml m_xmldocument; // xml document object CEasyXmlNode *RssNode; // root node object CEasyXmlNode *ChannelNode; // channel node object CEasyXmlNode *ChannelChildNodes[]; // array of channel child node objects
前述の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 - 親 Node オブジェクト
- ChannelNode - チャンネルノードのオブジェクト
- ChannelChildNodes 配列はチャンネルタグの直接継承者に対するポインターセットです。
われわれのクラスには公開されるメソッドは2つだけあります。
public: CRssReader(void); ~CRssReader(void); //--- create virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); //--- chart event handler virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
最初のメソッド Create() はアプリケーションダイアログのサイズと初期位置を設定します。
また RSS リーダーアプリケーションのコントロールをすべて初期化します(わえわれのクラスはCAppDialog クラスを継承しているため、親クラスのパブリックなメソッドとその上位クラスはのCRssReader インスタンスによって呼ばれることを思い出してください)。
//+------------------------------------------------------------------+ //| Create | //+------------------------------------------------------------------+ bool CRssReader::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); //--- create dependent controls if(!CreateEdit()) return(false); if(!CreateButton1()) return(false); if(!CreateButton2()) return(false); if(!CreateTitleView()) return(false); if(!CreateListView()) return(false); if(!CreateTextView()) return(false); //--- succeed return(true); }
次に、OnEvent() メソッドで、関数は対応するコントロールとハンドラ関数に特定イベントを割り当てることで連携を可能にします。
//+------------------------------------------------------------------+ //| Event Handling | //+------------------------------------------------------------------+ EVENT_MAP_BEGIN(CRssReader) ON_EVENT(ON_CHANGE,m_listview,OnChangeListView) ON_EVENT(ON_END_EDIT,m_edit,OnObjectEdit) ON_EVENT(ON_CLICK,m_button1,OnClickButton1) ON_EVENT(ON_CLICK,m_button2,OnClickButton2) EVENT_MAP_END(CAppDialog)
2.5. コントロールの初期化メソッド
保護メソッド CreateEdit()、CreateButton1()、CreateButton2()、 CreateTitleView()、CreateListView()、CreateTextView() はメイン関数 Create() によって対応するコントロールを初期化するため呼びだされます。
protected: // --- creating controls bool CreateEdit(void); bool CreateButton1(void); bool CreateButton2(void); bool CreateTitleView(void); bool CreateListView(void); bool CreateTextView(void);
コントロールのサイズ、ポジション、プロパティ(フォント、フォントサイズ、色、境界線色、境界線タイプ)はこれら関数にあります。
//+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateEdit(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+EDIT_HEIGHT; //--- create if(!m_edit.Create(m_chart_id,m_name+"Edit",m_subwin,x1,y1,x2,y2)) return(false); if(!m_edit.Text("Please enter the web address of an Rss feed")) return(false); if(!m_edit.ReadOnly(false)) return(false); if(!Add(m_edit)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create button 1 | //+------------------------------------------------------------------+ bool CRssReader::CreateButton1(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y); int x2=x1+BUTTON_WIDTH; int y2=y1+BUTTON_HEIGHT; //--- create if(!m_button1.Create(m_chart_id,m_name+"Button1",m_subwin,x1,y1,x2,y2)) return(false); if(!m_button1.Text("Reset")) return(false); if(!m_button1.Font("Comic Sans MS")) return(false); if(!m_button1.FontSize(8)) return(false); if(!m_button1.Color(clrWhite)) return(false); if(!m_button1.ColorBackground(clrBlack)) return(false); if(!m_button1.ColorBorder(clrBlack)) return(false); if(!m_button1.Pressed(true)) return(false); if(!Add(m_button1)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create button 2 | //+------------------------------------------------------------------+ bool CRssReader::CreateButton2(void) { //--- coordinates int x1=(ClientAreaWidth()-INDENT_RIGHT)-BUTTON_WIDTH; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y); int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+BUTTON_HEIGHT; //--- create if(!m_button2.Create(m_chart_id,m_name+"Button2",m_subwin,x1,y1,x2,y2)) return(false); if(!m_button2.Text("Update current feed")) return(false); if(!m_button2.Font("Comic Sans MS")) return(false); if(!m_button2.FontSize(8)) return(false); if(!m_button2.Color(clrWhite)) return(false); if(!m_button2.ColorBackground(clrBlack)) return(false); if(!m_button2.ColorBorder(clrBlack)) return(false); if(!m_button2.Pressed(true)) return(false); if(!Add(m_button2)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateTitleView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y)+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+(EDIT_HEIGHT*2); m_titleview.Current(); //--- create if(!m_titleview.Create(m_chart_id,m_name+"TitleView",m_subwin,x1,y1,x2,y2)) { Print("error creating title view"); return(false); } else { for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } } if(!Add(m_titleview)) { Print("error adding title view"); return(false); } //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the "ListView" element | //+------------------------------------------------------------------+ bool CRssReader::CreateListView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+TEXTAREA_HEIGHT+CONTROLS_GAP_Y+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+LIST_HEIGHT; //--- create if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2)) return(false); if(!Add(m_listview)) return(false); //--- fill out with strings for(int i=0;i<20;i++) if(!m_listview.AddItem(" ")) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateTextView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+TEXTAREA_HEIGHT; m_textview.Current(); //--- create if(!m_textview.Create(m_chart_id,m_name+"TextArea",m_subwin,x1,y1,x2,y2)) { Print("error creating text area view"); return(false); } else { for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); ChartRedraw(); } if(!Add(m_textview)) { Print("error adding text area view"); return(false); } //----success return(true); }
2.6. RSS ドキュメント処理メソッド
// --- rss document processing bool LoadDocument(string filename); int ItemNodesTotal(void); void FreeDocumentTree(void);
2.6.1. LoadDocument()
この関数には重要な役割が数個あります。主要なものはウェブリクエストを処理することです。loadXmlFromUrlWebReq() は RSS ファイルをダウンロードするために呼ばれます。
これが正常に完了すると、関数は2番目のタスクに進み、ポインターRssNode、ChannelNode を初期化し、また配列ChannelChildnodes を埋めます。プロパティ that m_rssurl および hereand m_shift はここで設定されます。これがすべて行われると、関数は真を返します。
ファイルがダウンロードされないと、タイトル領域、リストビュー領域、テキスト領域部はテキストをすべて消去され、タイトルバーに状態メッセージが表示されます。これに続き、テキスト領域部にエラーメッセージが表示されます。そうすると関数は偽を返します。
//+------------------------------------------------------------------+ //| load document | //+------------------------------------------------------------------+ bool CRssReader::LoadDocument(string filename) { if(!m_xmldocument.loadXmlFromUrlWebReq(filename)) { m_textview.ItemsClear(); m_listview.ItemsClear(); m_titleview.ItemsClear(); CDialog::Caption("Failed to load Feed"); if(!m_textview.AddItem(m_xmldocument.GetErrorMsg())) Print("error displaying error message"); return(false); } else { m_rssurl=filename; RssNode=m_xmldocument.getDocumentRoot(); ChannelNode=RssNode.FirstChild(); if(CheckPointer(RssNode)==POINTER_INVALID || CheckPointer(ChannelNode)==POINTER_INVALID) return(false); } ArrayResize(ChannelChildNodes,ChannelNode.Children().Total()); for(int i=0;i<ChannelNode.Children().Total();i++) { ChannelChildNodes[i]=ChannelNode.Children().At(i); } m_shift=ChannelNode.Children().Total()-ItemNodesTotal(); return(true); }
2.6.2. ItemNodesTotal()
このヘルパー関数は LoadDocument() メソッド内で使用されます。それはチャンネルタグの継承者である項目ノード数の整数値を返します。
項目ノードが存在しなければ、ドキュメントは無効な RSS ドキュメントとなり、関数は 0 を返します。
//+------------------------------------------------------------------+ //| function counts the number of item tags in document | //+------------------------------------------------------------------+ int CRssReader::ItemNodesTotal(void) { int t=0; for(int i=0;i<ChannelNode.Children().Total();i++) { if(ChannelChildNodes[i].getName()=="item") { t++; } else continue; } return(t); }
2.6.3. FreeDocumentTree()
この関数は CEasyXmlNode ポインターをすべてリセットします。
まず CArrayObj クラスの Shutdown() メソッドを呼ぶことでChannelChildnodes 配列のエレメントが削除されます。この配列は ArrayFree() を一度呼びだすことで解放されます。
次に、チャンネルノードに対するポインターが削除され、easyxml パサーのドキュメントツリーが消去されます。これら処理によりポインター RssNode および ChannelNode が不良ポインターとなります。それはこのポインターが両方NULL 値に割り当てられているためです。
//+------------------------------------------------------------------+ //| free document tree and reset pointer values | //+------------------------------------------------------------------+ void CRssReader::FreeDocumentTree(void) { ChannelNode.Children().Shutdown(); ArrayFree(ChannelChildNodes); RssNode.Children().Shutdown(); m_xmldocument.Clear(); m_shift=0; RssNode=NULL; ChannelNode=NULL; }
2.7. ドキュメントツリーから情報を抽出するメソッド
これら関数は RSS ドキュメントからテキストを取得するためのものです。
//--- getters string getChannelTitle(void); string getTitle(CEasyXmlNode *Node); string getDescription(CEasyXmlNode *Node); string getDate(CEasyXmlNode *Node);
2.7.1. getChannelTitle()
この関数は RSSドキュメントの現在のチャンネルタイトルを取得します。
それはチャンネルノードポインターの有効性を確認することで開始します。ポインターが有効であれば、それはタイトルタグを探してチャンネルノードの直接の継承者をすべてループします。
for ループが m_shift プロパティを使用し、検索対象のチャンネルノードの継承者数を制限します。関数が正常に動作すると NULL を返します。
//+------------------------------------------------------------------+ //| get channel title | //+------------------------------------------------------------------+ string CRssReader::getChannelTitle(void) { string ret=NULL; if(!CheckPointer(ChannelNode)==POINTER_INVALID) { for(int i=0;i<m_shift;i++) { if(ChannelChildNodes[i].getName()=="title") { ret=ChannelChildNodes[i].getValue(); break; } else continue; } } //---return value return(ret); }
2.7.2. getTitle()
関数はアイテムタグに対するポインターを入力として取り、タイトルタグを探してそのタグの継承者をトラバースし値を返します。
関数getDescription() およびgetDate() は同じフォーマットに従い、同様に動作します。関数が正常に呼びだすと文字列値を返し、それ以外はアウトプットとして NULL を返します。
//+------------------------------------------------------------------+ //| display title | //+------------------------------------------------------------------+ string CRssReader::getTitle(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="title") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| display description | //+------------------------------------------------------------------+ string CRssReader::getDescription(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="description") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| display date | //+------------------------------------------------------------------+ string CRssReader::getDate(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="pubDate") { n=subNode.getValue(); break; } else continue; } return(n); }
2.8. テキストをフォーマットするメソッド
これら関数はそのテキストオブジェクトが持つ限界を克服するために、テキストオブジェクトとして出力用テキストを準備するためのものです。
//--- text formating bool FormatString(string v,string &array[],int n); string removeTags(string _string); string removeSpecialCharacters(string s_tring); int tagPosition(string _string,int w);
2.8.1. FormatString()
これはアプリケーションに出力するため RSS ドキュメントから抽出されたテキストを準備する主要関数です。
基本的に文字列のインプット値をとり、テキストを "n" 文字の行に分割します。"n" はテキストの文字列行内の文字数を表す整数値です。テキスト内で "n" 文字ごとにコードが新しいラインフィード文字を挿入する適切な場所を検索します。それから文字列値すべてが処理され、新しいラインフィード文字が元のテキストの位置に挿入されます。
それぞれ "n" 文字より長くならない文字列の配列を作成するのには StringSplit() 関数が使用されます。この関数はブール値と出力準備のできている文字列値の配列を返します。
//+------------------------------------------------------------------+ //| format string for output to text area panel | //+------------------------------------------------------------------+ bool CRssReader::FormatString(string v,string &array[],int n) { ushort ch[],space,fullstop,comma,semicolon,newlinefeed; string _s,_k; space=StringGetCharacter(" ",0); fullstop=StringGetCharacter(".",0); comma=StringGetCharacter(",",0); semicolon=StringGetCharacter(";",0); newlinefeed=StringGetCharacter("\n",0); _k=removeTags(v); _s=removeSpecialCharacters(_k); int p=StringLen(_s); ArrayResize(ch,p+1); int d=StringToShortArray(_s,ch,0,-1); for(int i=1;i<d;i++) { int t=i%n; if(!t== 0)continue; else { if(ch[(i/n)*n]==fullstop || ch[(i/n)*n]==semicolon || ch[(i/n)*n]==comma) { ArrayFill(ch,((i/n)*n)+1,1,newlinefeed); } else { for(int k=i;k>=0;k--) { if(ch[k]==space) { ArrayFill(ch,k,1,newlinefeed); break; } else continue; } } } } _s=ShortArrayToString(ch,0,-1); int s=StringSplit(_s,newlinefeed,array); if(!s>0) {return(false);} // success return(true); }
2.8.2. removeTags()
この関数はかなりの数の RSS ドキュメントが XML ノード内でタグを持つことを知ってから必要となりました。
RSS ドキュメントの中にはこの方法で公開されるものがあります。多くの RSS 集約アプリケーションはブラウザで動作するためです。
この関数は文字列値を取り、テキスト内でタグを検索します。タグが見つかれば、この関数はテキストの文字すべてについてループし、二次元配列 a[][] にオープン/クローズするタグすべての位置を格納します。この配列はタグ同士からテキストを抽出するのに使用され、抽出された文字列が返されます。タグがみつからなければ、入力文字列がそのまま返されます。
//+------------------------------------------------------------------+ //| remove tags | //+------------------------------------------------------------------+ string CRssReader::removeTags(string _string) { string now=NULL; if(StringFind(_string,"<",0)>-1) { int v=0,a[][2]; ArrayResize(a,2024); for(int i=0;i<StringLen(_string);i++) { int t=tagPosition(_string,i); if(t>0) { v++; a[v-1][0]=i; a[v-1][1]=t; } else continue; } ArrayResize(a,v); for(int i=0;i<v-1;i++) { now+=StringSubstr(_string,(a[i][1]+1),(a[i+1][0]-(a[i][1]+1))); } } else { now=_string; } return(now); }
以下はその類のドキュメントの抜粋例です。
<item> <title>GIGABYTE X99-Gaming G1 WIFI Motherboard Review</title> <author>Ian Cutress</author> <description><![CDATA[ <p>The gaming motherboard range from a manufacturer is one with a lot of focus in terms of design and function due to the increase in gaming related PC sales. On the Haswell-E side of gaming, GIGABYTE is putting forward the X99-Gaming G1 WIFI at the top of its stack, and this is what we are reviewing today. </p> <p align="center"><a href='http://dynamic1.anandtech.com/www/delivery/ck.php?n=a1f2f01f&cb=582254849' target='_blank'><img src='http://dynamic1.anandtech.com/www/delivery/avw.php?zoneid=24&cb=582254849&n=a1f2f01f' border='0' alt='' /></a><img src="http://toptenreviews.122.2o7.net/b/ss/tmn-test/1/H.27.3--NS/0" height="1" width="1" border="0" alt="" /></p>]]></description> <link>http://www.anandtech.com/show/8788/gigabyte-x99-gaming-g1-wifi-motherboard-review</link> <pubDate>Thu, 18 Dec 2014 10:00:00 EDT</pubDate> <guid isPermaLink="false">tag:www.anandtech.com,8788:news</guid> <category><![CDATA[ Motherboards]]></category> </item>
2.8.3. removeSpecialCharacters()
この関数は特定の文字列の内容を正しい文字に置き換えるだけです。
たとえば、ドキュメント内のアンパサンド文字は "&" のように表記することができます。この関数は内蔵関数StringReplace()によってこういった文字の置き換えをします。
//+------------------------------------------------------------------+ //| remove special characters | //+------------------------------------------------------------------+ string CRssReader::removeSpecialCharacters(string s_tring) { string n=s_tring; StringReplace(n,"&","&"); StringReplace(n,"'","'"); StringReplace(n," "," "); StringReplace(n,"“","\'"); StringReplace(n,"”","\'"); StringReplace(n,""","\""); StringReplace(n,"–","-"); StringReplace(n,"’","'"); StringReplace(n,">",""); return(n); }
2.8.4. tagPosition()
これは removeTags() 関数内で呼び出されるヘルパー関数です。それはインプットとして文字列と整数値を取ります。
入力整数値は文字列内での文字の位置を表し、そこからこの関数が開始タグ文字 "<" の検索を始めます。開始タグが見つかると、その関数が終了タグを検索し、終了タグ文字 ">" に対応する位置をアウトプットとして返します。タグが見つからなければ、関数は-1 を返します。
//+------------------------------------------------------------------+ //| tag positions | //+------------------------------------------------------------------+ int CRssReader::tagPosition(string _string,int w) { int iClose=-1; if(StringCompare("<",StringSubstr(_string,w,StringLen("<")))==0) { iClose=StringFind(_string,">",w+StringLen("<")); } return(iClose); }
2.9. 個別コントロールのイベントを処理するメソッド
これら関数は取得された特定のコントロールイベントを処理します。
//--- handlers of the dependent controls events void OnChangeListView(void); void OnObjectEdit(void); void OnClickButton1(void); void OnClickButton2(void); };
2.9.1. OnChangeListView()
これはイベントハンドラ関数で、アプリケーションのリストビュー領域にあるリスト項目の一つがクリックされるときはいつも呼び出されます。
この関数はドキュメント内で参照される内容の概要の閲覧を可能にします。
テキストのテキスト領域とタイトル領域部を消去し、ドキュメントツリーから新しいデータを取得し、それの出力準備をします。上記はすべて ChannelChildnodes 配列が空でない場合に起こります。
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CRssReader::OnChangeListView(void) { int a=0,k=0,l=0; a=m_listview.Current()+m_shift; if(ArraySize(ChannelChildNodes)>a) { if(m_titleview.ItemsClear()) { if(!FormatString(getTitle(ChannelChildNodes[a]),m_titleareaoutput,55)) { return; } else if(ArraySize(m_titleareaoutput)>0) { for(l=0;l<ArraySize(m_titleareaoutput);l++) { m_titleview.AddItem(removeSpecialCharacters(m_titleareaoutput[l])); } } } if(m_textview.ItemsClear()) { if(!FormatString(getDescription(ChannelChildNodes[a]),m_textareaoutput,35)) return; else if(ArraySize(m_textareaoutput)>0) { for(k=0;k<ArraySize(m_textareaoutput);k++) { m_textview.AddItem(m_textareaoutput[k]); } m_textview.AddItem(" "); m_textview.AddItem("Date|"+getDate(ChannelChildNodes[a])); } else return; } } }
2.9.2. OnObjectEdit()
ハンドラ関数はユーザーが入力領域へのテキスト入力を終えると常に呼ばれます。
この関数はLoadDocument() メソッドを呼び出します。ダウンロードが正常に行われると、テキストはすべてのアプリケーションのから消去されます。次に見出しが変更され、リストビュー領域部に新しい内容が出力されます。
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CRssReader::OnObjectEdit(void) { string f=m_edit.Text(); if(StringLen(f)>0) { if(ArraySize(ChannelChildNodes)<1) { CDialog::Caption("Loading..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } else { FreeDocumentTree(); CDialog::Caption("Loading new RSS Feed..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } } else return; }
2.9.3. OnClickButton1/2()
これらハンドラはユーザーがリセットかフィード更新ボタンのいずれかをクリックするとき呼びだされます。
リセットボタンをクリックすると Expert advisor が初期起動されたときの状態にすいてのアプリケーションダイアログがリフレッシュされます。
『フィードアップデートの確認』ボタンをクリックすることでLoadDocument() メソッドのロードが再度呼びだされ、 RSS フィードデータがダウンロードされ、リストビュー領域部をリフレッシュします。
//+------------------------------------------------------------------+ //| Event handler refresh the app dialogue | //+------------------------------------------------------------------+ void CRssReader::OnClickButton1(void) { if(ArraySize(ChannelChildNodes)<1) { if(!m_edit.Text("Enter the web address of an Rss feed")) Print("error changing edit text"); if(!CDialog::Caption("RSSReader")) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("error adding to listview"); } m_listview.VScrolled(true); for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } return; } } else { FreeDocumentTree(); if(!m_edit.Text("Enter the web address of an Rss feed")) Print("error changing edit text"); if(!CDialog::Caption("RSSReader")) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("error adding to listview"); } m_listview.VScrolled(true); for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } return; } } } //+------------------------------------------------------------------+ //| Event handler update current feed | //+------------------------------------------------------------------+ void CRssReader::OnClickButton2(void) { string f=m_rssurl; if(ArraySize(ChannelChildNodes)<1) return; else { FreeDocumentTree(); CDialog::Caption("Checking for RSS Feed update..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } }
それが CRssReader クラス定義を締めくくります。
2.10. CRssReader クラスの実装
//+------------------------------------------------------------------+ //| Class CRssReader | //| Usage: main class for the RSS application | //+------------------------------------------------------------------+ class CRssReader : public CAppDialog { private: int m_shift; // index of first item tag string m_rssurl; // copy of web address of last feed string m_textareaoutput[]; // array of strings prepared for output to the text area panel string m_titleareaoutput[]; // array of strings prepared for output to title area panel CButton m_button1; // the button object CButton m_button2; // the button object CEdit m_edit; // input panel CTitleArea m_titleview; // the display field object CListViewArea m_listview; // the list object CTextArea m_textview; // text area object CEasyXml m_xmldocument; // xml document object CEasyXmlNode *RssNode; // root node object CEasyXmlNode *ChannelNode; // channel node object CEasyXmlNode *ChannelChildNodes[]; // array of channel child node objects public: CRssReader(void); ~CRssReader(void); //--- create virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); //--- chart event handler virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); protected: // --- creating controls bool CreateEdit(void); bool CreateButton1(void); bool CreateButton2(void); bool CreateTitleView(void); bool CreateListView(void); bool CreateTextView(void); // --- rss document processing bool LoadDocument(string filename); int ItemNodesTotal(void); void FreeDocumentTree(void); //--- getters string getChannelTitle(void); string getTitle(CEasyXmlNode *Node); string getDescription(CEasyXmlNode *Node); string getDate(CEasyXmlNode *Node); //--- text formating bool FormatString(string v,string &array[],int n); string removeTags(string _string); string removeSpecialCharacters(string s_tring); int tagPosition(string _string,int w); //--- handlers of the dependent controls events void OnChangeListView(void); void OnObjectEdit(void); void OnClickButton1(void); void OnClickButton2(void); }; //+------------------------------------------------------------------+ //| Event Handling | //+------------------------------------------------------------------+ EVENT_MAP_BEGIN(CRssReader) ON_EVENT(ON_CHANGE,m_listview,OnChangeListView) ON_EVENT(ON_END_EDIT,m_edit,OnObjectEdit) ON_EVENT(ON_CLICK,m_button1,OnClickButton1) ON_EVENT(ON_CLICK,m_button2,OnClickButton2) EVENT_MAP_END(CAppDialog) //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRssReader::CRssReader(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CRssReader::~CRssReader(void) { } //+------------------------------------------------------------------+ //| Create | //+------------------------------------------------------------------+ bool CRssReader::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); //--- create dependent controls if(!CreateEdit()) return(false); if(!CreateButton1()) return(false); if(!CreateButton2()) return(false); if(!CreateTitleView()) return(false); if(!CreateListView()) return(false); if(!CreateTextView()) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateEdit(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+EDIT_HEIGHT; //--- create if(!m_edit.Create(m_chart_id,m_name+"Edit",m_subwin,x1,y1,x2,y2)) return(false); if(!m_edit.Text("Please enter the web address of an Rss feed")) return(false); if(!m_edit.ReadOnly(false)) return(false); if(!Add(m_edit)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create button 1 | //+------------------------------------------------------------------+ bool CRssReader::CreateButton1(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y); int x2=x1+BUTTON_WIDTH; int y2=y1+BUTTON_HEIGHT; //--- create if(!m_button1.Create(m_chart_id,m_name+"Button1",m_subwin,x1,y1,x2,y2)) return(false); if(!m_button1.Text("Reset")) return(false); if(!m_button1.Font("Comic Sans MS")) return(false); if(!m_button1.FontSize(8)) return(false); if(!m_button1.Color(clrWhite)) return(false); if(!m_button1.ColorBackground(clrBlack)) return(false); if(!m_button1.ColorBorder(clrBlack)) return(false); if(!m_button1.Pressed(true)) return(false); if(!Add(m_button1)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create button 2 | //+------------------------------------------------------------------+ bool CRssReader::CreateButton2(void) { //--- coordinates int x1=(ClientAreaWidth()-INDENT_RIGHT)-BUTTON_WIDTH; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y); int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+BUTTON_HEIGHT; //--- create if(!m_button2.Create(m_chart_id,m_name+"Button2",m_subwin,x1,y1,x2,y2)) return(false); if(!m_button2.Text("Update current feed")) return(false); if(!m_button2.Font("Comic Sans MS")) return(false); if(!m_button2.FontSize(8)) return(false); if(!m_button2.Color(clrWhite)) return(false); if(!m_button2.ColorBackground(clrBlack)) return(false); if(!m_button2.ColorBorder(clrBlack)) return(false); if(!m_button2.Pressed(true)) return(false); if(!Add(m_button2)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateTitleView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y)+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+(EDIT_HEIGHT*2); m_titleview.Current(); //--- create if(!m_titleview.Create(m_chart_id,m_name+"TitleView",m_subwin,x1,y1,x2,y2)) { Print("error creating title view"); return(false); } else { for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } } if(!Add(m_titleview)) { Print("error adding title view"); return(false); } //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the "ListView" element | //+------------------------------------------------------------------+ bool CRssReader::CreateListView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+TEXTAREA_HEIGHT+CONTROLS_GAP_Y+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+LIST_HEIGHT; //--- create if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2)) return(false); if(!Add(m_listview)) return(false); //--- fill out with strings for(int i=0;i<20;i++) if(!m_listview.AddItem(" ")) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the display field | //+------------------------------------------------------------------+ bool CRssReader::CreateTextView(void) { //--- coordinates int x1=INDENT_LEFT; int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+BUTTON_HEIGHT+CONTROLS_GAP_Y; int x2=ClientAreaWidth()-INDENT_RIGHT; int y2=y1+TEXTAREA_HEIGHT; m_textview.Current(); //--- create if(!m_textview.Create(m_chart_id,m_name+"TextArea",m_subwin,x1,y1,x2,y2)) { Print("error creating text area view"); return(false); } else { for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); ChartRedraw(); } if(!Add(m_textview)) { Print("error adding text area view"); return(false); } //----success return(true); } //+------------------------------------------------------------------+ //| load document | //+------------------------------------------------------------------+ bool CRssReader::LoadDocument(string filename) { if(!m_xmldocument.loadXmlFromUrlWebReq(filename)) { m_textview.ItemsClear(); m_listview.ItemsClear(); m_titleview.ItemsClear(); CDialog::Caption("Failed to load Feed"); if(!m_textview.AddItem(m_xmldocument.GetErrorMsg())) Print("error displaying error message"); return(false); } else { m_rssurl=filename; RssNode=m_xmldocument.getDocumentRoot(); ChannelNode=RssNode.FirstChild(); if(CheckPointer(RssNode)==POINTER_INVALID || CheckPointer(ChannelNode)==POINTER_INVALID) return(false); } ArrayResize(ChannelChildNodes,ChannelNode.Children().Total()); for(int i=0;i<ChannelNode.Children().Total();i++) { ChannelChildNodes[i]=ChannelNode.Children().At(i); } m_shift=ChannelNode.Children().Total()-ItemNodesTotal(); return(true); } //+------------------------------------------------------------------+ //| function counts the number of item tags in document | //+------------------------------------------------------------------+ int CRssReader::ItemNodesTotal(void) { int t=0; for(int i=0;i<ChannelNode.Children().Total();i++) { if(ChannelChildNodes[i].getName()=="item") { t++; } else continue; } return(t); } //+------------------------------------------------------------------+ //| free document tree and reset pointer values | //+------------------------------------------------------------------+ void CRssReader::FreeDocumentTree(void) { ChannelNode.Children().Shutdown(); ArrayFree(ChannelChildNodes); RssNode.Children().Shutdown(); m_xmldocument.Clear(); m_shift=0; RssNode=NULL; ChannelNode=NULL; } //+------------------------------------------------------------------+ //| get channel title | //+------------------------------------------------------------------+ string CRssReader::getChannelTitle(void) { string ret=NULL; if(!CheckPointer(ChannelNode)==POINTER_INVALID) { for(int i=0;i<m_shift;i++) { if(ChannelChildNodes[i].getName()=="title") { ret=ChannelChildNodes[i].getValue(); break; } else continue; } } //---return value return(ret); } //+------------------------------------------------------------------+ //| display title | //+------------------------------------------------------------------+ string CRssReader::getTitle(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="title") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| display description | //+------------------------------------------------------------------+ string CRssReader::getDescription(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="description") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| display date | //+------------------------------------------------------------------+ string CRssReader::getDate(CEasyXmlNode *Node) { int k=Node.Children().Total(); string n=NULL; for(int i=0;i<k;i++) { CEasyXmlNode*subNode=Node.Children().At(i); if(subNode.getName()=="pubDate") { n=subNode.getValue(); break; } else continue; } return(n); } //+------------------------------------------------------------------+ //| format string for output to text area panel | //+------------------------------------------------------------------+ bool CRssReader::FormatString(string v,string &array[],int n) { ushort ch[],space,fullstop,comma,semicolon,newlinefeed; string _s,_k; space=StringGetCharacter(" ",0); fullstop=StringGetCharacter(".",0); comma=StringGetCharacter(",",0); semicolon=StringGetCharacter(";",0); newlinefeed=StringGetCharacter("\n",0); _k=removeTags(v); _s=removeSpecialCharacters(_k); int p=StringLen(_s); ArrayResize(ch,p+1); int d=StringToShortArray(_s,ch,0,-1); for(int i=1;i<d;i++) { int t=i%n; if(!t== 0)continue; else { if(ch[(i/n)*n]==fullstop || ch[(i/n)*n]==semicolon || ch[(i/n)*n]==comma) { ArrayFill(ch,((i/n)*n)+1,1,newlinefeed); } else { for(int k=i;k>=0;k--) { if(ch[k]==space) { ArrayFill(ch,k,1,newlinefeed); break; } else continue; } } } } _s=ShortArrayToString(ch,0,-1); int s=StringSplit(_s,newlinefeed,array); if(!s>0) {return(false);} // success return(true); } //+------------------------------------------------------------------+ //| remove special characters | //+------------------------------------------------------------------+ string CRssReader::removeSpecialCharacters(string s_tring) { string n=s_tring; StringReplace(n,"&","&"); StringReplace(n,"'","'"); StringReplace(n," "," "); StringReplace(n,"“","\'"); StringReplace(n,"”","\'"); StringReplace(n,""","\""); StringReplace(n,"–","-"); StringReplace(n,"’","'"); StringReplace(n,">",""); return(n); } //+------------------------------------------------------------------+ //| remove tags | //+------------------------------------------------------------------+ string CRssReader::removeTags(string _string) { string now=NULL; if(StringFind(_string,"<",0)>-1) { int v=0,a[][2]; ArrayResize(a,2024); for(int i=0;i<StringLen(_string);i++) { int t=tagPosition(_string,i); if(t>0) { v++; a[v-1][0]=i; a[v-1][1]=t; } else continue; } ArrayResize(a,v); for(int i=0;i<v-1;i++) { now+=StringSubstr(_string,(a[i][1]+1),(a[i+1][0]-(a[i][1]+1))); } } else { now=_string; } return(now); } //+------------------------------------------------------------------+ //| tag positions | //+------------------------------------------------------------------+ int CRssReader::tagPosition(string _string,int w) { int iClose=-1; if(StringCompare("<",StringSubstr(_string,w,StringLen("<")))==0) { iClose=StringFind(_string,">",w+StringLen("<")); } return(iClose); } //+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CRssReader::OnChangeListView(void) { int a=0,k=0,l=0; a=m_listview.Current()+m_shift; if(ArraySize(ChannelChildNodes)>a) { if(m_titleview.ItemsClear()) { if(!FormatString(getTitle(ChannelChildNodes[a]),m_titleareaoutput,55)) { return; } else if(ArraySize(m_titleareaoutput)>0) { for(l=0;l<ArraySize(m_titleareaoutput);l++) { m_titleview.AddItem(removeSpecialCharacters(m_titleareaoutput[l])); } } } if(m_textview.ItemsClear()) { if(!FormatString(getDescription(ChannelChildNodes[a]),m_textareaoutput,35)) return; else if(ArraySize(m_textareaoutput)>0) { for(k=0;k<ArraySize(m_textareaoutput);k++) { m_textview.AddItem(m_textareaoutput[k]); } m_textview.AddItem(" "); m_textview.AddItem("Date|"+getDate(ChannelChildNodes[a])); } else return; } } } //+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CRssReader::OnObjectEdit(void) { string f=m_edit.Text(); if(StringLen(f)>0) { if(ArraySize(ChannelChildNodes)<1) { CDialog::Caption("Loading..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } else { FreeDocumentTree(); CDialog::Caption("Loading new RSS Feed..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } } else return; } //+------------------------------------------------------------------+ //| Event handler refresh the app dialogue | //+------------------------------------------------------------------+ void CRssReader::OnClickButton1(void) { if(ArraySize(ChannelChildNodes)<1) { if(!m_edit.Text("Enter the web address of an Rss feed")) Print("error changing edit text"); if(!CDialog::Caption("RSSReader")) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("error adding to listview"); } m_listview.VScrolled(true); for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } return; } } else { FreeDocumentTree(); if(!m_edit.Text("Enter the web address of an Rss feed")) Print("error changing edit text"); if(!CDialog::Caption("RSSReader")) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("error adding to listview"); } m_listview.VScrolled(true); for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } return; } } } //+------------------------------------------------------------------+ //| Event handler update current feed | //+------------------------------------------------------------------+ void CRssReader::OnClickButton2(void) { string f=m_rssurl; if(ArraySize(ChannelChildNodes)<1) return; else { FreeDocumentTree(); CDialog::Caption("Checking for RSS Feed update..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("error changing caption"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<ItemNodesTotal()-1;i++) { if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift])))) { Print("can not add item to listview area"); return; } } } else { Print("text area/listview area not cleared"); return; } } else return; } }
これで Expert Advisor コードで使用可能です。
2.11. Expert Advisor コード
Expert Advisor には入力変数はありません。アプリケーションが完全にインタラクティブであることを意味するためです。
まずCRssReader クラスのインスタンスであるグローバル変数を宣言します。OnInit() 関数でメインメソッド Create() を呼び出し、アプリケーションダイアログを初期化します。これが正常に行われると、上位クラスの Run() メソッドが呼ばれます。
OnDeinit() 関数では親クラスの Destroy() メソッドが呼ばれ、アプリケーション全体を削除しチャートから Expert Advisor を消去します。
OnChartEvent() 関数には CRssReader クラスの上位メソッドの呼び出し機能があります。それはすべてのイベント処理を行います。
//Expert Advisor code begins here //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CRssReader ExtDialog; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create application dialog if(!ExtDialog.Create(0,"RSSReader",0,20,20,518,394)) return(INIT_FAILED); //--- run application ExtDialog.Run(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- ExtDialog.Destroy(reason); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- ExtDialog.ChartEvent(id,lparam,dparam,sparam); }
コードはその後コンパイルする必要があり、そうするとプログラムは使用準備完了となります。
RssReader.mq5 Expert Advisor がチャートにロードされるとき、空のアプリケーションダイアログは以下のように表示されます。
図2 RssReader ExpertAdvisor の空のアプリケーションダイアログのスクリーンショット
ウェブアドレスと RSS コンテンツ入力は以下の画像に表示されるようにアプリケーションダイアログにロードされます。
図3 ターミナルで動作しているRssReader EA
私は広範囲のRSS フィードでプログラムを検証しました。私が観察した唯一の問題は、不要な文字がいくつか表示されることに関連していました。ほとんどが通常 HTMLドキュメントに見られる文字を持つ RSS ドキュメントの結果でした。
また私はアプリケーション実行中にチャート期間を変更すると EA が再初期化を行い、アプリケーションコントロールが適切に描かれないことにつながることにも気づきました
私はこの現象を修正することができませんでした。よってリーダープログラム実行中にチャート期間を変更しないようにすることをアドバイスいたします。
おわりに
オブジェクト指向プログラミング技術を用いて、MetaTrader 5 用の完全にインタラクティブな RSS リーダーアプリケーションを完成しました。
アプリケーションに追加できる機能はまだまだあります。そして私はユーザーインターフェースを準備する方法はもっとたくさんあると確信しています。よりすぐれたアプリケーション GUI の設計スキルを持つ方々がこのアプリケーションを改善し、その成果物を共有してくださることを願っております。
P.S. ここからダウンロードできるeasyxml.mqh ファイルはコードベースから入手できるものとは異なることにご注意ください。それには本稿で述べた変更がすでに施されています。必要なインクルードはすべて RssReader.zipファイルにあります。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/1589





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索