2

Доброго времени суток, решил написать для практики парсер что бы узнать нынешний курс валюты и не зря решил. Столкнулся с проблемой установки типа идентификатора для XML документа т.к. метод XmlDocument.GetElementById(string elementId); выкидывает исключение NullRefernceExeption.

Алгоритм очень прост, если я буду искать регулярными выражениями от и до закрывающего атрибута (например "<tr id=\"vg_b_30\" class=\"" + @"(even|odd)" + "\">" + @"(.|\s)+</tr>") уходило очень много времени - я просто обрезал то что не нужно сверху, потом то что не нужно снизу и добавил шапку XML-документа, но дальше я хочу использовать метод с котором у меня возникла проблема.

DateTime now = DateTime.Now;
string Currency_Rate;
string document;
string pattern = "<tr id=\"vg_b_30\" class=\"" + @"(even|odd)" + "\">" + @"(.|\s)+";
using (WebClient Request = new WebClient { Encoding = Encoding.UTF8 })
    document = Request.DownloadString("http://myfin.by/bank/currency/mogilev");

document = Regex.Match(document, pattern, RegexOptions.IgnoreCase).ToString();

int tr_position = document.IndexOf("</tr>") + 6;

document = document.Remove(tr_position);
document = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<!DOCTYPE root [\r\n <!ATTLIST td class ID #REQUIRED>\r\n]>\r\n" + document;

pattern = @"(vg_mt_usd_buy number vg_mt_best|vg_mt_usd_buy number normal)";

document = Regex.Replace(document, pattern, "THIS IT");
//vg_mt_usd_buy number vg_mt_best"
//vg_mt_usd_sell number normal


XmlDocument XmlDock = new XmlDocument();
XmlDock.LoadXml(document);

Currency_Rate = XmlDock.GetElementById("THIS IT").InnerText;

Console.WriteLine(now - DateTime.Now);

Получившийся XML-документ

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE root [
 <!ATTLIST td class ID #REQUIRED>
]>
<tr id="vg_b_30" class="odd">
    <td class="THIS IT">17 790</td>
    <td class="vg_mt_usd_sell number normal">17 870</td>
    <td class="vg_mt_eur_buy number vg_mt_best">20 060</td>
    <td class="vg_mt_eur_sell number normal">20 190</td>
    <td class="vg_mt_rur_buy number normal">261.0</td>
    <td class="vg_mt_rur_sell number normal">265.0</td>
    <td class="vg_mt_eurusd_buy number vg_mt_best">1.1230</td>
    <td class="vg_mt_eurusd_sell number vg_mt_best">1.1340</td>
</tr>

Требуется достать значение из атрибута td помеченного THIS IT. Запись <!DOCTYPE root [<!ATTLIST td class ID #REQUIRED>]> взята с MSDN

2 Answers2

1

У вас xml не соответствует схеме. Или исправьте схему:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE tr [
  <!ELEMENT td ANY> 
  <!ATTLIST td class ID #REQUIRED>
]>
<tr id="vg_b_30" class="odd">
  <td class="THIS IT">17 790</td>
  <td class="vg_mt_usd_sell number normal">17 870</td>
  <td class="vg_mt_eur_buy number vg_mt_best">20 060</td>
  <td class="vg_mt_eur_sell number normal">20 190</td>
  <td class="vg_mt_rur_buy number normal">261.0</td>
  <td class="vg_mt_rur_sell number normal">265.0</td>
  <td class="vg_mt_eurusd_buy number vg_mt_best">1.1230</td>
  <td class="vg_mt_eurusd_sell number vg_mt_best">1.1340</td>
</tr>

Или используйте обычный XPath - SelectSingleNode - вместо GetElementById

 Currency_Rate = XmlDock.SelectSingleNode("//td[@class='THIS IT']").InnerText;
0

Попробуйте вот что

using System.Xml.Linq;

var el = XElement.Parse("<tr><td class=\"asdf\">text1</td><td class=\"qwe\">text2</td><td>text3</td></tr>");
Console.WriteLine(el.Elements("td").First(td => td.Attribute("class") != null && td.Attribute("class").Value == "asdf").Value);

Это просто пример составленный на ходу. Под ваш HTML код подстраивается легко.

Просто создаете XElement из вашего куска <tr> ... </tr>, который вы достаете из HTML документа и работаете с ним. Странно, что в интернете нет такого решения, а предлагают регулярки и Html Agility Pack.

iRumba
  • 5,946
  • вопрос же не в том, как выбрать элемент - XmlDock.SelectSingleNode("//td[@class='THIS IT']") вполне с этим справился бы. вопрос - почему не работает GetElementById –  Sep 14 '15 at 07:02
  • @PashaPash, нет же, вопрос звучит так "Требуется достать значение из атрибута td помеченного THIS IT" и я показал, как это сделать. могу выборку переписать под конкретный пример. Я понятия не имею почему не работает GetElementById, не я же автор метода :) – iRumba Sep 14 '15 at 07:34
  • GetElementById не работает их-за битой схемы. но вообще сама идея довольно хрупкая - html не обязательно является валидным xml (чаще всего не является) - поэтому в интернете и предлагают hap или регулярки –  Sep 14 '15 at 08:44
  • @PashaPash, ну еще есть Microsoft.mshtml – iRumba Sep 14 '15 at 09:44
  • да, но интероп поверх IE ради выдирания tr-ки - это из пушки по воробьям :) –  Sep 14 '15 at 09:47
  • @PashaPash, вот поэтому я и предложил другой пример на основе XElement. То, что он парсит, вполне валидно. ) – iRumba Sep 14 '15 at 09:50
  • XElement.Parse упадет на невалидном xml (но валидном html) точно так же, как и XmlDocument. не понимаю, чем он лучше чем просто SelectSingleNode. –  Sep 14 '15 at 09:56
  • @PashaPash, ничем не лучше. Даже хуже. Лишь быстрее при валидности. И еще, валидный html - это в том числе валидный xml. Браузеры для бесперебойной работы съедают любую разметку, а вот правила есть. И html, как наследник XML должен их соблюдать. Посмотрите код mail.ru, yandex.ru, google.com и многих других крупных известных ресурсов. Там код валиден на все 100%. Если ваш браузер поймет это <tr class=class1></tr>, то валидаторы html вам скажут, ты забыл поставить скобочки ) – iRumba Sep 14 '15 at 10:18
  • HTML - не наследник XML. HTML документ, с большой вероятностью - не валидный XML. Стандартный пример - тэг <img>, который в HTML не закрывается (валидатор HTML ругнется, если вы его закроете как </img>). mail.ru, ya.ru и google.com - используют HTML5 (<!DOCTYPE html>). Есть вариация HTML - XHTML - которая представляет собой валидный XML. Ее доктайп - <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> Но реально она нигде не используется. –  Sep 14 '15 at 10:29
  • попробуйте взять тот же ya.ru и загрузить его в XElement. с большой вероятностью не взлетит. –  Sep 14 '15 at 10:30