Close Menu
GeekBlog

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    Sony Is Putting Store Credit in 4.4 Million PSN Wallets. There Is No Claim Form.

    September 17, 2026

    Hundreds of Contractors Are Reading Real ChatGPT Chats. The Off Switch Is Two Menus Deep.

    September 17, 2026

    Smartwatches With the Longest Battery Life in 2026: Tested Picks That Go Weeks Between Charges

    September 17, 2026
    Facebook X (Twitter) Instagram Threads
    GeekBlog
    • Home
    • Mobile
    • Tech News
    • Blog
    • Gaming
    • Smartwatch
    • How-To Guides
    • AI & Software
    Facebook
    GeekBlog
    Home»Blog»How to Read XML in C++: 4 Libraries Compared
    Blog

    How to Read XML in C++: 4 Libraries Compared

    Ethan CaldwellBy Ethan CaldwellSeptember 4, 202611 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Email Copy Link
    Share
    Facebook Twitter LinkedIn Pinterest Email Copy Link

    C++ has no XML parser in its standard library, so reading XML means picking a third party library. For most projects the answer is pugixml: it is a single pair of source files, it is fast, it has real XPath support, and the API is pleasant. TinyXML2 is the alternative when you want the absolute smallest dependency, RapidXML when you need raw speed on a document you can modify in place, and libxml2 when you need validation, namespaces and XSLT.

    Quick answer: Install pugixml (sudo apt install libpugixml-dev or vcpkg install pugixml), include <pugixml.hpp>, call doc.load_file("data.xml"), check the returned parse result, then walk with doc.child("root").children("item"). Build with g++ -std=c++20 -Wall -Wextra main.cpp -o main -lpugixml.

    Both pugixml and TinyXML2 examples below were compiled and run with GCC 13.3 against the distribution packages on Ubuntu 24.04: pugixml 1.14 and TinyXML2 10.0.0. The current upstream releases are pugixml 1.16 and TinyXML2 11.0.0, and nothing shown here changed between those versions. Every output block is the real output from those runs.

    The sample document

    All the parsers below read this file, saved as books.xml next to the executable.

    <?xml version="1.0" encoding="UTF-8"?>
    <catalog>
      <book id="b101" stock="4">
        <title>The Pragmatic Programmer</title>
        <author>Andrew Hunt</author>
        <price currency="USD">39.95</price>
      </book>
      <book id="b102" stock="0">
        <title>Effective Modern C++</title>
        <author>Scott Meyers</author>
        <price currency="USD">44.50</price>
      </book>
      <book id="b103" stock="12">
        <title>Designing Data Intensive Applications</title>
        <author>Martin Kleppmann</author>
        <price currency="USD">54.00</price>
      </book>
    </catalog>

    Installing and building

    Get the library in place before you write any code. On Ubuntu the packaged versions are a release or two behind upstream, which is fine for the APIs used here.

    # Debian and Ubuntu
    sudo apt install libpugixml-dev libtinyxml2-dev
    
    # macOS
    brew install pugixml tinyxml2
    
    # Cross platform, any of the three
    vcpkg install pugixml tinyxml2
    conan install --requires=pugixml/1.16
    
    # Confirm what you actually got
    pkg-config --modversion pugixml tinyxml2

    Both ship a CMake config package, so integration is three lines. I built and ran the first example below through exactly this file to confirm it works.

    cmake_minimum_required(VERSION 3.16)
    project(xmldemo CXX)
    
    set(CMAKE_CXX_STANDARD 20)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    find_package(pugixml REQUIRED)
    
    add_executable(xmldemo main.cpp)
    target_link_libraries(xmldemo PRIVATE pugixml::pugixml)

    For TinyXML2 the target is tinyxml2::tinyxml2 and the package name is tinyxml2. RapidXML has no build step at all: drop the headers in your tree and include them. libxml2 exposes a pkg-config file named libxml-2.0, which CMake finds through PkgConfig.

    Reading XML with pugixml

    The whole document is loaded into a DOM tree in one call. Notice that the parse result converts to bool, and that it carries a byte offset so you can point at the problem in a large file.

    #include <iostream>
    #include <pugixml.hpp>
    
    int main() {
        pugi::xml_document doc;
        pugi::xml_parse_result result = doc.load_file("books.xml");
    
        if (!result) {
            std::cerr << "parse failed: " << result.description()
                      << " at offset " << result.offset << '\n';
            return 1;
        }
    
        for (pugi::xml_node book : doc.child("catalog").children("book")) {
            std::cout << book.attribute("id").value() << " | "
                      << book.child("title").child_value() << " | "
                      << book.child("author").child_value() << " | "
                      << book.child("price").text().as_double() << " "
                      << book.child("price").attribute("currency").value()
                      << " | stock " << book.attribute("stock").as_int()
                      << '\n';
        }
        return 0;
    }
    g++ -std=c++20 -Wall -Wextra pugi.cpp -o pugi -lpugixml
    ./pugi
    # b101 | The Pragmatic Programmer | Andrew Hunt | 39.95 USD | stock 4
    # b102 | Effective Modern C++ | Scott Meyers | 44.5 USD | stock 0
    # b103 | Designing Data Intensive Applications | Martin Kleppmann | 54 USD | stock 12

    Recommended for you:

    Why Is Jack the Black Cat Squishmallow So Rare?
    Blog·Sep 3, 2026

    Why Is Jack the Black Cat Squishmallow So Rare?

    Tip: Missing nodes and attributes in pugixml return null objects rather than throwing or crashing. book.child("isbn").child_value() on a book with no isbn gives you an empty string. That makes optional fields easy, but it also means a typo in a tag name fails silently. Assert on the nodes you require.

    XPath queries with pugixml

    This is the feature that puts pugixml ahead of TinyXML2 and RapidXML for anything beyond a flat list. Instead of nesting loops and conditionals, you describe the nodes you want.

    #include <iostream>
    #include <pugixml.hpp>
    
    int main() {
        pugi::xml_document doc;
        if (!doc.load_file("books.xml")) {
            return 1;
        }
    
        pugi::xpath_node_set in_stock = doc.select_nodes("/catalog/book[@stock > 0]");
        std::cout << "in stock: " << in_stock.size() << '\n';
        for (const pugi::xpath_node& hit : in_stock) {
            std::cout << "  " << hit.node().child("title").child_value() << '\n';
        }
    
        pugi::xpath_node cheapest = doc.select_node("/catalog/book[not(../book/price < price)]");
        if (cheapest) {
            std::cout << "cheapest: " << cheapest.node().child("title").child_value() << '\n';
        }
    
        pugi::xml_node meyers = doc.select_node("//book[author='Scott Meyers']").node();
        std::cout << "Meyers id: " << meyers.attribute("id").value() << '\n';
        return 0;
    }
    # in stock: 2
    #   The Pragmatic Programmer
    #   Designing Data Intensive Applications
    # cheapest: The Pragmatic Programmer
    # Meyers id: b102

    Three queries, each of which would be a dozen lines of manual traversal. A malformed XPath expression throws pugi::xpath_exception, so wrap dynamic query strings in a try block. Static expressions like these are worth precompiling into a pugi::xpath_query object if you run them in a loop.

    Reading XML with TinyXML2

    TinyXML2 is two files, one header and one source, with no dependencies and no exceptions. The API is pointer based and returns error codes, so it feels closer to C than pugixml does.

    #include <iostream>
    #include <tinyxml2.h>
    
    using namespace tinyxml2;
    
    int main() {
        XMLDocument doc;
        XMLError err = doc.LoadFile("books.xml");
        if (err != XML_SUCCESS) {
            std::cerr << "load failed: " << XMLDocument::ErrorIDToName(err) << '\n';
            return 1;
        }
    
        XMLElement* catalog = doc.FirstChildElement("catalog");
        if (catalog == nullptr) {
            std::cerr << "no <catalog> root\n";
            return 1;
        }
    
        for (XMLElement* book = catalog->FirstChildElement("book");
             book != nullptr;
             book = book->NextSiblingElement("book")) {
    
            const char* id = book->Attribute("id");
            int stock = 0;
            book->QueryIntAttribute("stock", &stock);
    
            XMLElement* title = book->FirstChildElement("title");
            XMLElement* price = book->FirstChildElement("price");
    
            double amount = 0.0;
            if (price != nullptr) {
                price->QueryDoubleText(&amount);
            }
    
            std::cout << (id ? id : "?") << " | "
                      << (title ? title->GetText() : "?") << " | "
                      << amount << " | stock " << stock << '\n';
        }
        return 0;
    }
    g++ -std=c++20 -Wall -Wextra tiny.cpp -o tiny -ltinyxml2
    ./tiny
    # b101 | The Pragmatic Programmer | 39.95 | stock 4
    # b102 | Effective Modern C++ | 44.5 | stock 0
    # b103 | Designing Data Intensive Applications | 54 | stock 12
    Warning: Every accessor in TinyXML2 can return a null pointer, and GetText() returns null for an element with no text child as well as for one that does not exist. Dereferencing without a check is the single most common way to crash a TinyXML2 program. Check every pointer, as the loop above does.

    Error handling

    Real XML from the network or from users is malformed regularly. Neither library throws by default, so you have to look at the result.

    #include <iostream>
    #include <string>
    #include <pugixml.hpp>
    
    int main() {
        const char* broken = "<catalog><book><title>Unclosed</book></catalog>";
    
        pugi::xml_document doc;
        pugi::xml_parse_result r = doc.load_string(broken);
    
        if (!r) {
            std::cerr << "status  " << static_cast<int>(r.status) << '\n';
            std::cerr << "reason  " << r.description() << '\n';
            std::cerr << "offset  " << r.offset << '\n';
            std::cerr << "near    " << std::string(broken).substr(
                            static_cast<size_t>(r.offset), 20) << '\n';
            return 1;
        }
        std::cout << "parsed cleanly\n";
        return 0;
    }
    # status  14
    # reason  Start-end tags mismatch
    # offset  32
    # near    book></catalog>

    The offset pointing straight at the mismatched closing tag is what makes this usable in production. Log the description and the offset together and a support ticket about a bad feed becomes a two minute investigation.

    Comparing the four libraries

    LibraryLicenseAPI styleXPathBest for
    pugixmlMITValue handles, range based forYes, XPath 1.0The default choice
    TinyXML2zlibRaw pointers, error codesNoSmallest possible dependency
    RapidXMLBoost or MITHeader only, in place parseNoMaximum throughput
    libxml2MITC API, manual cleanupYes, plus XSLTSchema validation, namespaces

    On raw parse throughput, RapidXML is generally the fastest of the four because it parses destructively in place and hands you pointers into your own buffer, doing almost no allocation. pugixml is close behind and does not require you to keep the source buffer alive. Unless you have profiled and found XML parsing on your hot path, the difference will not be what limits your program.

    The licensing column is the one that decides things in commercial work. All four are permissive, so none of them will force you to publish source. libxml2 is the heaviest dependency by a wide margin, and the only one whose C API requires you to free nodes and documents by hand. Wrap its handles in a std::unique_ptr with a custom deleter the moment you adopt it.

    When to reach for libxml2 instead

    The three lightweight parsers all read well formed XML and stop there. libxml2 is the one that answers a different question: is this document valid according to a schema, and what do these namespace prefixes actually mean?

    NeedpugixmlTinyXML2libxml2
    Well formedness checkYesYesYes
    XSD or DTD validationNoNoYes
    Namespace resolutionPrefix is part of the namePrefix is part of the nameFull support
    Streaming a file too large for RAMLimitedNoYes, reader API
    XSLT transformsNoNoYes, with libxslt

    Note that none of these examples were run against libxml2 on the machine used for this guide, because its development package was not installed there. The rows above describe documented capability, not measured behavior. If you adopt it, budget time for the C ownership model: every document, node set and XPath context you create has a matching free function you must call.

    Troubleshooting

    fatal error: pugixml.hpp: No such file or directory. The development headers are not installed. On Debian or Ubuntu, sudo apt install libpugixml-dev. With vcpkg, vcpkg install pugixml and then let the toolchain file handle the include path.

    undefined reference to pugi::xml_document::load_file. You compiled but did not link. Add -lpugixml at the end of the command line, after your source files. Order matters with GNU ld.

    load_file returns a file not found status. The path is relative to the working directory, not to the executable. Print the result description, which distinguishes a missing file from malformed content, and pass an absolute path while you debug.

    Recommended for you:

    Best Battery Stocks to Invest In: What the 2026 Numbers Actually Show
    Blog·Sep 3, 2026

    Best Battery Stocks to Invest In: What the 2026 Numbers Actually Show

    Element lookups return nothing on a document with namespaces. pugixml and TinyXML2 do not resolve namespace prefixes. The node name is literally ns:book, prefix included. Match on the full prefixed name, or move to libxml2 if you need real namespace handling.

    Non ASCII text comes out as mojibake. Both libraries hand you UTF-8 bytes. The corruption is almost always in how you print them: a Windows console needs its code page set, and a narrow std::string written to a stream is not transcoded for you.

    Frequently asked questions

    Does the C++ standard library include an XML parser?

    No, and no proposal to add one is close to adoption. The standard library covers strings, containers, algorithms and, since C++17, filesystem access. XML, JSON and YAML all require a third party library.

    Which XML library is fastest?

    RapidXML generally leads on raw parse throughput because it modifies your buffer in place and allocates almost nothing, with pugixml close behind. The tradeoff is that your source buffer must outlive the document. For most applications parsing is not the bottleneck.

    Can I read a huge XML file without loading it all into memory?

    Yes, but not with a DOM parser. Use a streaming approach: libxml2’s reader API, or pugixml’s xml_document::load_buffer_inplace combined with chunking your own input. Every example in this guide builds a full tree in memory.

    How do I write XML rather than read it?

    All four libraries support it. In pugixml, build nodes with append_child and append_attribute, then call doc.save_file("out.xml"). In TinyXML2, use NewElement and InsertEndChild followed by SaveFile. If you are new to file output in C++, see our guide on writing a file in C++.

    Should I use header only RapidXML or a linked library?

    Header only is simpler to vendor and needs no build system changes, which is why RapidXML is attractive for small tools. A linked library keeps compile times down on large codebases because the parser is compiled once rather than in every translation unit that includes it.

    The bottom line

    Start with pugixml. The install is one package, the API reads like modern C++, and XPath turns most extraction tasks into a single expression. Both examples above compiled cleanly under -Wall -Wextra and ran on the first try.

    Move to TinyXML2 only if vendoring two files matters more than the API, to RapidXML only after profiling proves parsing is your bottleneck, and to libxml2 only when you genuinely need schema validation or namespaces. The official pugixml manual is thorough and worth reading once through. For more C++ fundamentals see our guides on storing text file data in a vector and joining two vectors in C++.

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Telegram Email Copy Link
    Previous ArticleBest Google Ads Books to Read in 2026
    Next Article Google’s URL Parameters Tool Is Gone: What to Do Now
    Ethan Caldwell

      Ethan Caldwell is GeekBlog's resident Apple specialist, covering the entire Apple ecosystem - iPhone, iPad, Mac, Apple Watch, AirPods and the software that ties them together. A longtime iOS user and gadget collector, Ethan tracks Cupertino's every move, breaking down Apple keynotes, A- and M-series chip benchmarks, iOS feature updates and the rumor mill into clear, practical takes that help readers decide whether the latest Apple hardware is worth the upgrade.

      Related Posts

      11 Mins Read

      South Carolina vs Michigan: Which State Is Better to Live In?

      11 Mins Read

      Are There Cordless Vacuums With Replaceable Batteries?

      12 Mins Read

      How to Use Parabolic SAR (Stop and Reverse) for Day Trading

      10 Mins Read

      Michigan vs Illinois: Which State Is Better to Live In?

      11 Mins Read

      California or Florida: Which State Is Better to Move To?

      11 Mins Read

      Best Front End Development Books to Learn From in 2026

      Top Posts

      The Mesh Router Placement Strategy That Finally Gave Me Full Home Coverage

      September 9, 20262 Views

      How to Use an iPhone for Beginners

      July 7, 20262 Views

      Best Free Online Music Apps in 2026

      July 7, 20262 Views
      Stay In Touch
      • Facebook

      Subscribe to Updates

      Get the latest tech news from FooBar about tech, design and biz.

      Most Popular

      How to Convert HEIC to JPG on iPhone, Mac, Android and Windows

      September 3, 20266 Views

      Gal Gadot’s Lawyers Spent Six Months on One AI Clause. Then SAG Called Them for Pointers.

      September 2, 20265 Views

      How to Spot AI Generated Images in 2026 (The Old Tricks Stopped Working)

      September 3, 20263 Views
      Our Picks

      Sony Is Putting Store Credit in 4.4 Million PSN Wallets. There Is No Claim Form.

      September 17, 2026

      Hundreds of Contractors Are Reading Real ChatGPT Chats. The Off Switch Is Two Menus Deep.

      September 17, 2026

      Smartwatches With the Longest Battery Life in 2026: Tested Picks That Go Weeks Between Charges

      September 17, 2026

      Subscribe to Updates

      Get the latest creative news from FooBar about art, design and business.

      HEICJPG.online - Convert HEIC to JPG online
      Facebook
      • About Us
      • Contact us
      • Privacy Policy
      • Disclaimer
      • Terms and Conditions
      • Editorial Policy
      • Cookie Policy
      © 2026 GeekBlog

      Type above and press Enter to search. Press Esc to cancel.