我想向Web服务发出请求,获取XML内容,然后解析它以获取服务返回的特定值。

该代码将使用本机C++ 11(MS Visual Studio 2013)编写。选择了Cassablanca PPL库。对于XML解析,选择了XmlLite。

我习惯了C++编程;但是,PPL库中的异步任务编程(这种方法)对我来说是新的。我知道异步编程是什么,并且我知道并行编程的原理。但是,我不习惯使用延续(.then(...)),而只是慢慢地将这个概念包住了头。

到目前为止,我已经修改了示例以获取XML结果并将其写入文本文件:

// Open a stream to the file to write the HTTP response body into.
auto fileBuffer = std::make_shared<concurrency::streams::streambuf<uint8_t>>();
file_buffer<uint8_t>::open(L"test.xml", std::ios::out)
    .then([=](concurrency::streams::streambuf<uint8_t> outFile) -> pplx::task < http_response >
{
    *fileBuffer = outFile;

    // Create an HTTP request.
    // Encode the URI query since it could contain special characters like spaces.
    // Create http_client to send the request.
    http_client client(L"http://api4.mapy.cz/");

    // Build request URI and start the request.
    uri_builder builder(L"/geocode");
    builder.append_query(L"query", address);

    return client.request(methods::GET, builder.to_string());
})

    // Write the response body into the file buffer.
    .then([=](http_response response) -> pplx::task<size_t>
{
    printf("Response status code %u returned.\n", response.status_code());

    return response.body().read_to_end(*fileBuffer);
})

    // Close the file buffer.
    .then([=](size_t)
{
    return fileBuffer->close();
})

    // Wait for the entire response body to be written into the file.
    .wait();

