How to extract some info from the http_client response via XmlLite?

Jun 11, 2014 at 2:56 PM
Edited Jun 11, 2014 at 2:59 PM
Following the discussion:541059, I was able to get the XML body of the response into wstring. Full example looks like this.
#include <cpprest/filestream.h>

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency;
using namespace concurrency::streams;

void output_longlat_for_address(const std::wstring & address)
{
    // Create the client, build the query, and start the request.
    http_client client(L"http://api4.mapy.cz/");
    uri_builder builder(L"/geocode");
    builder.append_query(L"query", address);

    client.request(methods::GET, builder.to_string())
    .then([](http_response response)
    {
        // Headers arrived.
        return response.extract_string();
    }).then([](utility::string_t responseBody)
    {
        // Body arrived.
        std::wcout << responseBody;
    }).wait();
}

int main()
{
    output_longlat_for_address(L"Vítězství 27, Olomouc");
    return 0;
}
Now, my goal is to extract longitude and latitude info (geocoding service) from the XML. I have read elsewhere that XmlLite can be a good way to parse the result. However, I did not find any example on how Casablanca can be put together with XmlLite. Is there any recommended way of probably redirecting the body stream into the parser?
Jun 12, 2014 at 2:10 AM
Hi pepr,

Yes you are correct the C++ Rest SDK doesn't contain an XML library. You can get the response body as a string, like is done in your example code, from there you can then use with any existing XML library. As far as how to use other XML libraries you should consult the respective documentation. For a cross platform option RapidXml might be a good option. For Windows perhaps XmlLite, like you mention.

Steve
Jun 12, 2014 at 2:38 PM
Edited Jun 12, 2014 at 2:39 PM
Hi Steve,

I have a spike solution for XmlLite that consumes the content of the XML file that was obtained via the above request (and stored to the file) like this (simlified):
IStream * OpenFileReadIStream(const std::wstring &fname)
{
    IStream * pFileStream{};
    HRESULT hr = SHCreateStreamOnFile(fname.c_str(), STGM_READ, &pFileStream));
    return pFileStream;
}

IXmlReader * CreateXmlReader()
{
    IXmlReader * pReader{};
    HRESULT hr = CreateXmlReader(__uuidof(IXmlReader), (void**)&pReader, nullptr));
    return pReader;
}

int main()
{
    wstring fname{ L"response.xml" };
    CComPtr<IXmlReader> reader{ CreateXmlReader() };
    CComPtr<IStream> filestream{ OpenFileReadIStream(fname) };
    XmlNodeType nodeType;
    HRESULT hr;
    ...
    reader->SetInput(filestream);
    while (S_OK == (hr = reader->Read(&nodeType)))
    {
        switch (nodeType)
        {
        case XmlNodeType_Element:
            ...
            break;
        case etc...:
        }
    }
    return 0;
}
Now, I would like to modify the above function that uses casablanca to redirect the stream to the parser:
void output_longlat_for_address(const std::wstring & address)
{
    CComPtr<IStream> stream{};                                        // <---------- first modification 

    // Create the client, build the query, and start the request.
    http_client client(L"http://api4.mapy.cz/");
    uri_builder builder(L"/geocode");
    builder.append_query(L"query", address);

    client.request(methods::GET, builder.to_string())
    .then([stream](http_response response)
    {
        // Headers arrived.
        return response.body();                            // <---------- second modification 
    }).then([stream]concurrency::streams::istream bodyStream) // <----'
    {
        stream ???= bodyStream;                                       // <----- the question
    }).wait();

    CComPtr<IXmlReader> reader{ CreateXmlReader() };   // <---------- here redirecting to the reader
    reader->SetInput(stream);                            // <----'

    while (...)
    {
        ... parsing....
    }
    return result;
}
But I did not find how to attach the stream to the bodyStream. It means, how to give the bodyStream the IStream interface. Is it possible at all? Also (this is not my case), if the body would be extremely long, could the xmllite pull parser be somehow launched early, when the first part of the body is retrieved?

Thanks,
Petr
Jun 12, 2014 at 5:43 PM
Hi Petr,

We don't have any built in facilities in Casablanca to convert from one of our streams to a Windows IStream interface, although we do to std iostreams. If you really want to do this you'd have to implement the IStream interface yourself. Instead I'd recommend you just use response.extract_string() to obtain a string which can be passed to XmlLite. Start with the simple way and then you can always improve in the future if profiling shows it to be valuable. In practice I'm skeptical as to how much benefit you'd actually see by streaming into the XML parser. The XML document, like you state would have to be quite large.

Steve
Jun 13, 2014 at 2:23 PM
Thanks Steve,
use response.extract_string() to obtain a string which can be passed to XmlLite.
This seems reasonable. I do not need anything more complex. However, I did not find a way to pass a string to XmlLite. The only way I was able to find is to pass the IXmlReader object the IStream object. I tried the code like:
IStream * OpenIStreamFromString(const std::wstring & str)
{
    HGLOBAL    hMem = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(wchar_t)*str.length());
    IStream * pStream{};
    DWORD dwWritten{ 0 };
    HRESULT hr{ S_OK };

    if (FAILED(hr = CreateStreamOnHGlobal(hMem, TRUE, &pStream)))
    {
        ostringstream oss;
        oss << "Error when CreateStreamOnHGlobal, error is 0x" << hex << hr << endl;
        throw runtime_error(oss.str());
    }

    if (FAILED(hr = pStream->Write(str.c_str(), str.length() * sizeof(wchar_t), &dwWritten)))
    {
        ostringstream oss;
        oss << "Memory stream was not initialized, error is 0x" << hex << hr << endl;
        throw runtime_error(oss.str());
    }

    LARGE_INTEGER pos;
    pos.QuadPart = 0;
    if (FAILED(hr = pStream->Seek(pos, STREAM_SEEK_SET, nullptr)))
    {
        ostringstream oss;
        oss << "Memory stream seek to start failed, error is 0x" << hex << hr << endl;
        throw runtime_error(oss.str());
    }
    return pStream;
}
But it apparently contains a bug. The while loop in the following code does not enter the body even though reader->SetInput(stream); returns S_OK:
    CComPtr<IStream> stream{ OpenIStreamFromString(str) };
    CComPtr<IXmlReader> reader{ CreateXmlReader() };
    hr = reader->SetInput(stream);
    ...
    while (S_OK == (hr = reader->Read(&nodeType)))
    {
        ...
    }
Any help is appreciated (pointer to another answer, pointer to code snippet, whatever). I am stuck.

Petr
Jun 13, 2014 at 8:03 PM
Hi Petr,

I'm really not familiar with this, but if you want to use XmlLite I'd recommend by starting to look at their samples. Also take a look at the documentation for the CreateStreamOnHGlobal function.

Thanks,
Steve
Jun 17, 2014 at 3:12 PM
Hi Steve,

I have actually tried the CreateStreamOnHGlobal as seen in the code above. I did not find anything appropriate in the samples and I could not make the code work (too black box). I have finally implemented a slopy data extractor that works fine for the purpose.

Thanks for your help and have a nice day,

Petr