1

I know it seems I have a habit of answering my own questions, but so far I have been stuck on this one and need some help.

I have made some code to load a json formatted file into a class system. I put all the code up here: https://github.com/tomzooi/readreq

in short of what I am trying to do: first I created some code that can read in a "requirements" file and store it using the Requirement class from class.h This again I can output in human readable format to screen or store it in a json file. this works.

Then I want to be able to read in a JSON file and store it in memory using the same Requirement object again, this however is not working so well (so far).

The main issue now is the part where I am traversing the property tree, this is mainly done in this recursive function:

    void display(const int depth, const boost::property_tree::ptree& tree, Requirement * cur_requirement, std::vector<Requirement> &requirements) { 
    unsigned int count;
   std::string label,level,description;
   boost::property_tree::ptree kids = tree.get_child("");
    bool godown = false;
    for (const auto& v : kids) { // v is of type ptree::value_type
        std::cout << std::string("").assign(depth+1,'#') << " ";
        std::string nodestr = tree.get<std::string>(v.first);  
        //std::cout << v.first << " = " << nodestr << std::endl;
        if (v.first == "label") {
            label = nodestr;
            std::cout << "lbl: " << label << std::endl;
        }
        else if(v.first == "level") {
            //std::cout << "LABEL!";
                level = nodestr;
                std::cout << "lvl: " << level << std::endl;
        }
        else if(v.first == "description") {
                description = nodestr;
                std::cout << "dsc: " << description << std::endl;
        }
        else if(v.first == "children") { //going down, store stuff first
            if(depth == 0) { //zero depth
                std::cout << "zero depth...";
                requirements.emplace_back(level, description, label,cur_requirement);
                cur_requirement = &requirements.back();
            }
            else { //one or higher depth
                std::cout << "at depth " << depth << "..." << std::flush; 
                cur_requirement->children.emplace_back(level,description,label,cur_requirement->parent);
                cur_requirement = &cur_requirement->children.back();
            }
            std::cout << "going down" << std::endl;
            //cur_requirement = &cur_requirement->children.back();
            display(depth+1, v.second, cur_requirement,requirements);
        }
        else if(v.first == "") {
            std::cout << "empty v.first ... level: " << level << std::endl;
            if(depth == 0) { //zero depth
                std::cout << "store at zero depth...";
                requirements.emplace_back(level, description, label,cur_requirement);
                cur_requirement = &requirements.back();
            }
            else { //one or higher depth
                std::cout << "store at depth " << depth << " : " << level << "--" << description << std::flush; 
                cur_requirement->children.emplace_back(level,description,label,cur_requirement->parent);
                //cur_requirement = &cur_requirement->children.back();
            }
            std:: cout << " going to next " << std::endl;
            //cur_requirement = &cur_requirement->children.back();
            display(depth, v.second, cur_requirement,requirements);
        }
        else {
            std:: cout << "what else..." << std::endl;
            }
     // v.first is the name of the child
    // v.second is the child tree
    }
};  

The output I get currently is this:

[tom@tomtop dev]$ ./readreq The_system.F.req.json 
name: The system prefix: F

# lvl: should
# dsc: very well performance wise
# lbl: goperf
# zero depth...going down
## empty v.first ... level: 
store at depth 1 : -- going to next 
## lvl: should
## dsc: be listening to spaces as well
## lbl: lisspace
## empty v.first ... level: 
store at depth 1 : -- going to next 
## lvl: will
## dsc: a lot of levels back down again
## at depth 1...going down
### empty v.first ... level: 
store at depth 2 : -- going to next 
### lvl: empty
### dsc: empty
### lbl: empty
### at depth 2...going down
#### empty v.first ... level: 
store at depth 3 : -- going to next 
#### lvl: can
#### dsc: skip all the way back here
#### lbl: skiphere
#### empty v.first ... level: 
store at depth 3 : -- going to next 
#### lvl: can
#### dsc: take three linestr
#### lbl: threelines


level: should description:very well performance wise label: goperf
    level:  description: label: 
    level:  description: label: 
    level: will description:a lot of levels back down again label: 
        level:  description: label: 
        level: empty description:empty label: empty
            level:  description: label: 
            level:  description: label: 

Most of which makes sense, and most of it seems to work, but there is one thing that puzzles me. The property tree is organized in such a way there is an "empty" node before each "child" and also inbetween the elements of arrays. (correct me if I'm wrong, i'm not that familiar with property tree).

So after I come upon a "children" or "" (empty) element I want to store the data I previously collected, stored in the variables level, description and label.

