Создание интерактивного приложения для отображения RSS-каналов в MetaTrader 5
Содержание
Введение
В статье "Чтение новостей в формате RSS средствами MQL4" рассматривался довольно элементарный скрипт, который может отображать RSS-каналы в консоли терминала средствами простой библиотеки, изначально предназначенной для парсинга HTML-документов.
Появление MetaTrader 5 и языка программирования MQL5, по нашему мнению, предоставило возможность создания интерактивного приложения, которое смогло бы лучше отображать RSS-контент. В данной статье говорится о способе создания такого приложения при помощи расширенной Стандартной библиотеки MQL5 и иных инструментов, разработанных участниками MQL5.community.
1. Общие характеристики RSS-документации
Прежде чем перейти к особенностям приложения, необходимо рассмотреть общую структуру RSS-документа.
Для более полного понимания необходимо разбираться в расширяемом языке разметки и связанных с ним понятиях. Если вы не сталкивались с XML-документами ранее, ознакомьтесь с XML Tutorial. Обратите внимание, что в данной статье под узлом подразумевается тег в XML-документе. Как уже говорилось в упомянутой статье про MQL4, RSS-файлы по сути представляют собой XML-документы с особой структурой тегов.
Каждый RSS-документ имеет глобальное хранилище - RSS-тег. Это обычное явление для всех RSS-документов. Тег channel всегда исходит от тега RSS. Он содержит информацию об описываемом каналом веб-сайте. С этого момента специфичные теги в разных RSS-документах могут отличаться друг от друга. Но наличие некоторых тегов обязательно для всех документов. Такие теги и делают из документа проверенный RSS-файл.
Необходимые теги:
- title - название канала. Должно содержать название веб-сайта;
- link - URL веб-сайта, предоставляющего данный канал;
- description - краткое описание веб-сайта;
- item - по меньшей мере один тег item на контент.
Указанные выше теги являются дочерними узлами тега channel. Узел item содержит данные, относящиеся к конкретному контенту.
В свою очередь каждый узел 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:<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 Reader, и обзор графического пользовательского интерфейса приложения.
Первое, что должно отображать приложение - это название канала, указанное в теге title. Эта информация указывает на веб-сайт, на который ссылается канал.
Также в приложении должна отображаться сводка всего описываемого каналом контента. Это относится ко всем тегам item, содержащимся в документе. Название контента будет отображаться для каждого тега item. И наконец, необходимо, чтобы RSS Reader мог отображать описание контента. Описанием является информация, содержащаяся в теге description каждого узла item.
2.1. Пользовательский интерфейс
Пользовательский интерфейс представляет собой средство отображения информации приложением.
Представленная ниже схема лучше всего отражает нашу концепцию пользовательского интерфейса.
Рис 1. Эскиз диалогового окна приложения
На схеме изображено несколько секций, вместе составляющих пользовательский интерфейс.
- Сначала идет строка заголовка. В ней отображается заголовок канала;
- Область ввода. Сюда пользователи вводят веб-адрес RSS-канала;
- Область заголовка. Здесь отображается заголовок конкретного контента;
- Область текста. Здесь отображается описание контента;
- Область списка. В данном прокручиваемом списке отображаются заголовки всего контента, содержащегося в канале;
- Кнопка слева очищает области заголовка, текста и списка;
- Кнопка "Update Feed" делает выборку обновлений для текущего канала.
RSS Reader действует следующим образом: при загрузке программы отображается пустое диалоговое окно приложения, затем пользователь вводит веб-адрес RSS-канала в область ввода и нажимает "Enter". Таким образом загружаются заголовки контента, т.е. теги title для каждого тега item в области списка. Нумерация списка начинается с единицы (1), под которой стоит наиболее часто публикуемый контент.
Каждый пункт списка интерактивен. При нажатии пункт списка подсвечивается, а в области текста отображается соответствующее описание контента заголовка. При этом в области заголовка более четко отображается заголовок контента. Если по какой-либо причине во время загрузки канала произойдет сбой, в области текста появится сообщение об ошибке.
Любой текст в областях текста, списка или заголовка удаляется кнопкой сброса.
При помощи кнопки "Update Feed" проверяется наличие обновления для текущего канала.
2.2. Реализация кода
RSS Reader выполнен в виде советника. Используется Стандартная библиотека MQL5.
Код содержится в классе CRssReader, который в свою очередь является потомком класса CAppDialog. Класс CAppDialog во включаемом файле Dialog.mqh реализует диалоговое окно приложения, которое обеспечивает функциональные возможности строки заголовка, и элементы управления, сворачивающие, разворачивающие и закрывающие приложение. Это является фундаментом пользовательского интерфейса, поверх которого будут добавлены другие разделы. Планируемые к добавлению разделы: область заголовка, область текста, область списка и кнопки. Каждый будет являться элементом управления. Кнопки будут реализованы в качестве элементов управления, описанных во включаемом файле Button.mqh.
Описываемый во включаемом файле ListViewArea.mqh элемент управления списком применяется для создания области списка RSS Reader.
Элемента редактирования достаточно для создания области ввода. Этот элемент управления описан в файле Edit.mqh.
Что же касается областей заголовка и текста, их реализация представляет сложную, но интересную задачу. Дело в том, что обе области должны поддерживать текст, отображаемый в несколько строк. В MQL5 текстовые объекты не распознают символы перевода строки. Другой проблемой является то, что в одной строке текстового объекта может отображаться ограниченное количество символов. Это означает, что текстовый объект с достаточно длинным описанием будет отображаться в обрезанном виде, и будет видно только определенное количество знаков. Методом проб и ошибок выяснилось, что ограничение по знакам составляет 63 символа с учетом пробелов и знаков препинания.
В целях преодоления данной проблемы было решено реализовать обе области в виде модифицированных элементов управления списком. В области заголовка модифицированный элемент управления списком будет непрокручиваемым, а сам список ограничится двумя пунктами. Пункты списка нельзя будет прокрутить или выбрать, а по внешнему виду элемент управления не будет напоминать список. Эти два пункта списка будут выглядеть как две строки текста. Если текст не умещается в одну строку, он разбивается и отображается на двух строках. Элемент управления области заголовка описывается в файле TitleArea.mqh.
Тот же подход применяется к области текста, только в этом случае количество пунктов в списке можно менять, а сам модифицированный элемент управления списком вертикально прокручивается. Данный элемент управления описывается в файле TextArea.mqh.
Пользовательский интерфейс реализуется упомянутыми библиотеками. Но существует еще одна ключевая для приложения библиотека, о которой необходимо рассказать. Это библиотека для парсинга XML-документов.
2.3. Простой парсер XML
В связи с тем, что RSS-документ является XML-файлом, мы применяем опубликованную в Code Base библиотеку EasyXML - XML Parser, разработанную liquinaut.
Библиотека достаточно обширна и включает все необходимые для нашего советника RSS Reader функции. Мы немного модифицировали оригинальную библиотеку и добавили некоторые, как мы считаем, необходимые свойства.
Вот эти незначительные добавления. Сначала был добавлен метод под названием loadXmlFromUrlWebReq(). Этот метод представляет альтернативу использованию loadXmlFromUrl(), который при обработке веб-запросов опирается на библиотеку WinInet. Для загрузок из Интернета loadXmlFromUrlWebReq() применяет встроенную функцию WebRequest().
//+------------------------------------------------------------------+ //| загрузка xml через url при помощи функции веб-запроса MQL5 | //+------------------------------------------------------------------+ bool CEasyXml::loadXmlFromUrlWebReq(string pUrl) { //--- string cookie=NULL,headers; char post[],result[]; int res; //--- string _url=pUrl; string sStream; res=WebRequest("GET",_url,cookie,NULL,5000,post,0,result,headers); //--- проверка на ошибки 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. Код советника
Теперь, когда все необходимые для приложения библиотеки описаны, настало время свести их воедино и дать определение классу CRssReader.
Обратите внимание, что код советника RSS Reader начинается с определения класса 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Мы начнем с включения необходимых файлов. Для настройки физических параметров элементов управления в пикселях были использованы директивы define.
INDENT_LEFT, INDENT_RIGHT, INDENT_TOP и INDENT_DOWN устанавливают расстояние между элементом управления и краем диалогового окна приложения.
- CONTROLS_GAP_Y - вертикальное расстояние между двумя элементами управления;
- EDIT_HEIGHT устанавливает высоту элемента редактирования, который составляет область ввода;
- BUTTON_WIDTH и BUTTON_HEIGHT определяют ширину и высоту всех кнопок управления;
- TEXTAREA_HEIGHT - высота области текста;
- LIST_HEIGHT устанавливает высоту элемента управления списком.
После директив define мы приступаем к определению класса CRssReader.
//+------------------------------------------------------------------+ //| Класс CRssReader | //| Применение: основной класс для RSS-приложения | //+------------------------------------------------------------------+ class CRssReader : public CAppDialog { private: int m_shift; // индекс первого тега item 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 - в таких переменных типа integer хранится индекс первого узла item в массиве 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 Reader (не забывайте, что наш класс наследует класс 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. Методы инициализации элементов управления
Для инициализации соответствующего элемента управления основная функция Create() вызывает защищенные методы CreateEdit(), CreateButton1() ,CreateButton2(), CreateTitleView(), CreateListView() и CreateTextView().
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("Введите веб-адрес Rss-канала")) 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("Сброс")) 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("Обновить текущий канал")) 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("ошибка при создании заголовка"); return(false); } else { for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } } if(!Add(m_titleview)) { Print("ошибка при добавлении заголовка"); 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("ошибка при создании области текста"); return(false); } else { for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); ChartRedraw(); } if(!Add(m_textview)) { Print("ошибка при добавлении области текста"); 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("Не удалось загрузить канал"); if(!m_textview.AddItem(m_xmldocument.GetErrorMsg())) Print("невозможно отобразить сообщение об ошибке"); 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(). Она возвращает значение integer, которое по сути является количеством узлов item, вложенных в элемент channel.
При отсутствии узла item RSS-документ будет невалидным, а функция вернет значение 0.
//+------------------------------------------------------------------+ //| функция считает количество тегов item в документе | //+------------------------------------------------------------------+ 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.
Сначала путем вызова метода Shutdown() класса CArrayObj удаляются все элементы массива ChannelChildnodes. Затем массив освобождается посредством единственного вызова ArrayFree().
Следом удаляется указатель на узел channel, и очищается дерево документа парсера 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-документа.
Для начала она проверяет корректность указателя узла channel. Если указатель некорректен, она проходит по всем прямым потомкам узла channel в поиске тега title.
При помощи свойства m_shift поиск осуществляется среди определенного количества потомков узла channel. При неудачном выполнении функция возвращает значение 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(ret); }
2.7.2. getTitle()
Функция принимает указатель на тег item в качестве входного параметра, просматривает вложенные в него теги в поиске тега title и возвращает его значение.
Функции 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" - это значение integer количества знаков в одной строчке текста. После каждого 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-документов содержит теги HTML в узлах XML.
Некоторые RSS-документы так и публикуются, поскольку многие RSS-агрегаторы работают в браузере.
Функция берет строковое значение и осуществляет поиск тегов по тексту. При нахождении каких-либо тегов функция перебирает каждый знак текста и сохраняет положение каждого знака открывающего и закрывающего тега в двухмерном массиве 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. </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()
Данная функция замещает определенные строковые константы на корректные знаки.
Например, в некоторых XML-документах символ амперсанда обозначается как "&". В таких случаях применяется встроенная функция StringReplace().
//+------------------------------------------------------------------+ //| удаление особых знаков | //+------------------------------------------------------------------+ 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(). В качестве входных значений она берет значения string и integer.
Входное значение integer представляет позицию знака в строке, от которого функция начинает поиск знака открывающего тега, т.е. "<". Если открывающий тег найден, функция начинает поиск закрывающего тега и выводит позицию соответствующего знака ">". При отсутствии тегов функция возвращает значение -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("Загрузка..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("ошибка при смене надписи"); 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("невозможно добавить пункт в область списка"); return; } } } else { Print("область текста/списка не очищена"); return; } } else return; } else { FreeDocumentTree(); CDialog::Caption("Загрузка нового RSS-канала..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("ошибка при смене надписи"); 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("невозможно добавить пункт в область списка"); return; } } } else { Print("область текста/списка не очищена"); return; } } else return; } } else return; }
2.9.3. OnClickButton1/2()
Эти вспомогательные функции вызываются, когда пользователь нажимает кнопку сброса или проверки обновлений.
Нажатие кнопки сброса возвращает диалоговое окно приложения в его состояние перед первым запуском советника.
Нажатие кнопки проверки обновлений повторно вызывает метод LoadDocument(), и данные RSS-канала будут загружены, обновляя область списка.
//+------------------------------------------------------------------+ //| Обработчик событий обновляет диалоговое окно приложения | //+------------------------------------------------------------------+ void CRssReader::OnClickButton1(void) { if(ArraySize(ChannelChildNodes)<1) { if(!m_edit.Text("Введите веб-адрес Rss-канала")) Print("ошибка при редактировании текста"); if(!CDialog::Caption("RSSReader")) Print("ошибка при смене надписи"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("ошибка при добавлении списка"); } 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("Введите веб-адрес Rss-канала")) Print("ошибка при редактировании текста"); if(!CDialog::Caption("RSSReader")) Print("ошибка при смене надписи"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("ошибка при добавлении списка"); } 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("Проверка обновлений RSS-канала..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("ошибка при смене надписи"); 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("невозможно добавить пункт в область списка"); return; } } } else { Print("область текста/списка не очищена"); return; } } else return; } }Этим заканчивается определение класса CRssReader.
2.10. Реализация класса CRssReader
//+------------------------------------------------------------------+ //| Класс CRssReader | //| Применение: основной класс для RSS-приложения | //+------------------------------------------------------------------+ class CRssReader : public CAppDialog { private: int m_shift; // индекс первого тега item 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("Введите веб-адрес Rss-канала")) 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("Сброс")) 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("Обновить текущий канал")) 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("ошибка при создании заголовка"); return(false); } else { for(int i=0;i<2;i++) { m_titleview.AddItem(" "); } } if(!Add(m_titleview)) { Print("ошибка при добавлении заголовка"); 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("ошибка при создании области текста"); return(false); } else { for(int i=0;i<1;i++) { m_textview.AddItem(" "); } m_textview.VScrolled(true); ChartRedraw(); } if(!Add(m_textview)) { Print("ошибка при добавлении области текста"); 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("Не удалось загрузить канал"); if(!m_textview.AddItem(m_xmldocument.GetErrorMsg())) Print("невозможно отобразить сообщение об ошибке"); 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); } //+------------------------------------------------------------------+ //| функция считает количество тегов item в документе | //+------------------------------------------------------------------+ 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(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,"&","&"); StringReplace(n,"'","'"); StringReplace(n," "," "); StringReplace(n,"“","\'"); StringReplace(n,"”","\'"); StringReplace(n,""","\""); StringReplace(n,"–","-"); StringReplace(n,"’","'"); StringReplace(n,">",""); 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("Загрузка..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("ошибка при смене надписи"); 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("невозможно добавить пункт в область списка"); return; } } } else { Print("область текста/списка не очищена"); return; } } else return; } else { FreeDocumentTree(); CDialog::Caption("Загрузка нового RSS-канала..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("ошибка при смене надписи"); 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("невозможно добавить пункт в область списка"); return; } } } else { Print("область текста/списка не очищена"); return; } } else return; } } else return; } //+------------------------------------------------------------------+ //| Обработчик событий обновляет диалоговое окно приложения | //+------------------------------------------------------------------+ void CRssReader::OnClickButton1(void) { if(ArraySize(ChannelChildNodes)<1) { if(!m_edit.Text("Введите веб-адрес Rss-канала")) Print("ошибка при редактировании текста"); if(!CDialog::Caption("RSSReader")) Print("ошибка при смене надписи"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("ошибка при добавлении списка"); } 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("Введите веб-адрес Rss-канала")) Print("ошибка при редактировании текста"); if(!CDialog::Caption("RSSReader")) Print("ошибка при смене надписи"); if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear()) { for(int i=0;i<20;i++) { if(!m_listview.AddItem(" ")) Print("ошибка при добавлении списка"); } 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("Проверка обновлений RSS-канала..."); if(LoadDocument(f)) { if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle()))) Print("ошибка при смене надписи"); 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("невозможно добавить пункт в область списка"); return; } } } else { Print("область текста/списка не очищена"); return; } } else return; } }Теперь его можно использовать в коде советника.
2.11. Код советника
Так как приложение должно быть полностью интерактивным, в советнике нет входных переменных.
Сначала мы объявляем глобальную переменную, которая является экземпляром класса CRssReader. В функции OnInit() мы инициализируем диалоговое окно приложения с помощью основного метода Create(). При успешном исходе вызывается метод Run() класса-предка.
Для удаления приложения целиком и советника из графика в функции OnDeinit() вызывается метод Destroy() родительского класса.
В функции OnChartEvent() содержится вызов унаследованного метода класса CRssReader, который обрабатывает все события.
//Здесь начинается код советника //+------------------------------------------------------------------+ //| Глобальные переменные | //+------------------------------------------------------------------+ CRssReader ExtDialog; //+------------------------------------------------------------------+ //| Функция инициализации советника | //+------------------------------------------------------------------+ int OnInit() { //--- создание диалога приложения if(!ExtDialog.Create(0,"RSSReader",0,20,20,518,394)) return(INIT_FAILED); //--- работа приложения ExtDialog.Run(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Функция деинициализации советника | //+------------------------------------------------------------------+ 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 на графике появляется следующее пустое диалоговое окно приложения:
Рис 2. Скриншот пустого диалогового окна приложения советника RssReader
Введите адрес веб-сайта, и в диалоговое окно загрузится RSS-контент (см. рис. ниже):
Рис 3. Советник RssReader, работающий в терминале
Программа была протестирована на различных RSS-каналах. Единственная обнаруженная проблема была связана с отображением некоторых нежелательных знаков, как правило, из-за того, что RSS-документы содержали знаки, обычно использующиеся в HTML-документах.
Также было замечено, что изменение периода графика во время работы приложения вызывает повторную инициализацию советника и ведет к неправильной прорисовке элементов управления.
Исправить это не удалось, так что постарайтесь не менять период графика при работающей программе RSS Reader.
Заключение
На этом мы завершили создание полностью интерактивного приложения RSS Reader для MetaTrader 5 при помощи приемов объектно-ориентированного программирования.
В приложение можно добавить и другие возможности, и, безусловно, существует много способов организации пользовательского интерфейса. Возможно те, кто лучше владеет навыками дизайна пользовательских интерфейсов, улучшат данное приложение и поделятся своими творениями с остальными.
P.S. Помните, что доступный для загрузки файл easyxml.mqh отличается от файла, доступного в Code Base. В нашем файле есть упомянутые в статье модификации. Все необходимые включения находятся в файле RssReader.zip.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/1589
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Гм. Такой вопрос.. Мало-ли, кто сталкивался.
А на сколько RSS лента тормозит у первоисточников новости?
п.с. допустим, здесь: http://www.bls.gov/feed/bls_latest.rss
п.п.с. если у вас свой пример, тоже будет интересно узнать.
просто вопрос:
А RSS где ещё используется ?
просто вопрос:
А RSS где ещё используется ?