现在,我需要了解如何修改代码以获取可以消耗的结果XmlLite(Microsoft实现为xmllite.hxmllite.libxmllite.dll。我知道什么是提取解析器。但是,我还是一个很新的图书馆在与PPL相关的流和其他类中我仍然迷失了方向,我不知道如何正确使用它们,欢迎任何解释。

卡萨布兰卡人说他们将XmlLite与卡萨布兰卡一起使用来处理结果,但是我没有找到任何示例。你能指出我一些吗?谢谢。

更新(2014年6月4日):上面的代码实际上是像这样包装的函数(wxString来自wxWidgets,但是可以很容易地用std::stringstd::wstring替换它):
std::pair<double, double> getGeoCoordinatesFor(const wxString & address)
{
    ...the above code...
    ...here should be the XML parsing code...
    return {longitude, latitude};
}

实际上,目标是代替将流写入test.xml文件以提供XmlLite解析器。 XML很小,它包含一个或多个(如果地址不明确)带有我要提取的x和y属性的项目元素-像这样:
<?xml version="1.0" encoding="utf-8"?>
<result>
    <point query="Vítězství 27, Olomouc">
        <item
                x="17.334045"
                y="49.619723"
                id="9025034"
                source="addr"
                title="Vítězství 293/27, Olomouc, okres Olomouc, Česká republika"
        />
        <item
                x="17.333067"
                y="49.61618"
                id="9024797"
                source="addr"
                title="Vítězství 27/1, Olomouc, okres Olomouc, Česká republika"
        />
    </point>
</result>

我不需要那个test.xml文件。如何获取流以及如何将其重定向到XmlLite解析器?

最佳答案

我还没有使用过卡萨布兰卡,所以可能有点差。 (我很想和Casablanca一起工作,但是我必须先花更多时间。)也就是说,您显示的代码似乎将下载xml文件并将其保存到本地文件test.xml中。从那时起,如果xml文件是用UTF-8编码的,则可以很容易地将文件加载到XmlLite中。如果不是UTF-8,则必须在内存中或通过 CreateXmlReaderInputWithEncodingName CreateXmlReaderInputWithCodePage 跳过一些额外的循环以对其进行解码,在此不做介绍。

在获得UTF-8文件或正在处理编码后, CreateXmlReader 的文档中显示了使用XmlLite启动XML解析的最简单方法:

//Open read-only input stream
if (FAILED(hr = SHCreateStreamOnFile(argv[1], STGM_READ, &pFileStream)))
{
    wprintf(L"Error creating file reader, error is %08.8lx", hr);
    return -1;
}

if (FAILED(hr = CreateXmlReader(__uuidof(IXmlReader), (void**) &pReader, NULL)))
{
    wprintf(L"Error creating xml reader, error is %08.8lx", hr);
    return -1;
}

在这种情况下,您想跳过文件,因此需要在内存中创建一个IStream。您有三个主要选择:
  • 将您的字符串视为内存缓冲区,并使用 pMemStream = SHCreateMemStream(szData, cbData)
  • 从卡萨布兰卡流式传输为使用 IStream 创建的CreateStreamOnHGlobal(NULL, true, &pMemStream),然后在完成检索
  • 后将其用作源
  • 为卡萨布兰卡的IStream创建concurrency::streams::istream包装器,将其异步性隐藏在IStream接口(interface)
  • 后面

    一旦有了流,就必须使用IXmlReader::SetInput告诉读者。
    hr = pReader->SetInput(pStream);
    

    无论上述选项如何,我建议对它们显示为CComPtr<IStream>CComPtr<IXMLReader>或我建议的pFileStream的变量使用RAII类(例如ATL的pReaderpMemStream)。这也是当您需要override any properties时,比如说您是否需要处理比XmlLite默认值更深的递归。然后,这一切都是关于读取文件的。最简单的循环记录在IXmlReader::Read方法中。以下是一些最重要的部分,但请注意,为了便于阅读,我省略了错误检测:
    void Summarize(IXmlReader *pReader, LPCWSTR wszType)
    {
        LPCWSTR wszNamespaceURI, wszPrefix, wszLocalName, wszValue;
        UINT cchNamespaceURI, cchPrefix, cchLocalName, cchValue;
    
        pReader->GetNamespaceURI(&wszNamespaceURI, &cchNamespaceURI);
        pReader->GetPrefix(&wszPrefix, &cchPrefix);
        pReader->GetLocalName(&wszLocalName, &cchLocalName);
        pReader->GetValue(&wszValue, &cchValue);
        std::wcout << wszType << L": ";
        if (cchNamespaceURI) std::wcout << L"{" << wszNamespaceURI << L"} ";
        if (cchPrefix)       std::wcout << wszPrefix << L":";
        std::wcout << wszLocalName << "='" << wszValue << "'\n";
    }
    
    void Parse(IXmlReader *pReader)
    {
        // Read through each node until the end
        while (!pReader->IsEOF())
        {
            hr = pReader->Read(&nodeType);
            if (hr != S_OK)
                break;
    
            switch (nodeType)
            {
                //  : : :
    
                case XmlNodeType_Element:
                    Summarize(pReader, L"BeginElement");
                    while (S_OK == pReader->MoveToNextAttribute())
                        Summarize(pReader, L"Attribute");
                    pReader->MoveToElement();
                    if (pReader->IsEmptyElement())
                        std::wcout << L"EndElement\n";
                    break;
    
                case XmlNodeType_EndElement:
                    std::wcout << L"EndElement\n";
                    break;
    
                //  : : :
             }
        }
    }
    

    该示例代码中的其他一些内容包括对E_PENDING的检查,如果整个文件尚不可用,则该检查可能是相关的。让Casablanca http_resposne::body提供一个自定义IStream实现可能会“更好”,XmlLite可以开始与下载并行进行处理。 this discussion thread涵盖了这个想法,但似乎没有规范的解决方案。以我的经验,XmlLite是如此之快,以至于造成的延迟是无关紧要的,因此从完整文件中处理它可能就足够了,特别是如果您确实需要完整文件才能完成处理。

    如果您需要更好地将其集成到异步系统中,将会遇到更多麻烦。显然,上面的while循环本身不是异步的。我的猜测是使它异步的正确方法将在很大程度上取决于文件的内容以及在读取文件时必须执行的处理,以及是否将其绑定(bind)到可能没有全部数据可用的自定义IStream上。由于我对Casabalanca的异步性没有任何经验,因此我无法对此发表有用的评论。

    这是否满足您的需求,或者这是您已经知道的部分,并且您正在寻找Casabalanca的IStreamhttp_response::body包装器,还是有关使XmlLite的处理异步的提示?

    10-05 22:02