and here is the funny part, when the element is "children", this works like a charm, however, when the element is "", suddenly the variables are empty, even though the variables were not reinitialized, neither did I go deeper into the property tree, I only iterate to the next "kid" within the for loop.

So where I expect the output to be this:

## lvl: should
## dsc: be listening to spaces as well
## lbl: lisspace
## empty v.first ... level: should
store at depth 1 : should -- be listening to spaces as well going to next 

the last line (generated by

std::cout << "empty v.first ... level: " << level << std::endl; std::cout << "store at depth " << depth << " : " << level << "--" << description << std::flush;

) shows this:

store at depth 1 :  --  going to next 

giving the impression that label, description and level where somehow empty, and nowhere is there an assignment which could make them empty.

So if anyone can explain this misterious behaviour to me I would be very glad.

tomzooi
  • 90
  • 2
  • 8
  • 1
    Have you tried using a debugger to see where exactly the «misterious behaviour» happens first? – Otomo Mar 24 '15 at 08:10
  • So far I noticed that the behaviour happens when there is a JSON array element which has no "children", meaning whenever the property tree encounters an "empty" v.first (stating a node to the next element) it suddenly "empties" the label,level and description string variables for unclear reason. As for the debugger, I come from programming C for microcontrollers and other basic programming, so no, I will have alook but I'm not really sure where to look for though... – tomzooi Mar 24 '15 at 09:42

2 Answers2

1

I tried for 15 minutes. I could not work out what you're trying to achieve. See Update

Notes

  • cur_requirement should be initialized.
  • You invoke UB here:

    requirements.emplace_back(level, description, label, cur_requirement);
    cur_requirement = &requirements.back();
    

    You are storing pointers to vector elements inside the emplaced requirement. However, emplacing may reallocate, invalidating all pointers and iterators.

Rethink your data model (prefer value-semantics? use containers with stable iterators? reserve up-front?)

Update

So, here's my take at cleaning up the display function (which I renamed to parse_json because that's what it does):

void parse_json(int depth, boost::property_tree::ptree const& tree, Requirement& cur)
{
    cur.label       = tree.get("label",       "");
    cur.level       = tree.get("level",       "");
    cur.description = tree.get("description", "");

    if (auto kids = tree.get_child_optional("children")) {
        for (auto& kid : *kids) {
            std::cout << "at depth " << depth << "... " << std::flush;

            cur.children.emplace_back(&cur);

            std::cout << "going down" << std::endl;
            parse_json(depth + 1, kid.second, cur.children.back());
        }
    }
}

Full Demo

See the whole program cleaned up. Note I've replaced the vector by a list to avoid invalidating the parent pointers on reading more child nodes

Live On Coliru

#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <exception>
#include <fstream>
#include <iostream>
#include <string>
#include <list>

class Requirement {
    public:
        bool empty;
        std::string level;
        std::string description;
        std::string label;
        Requirement const* parent;
        std::list <Requirement> children;

        Requirement(Requirement const* p);
        Requirement(std::string l, std::string d, std::string la, Requirement const* p); // unused

        void print(std::string indent = "");
        void print_json(std::ostream &os, std::string indent = "");
};

Requirement::Requirement(Requirement const* p) 
    : empty(false), parent(p)
{
}

Requirement::Requirement(std::string l, std::string d, std::string la,Requirement const* p) // unused
    : empty(false), 
      level(std::move(l)), description(std::move(d)), label(std::move(la)), parent(p)
{
}

void Requirement::print_json(std::ostream &os, std::string indent) {
    os  << "{";
    indent += '\t';

    os
         << "\n" << indent << "\"level\":\""       << level       << "\", "
         << "\n" << indent << "\"description\":\"" << description << "\"";

    if(label.length() > 1) {
        os << ",\n" << indent << "\"label\":\"" << label <<"\"";
    }

    if (!children.empty()) {
        os << ", \"children\":[\n";

        bool first = true;
        for(auto& child : children) {
            if (!first)
                os << ',';

            first=false;

            os << "\n" << indent;
            child.print_json(os, indent);
        }
        os << "]";
    }

    indent.resize(indent.size() - 1);
    os << "\n" << indent << "}";
}

void Requirement::print(std::string indent) {
    std::cout << indent << "level: " << level << " description:" << description << " label: " << label << std::endl;
    for (Requirement kid : children)
        kid.print(indent + '\t');
}

