Line data Source code
1 : /********************************************************************\
2 : * kvp-frame.hpp -- Implements a key-value frame system *
3 : * Copyright (C) 2014 Aaron Laws *
4 : * Copyright 2015 John Ralls *
5 : * *
6 : * This program is free software; you can redistribute it and/or *
7 : * modify it under the terms of the GNU General Public License as *
8 : * published by the Free Software Foundation; either version 2 of *
9 : * the License, or (at your option) any later version. *
10 : * *
11 : * This program is distributed in the hope that it will be useful, *
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 : * GNU General Public License for more details. *
15 : * *
16 : * You should have received a copy of the GNU General Public License*
17 : * along with this program; if not, contact: *
18 : * *
19 : * Free Software Foundation Voice: +1-617-542-5942 *
20 : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 : * Boston, MA 02110-1301, USA gnu@gnu.org *
22 : * *
23 : \********************************************************************/
24 : /** @addtogroup KVP
25 :
26 : * A KvpFrame is a set of associations between character strings
27 : * (keys) and KvpValues. A KvpValue is notionally a union with
28 : * possible types enumerated in the KvpValue::Type enum, and includes,
29 : * among other things, ints, doubles, strings, guids, lists, time
30 : * and numeric values. KvpValues may also be other frames, so
31 : * KVP is inherently hierarchical.
32 : *
33 : * Values are stored in a 'slot' associated with a key.
34 : * Pointers passed as arguments into set_slot and get_slot are the
35 : * responsibility of the caller. Pointers returned by get_slot are
36 : * owned by the kvp_frame. Make copies as needed.
37 : *
38 : * A 'path' is a sequence of keys that can be followed to a value. Paths are
39 : * passed as either '/'-delimited strings or as std::vectors of keys. Unlike
40 : * file system paths, the tokens '.' and '..' have no special meaning.
41 : *
42 : * KVP is an implementation detail whose direct use should be avoided; create an
43 : * abstraction object in libqof to keep KVP encapsulated here and ensure that
44 : * KVP modifications are written to the database. Two generic abstractions are
45 : * provided:
46 : *
47 : * * @ref qof_book_set_option and @ref qof_book_get_option provide similar
48 : * access for book options.
49 : *
50 : * @ref kvpvalues provides a catolog of KVP entries including what objects
51 : * they're part of and how they're used.
52 : *
53 : * ## Purpose
54 : * KVP is used to extend the class structure without directly reflecting the
55 : * extension in the database or xML schema. The backend will directly load and
56 : * store KVP slots without any checking, which allows older versions of GnuCash
57 : * to load the database without complaint and without damaging the KVP data that
58 : * they don't understand.
59 : *
60 : * When a feature is entirely implemented in KVP and doesn't affect the meaning
61 : * of the books or other features, this isn't a problem, but when it's not true
62 : * then it should be registered in @ref UtilFeature so that older versions of
63 : * GnuCash will refuse to load the database.
64 : *
65 : * ## Policy
66 : * * Document every KVP slot in src/engine/kvp_doc.txt so that it is presented
67 : * in @ref kvpvalues.
68 : * * Register a feature in @ref UtilFeature if the use of the KVP in any way
69 : affects the books or business computations.
70 : * * Key strings should be all lower case with '-', not spaces, separating words
71 : and '/' separating frames. Prefer longer and more descriptive names to
72 : abbreviations, and define a global const char[] to the key or path string so
73 : that the compiler will catch any typos.
74 : * * Make good use of the hierarchical nature of KVP by using frames to group
75 : related slots.
76 : * * Don't use the KVP API directly outside of libqof, and prefer to use the
77 : QofInstance and QofBook functions whenever feasible. If those functions
78 : aren't feasible write a class in libqof to abstract the use of KVP.
79 : * * Avoid re-using key names in different contexts (e.g. Transactions and
80 : Splits) unless the slot is used for the same purpose in both.
81 : * @{
82 : */
83 :
84 : #ifndef GNC_KVP_FRAME_TYPE
85 : #define GNC_KVP_FRAME_TYPE
86 :
87 : #include "kvp-value.hpp"
88 : #include <map>
89 : #include <string>
90 : #include <vector>
91 : #include <cstring>
92 : #include <algorithm>
93 : #include <iostream>
94 : using Path = std::vector<std::string>;
95 : using KvpEntry = std::pair <std::vector <std::string>, KvpValue*>;
96 :
97 : /** Implements KvpFrame.
98 : * It's a struct because QofInstance needs to use the typename to declare a
99 : * KvpFrame* member, and QofInstance's API is C until its children are all
100 : * rewritten in C++.
101 : *
102 : * N.B.** Writes to KvpFrames must** be wrapped in BeginEdit and Commit
103 : * for the containing QofInstance and the QofInstance must be marked dirty. This
104 : * is not** done by the KvpFrame API. In general Kvp items should be
105 : * accessed using either QofInstance or QofBook methods in order to ensure that
106 : * this is done.
107 : * @{
108 : */
109 : struct KvpFrameImpl
110 : {
111 : class cstring_comparer
112 : {
113 : public:
114 : /* Returns true if one is less than two. */
115 562380 : bool operator()(const char * one, const char * two) const
116 : {
117 562380 : auto ret = std::strcmp(one, two) < 0;
118 562380 : return ret;
119 : }
120 : };
121 : using map_type = std::map<const char *, KvpValue*, cstring_comparer>;
122 :
123 : public:
124 45185 : KvpFrameImpl() noexcept {};
125 :
126 : /**
127 : * Performs a deep copy.
128 : */
129 : KvpFrameImpl(const KvpFrameImpl &) noexcept;
130 :
131 : /**
132 : * Perform a deep delete.
133 : */
134 : ~KvpFrameImpl() noexcept;
135 :
136 : /**
137 : * Set the value with the key in the immediate frame, replacing and
138 : * returning the old value if it exists or nullptr if it doesn't. Takes
139 : * ownership of new value and releases ownership of the returned old
140 : * value. Values must be allocated on the free store with operator new.
141 : * @param key: The key to insert/replace.
142 : * @param newvalue: The value to set at key.
143 : * @return The old value if there was one or nullptr.
144 : */
145 : //KvpValue* set(const char * key, KvpValue* newvalue) noexcept;
146 : /**
147 : * Set the value with the key in a subframe following the keys in path,
148 : * replacing and returning the old value if it exists or nullptr if it
149 : * doesn't. Takes ownership of new value and releases ownership of the
150 : * returned old value. Values must be allocated on the free store with
151 : * operator new.
152 : * @param key: The key to insert/replace.
153 : * @throw invalid_argument if the path doesn't exist.
154 : * @param path: The path of subframes leading to the frame in which to
155 : * insert/replace.
156 : * @param newvalue: The value to set at key.
157 : * @return The old value if there was one or nullptr.
158 : */
159 : KvpValue* set(Path path, KvpValue* newvalue) noexcept;
160 : /**
161 : * Set the value with the key in a subframe following the keys in path,
162 : * replacing and returning the old value if it exists or nullptr if it
163 : * doesn't. Creates any missing intermediate frames.Takes
164 : * ownership of new value and releases ownership of the returned old
165 : * value. Values must be allocated on the free store with operator new.
166 : * @param path: The path of subframes as a std::vector leading to the
167 : * frame in which to insert/replace.
168 : * @param newvalue: The value to set at key.
169 : * @return The old value if there was one or nullptr.
170 : */
171 : KvpValue* set_path(Path path, KvpValue* newvalue) noexcept;
172 : /**
173 : * Make a string representation of the frame. Mostly useful for debugging.
174 : * @return A std::string representing the frame and all its children.
175 : */
176 : std::string to_string() const noexcept;
177 : /**
178 : * Make a string representation of the frame with the specified string
179 : * prefixed to every item in the frame.
180 : * @return A std::string representing all the children of the frame.
181 : */
182 : std::string to_string(std::string const &) const noexcept;
183 : /**
184 : * Report the keys in the immediate frame. Be sensible about using this, it
185 : * isn't a very efficient way to iterate.
186 : * @return std::vector of keys as std::strings.
187 : */
188 : std::vector<std::string> get_keys() const noexcept;
189 :
190 : /** Get the value for the tail of the path or nullptr if it doesn't exist.
191 : * @param path: Path of keys leading to the desired value.
192 : * @return The value at the key or nullptr.
193 : */
194 : KvpValue* get_slot(Path keys) noexcept;
195 :
196 : /** The function should be of the form:
197 : * <anything> func (char const *, KvpValue *, data_type &);
198 : * Do not pass nullptr as the function.
199 : */
200 : template <typename func_type, typename data_type>
201 : void for_each_slot_temp(func_type const &, data_type &) const noexcept;
202 :
203 : template <typename func_type>
204 : void for_each_slot_temp(func_type const &) const noexcept;
205 :
206 : /**
207 : * Like for_each_slot, but doesn't traverse nested values. This will only loop
208 : * over root-level values whose keys match the specified prefix.
209 : */
210 : template <typename func_type, typename data_type>
211 : void for_each_slot_prefix(std::string const & prefix, func_type const &, data_type &) const noexcept;
212 :
213 : template <typename func_type>
214 : void for_each_slot_prefix(std::string const & prefix, func_type const &) const noexcept;
215 :
216 : /**
217 : * Returns all keys and values of this frame recursively, flattening
218 : * the frame-containing values.
219 : */
220 : std::vector <KvpEntry>
221 : flatten_kvp(void) const noexcept;
222 :
223 : /** Test for emptiness
224 : * @return true if the frame contains nothing.
225 : */
226 2960 : bool empty() const noexcept { return m_valuemap.empty(); }
227 : friend int compare(const KvpFrameImpl&, const KvpFrameImpl&) noexcept;
228 :
229 5 : map_type::iterator begin() { return m_valuemap.begin(); }
230 5 : map_type::iterator end() { return m_valuemap.end(); }
231 :
232 : private:
233 : map_type m_valuemap;
234 :
235 : KvpFrame * get_child_frame_or_nullptr (Path const &) noexcept;
236 : KvpFrame * get_child_frame_or_create (Path const &) noexcept;
237 : void flatten_kvp_impl(std::vector <std::string>, std::vector <KvpEntry> &) const noexcept;
238 : KvpValue * set_impl (std::string const &, KvpValue *) noexcept;
239 : };
240 :
241 : template<typename func_type, typename data_type>
242 13 : void KvpFrame::for_each_slot_prefix(std::string const & prefix,
243 : func_type const & func, data_type & data) const noexcept
244 : {
245 13 : std::for_each (m_valuemap.begin(), m_valuemap.end(),
246 132 : [&prefix,&func,&data](const KvpFrameImpl::map_type::value_type & a)
247 : {
248 : /* Testing for prefix matching */
249 66 : if (strncmp(a.first, prefix.c_str(), prefix.size()) == 0)
250 12 : func (&a.first[prefix.size()], a.second, data);
251 : }
252 : );
253 13 : }
254 :
255 : template <typename func_type>
256 0 : void KvpFrame::for_each_slot_temp(func_type const & func) const noexcept
257 : {
258 0 : std::for_each (m_valuemap.begin(), m_valuemap.end(),
259 0 : [&func](const KvpFrameImpl::map_type::value_type & a)
260 : {
261 0 : func (a.first, a.second);
262 : }
263 : );
264 0 : }
265 :
266 : template <typename func_type, typename data_type>
267 610 : void KvpFrame::for_each_slot_temp(func_type const & func, data_type & data) const noexcept
268 : {
269 610 : std::for_each (m_valuemap.begin(), m_valuemap.end(),
270 4118 : [&func,&data](const KvpFrameImpl::map_type::value_type & a)
271 : {
272 2059 : func (a.first, a.second, data);
273 : }
274 : );
275 610 : }
276 :
277 : int compare (const KvpFrameImpl &, const KvpFrameImpl &) noexcept;
278 : int compare (const KvpFrameImpl *, const KvpFrameImpl *) noexcept;
279 : /** @} Doxygen Group */
280 :
281 : #endif
|