c++pugixml

Sort nodes in xml file


I'm using the pugixml library, and i try to add some content (nodes) with the fillWeek() function after sorting existing nodes in my xml file, but the problem is, when i call the sort() function, nothing happens, the _weekNode is not updated. Here is a minimal and reproducible example code :

#include <pugixml.hpp>
#include <cstring>
#include <vector>
#include <algorithm>
#include <iostream>

pugi::xml_document _doc;
pugi::xml_node _rootNode;
pugi::xml_node _weekNode;

bool compareNodesByAttribute(const pugi::xml_node &node1, const pugi::xml_node &node2)
{
    return std::strcmp(node1.attribute("From").value(), node2.attribute("From").value()) < 0;
}

void sort()
{
    std::vector<pugi::xml_node> nodesToSort;

    for (pugi::xml_node node = _rootNode.first_child(); node; node = node.next_sibling())
    {
        nodesToSort.push_back(node);
    }

    std::sort(nodesToSort.begin(), nodesToSort.end(), compareNodesByAttribute);

    for (const pugi::xml_node& node : nodesToSort)
    {
        _rootNode.remove_child(node);
    }

    for (pugi::xml_node node : nodesToSort)
    {
        _rootNode.append_copy(node);
    }

}

void loadXML()
{
    pugi::xml_parse_result result = _doc.load_file("Test.xml");

    if (result.status != pugi::status_ok)
    {
        pugi::xml_node declNode = _doc.prepend_child(pugi::node_declaration);
        declNode.append_attribute("version") = "1.0";
        declNode.append_attribute("encoding") = "UTF-8";
        declNode.append_attribute("standalone") = "yes";

        _rootNode = _doc.append_child("Root");
    }

    _rootNode = _doc.child("Root");
}

void makeWeek(const std::string& from, const std::string& to)
{
    _weekNode = _rootNode.append_child("Week");
    _weekNode.append_attribute("From").set_value(from.c_str());
    _weekNode.append_attribute("To").set_value(to.c_str());
}

void fillWeek()
{
    auto activity = _weekNode.append_child("Activity");
    activity.append_attribute("Type").set_value("0");
}

int main()
{
    loadXML();

    makeWeek("15/10","19/10");
    makeWeek("08/10","12/10");

    sort();

    fillWeek();

    _doc.save_file("Test.xml");

    return 0;
}

How to solve this problem please ?

Edit (Output):

Here is the actual result :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root>
    <Week From="08/10" To="12/10" />
    <Week From="15/10" To="19/10" />
</Root>

And here is the desired result :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root>
   <Week From="08/10" To="12/10">
       <Activity Type="0" />
   </Week>
   <Week From="15/10" To="19/10" />
</Root>

Solution

  • Your sorting function removes and reinserts nodes so _weekNode seems to not be a reference to the latest inserted weeknode after sorting.

    I suggest calling fillWeek() before sorting:

    fillWeek();
    sort();
    

    If you want to do the sorting before calling fillWeek() you need to find the node you want to fill first.

    void fillWeek()
    {
        // _weekNode will now reference the first of the sorted nodes:
        _weekNode = _rootNode.first_child();
        auto activity = _weekNode.append_child("Activity");
        activity.append_attribute("Type").set_value("0");
    }
    

    You could also use

    template<typename Predicate>
    xml_node pugi::xml_node::find_node(Predicate pred) const
    

    to find an arbitrary node.


    If you instead move the nodes around in your sort() function, your _weekNode will keep the reference to the last week node you appended and you will not have to find it again:

    #include <iterator>
    
    void sort() {
        std::vector<pugi::xml_node> nodesToSort(
            std::move_iterator(_rootNode.begin()),
            std::move_iterator(_rootNode.end()));
    
        std::sort(nodesToSort.begin(), nodesToSort.end(), compareNodesByAttribute);
    
        for(pugi::xml_node node : nodesToSort) {
            _rootNode.append_move(node);
        }
    }