void parse_json(int depth, boost::property_tree::ptree const& tree, Requirement& cur)
{
    cur.label       = tree.get("label",       "");
    cur.level       = tree.get("level",       "");
    cur.description = tree.get("description", "");

    if (auto kids = tree.get_child_optional("children")) {
        for (auto& kid : *kids) {
            std::cout << "at depth " << depth << "... " << std::flush;

            cur.children.emplace_back(&cur);

            std::cout << "going down" << std::endl;
            parse_json(depth + 1, kid.second, cur.children.back());
        }
    }
}

int main(int argc, char* argv[])
{
    if (argc>1) try {
        std::ifstream ss(argv[1]);

        boost::property_tree::ptree pt;
        boost::property_tree::read_json(ss, pt);

        Requirement root(nullptr);
        parse_json(0, pt, root);

        std::cout << std::endl << std::endl;
        root.print("; debug: ");
        root.print_json(std::cout);
    }
    catch (std::exception const& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
}

The output is:

at depth 0... going down
at depth 0... going down
at depth 1... going down
at depth 2... going down
at depth 2... going down


; debug: level: should description:very well performance wise label: goperf
; debug:    level: should description:be listening to spaces as well label: lisspace
; debug:    level: will description:a lot of levels back down again label: 
; debug:        level: empty description:empty label: empty
; debug:            level: can description:skip all the way back here label: skiphere
; debug:            level: can description:take three linestr label: threelines
{
    "level":"should", 
    "description":"very well performance wise",
    "label":"goperf", "children":[

    {
        "level":"should", 
        "description":"be listening to spaces as well",
        "label":"lisspace"
    },
    {
        "level":"will", 
        "description":"a lot of levels back down again", "children":[

        {
            "level":"empty", 
            "description":"empty",
            "label":"empty", "children":[

            {
                "level":"can", 
                "description":"skip all the way back here",
                "label":"skiphere"
            },
            {
                "level":"can", 
                "description":"take three linestr",
                "label":"threelines"
            }]
        }]
    }]
}

Note that the code is roughly half the size :)

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks for trying, what I am trying to achieve is to store the json formatted files into nested requirements but using the c++ objects. So far this worked in the main.cpp file (pushed that one to github as well), this program was able to store all the reqs via the given objects using emplace_back (I played with push_back and push_back(new), but emplace_back I got to work, take a look at main.cpp, it is somewhat similar as this code). What I can't understand is why those variables level,description and label suddenly appear empty. In the first iterations they are written, and then appear empty – tomzooi Mar 24 '15 at 09:31
  • @tomzooi You _cannot_ ignore the issue with the code I explained. It's the nature of [Undefined Behaviour](http://en.wikipedia.org/wiki/Undefined_behavior) that it may appear to work. Be sure to read up. On your comment: I might have a look later, but really you should simplify the code so that it **just** demonstrates the issue. Clearly. (There's so much messy output that I have no clue what output you actually do mean.). – sehe Mar 24 '15 at 09:36
  • 1
    I looked at the output, it is however no different then what I get. I will look into these links, I am quit new to iterators so that could defintly be the problem. I will see if I can convince Coliru to show what is happening in a simple way. Ow, and by the way, I spotted an error in my post about the expected output and what changed, that might clear stuff up a bit ;) – tomzooi Mar 24 '15 at 09:48
  • @tomzooi I think I understood it now. (A bit of messy code can obscure a lot of simplicity!). Updated the answer [with a 14-line version](http://coliru.stacked-crooked.com/a/883730228f686f5d). Deleting more code I got the JSON output to pretty print as well :) – sehe Mar 24 '15 at 11:08
  • This works awesome, thank you. Lets see if I can now also use this style of coding to create the structure from the "base format" (The_system.F.req) in main.cpp since this code is now broken. One question though, does this work with say multiple level "0" requirements? So a list as root instead of a pointer for instance? – tomzooi Mar 25 '15 at 03:18
  • You just forget about the root? I mean, why would you create a separate code path to fill the vector when, clearly you already have it. If you _really_ must have it, consider: **[demo with tree recursion](http://coliru.stacked-crooked.com/a/99686d06d71d4af3)**. So, now you have `load_object` and `load_list` – sehe Mar 25 '15 at 13:03
  • So far I think I want a list at the root, and not one requirement (since the file can contain multiple "level 0" requirements). At the moment the code is far from perfect, and your demo is interesting. But JSON does not allow me to start of with an array so I had to start with { "requirements" : [ ....]}. The code could defintly be improved, but ATM I am rather happy with a working system that I made to be able to share and latex-format my requirements conveniently. When I am finished with writing the requirements, I might consider improving upon the code. See github if you want to make a pull – tomzooi Mar 26 '15 at 05:32
  • Oh [JSON](https://tools.ietf.org/html/rfc7159) supports that just fine. It's just... [Boost Property Tree is ***not*** a JSON library](http://stackoverflow.com/a/29245031/85371). Trees have roots. Simple as that. – sehe Mar 26 '15 at 07:46
0

I ended up chaingin the main.cpp (now known as readreq.cpp) as well, I didn't managed to make it nicely recursive though, but it does the trick. Had to change a couple of other things as well, to be able to have a base of a requirement list (not just one root), code is on github as well https://github.com/tomzooi/readreq basic changes:

#include <fstream>
#include <iostream>
#include "class.h" 
#include <vector>
#include <string>
//#include <regex>
#include <boost/regex.hpp>
#include <iterator>
#include <algorithm>
#include <math.h>

//how many spaces are interpreted as one tab or level
#define SPACES 8 
#define LINEBUFSIZE 500 

void parse_req(unsigned int cur_depth, std::ifstream &f, std::list<Requirement> &reqlist,unsigned int linenr=0) {
    std::string line;
    unsigned int count = 0;
    unsigned int depth = 0;
    Requirement * cur(nullptr);
    boost::regex re("\\t+|\\s{2,}"); //regex split parameter (one or more tabs or 2 or more spaces)

    std::list<Requirement>* lptr(&reqlist);
    while (std::getline(f, line))   {
        linenr++;
        //  std::cout << "(" << linenr << "): " << line; 
        depth = line.find_first_not_of("    "); //find first none space or tab character


        boost::sregex_token_iterator i(line.begin()+depth, line.end(), re, -1); //split line by tabs and spaces
        boost::sregex_token_iterator j;
        count = 0; //reset count
        if (depth == cur_depth) {
            lptr->emplace_back(lptr->back().parent);
            cur = &lptr->back();
        }
        else if (depth > cur_depth) { //go deeper
            cur_depth = depth;
            lptr = &lptr->back().children;
            lptr->emplace_back(cur);
            cur = &lptr->back();
        }
        else if (depth < cur_depth) { //go down
            //lptr = cur->parent;
            while (cur_depth > depth) { 
                cur_depth--;
                std::cout << "deeper : " << cur_depth << std::endl << std::flush;
                lptr = &(lptr->back().parent->parent->children);
            }
            if(cur_depth != 0) {
                lptr->emplace_back(lptr->back().parent);
                cur = &lptr->back();
            }
            else {
                reqlist.emplace_back(nullptr);
                cur = &reqlist.back();
            }
        }
        while(i != j) { //for all splitted parts
            switch(count) {
                case 0:
                    cur->level  =*i++;
                    //std::cout << "lvl: " << cur->level << " ";
                    break;
                case 1:
                    cur->description = *i++;
                    //std::cout << "dsc: " << cur.description << std::endl;
                    break;
                case 2:
                    cur->label = *i++;
                    break;
                default:
                    *i++;
                    break;
            }
            count++;
        }
        if (count < 2) { //too less arguments
            std::cout << "ERROR(" << linenr << "): nead at least two parts for an requirement (level and description), less than two found, halting." << std::endl;
            break;
        }
        if (count > 3) { //too much arguments
            std::cout << "WARNING(" << linenr << "): More then three arguments found, ignoring fourth or more argument" << std::endl;
        }
    }
}
int main(int argc, char *argv[]) {
//  class Requirement req("will",  "do good work", "good");
    if ( argc != 3 ) // argc should be 2 for correct execution
        // We print argv[0] assuming it is the program name
        std::cout<<"usage: "<< argv[0] <<" <filename input> <filename output>\n";
     else {
        std::ifstream file(argv[1]); //try to open file
        if(!file.is_open()) { //if we can't
            std::cout << "Could not open file" ;
        }
        else { //sucesfully opened file
            std::list<Requirement> requirements;
            parse_req(0,file,requirements);

            std::ofstream outfile(argv[2]); //try to open file
            outfile << "{ \"requirements\":[";
            for (Requirement req : requirements) {

                req.print();
                req.print_json(outfile);
                if (requirements.back().description != req.description) {
                    outfile << ",";
                }
            }
            outfile << "]}";
            std::cout << "success!" << std::endl;
            }

        }

    return 0;
}
tomzooi
  • 90
  • 2
  • 8
  • And yes I need to update on my C++(11) skills and syntax I know, I feel there is some easier ways but so far this is the best I can muster. – tomzooi Mar 25 '15 at 10:21