Opened 6 years ago
Last modified 6 years ago
#12368 new Feature Requests
Improve property_tree documentation for path_of
Reported by: | Owned by: | Sebastian Redl | |
---|---|---|---|
Milestone: | To Be Determined | Component: | property_tree |
Version: | Boost 1.61.0 | Severity: | Problem |
Keywords: | Cc: |
Description
There is no documentation on the path_of system,
I did find this helpful email: http://lists.boost.org/Archives/boost/2009/06/152883.php
It would be great if something like that were in the official documentation.
In the end, I managed to create my own extension for storing a tree of filenames, here is the key code, just in case you feel its a good example for the documentation.
(Code is still not fully tested)
// a custom path-string, // I am not using fs::path because I need to treat a directory and a file // differently in terms of the name, where // a directory has a / on the end of its name. // This allows me to have a tree with a file and folder existing in the tree // at the same time (the file may have been replaced with the folder, so one // is deleted and the other is created). // class PathString { string filename; public: PathString( string const& p ) : filename(p) {} bool is_dir() const { return not empty() and filename[filename.size()-1] == '/'; } string const& str() const { return filename; } bool empty() const { return filename.empty(); } bool operator<( PathString const& b ) const { return filename < b.filename; } bool operator==( PathString const& b ) const { return filename == b.filename; } }; namespace boost { namespace property_tree { // specialisation of path_of for PathString template <> struct path_of<PathString> { struct type { std::string filename; std::string::size_type start, size; public: type( std::string const& s ) : filename(s), start(0), size(filename.size()) {} std::string dump() const { return filename.substr(start); } bool empty() const { return start == size; } // aka pop_front() and return the fragment // modifies this value std::string reduce() { assert(not empty()); std::string::size_type next_sep = filename.find('/', start); // no separator, or separator found at the end if (next_sep == std::string::npos or next_sep+1 == size) { std::string part; part.swap(filename); return part; } // include the trailing separator std::string::size_type old_start = start; start = next_sep+1; return filename.substr(old_start, start-old_start); } bool single() const { // will return true if empty, it should not be asked for empty paths anyway // it is a "single" path if there is a slash BEFORE the last character. // if the last character is /, then it can be a "single" directory. // // so check if idx < filename.size()-1 --therefore--> idx+1 < filename.size() std::string::size_type idx = filename.find('/'); return idx == std::string::npos or idx+1 < filename.size(); } }; };
Attachments (1)
Change History (4)
comment:1 by , 6 years ago
comment:2 by , 6 years ago
Some bugs in the original posted code, see attached file which tests the path_of component. Let me know if you would like me to help make a proper example.
comment:3 by , 6 years ago
I have some more utilities that I wrote along the way to work with the ptree. Might be useful for someone...
For just visiting every node in the tree:
// helper, note that visitor is NOT returned, // its designed to call the visitor ONCE per node, // and then copy that visitor for the children of the node. // A "finishing" stage can be done in the visitor's destructor, as it unwinds back up the tree. // // Note that 'path' passed by reference and maintained during the walk. template <class Key, class Data, class Compare, class Visitor> void visit_ptree_inner( Key & path, boost::property_tree::basic_ptree<Key,Data,Compare> & tree, Visitor visitor, Key const& key ) { // the key passed is our parent's name for me // add to the path, and be prepared to take me off the path when we are done at the end. size_t before_path_size = path.size(); path /= key; // add our name to the path // visit self first visitor(path, tree.data()); // note: visit in order of Compare // typedef typename boost::property_tree::basic_ptree<Key,Data,Compare>::iterator It; // It end = tree.end(); typedef typename boost::property_tree::basic_ptree<Key,Data,Compare>::assoc_iterator It; const It end = tree.not_found(); for (It begin = tree.ordered_begin(); begin != end; ++begin) visit_ptree_inner(path, begin->second, visitor, begin->first); assert(before_path_size <= path.size()); path.resize(before_path_size); } template <class Key, class Data, class Compare, class Visitor> void visit_ptree( boost::property_tree::basic_ptree<Key,Data,Compare> & tree, Visitor visitor, Key const& key = Key(), Key init_path = Key() ) { visit_ptree_inner(init_path, tree, visitor, key); }
I found it annoying to find and edit, or create, nodes in the tree and avoid copies.
So I wrote get_create_ptree() which you can also pass a special Creator function if your node requires some special construction if it doesn't already exist.
You can do things like
NodeData & data = get_create_ptree( TREE, PATH ).second;
// creates node if it doesn't exist, // returns true-flag if node already existed. // if it creates the edge-node, it will use the 'creator' visitor to build. template <class Key, class Data, class Compare, class CreateEdgeNode> pair<bool, Data&> get_create_ptree( boost::property_tree::basic_ptree<Key,Data,Compare> & tree, typename boost::property_tree::path_of<Key>::type path, CreateEdgeNode creator ) { typedef boost::property_tree::basic_ptree<Key,Data,Compare> Tree; typedef typename Tree::assoc_iterator It; typedef typename Tree::value_type Value; bool existed = true; Tree * parent = &tree; while (not path.empty()) { Key fragment = path.reduce(); // this also updates 'path' - shortens it by removing front fragment It found_child = parent->find(fragment); if (found_child != parent->not_found()) { parent = &found_child->second; } else { existed = false; // while we haven't been through all the parents... while (not path.empty()) { // create a parent node parent = &parent->push_back(Value(fragment, Tree()))->second; fragment = path.reduce(); // this reduces the path } parent = &parent->push_back(Value(fragment, Tree(creator())))->second; break; // all done } } return pair<bool, Data&>(existed, parent->data()); } template <class T> struct Visitor_create_node_default { T operator()() const { return T(); } }; template <class Key, class Data, class Compare> pair<bool, Data&> get_create_ptree( boost::property_tree::basic_ptree<Key,Data,Compare> & tree, typename boost::property_tree::path_of<Key>::type path ) { typedef boost::property_tree::basic_ptree<Key,Data,Compare> Tree; return get_create_ptree(tree, path, Visitor_create_node_default<Tree>()); }
And the most complex one of this post, the 'mirror_ptree()' function.
It traverses two trees at once, the "out_tree" and "in_tree". It has options to create or delete nodes that are missing/extra in the out_tree,
And for each corresponding node, the visitor's methods are called like so:
struct Example_visitor_mirror { // called when source is missing AND mirror_ptree() has delete_missing=false void operator()( Path const& path, OutTreeNode & out ) const { } // called to copy source to destination (out <-- in) void operator()( Path const& path, OutTreeNode & out, InTreeNode const& in ) const { } // only called when mirror_ptree() has insert_missing=true OutTreeNode create_missing( Path const& path ) const { return OutTreeNode(construction etc); } };
template <class Key, class DataIn, class DataOut, class Compare, class Visitor> void mirror_ptree_inner( Key & path, boost::property_tree::basic_ptree<Key,DataOut,Compare> & out_tree, boost::property_tree::basic_ptree<Key,DataIn,Compare> const& in_tree, bool insert_missing, bool delete_missing, Visitor visitor, Key const& key ) { // the key passed is our parent's name for me // add to the path, and be prepared to take me off the path when we are done at the end. size_t before_path_size = path.size(); path /= key; // copy across the data for this node visitor(path, out_tree.data(), in_tree.data()); typedef boost::property_tree::basic_ptree<Key,DataOut,Compare> OutTree; typedef typename OutTree::assoc_iterator It; typedef typename OutTree::value_type Value; typedef typename boost::property_tree::basic_ptree<Key,DataIn,Compare>::const_assoc_iterator CIt; const CIt in_end = in_tree.not_found(); if (not in_tree.empty()) { CIt a = in_tree.ordered_begin(); CIt b = a; ++b; for (; b != in_tree.not_found(); ++a, ++b) assert(a->first != b->first); } Compare lessthan; CIt in = in_tree.ordered_begin(); It out = out_tree.ordered_begin(); while (true) // in != in_end or out != out_tree.not_found()) { // erase out_tree that are missing from in_tree if ( out != out_tree.not_found() and ( in == in_end or lessthan(out->first, in->first) ) ) { // erase and increment 'out' if (delete_missing) out_tree.erase( out_tree.to_iterator(out++) ); else { // recurse to visit all of the subitems, // we aren't deleting or inserting so it can be a plain visit. visit_ptree_inner( path, out->second, visitor, out->first ); ++out; } } // insert in_tree that are missing else if ( in != in_end and ( out == out_tree.not_found() or lessthan(in->first, out->first) ) ) { if (insert_missing) { size_t before_path_size2 = path.size(); path /= in->first; // build the path of this child now out_tree.push_back( Value(in->first, OutTree(visitor.create_missing(path))) ); // insert empty node path.resize(before_path_size2); // and go back to our path out = out_tree.find(in->first); // find our new location assert(out != out_tree.not_found()); // must find! // recurse! mirror_ptree_inner( path, out->second, in->second, insert_missing, delete_missing, visitor, out->first ); ++in; ++out; } else { ++in; } } else if ( in != in_end and out != out_tree.not_found() ) { // must match if we are here assert(not lessthan(in->first, out->first)); assert(not lessthan(out->first, in->first)); assert(in->first == out->first); // not always a possible comparison // recurse! mirror_ptree_inner( path, out->second, in->second, insert_missing, delete_missing, visitor, out->first ); ++in; ++out; } else { // if we are here, must have finished both assert(in == in_end and out == out_tree.not_found()); break; } } // and reset path to previous size assert(before_path_size <= path.size()); path.resize(before_path_size); } template <class Key, class DataIn, class DataOut, class Compare, class Visitor> void mirror_ptree( boost::property_tree::basic_ptree<Key,DataOut,Compare> & out_tree, boost::property_tree::basic_ptree<Key,DataIn,Compare> const& in_tree, bool insert_missing, bool delete_missing, Visitor visitor, Key const& key = Key(), Key init_path = Key() ) { mirror_ptree_inner( init_path, out_tree, in_tree, insert_missing, delete_missing, visitor, key ); }
And here is a kind of for_each for ptree, but you can use it in a plain loop so no functors or closures etc etc. It handles the path efficiently and uses a vector<> stack instead of recursion.
The switch_type stuff is so that you can use it with a const tree.
// This is a simple stack-based walker that can be used like so: // // Visits recursively in order of the key, // but does not indicate whether you are going deeper or moving up the tree. // // Will always visit at least once (the base node). // // for (PTreeWalker it(changes); not it.finished(); it.next()) // { // path to node = it.path(); // nodes data = it.data(); // } // // Note: could be extended to match the interface of fs::recursive_directory_iterator // template <class Choice, class Yes, class No> struct switch_type; template <class Yes, class No> struct switch_type<boost::true_type, Yes, No> { typedef Yes type; }; template <class Yes, class No> struct switch_type<boost::false_type, Yes, No> { typedef No type; }; template <class PTree> class PTreeWalker : noncopyable // only noncopyable to help detect incorrect usage, // but could be copied to save loop state { typedef typename PTree::key_type Key; typedef typename boost::is_const<PTree>::type TreeIsConst; typedef typename switch_type< TreeIsConst, typename boost::add_const<typename PTree::data_type>::type, typename PTree::data_type >::type Data; typedef typename switch_type< TreeIsConst, typename PTree::const_assoc_iterator, typename PTree::assoc_iterator >::type It; // maintains this, appending and resizing as required Key m_path; struct Level { Level() : path_len(), subtree(), cursor() {} Level(size_t path_len, PTree & tree) : path_len(path_len), subtree(&tree), cursor(tree.ordered_begin()) {} size_t path_len; PTree * subtree; // ALWAYS valid, only a pointer due to vector<> requirements It cursor; }; vector<Level> stack; public: PTreeWalker( PTree & tree, Key init_path = Key() ) : m_path(init_path) { stack.push_back( Level(m_path.size(), tree) ); } Key const& path() const { return m_path; } Data & data() { return stack.back().subtree->data(); } bool finished() { return stack.empty(); } void next() { // are we finished at this level? then, go up. while (not stack.empty() and stack.back().cursor == stack.back().subtree->not_found()) // aka ::ordered_end(); stack.pop_back(); // we fix-up the path in the next step // not finished here? // then go down a level. if (stack.empty()) { m_path.resize(0); } else { Level & current = stack.back(); m_path.resize(current.path_len); assert(current.cursor != current.subtree->not_found()); // note: ++ : remember we have visited this item on this level It target = current.cursor++; m_path /= target->first; stack.push_back( Level(m_path.size(), target->second) ); } } };
Oh, and to use the property tree, you declare: