c++xmlsearchtraversalpugixml

C++ Best way to search/traverse/replace multiple tags with pugixml?


I have to replace in multiple template_xml multiple tags to build some web services requests. While with pugixml i can access a tag like this doc.child("tag1").child("tag2").etc i dont know if that is the best way since with multiple templates and multiple nested tags there would be multiple lines of code for each tag (since they have different paths).

My input would be a struct or something with multiple std::strings that i need to replace. This is one xml template.

example xml_template:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="http://mywebservice.com/">
   <soapenv:Header />
   <soapenv:Body>
      <es:Tag1>
         %(auth)
         <es:AnotherTag>
            <es:Date>
               <es:CantReg>%(num_records)</es:CantReg>
               <es:Pto>%(pto_data)</es:Pto>
               <es:DataType>%(data_type)</es:DataType>
            </es:Date>
            <es:Req>
               <es:DetRequest>
                  %(user_data)
                  <es:CA>%(CA)</es:CA>
                  <es:GenDate>%(datetime_date)</es:GenDate>
               </es:DetRequest>
            </es:Req>
         </es:AnotherTag>
      </es:Tag1>
   </soapenv:Body>
</soapenv:Envelope>

I though of using regex but probably there is a better way with xml directly. Pugixml has this example, but it doesnt traverse the all xml childs (nested/depth full) and in my case with multiple strtings/template i still would have different functions for each template and each tag.

pugixml example:

    pugi::xml_document doc;
    if (!doc.load_file("xgconsole.xml")) return -1;

    pugi::xml_node tools = doc.child("Profile").child("Tools");
    for (pugi::xml_node tool = tools.first_child(); tool; tool = tool.next_sibling())
    {
        std::cout << "Tool:";
        for (pugi::xml_attribute attr = tool.first_attribute(); attr; attr = attr.next_attribute())
            std::cout << " " << attr.name() << "=" << attr.value();

        std::cout << std::endl;
    }
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<Profile FormatVersion="1">
    <Tools>
        <Tool Filename="jam" AllowIntercept="true">
            <Description>Jamplus build system</Description>
        </Tool>
        <Tool Filename="mayabatch.exe" AllowRemote="true" OutputFileMasks="*.dae" DeriveCaptionFrom="lastparam" Timeout="40" />
        <Tool Filename="meshbuilder_*.exe" AllowRemote="false" OutputFileMasks="*.mesh" DeriveCaptionFrom="lastparam" Timeout="10" />
        <Tool Filename="texbuilder_*.exe" AllowRemote="true" OutputFileMasks="*.tex" DeriveCaptionFrom="lastparam" />
        <Tool Filename="shaderbuilder_*.exe" AllowRemote="true" DeriveCaptionFrom="lastparam" />
    </Tools>
</Profile>

Couldn't find in pugixml a method like doc.find_tag_by_name(tag_name), only doc.find_child_by_attribute() but isnt depth full, only for the childs of that tag.

The ideal would be to have a function that i invoque like replace_tag(std::string tag, std:.string new_text) and then another function that i do a for loop for each string/tag that i need.

What would be a good way to do it? Probably an algorithm to search through all the xml and return the node path when the node.name() matches my tag_name (or replace it directly) but im not very familiar with search algorithms neither with search algorithms for an xml structure.


Solution

  • Finally end up using this.

    int get_tag_value(pugi::xml_document *xml_doc, std::string tag_name, std::string *tag_value)
    {
        std::string search_str = "//*/";  // xpath search for nested tags
        search_str += tag_name;
    
        pugi::xpath_node xpath_node = xml_doc->select_node(search_str.c_str());  // search node
        if(!xpath_node)
            return -1;
        pugi::xml_node selected_node = xpath_node.node().first_child();
        *tag_value = selected_node.value();
        return 0;
    }
    

    Similar with replace_tag but using selected_node.set_value(value.c_str()); at the end.