In the "Do The Same Series", I am going to explore programming tasks which I perform in C#.NET and try to compare them with C++ alternatives, often involving third party libraries. This will probably end up being the only entry in the series.
This post is about downloading and querying very specific regions of an xml document easily using xpath. I am going to use the url of an xml file which has currency rates.
C#.NET:XmlDocument xml_document= new XmlDocument();
xml_document.Load("http://www.tcmb.gov.tr/kurlar/today.xml");
String xpath= "/Tarih_Date/Currency[@CurrencyCode='USD']/BanknoteBuying";
XmlNode node= xml_document.SelectSingleNode(xpath);
String value= node.InnerText; // gives me the current $/YTL ratio
Very few lines of code and almost no margin of error. I like C#.NET.
C++ (and libcurl and libxml++2):There is no short way of doing this. The C++ wrapper for libcurl just introduces overhead with no useful facilities. So I will introduce a few of my own.
The C API of curl is very sane, giving great flexibility to programmers with C style callbacks. I won't spend time wrapping them to use member functions and such. The function callbacks are ok for now.
There are two curl options of interest here:
CURLOPT_WRITEDATA:The option which tells curl where the write function will output its data. The default is stdout.
CURLOPT_WRITEFUNCTION:The option which tells curl which funciton to call when it needs to write downloaded data. The default is a function which writes to a file.
So I can change the writedata to a C++ container and the writefunction to something which appends to that container. I will also write a simple function which takes a url and puts it into the given container.
namespace pwned{ namespace curl
{
template <typename containerT>
std::size_t output_to_container(void* data, std::size_t size, std::size_t nmemb, void* input)
{
containerT* con= static_cast<containerT*>(input);
char* cstr= static_cast<char*>(data);
std::size_t len= size* nmemb;
// insert will work on any sequence including std::string
try { con-> insert(con-> end(), cstr, cstr+ len); }
catch(...) { return CURLE_WRITE_ERROR; }
return len;
}
template <typename containerT>
void set_output_container(CURL* c, containerT &s)
{
curl_easy_setopt(c, CURLOPT_WRITEDATA, &s);
std::size_t(*f)(void*, std::size_t, std::size_t, void*)= output_to_container<containerT>;
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, f);
}
template <typename containerT>
void url_to_container(std::string const &url, containerT &c)
{
CURL* curl= curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
pwned::curl::set_output_container(curl, c);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
} // curl
} // pwned
Now, on to xml parsing. The library I chose was libxml++2, a very nice C++ wrapper for libxml2. The only sin of libxml2 is that it can't validate xml, but we are not bothered with schemas right now. The point is, it provides xpath search and can use utf-8 strings from glibmm, so it is as close as they get to .NET's functionality. It is also very accessible on windows and linux. (accompanies gtkmm on win32)
std::string xml;
pwned::curl::url_to_container("http://www.tcmb.gov.tr/kurlar/today.xml", xml);
xmlpp::DomParser parser;
parser.parse_memory(xml);
std::string xpath= "/Tarih_Date/Currency[@CurrencyCode='USD']/BanknoteBuying";
xmlpp::NodeSet nodes= parser.get_document()-> get_root_node()-> find(xpath);
xmlpp::Element* element= dynamic_cast<xmlpp::Element*>(nodes.at(0));
std::string value= element-> get_child_text()-> get_content();
On my linux distro I could build and run the test program using:
g++ test.cpp `pkg-config libcurl libxml++2 --cflags --libs` && ./a.out
See you next time, should there be one.