1

我想向 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 相关的流和其他类中仍然有点迷失。我不知道如何正确使用它们。非常欢迎任何解释。

cassablanca 的人说他们使用 XmlLite 和 Cassablanca 来处理结果,但我没有找到任何示例。你能指点我一些吗?谢谢。

更新(2014 年 6 月 4 日):上面的代码实际上被包装成这样的函数(wxString来自 wxWidgets,但可以很容易地用std::stringor替换它std::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 解析器?

4

1 回答 1

1

我还没用过卡萨布兰卡,所以这可能有点不对劲。(我很想和卡萨布兰卡一起工作,但我得先多花点时间。)也就是说,看起来您显示的代码将下载一个 xml 文件并将其保存到本地文件test.xml。从那时起,如果 xml 文件以 UTF-8 编码,则可以直接将文件加载到 XmlLite。如果它不是 UTF-8,您将不得不跳过一些额外的环节来解码它,无论是在内存中还是通过CreateXmlReaderInputWithEncodingNameor CreateXmlReaderInputWithCodePage,我不会在这里介绍。

获得 UTF-8 文件或处理编码后,使用 XmlLite 开始 XML 解析的最简单方法显示在以下文档中CreateXmlReader

//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在内存中创建一个。您有三个主要选择:

  1. 将您的字符串视为内存缓冲区并使用pMemStream = SHCreateMemStream(szData, cbData)
  2. 从 Casablanca 流式传输到IStreamcreated with CreateStreamOnHGlobal(NULL, true, &pMemStream),然后在您完成检索后将其用作您的源
  3. 为 Casablanca's创建一个IStream包装器concurrency::streams::istream,将其异步性隐藏在IStream界面后面

一旦你有了你的流,你必须用IXmlReader::SetInput告诉你的读者。

hr = pReader->SetInput(pStream);

无论上述选项如何,我建议使用 RAII 类(例如 ATL)CComPtr<IStream>CComPtr<IXMLReader>它们显示为的变量pFileStreampReader,或我建议pMemStream的 。这也是您需要覆盖任何属性的时候,例如,如果您必须处理比 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如果整个文件尚不可用,则该检查可能是相关的。http_resposne::body让 Casablanca提供一个自定义IStream实现,XmlLite 可以在下载的同时开始处理,这可能会“更好” ;这个讨论线程涵盖了这个想法,但似乎没有规范的解决方案。根据我的经验,XmlLite 是如此之快,以至于它导致的延迟无关紧要,因此从完整文件中处理它可能就足够了,特别是如果您确实需要完整文件才能完成处理。

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

这是否解决了您正在寻找的内容,或者这是您已经知道的部分并且您正在寻找IStreamCasabalanca 的包装器http_response::body或关于使 XmlLite 的处理异步的技巧?

于 2014-06-03T15:41:16.743 回答