/********************************************************************\ * kvp-frame.hpp -- Implements a key-value frame system * * Copyright (C) 2014 Aaron Laws * * Copyright 2015 John Ralls * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License* * along with this program; if not, contact: * * * * Free Software Foundation Voice: +1-617-542-5942 * * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * * Boston, MA 02110-1301, USA gnu@gnu.org * * * \********************************************************************/ /** @addtogroup KVP * A KvpFrame is a set of associations between character strings * (keys) and KvpValues. A KvpValue is notionally a union with * possible types enumerated in the KvpValue::Type enum, and includes, * among other things, ints, doubles, strings, guids, lists, time * and numeric values. KvpValues may also be other frames, so * KVP is inherently hierarchical. * * Values are stored in a 'slot' associated with a key. * Pointers passed as arguments into set_slot and get_slot are the * responsibility of the caller. Pointers returned by get_slot are * owned by the kvp_frame. Make copies as needed. * * A 'path' is a sequence of keys that can be followed to a value. Paths are * passed as either '/'-delimited strings or as std::vectors of keys. Unlike * file system paths, the tokens '.' and '..' have no special meaning. * * KVP is an implementation detail whose direct use should be avoided; create an * abstraction object in libqof to keep KVP encapsulated here and ensure that * KVP modifications are written to the database. Two generic abstractions are * provided: * * * @ref qof_book_set_option and @ref qof_book_get_option provide similar * access for book options. * * @ref kvpvalues provides a catolog of KVP entries including what objects * they're part of and how they're used. * * ## Purpose * KVP is used to extend the class structure without directly reflecting the * extension in the database or xML schema. The backend will directly load and * store KVP slots without any checking, which allows older versions of GnuCash * to load the database without complaint and without damaging the KVP data that * they don't understand. * * When a feature is entirely implemented in KVP and doesn't affect the meaning * of the books or other features, this isn't a problem, but when it's not true * then it should be registered in @ref UtilFeature so that older versions of * GnuCash will refuse to load the database. * * ## Policy * * Document every KVP slot in src/engine/kvp_doc.txt so that it is presented * in @ref kvpvalues. * * Register a feature in @ref UtilFeature if the use of the KVP in any way affects the books or business computations. * * Key strings should be all lower case with '-', not spaces, separating words and '/' separating frames. Prefer longer and more descriptive names to abbreviations, and define a global const char[] to the key or path string so that the compiler will catch any typos. * * Make good use of the hierarchical nature of KVP by using frames to group related slots. * * Don't use the KVP API directly outside of libqof, and prefer to use the QofInstance and QofBook functions whenever feasible. If those functions aren't feasible write a class in libqof to abstract the use of KVP. * * Avoid re-using key names in different contexts (e.g. Transactions and Splits) unless the slot is used for the same purpose in both. * @{ */ #ifndef GNC_KVP_FRAME_TYPE #define GNC_KVP_FRAME_TYPE #include "kvp-value.hpp" #include #include #include #include #include #include using Path = std::vector; using KvpEntry = std::pair , KvpValue*>; /** Implements KvpFrame. * It's a struct because QofInstance needs to use the typename to declare a * KvpFrame* member, and QofInstance's API is C until its children are all * rewritten in C++. * * N.B.** Writes to KvpFrames must** be wrapped in BeginEdit and Commit * for the containing QofInstance and the QofInstance must be marked dirty. This * is not** done by the KvpFrame API. In general Kvp items should be * accessed using either QofInstance or QofBook methods in order to ensure that * this is done. * @{ */ struct KvpFrameImpl { class cstring_comparer { public: /* Returns true if one is less than two. */ bool operator()(const char * one, const char * two) const { auto ret = std::strcmp(one, two) < 0; return ret; } }; using map_type = std::map; public: KvpFrameImpl() noexcept {}; /** * Performs a deep copy. */ KvpFrameImpl(const KvpFrameImpl &) noexcept; /** * Perform a deep delete. */ ~KvpFrameImpl() noexcept; /** * Set the value with the key in the immediate frame, replacing and * returning the old value if it exists or nullptr if it doesn't. Takes * ownership of new value and releases ownership of the returned old * value. Values must be allocated on the free store with operator new. * @param key: The key to insert/replace. * @param newvalue: The value to set at key. * @return The old value if there was one or nullptr. */ //KvpValue* set(const char * key, KvpValue* newvalue) noexcept; /** * Set the value with the key in a subframe following the keys in path, * replacing and returning the old value if it exists or nullptr if it * doesn't. Takes ownership of new value and releases ownership of the * returned old value. Values must be allocated on the free store with * operator new. * @param key: The key to insert/replace. * @throw invalid_argument if the path doesn't exist. * @param path: The path of subframes leading to the frame in which to * insert/replace. * @param newvalue: The value to set at key. * @return The old value if there was one or nullptr. */ KvpValue* set(Path path, KvpValue* newvalue) noexcept; /** * Set the value with the key in a subframe following the keys in path, * replacing and returning the old value if it exists or nullptr if it * doesn't. Creates any missing intermediate frames.Takes * ownership of new value and releases ownership of the returned old * value. Values must be allocated on the free store with operator new. * @param path: The path of subframes as a std::vector leading to the * frame in which to insert/replace. * @param newvalue: The value to set at key. * @return The old value if there was one or nullptr. */ KvpValue* set_path(Path path, KvpValue* newvalue) noexcept; /** * Make a string representation of the frame. Mostly useful for debugging. * @return A std::string representing the frame and all its children. */ std::string to_string() const noexcept; /** * Make a string representation of the frame with the specified string * prefixed to every item in the frame. * @return A std::string representing all the children of the frame. */ std::string to_string(std::string const &) const noexcept; /** * Report the keys in the immediate frame. Be sensible about using this, it * isn't a very efficient way to iterate. * @return std::vector of keys as std::strings. */ std::vector get_keys() const noexcept; /** Get the value for the tail of the path or nullptr if it doesn't exist. * @param path: Path of keys leading to the desired value. * @return The value at the key or nullptr. */ KvpValue* get_slot(Path keys) noexcept; /** The function should be of the form: * func (char const *, KvpValue *, data_type &); * Do not pass nullptr as the function. */ template void for_each_slot_temp(func_type const &, data_type &) const noexcept; template void for_each_slot_temp(func_type const &) const noexcept; /** * Like for_each_slot, but doesn't traverse nested values. This will only loop * over root-level values whose keys match the specified prefix. */ template void for_each_slot_prefix(std::string const & prefix, func_type const &, data_type &) const noexcept; template void for_each_slot_prefix(std::string const & prefix, func_type const &) const noexcept; /** * Returns all keys and values of this frame recursively, flattening * the frame-containing values. */ std::vector flatten_kvp(void) const noexcept; /** Test for emptiness * @return true if the frame contains nothing. */ bool empty() const noexcept { return m_valuemap.empty(); } friend int compare(const KvpFrameImpl&, const KvpFrameImpl&) noexcept; map_type::iterator begin() { return m_valuemap.begin(); } map_type::iterator end() { return m_valuemap.end(); } private: map_type m_valuemap; KvpFrame * get_child_frame_or_nullptr (Path const &) noexcept; KvpFrame * get_child_frame_or_create (Path const &) noexcept; void flatten_kvp_impl(std::vector , std::vector &) const noexcept; KvpValue * set_impl (std::string const &, KvpValue *) noexcept; }; template void KvpFrame::for_each_slot_prefix(std::string const & prefix, func_type const & func, data_type & data) const noexcept { std::for_each (m_valuemap.begin(), m_valuemap.end(), [&prefix,&func,&data](const KvpFrameImpl::map_type::value_type & a) { /* Testing for prefix matching */ if (strncmp(a.first, prefix.c_str(), prefix.size()) == 0) func (&a.first[prefix.size()], a.second, data); } ); } template void KvpFrame::for_each_slot_temp(func_type const & func) const noexcept { std::for_each (m_valuemap.begin(), m_valuemap.end(), [&func](const KvpFrameImpl::map_type::value_type & a) { func (a.first, a.second); } ); } template void KvpFrame::for_each_slot_temp(func_type const & func, data_type & data) const noexcept { std::for_each (m_valuemap.begin(), m_valuemap.end(), [&func,&data](const KvpFrameImpl::map_type::value_type & a) { func (a.first, a.second, data); } ); } int compare (const KvpFrameImpl &, const KvpFrameImpl &) noexcept; int compare (const KvpFrameImpl *, const KvpFrameImpl *) noexcept; /** @} Doxygen Group */ #endif