Perform the following three XPath queries on the XML Document below:

 //item[1]: Retrieve the first "item" element 
 //price/text(): Perform an action on each "price" element (print it out)
 //name: Get an array of all the "name" elements
XML Document:

<inventory title="OmniCorp Store #45x10^3">
  <section name="health">
    <item upc="123456789" stock="12">
      <name>Invisibility Cream</name>
      <price>14.50</price>
      <description>Makes you invisible</description>
    </item>
    <item upc="445322344" stock="18">
      <name>Levitation Salve</name>
      <price>23.99</price>
      <description>Levitate yourself for up to 3 hours per application</description>
    </item>
  </section>
  <section name="food">
    <item upc="485672034" stock="653">
      <name>Blork and Freen Instameal</name>
      <price>4.95</price>
      <description>A tasty meal in a tablet; just add water</description>
    </item>
    <item upc="132957764" stock="44">
      <name>Grob winglets</name>
      <price>3.56</price>
      <description>Tender winglets of Grob. Just add water</description>
    </item>
  </section>
</inventory>

#include <cassert>
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <utility>
#include <vector>
 
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlerror.h>
#include <libxml/xmlstring.h>
#include <libxml/xmlversion.h>
#include <libxml/xpath.h>
 
#ifndef LIBXML_XPATH_ENABLED
#   error libxml was not configured with XPath support
#endif
 
// Because libxml2 is a C library, we need a couple things to make it work
// well with modern C++:
//   1) a ScopeGuard-like type to handle cleanup functions; and
//   2) an exception type that transforms the library's errors.
 
// ScopeGuard-like type to handle C library cleanup functions.
template <typename F>
class [[nodiscard]] scope_exit
{
public:
    // C++20: Constructor can (and should) be [[nodiscard]].
    /*[[nodiscard]]*/ constexpr explicit scope_exit(F&& f) :
        f_{std::move(f)}
    {}
 
    ~scope_exit()
    {
        f_();
    }
 
    // Non-copyable, non-movable.
    scope_exit(scope_exit const&) = delete;
    scope_exit(scope_exit&&) = delete;
    auto operator=(scope_exit const&) -> scope_exit& = delete;
    auto operator=(scope_exit&&) -> scope_exit& = delete;
 
private:
    F f_;
};
 
// Exception that gets last libxml2 error.
class libxml_error : public std::runtime_error
{
public:
    libxml_error() : libxml_error(std::string{}) {}
 
    explicit libxml_error(std::string message) :
        std::runtime_error{make_message_(std::move(message))}
    {}
 
private:
    static auto make_message_(std::string message) -> std::string
    {
        if (auto const last_error = ::xmlGetLastError(); last_error)
        {
            if (not message.empty())
                message += ": ";
            message += last_error->message;
        }
 
        return message;
    }
};
 
auto main() -> int
{
    try
    {
        // Initialize libxml.
        ::xmlInitParser();
        LIBXML_TEST_VERSION
        auto const libxml_cleanup = scope_exit{[] { ::xmlCleanupParser(); }};
 
        // Load and parse XML document.
        auto const doc = ::xmlParseFile("test.xml");
        if (not doc)
            throw libxml_error{"failed to load document"};
        auto const doc_cleanup = scope_exit{[doc] { ::xmlFreeDoc(doc); }};
 
        // Create XPath context for document.
        auto const xpath_context = ::xmlXPathNewContext(doc);
        if (not xpath_context)
            throw libxml_error{"failed to create XPath context"};
        auto const xpath_context_cleanup = scope_exit{[xpath_context]
            { ::xmlXPathFreeContext(xpath_context); }};
 
        // Task 1 ============================================================
        {
            std::cout << "Task 1:\n";
 
            auto const xpath =
                reinterpret_cast<::xmlChar const*>(u8"//item[1]");
 
            // Create XPath object (same for every task).
            auto xpath_obj = ::xmlXPathEvalExpression(xpath, xpath_context);
            if (not xpath_obj)
                throw libxml_error{"failed to evaluate XPath"};
            auto const xpath_obj_cleanup = scope_exit{[xpath_obj]
                { ::xmlXPathFreeObject(xpath_obj); }};
 
            // 'result' a xmlNode* to the desired node, or nullptr if it
            // doesn't exist. If not nullptr, the node is owned by 'doc'.
            auto const result = xmlXPathNodeSetItem(xpath_obj->nodesetval, 0);
            if (result)
                std::cout << '\t' << "node found" << '\n';
            else
                std::cout << '\t' << "node not found" << '\n';
        }
 
        // Task 2 ============================================================
        {
            std::cout << "Task 2:\n";
 
            auto const xpath =
                reinterpret_cast<::xmlChar const*>(u8"//price/text()");
 
            // Create XPath object (same for every task).
            auto xpath_obj = ::xmlXPathEvalExpression(xpath, xpath_context);
            if (not xpath_obj)
                throw libxml_error{"failed to evaluate XPath"};
            auto const xpath_obj_cleanup = scope_exit{[xpath_obj]
                { ::xmlXPathFreeObject(xpath_obj); }};
 
            // Printing the results.
            auto const count =
                xmlXPathNodeSetGetLength(xpath_obj->nodesetval);
            for (auto i = decltype(count){0}; i < count; ++i)
            {
                auto const node =
                    xmlXPathNodeSetItem(xpath_obj->nodesetval, i);
                assert(node);
 
                auto const content = XML_GET_CONTENT(node);
                assert(content);
 
                // Note that reinterpret_cast here is a Bad Idea, because
                // 'content' is UTF-8 encoded, which may or may not be the
                // encoding cout expects. A *PROPER* solution would translate
                // content to the correct encoding (or at least verify that
                // UTF-8 *is* the correct encoding).
                //
                // But this "works" well enough for illustration.
                std::cout << "\n\t" << reinterpret_cast<char const*>(content);
            }
 
            std::cout << '\n';
        }
 
        // Task 3 ============================================================
        {
            std::cout << "Task 3:\n";
 
            auto const xpath =
                reinterpret_cast<::xmlChar const*>(u8"//name");
 
            // Create XPath object (same for every task).
            auto xpath_obj = ::xmlXPathEvalExpression(xpath, xpath_context);
            if (not xpath_obj)
                throw libxml_error{"failed to evaluate XPath"};
            auto const xpath_obj_cleanup = scope_exit{[xpath_obj]
                { ::xmlXPathFreeObject(xpath_obj); }};
 
            // 'results' is a vector of pointers to the result nodes. The
            // nodes pointed to are owned by 'doc'.
            auto const results = [ns=xpath_obj->nodesetval]()
            {
                auto v = std::vector<::xmlNode*>{};
                if (ns && ns->nodeTab)
                    v.assign(ns->nodeTab, ns->nodeTab + ns->nodeNr);
                return v;
            }();
            std::cout << '\t' << "set of " << results.size()
                << " node(s) found" << '\n';
        }
    }
    catch (std::exception const& x)
    {
        std::cerr << "ERROR: " << x.what() << '\n';
        return EXIT_FAILURE;
    }
}
 