Merge lp:~thomir-deactivatedaccount/xpathselect/trunk-extend-type-name-options into lp:xpathselect
- trunk-extend-type-name-options
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~thomir-deactivatedaccount/xpathselect/trunk-extend-type-name-options |
Merge into: | lp:xpathselect |
Diff against target: |
1347 lines (+609/-308) 8 files modified
lib/CMakeLists.txt (+2/-2) lib/parser.cpp (+34/-1) lib/parser.h (+55/-50) lib/xpathquerypart.cpp (+129/-0) lib/xpathquerypart.h (+57/-73) lib/xpathselect.cpp (+69/-31) test/test_parser.cpp (+241/-150) test/test_xpath_tree.cpp (+22/-1) |
To merge this branch: | bzr merge lp:~thomir-deactivatedaccount/xpathselect/trunk-extend-type-name-options |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Autopilot Hackers | Pending | ||
Review via email: mp+227854@code.launchpad.net |
Commit message
Extend xpathselect grammar to allow multiple node names per query.
Description of the change
This branch changes the XPathSelect grammar such that you can now say "get me all the nodes named X, Y, or Z at this location". This is required to work around a problem with versioned Qml objects, and is also useful in other scenarios.
This involved extending the boost::spirit grammar (in parser.h), which in turn meant re-working the parser return types to be different structs, and using boost::variant.
As you'd expect, tests have been added / updated.
Brendan Donegan (brendan-donegan) wrote : | # |
That's a whole lotta code to parse! A couple of probably useless comments for you though.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:64
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Thomi Richards (thomir-deactivatedaccount) : | # |
Christopher Lee (veebers) wrote : | # |
Just a quick look at this point, couple of inline comments re; TODOs. Deeper review to come.
- 65. By Thomi Richards
-
Code cleanups.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:65
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Martin Pitt (pitti) wrote : | # |
I didn't see anything obviously wrong with this, but then again my C++ skills are largely nonexisting, and most of the syntax is totally unfamiliar to me :/ Just two small and unimportant comments. But the test case coverage of this is quite good, and I suppose you'll test it together with the AP testsuite.
- 66. By Thomi Richards
-
Fix test as per MP comment.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:66
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Unmerged revisions
- 66. By Thomi Richards
-
Fix test as per MP comment.
- 65. By Thomi Richards
-
Code cleanups.
- 64. By Thomi Richards
-
Remove use of boost::optional that wasn't needed.
- 63. By Thomi Richards
-
whitespace-only change.
- 62. By Thomi Richards
-
Addded test, cleaned up the code a little bit.
- 61. By Thomi Richards
-
Remove some commented out code.
- 60. By Thomi Richards
-
Add missing file - whoops!
- 59. By Thomi Richards
-
Use a template function to remove some code.
- 58. By Thomi Richards
-
Hook up new xpathselect grammar, added test to those already present.
- 57. By Thomi Richards
-
Builds, and tests all pass again.
Preview Diff
1 | === modified file 'lib/CMakeLists.txt' |
2 | --- lib/CMakeLists.txt 2013-01-22 02:39:32 +0000 |
3 | +++ lib/CMakeLists.txt 2014-07-28 19:46:11 +0000 |
4 | @@ -1,11 +1,11 @@ |
5 | FIND_PACKAGE( Boost 1.40 REQUIRED ) |
6 | INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIR} ) |
7 | |
8 | -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wl,--no-undefined") |
9 | +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wl,--no-undefined -ftemplate-backtrace-limit=0") |
10 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") |
11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG" ) |
12 | endif(CMAKE_BUILD_TYPE STREQUAL "Debug") |
13 | -set(SOURCES node.cpp xpathselect.h xpathselect.cpp parser.cpp parser.h) |
14 | +set(SOURCES node.cpp xpathselect.h xpathselect.cpp xpathquerypart.cpp parser.cpp parser.h) |
15 | set(HEADERS node.h xpathselect.h) |
16 | |
17 | if(CMAKE_COMPILER_IS_GNUCXX) |
18 | |
19 | === modified file 'lib/parser.cpp' |
20 | --- lib/parser.cpp 2013-01-21 02:27:45 +0000 |
21 | +++ lib/parser.cpp 2014-07-28 19:46:11 +0000 |
22 | @@ -1,5 +1,5 @@ |
23 | /* |
24 | -* Copyright (C) 2013 Canonical Ltd |
25 | +* Copyright (C) 2013-2014 Canonical Ltd |
26 | * |
27 | * This program is free software: you can redistribute it and/or modify |
28 | * it under the terms of the GNU General Public License version 3 as |
29 | @@ -16,3 +16,36 @@ |
30 | */ |
31 | |
32 | #include "parser.h" |
33 | + |
34 | +xpathselect::QueryList xpathselect::parser::parse_query(std::string const& query) |
35 | +{ |
36 | + bool s_; |
37 | + return parse_query(query, s_); |
38 | +} |
39 | + |
40 | +xpathselect::QueryList xpathselect::parser::parse_query(std::string const& query, bool& success) |
41 | +{ |
42 | + xpathselect::parser::xpath_grammar<std::string::const_iterator> grammar; |
43 | + QueryList query_parts; |
44 | + NodeSequence results; |
45 | + |
46 | + auto begin = query.cbegin(); |
47 | + auto end = query.cend(); |
48 | + if (boost::spirit::qi::parse(begin, end, grammar, results) && (begin == end)) |
49 | + { |
50 | + success = true; |
51 | + for (auto n: results) |
52 | + { |
53 | + if (auto p1 = boost::fusion::at_c<0>(n)) |
54 | + { |
55 | + query_parts.push_back(xpathselect::AnyNode(*p1)); |
56 | + } |
57 | + query_parts.push_back(boost::fusion::at_c<1>(n)); |
58 | + } |
59 | + } |
60 | + else |
61 | + { |
62 | + success = false; |
63 | + } |
64 | + return query_parts; |
65 | +} |
66 | |
67 | === modified file 'lib/parser.h' |
68 | --- lib/parser.h 2014-02-19 19:18:58 +0000 |
69 | +++ lib/parser.h 2014-07-28 19:46:11 +0000 |
70 | @@ -1,5 +1,5 @@ |
71 | /* |
72 | -* Copyright (C) 2013 Canonical Ltd |
73 | +* Copyright (C) 2013-2014 Canonical Ltd |
74 | * |
75 | * This program is free software: you can redistribute it and/or modify |
76 | * it under the terms of the GNU General Public License version 3 as |
77 | @@ -22,26 +22,27 @@ |
78 | |
79 | #include <boost/config/warning_disable.hpp> |
80 | #include <boost/fusion/include/adapt_struct.hpp> |
81 | -#include <boost/spirit/include/phoenix_object.hpp> |
82 | -#include <boost/spirit/include/phoenix_operator.hpp> |
83 | -#include <boost/spirit/include/qi.hpp> |
84 | -#include <boost/spirit/include/qi_bool.hpp> |
85 | -#include <boost/spirit/include/qi_int.hpp> |
86 | +#include <boost/spirit/include/phoenix.hpp> |
87 | +#include <boost/spirit/include/qi.hpp> |
88 | |
89 | #include "xpathquerypart.h" |
90 | |
91 | -// this allows spirit to lazily construct these two structs... |
92 | -BOOST_FUSION_ADAPT_STRUCT( |
93 | - xpathselect::XPathQueryPart, |
94 | - (std::string, node_name_) |
95 | - (xpathselect::ParamList, parameter) |
96 | - ); |
97 | +BOOST_FUSION_ADAPT_STRUCT( |
98 | + xpathselect::XPathWildcardNode, |
99 | + (xpathselect::ParamList, parameters) |
100 | +); |
101 | + |
102 | +BOOST_FUSION_ADAPT_STRUCT( |
103 | + xpathselect::XPathSpecifiedNode, |
104 | + (xpathselect::NodeNameList, names) |
105 | + (xpathselect::ParamList, parameters) |
106 | +); |
107 | |
108 | BOOST_FUSION_ADAPT_STRUCT( |
109 | xpathselect::XPathQueryParam, |
110 | (std::string, param_name) |
111 | (xpathselect::XPathQueryParam::ParamValueType, param_value) |
112 | - ); |
113 | +); |
114 | |
115 | namespace xpathselect |
116 | { |
117 | @@ -85,7 +86,7 @@ |
118 | // on - it must adhere to std::forward_iterator. The second template parameter is the type |
119 | // that this grammar will produce (in this case: a list of XPathQueryPart objects). |
120 | template <typename Iterator> |
121 | - struct xpath_grammar : qi::grammar<Iterator, QueryList()> |
122 | + struct xpath_grammar : qi::grammar<Iterator, NodeSequence()> |
123 | { |
124 | xpath_grammar() : xpath_grammar::base_type(node_sequence) // node_sequence is the start rule. |
125 | { |
126 | @@ -135,80 +136,75 @@ |
127 | // it must start and end with a non-space character, but you can have |
128 | // spaces in the middle. |
129 | spec_node_name = +qi::char_("a-zA-Z0-9_\\-") >> *(+qi::char_(" :") >> +qi::char_("a-zA-Z0-9_\\-")); |
130 | + spec_node_name_list = spec_node_name % ','; |
131 | // a wildcard node name is simply a '*' |
132 | - wildcard_node_name = qi::char_("*"); |
133 | + wildcard_node_name = "*"; |
134 | |
135 | |
136 | // a spec_node consists of a specified node name, followed by an *optional* parameter list. |
137 | - spec_node %= spec_node_name >> -(param_list); |
138 | + spec_node %= spec_node_name_list >> -(param_list); |
139 | // a wildcard node is a '*' without parameters: |
140 | wildcard_node %= wildcard_node_name >> !param_list; |
141 | // wildcard nodes can also have parameters: |
142 | wildcard_node_with_params %= wildcard_node_name >> param_list; |
143 | // A parent node is '..' as long as it's followed by a normal separator or end of input: |
144 | - parent_node = qi::lit("..")[qi::_val = XPathQueryPart("..")]; |
145 | + parent_node = qi::lit("..") >> qi::attr(XPathParentNode()); |
146 | |
147 | // node is simply any kind of code defined thus far: |
148 | - node = spec_node | wildcard_node_with_params | wildcard_node | parent_node; |
149 | + any_node = spec_node | wildcard_node_with_params | wildcard_node | parent_node; |
150 | |
151 | // a search node is '//' as long as it's followed by a spec node or a wildcard node with parameters. |
152 | // we don't allow '//*' since it would match everything in the tree, and cause HUGE amounts of |
153 | // data to be transmitted. |
154 | - search_node = "//" >> &(spec_node | wildcard_node_with_params)[qi::_val = XPathQueryPart()]; |
155 | - |
156 | + search_node = qi::lit("//") >> &(spec_node | wildcard_node_with_params) >> qi::attr(XPathSearchNode()); |
157 | |
158 | // a normal separator is a '/' as long as it's followed by something other than another '/' |
159 | normal_sep = '/' >> !qi::lit('/'); |
160 | - separator = normal_sep | search_node; // nodes can be separated by normal_sep or search_node. |
161 | + separator = search_node | normal_sep; // nodes can be separated by normal_sep or search_node. |
162 | // this is the money shot: a node sequence is one or more of a separator, followed by an |
163 | // optional node. |
164 | - node_sequence %= +(separator >> -node); |
165 | + sep_node_pair = separator >> any_node; |
166 | + node_sequence = +sep_node_pair; |
167 | |
168 | // DEBUGGING SUPPORT: |
169 | // define DEBUG in order to have boost::spirit spit out useful debug information: |
170 | #ifdef DEBUG |
171 | // this gives english names to all the grammar rules: |
172 | spec_node_name.name("spec_node_name"); |
173 | + spec_node_name_list.name("spec_node_name_list"); |
174 | wildcard_node_name.name("wildcard_node_name"); |
175 | search_node.name("search_node"); |
176 | - normal_sep.name("normal_separator"); |
177 | + parent_node.name("parent_node"); |
178 | + normal_sep.name("normal_sep"); |
179 | separator.name("separator"); |
180 | param_name.name("param_name"); |
181 | param_value.name("param_value"); |
182 | param.name("param"); |
183 | + param_list.name("param_list"); |
184 | spec_node.name("spec_node"); |
185 | wildcard_node.name("wildcard_node"); |
186 | - wildcard_node.name("wildcard_node_with_params"); |
187 | - node.name("node"); |
188 | + wildcard_node_with_params.name("wildcard_node_with_params"); |
189 | + any_node.name("any_node"); |
190 | + sep_node_pair.name("sep_node_pair"); |
191 | node_sequence.name("node_sequence"); |
192 | - param_list.name("param_list"); |
193 | |
194 | - // set up error logging: |
195 | - qi::on_error<qi::fail>( |
196 | - node_sequence, |
197 | - std::cout |
198 | - << phoenix::val("Error! Expecting ") |
199 | - << qi::_4 // what failed? |
200 | - << phoenix::val(" here: \"") |
201 | - << phoenix::construct<std::string>(qi::_3, qi::_2) // iterators to error-pos, end |
202 | - << phoenix::val("\"") |
203 | - << std::endl |
204 | - ); |
205 | - // specify which rules we want debug info about (all of them): |
206 | qi::debug(spec_node_name); |
207 | + qi::debug(spec_node_name_list); |
208 | qi::debug(wildcard_node_name); |
209 | qi::debug(search_node); |
210 | + qi::debug(parent_node); |
211 | qi::debug(normal_sep); |
212 | qi::debug(separator); |
213 | qi::debug(param_name); |
214 | qi::debug(param_value); |
215 | qi::debug(param); |
216 | + qi::debug(param_list); |
217 | qi::debug(spec_node); |
218 | qi::debug(wildcard_node); |
219 | qi::debug(wildcard_node_with_params); |
220 | - qi::debug(node); |
221 | + qi::debug(any_node); |
222 | + qi::debug(sep_node_pair); |
223 | qi::debug(node_sequence); |
224 | - qi::debug(param_list); |
225 | #endif |
226 | } |
227 | // declare all the rules. The second template parameter is the type they produce. |
228 | @@ -222,30 +218,39 @@ |
229 | // symbol table for chracter scape codes. |
230 | qi::symbols<char const, char const> unesc_char; |
231 | |
232 | - // parse integers, first signed then unsigned: |
233 | + // parse integers: |
234 | qi::rule<Iterator, int32_t()> int_type; |
235 | |
236 | // more complicated language rules: |
237 | qi::rule<Iterator, std::string()> spec_node_name; |
238 | - qi::rule<Iterator, std::string()> wildcard_node_name; |
239 | - qi::rule<Iterator, XPathQueryPart()> search_node; |
240 | - qi::rule<Iterator, XPathQueryPart()> parent_node; |
241 | + qi::rule<Iterator, xpathselect::NodeNameList()> spec_node_name_list; |
242 | + qi::rule<Iterator> wildcard_node_name; |
243 | + qi::rule<Iterator, XPathSearchNode()> search_node; |
244 | + qi::rule<Iterator, XPathParentNode()> parent_node; |
245 | qi::rule<Iterator> normal_sep; |
246 | - qi::rule<Iterator, xpathselect::QueryList()> separator; |
247 | + qi::rule<Iterator, xpathselect::MaybeSearchNode()> separator; |
248 | |
249 | qi::rule<Iterator, std::string()> param_name; |
250 | qi::rule<Iterator, xpathselect::XPathQueryParam::ParamValueType()> param_value; |
251 | qi::rule<Iterator, XPathQueryParam()> param; |
252 | qi::rule<Iterator, xpathselect::ParamList()> param_list; |
253 | |
254 | - qi::rule<Iterator, XPathQueryPart()> spec_node; |
255 | - qi::rule<Iterator, XPathQueryPart()> wildcard_node; |
256 | - qi::rule<Iterator, XPathQueryPart()> wildcard_node_with_params; |
257 | - qi::rule<Iterator, XPathQueryPart()> node; |
258 | + qi::rule<Iterator, XPathSpecifiedNode()> spec_node; |
259 | + qi::rule<Iterator, XPathWildcardNode()> wildcard_node; |
260 | + qi::rule<Iterator, XPathWildcardNode()> wildcard_node_with_params; |
261 | + qi::rule<Iterator, AnyNode()> any_node; |
262 | + qi::rule<Iterator, SepNodePair()> sep_node_pair; |
263 | |
264 | - qi::rule<Iterator, xpathselect::QueryList()> node_sequence; |
265 | + qi::rule<Iterator, NodeSequence()> node_sequence; |
266 | }; |
267 | |
268 | + /// Take some input, parse it, and return a flattened data structure, |
269 | + /// which is what most people actually want (means they don't need to |
270 | + /// deal with boost::optional components) |
271 | + QueryList parse_query(std::string const& query); |
272 | + /// Similar to the above, but allows the caller to determine if the grammar |
273 | + /// parsed the whole input or not. |
274 | + QueryList parse_query(std::string const& query, bool& success); |
275 | } |
276 | } |
277 | |
278 | |
279 | === added file 'lib/xpathquerypart.cpp' |
280 | --- lib/xpathquerypart.cpp 1970-01-01 00:00:00 +0000 |
281 | +++ lib/xpathquerypart.cpp 2014-07-28 19:46:11 +0000 |
282 | @@ -0,0 +1,129 @@ |
283 | +/* |
284 | +* Copyright (C) 2013 Canonical Ltd |
285 | +* |
286 | +* This program is free software: you can redistribute it and/or modify |
287 | +* it under the terms of the GNU General Public License version 3 as |
288 | +* published by the Free Software Foundation. |
289 | +* |
290 | +* This program is distributed in the hope that it will be useful, |
291 | +* but WITHOUT ANY WARRANTY; without even the implied warranty of |
292 | +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
293 | +* GNU General Public License for more details. |
294 | +* |
295 | +* You should have received a copy of the GNU General Public License |
296 | +* along with this program. If not, see <http://www.gnu.org/licenses/>. |
297 | +* |
298 | +*/ |
299 | + |
300 | +#include <string> |
301 | +#include <vector> |
302 | +#include <memory> |
303 | +#include <algorithm> |
304 | +#include <iostream> |
305 | + |
306 | +#include <boost/optional/optional.hpp> |
307 | +#include <boost/variant/variant.hpp> |
308 | +#include <boost/variant/get.hpp> |
309 | + |
310 | +#include "xpathquerypart.h" |
311 | +#include "node.h" |
312 | + |
313 | +namespace xpathselect |
314 | +{ |
315 | + namespace |
316 | + { |
317 | + bool nodeMatchesParameters(Node::Ptr const& node, ParamList const& parameters) |
318 | + { |
319 | + bool matches = true; |
320 | + if (!parameters.empty()) |
321 | + { |
322 | + for (auto param : parameters) |
323 | + { |
324 | + switch(param.param_value.which()) |
325 | + { |
326 | + case 0: |
327 | + { |
328 | + matches &= node->MatchStringProperty(param.param_name, boost::get<std::string>(param.param_value)); |
329 | + } |
330 | + break; |
331 | + case 1: |
332 | + { |
333 | + matches &= node->MatchBooleanProperty(param.param_name, boost::get<bool>(param.param_value)); |
334 | + } |
335 | + break; |
336 | + case 2: |
337 | + { |
338 | + matches &= node->MatchIntegerProperty(param.param_name, boost::get<int>(param.param_value)); |
339 | + } |
340 | + break; |
341 | + } |
342 | + } |
343 | + } |
344 | + return matches; |
345 | + } |
346 | + } |
347 | + |
348 | +#ifdef DEBUG |
349 | + std::string XPathSearchNode::Dump() const |
350 | + { |
351 | + return "SearchNode"; |
352 | + } |
353 | + |
354 | + std::string XPathParentNode::Dump() const |
355 | + { |
356 | + return "ParentNode"; |
357 | + } |
358 | + |
359 | + std::string XPathWildcardNode::Dump() const |
360 | + { |
361 | + return "WildcardNode"; |
362 | + } |
363 | + |
364 | + std::string XPathSpecifiedNode::Dump() const |
365 | + { |
366 | + std::string s("SpecifiedNode("); |
367 | + for (auto n: names) |
368 | + s += n + " "; |
369 | + s += ")"; |
370 | + return s; |
371 | + } |
372 | +#endif |
373 | + |
374 | + bool XPathWildcardNode::Matches(Node::Ptr const& node) const |
375 | + { |
376 | + return nodeMatchesParameters(node, parameters); |
377 | + } |
378 | + |
379 | + bool XPathSpecifiedNode::Matches(Node::Ptr const& node) const |
380 | + { |
381 | + bool matches = std::find(names.cbegin(), names.cend(), node->GetName()) != names.cend(); |
382 | + return matches && nodeMatchesParameters(node, parameters); |
383 | + } |
384 | + |
385 | +#ifdef DEBUG |
386 | + std::ostream &operator<<(std::ostream &stream, XPathWildcardNode const& ob) |
387 | + { |
388 | + stream << ob.Dump() << ' '; |
389 | + return stream; |
390 | + } |
391 | + |
392 | + std::ostream &operator<<(std::ostream &stream, XPathSpecifiedNode const& ob) |
393 | + { |
394 | + stream << ob.Dump() << ' '; |
395 | + return stream; |
396 | + } |
397 | + |
398 | + std::ostream &operator<<(std::ostream &stream, XPathSearchNode const& ob) |
399 | + { |
400 | + stream << ob.Dump() << ' '; |
401 | + return stream; |
402 | + } |
403 | + |
404 | + std::ostream &operator<<(std::ostream &stream, XPathParentNode const& ob) |
405 | + { |
406 | + stream << ob.Dump() << ' '; |
407 | + return stream; |
408 | + } |
409 | +#endif |
410 | + |
411 | +} |
412 | |
413 | === modified file 'lib/xpathquerypart.h' |
414 | --- lib/xpathquerypart.h 2013-09-03 03:28:14 +0000 |
415 | +++ lib/xpathquerypart.h 2014-07-28 19:46:11 +0000 |
416 | @@ -1,5 +1,5 @@ |
417 | /* |
418 | -* Copyright (C) 2013 Canonical Ltd |
419 | +* Copyright (C) 2013-2014 Canonical Ltd |
420 | * |
421 | * This program is free software: you can redistribute it and/or modify |
422 | * it under the terms of the GNU General Public License version 3 as |
423 | @@ -20,12 +20,10 @@ |
424 | |
425 | #include <string> |
426 | #include <vector> |
427 | -#include <memory> |
428 | -#include <iostream> |
429 | |
430 | #include <boost/optional/optional.hpp> |
431 | #include <boost/variant/variant.hpp> |
432 | -#include <boost/variant/get.hpp> |
433 | +#include <boost/fusion/include/vector.hpp> |
434 | |
435 | #include "node.h" |
436 | |
437 | @@ -40,76 +38,62 @@ |
438 | std::string param_name; |
439 | ParamValueType param_value; |
440 | }; |
441 | - |
442 | + |
443 | typedef std::vector<XPathQueryParam> ParamList; |
444 | - |
445 | - // Stores a part of an XPath query. |
446 | - struct XPathQueryPart |
447 | - { |
448 | - public: |
449 | - XPathQueryPart() {} |
450 | - XPathQueryPart(std::string node_name) |
451 | - : node_name_(node_name) |
452 | - {} |
453 | - |
454 | - enum class QueryPartType {Normal, Search, Parent}; |
455 | - |
456 | - bool Matches(Node::Ptr const& node) const |
457 | - { |
458 | - bool matches = (node_name_ == "*" || node->GetName() == node_name_); |
459 | - if (!parameter.empty()) |
460 | - { |
461 | - for (auto param : parameter) |
462 | - { |
463 | - switch(param.param_value.which()) |
464 | - { |
465 | - case 0: |
466 | - { |
467 | - matches &= node->MatchStringProperty(param.param_name, boost::get<std::string>(param.param_value)); |
468 | - } |
469 | - break; |
470 | - case 1: |
471 | - { |
472 | - matches &= node->MatchBooleanProperty(param.param_name, boost::get<bool>(param.param_value)); |
473 | - } |
474 | - break; |
475 | - case 2: |
476 | - { |
477 | - matches &= node->MatchIntegerProperty(param.param_name, boost::get<int>(param.param_value)); |
478 | - } |
479 | - break; |
480 | - } |
481 | - } |
482 | - } |
483 | - |
484 | - return matches; |
485 | - } |
486 | - |
487 | - QueryPartType Type() const |
488 | - { |
489 | - if (node_name_ == "") |
490 | - return QueryPartType::Search; |
491 | - else if (node_name_ == "..") |
492 | - return QueryPartType::Parent; |
493 | - else |
494 | - return QueryPartType::Normal; |
495 | - } |
496 | - |
497 | - void Dump() const |
498 | - { |
499 | - if (Type() == QueryPartType::Search) |
500 | - std::cout << "<search> "; |
501 | - else |
502 | - std::cout << "[" << node_name_ << "] "; |
503 | - } |
504 | - |
505 | - std::string node_name_; |
506 | - ParamList parameter; |
507 | - }; |
508 | - |
509 | - |
510 | - |
511 | - typedef std::vector<XPathQueryPart> QueryList; |
512 | + typedef std::vector<std::string> NodeNameList; |
513 | + |
514 | + struct XPathSearchNode |
515 | + { |
516 | +#ifdef DEBUG |
517 | + std::string Dump() const; |
518 | +#endif |
519 | + }; |
520 | + |
521 | + struct XPathParentNode |
522 | + { |
523 | +#ifdef DEBUG |
524 | + std::string Dump() const; |
525 | +#endif |
526 | + }; |
527 | + |
528 | + struct XPathWildcardNode |
529 | + { |
530 | + bool Matches(Node::Ptr const& node) const; |
531 | +#ifdef DEBUG |
532 | + std::string Dump() const; |
533 | +#endif |
534 | + ParamList parameters; |
535 | + }; |
536 | + |
537 | + struct XPathSpecifiedNode |
538 | + { |
539 | + bool Matches(Node::Ptr const& node) const; |
540 | +#ifdef DEBUG |
541 | + std::string Dump() const; |
542 | +#endif |
543 | + NodeNameList names; |
544 | + ParamList parameters; |
545 | + }; |
546 | + |
547 | +#ifdef DEBUG |
548 | + std::ostream &operator<<(std::ostream &stream, XPathWildcardNode const& ob); |
549 | + std::ostream &operator<<(std::ostream &stream, XPathSpecifiedNode const& ob); |
550 | + std::ostream &operator<<(std::ostream &stream, XPathSearchNode const& ob); |
551 | + std::ostream &operator<<(std::ostream &stream, XPathParentNode const& ob); |
552 | +#endif |
553 | + |
554 | + typedef boost::variant< |
555 | + XPathSearchNode, |
556 | + XPathParentNode, |
557 | + XPathWildcardNode, |
558 | + XPathSpecifiedNode |
559 | + > AnyNode; |
560 | + typedef boost::optional<XPathSearchNode> MaybeSearchNode; |
561 | + |
562 | + typedef boost::fusion::vector<MaybeSearchNode, AnyNode> SepNodePair; |
563 | + typedef std::vector<SepNodePair> NodeSequence; |
564 | + |
565 | + typedef std::vector<AnyNode> QueryList; |
566 | |
567 | } |
568 | |
569 | |
570 | === modified file 'lib/xpathselect.cpp' |
571 | --- lib/xpathselect.cpp 2013-09-03 03:28:14 +0000 |
572 | +++ lib/xpathselect.cpp 2014-07-28 19:46:11 +0000 |
573 | @@ -25,35 +25,76 @@ |
574 | |
575 | namespace xpathselect |
576 | { |
577 | +#ifdef DEBUG |
578 | + namespace debug |
579 | + { |
580 | + class dump_visitor: public boost::static_visitor<std::string> |
581 | + { |
582 | + public: |
583 | + |
584 | + template <typename T> |
585 | + std::string operator()(const T& t) const |
586 | + { |
587 | + return t.Dump(); |
588 | + } |
589 | + }; |
590 | + |
591 | + void dump_query_list(QueryList const& ql) |
592 | + { |
593 | + std::cout << "Query parts are: "; |
594 | + for (auto n : ql) |
595 | + std::cout << boost::apply_visitor(dump_visitor(), n) << " "; |
596 | + std::cout << std::endl; |
597 | + } |
598 | + } |
599 | +#endif |
600 | // anonymous namespace for internal-only utility class: |
601 | namespace |
602 | { |
603 | QueryList GetQueryPartsFromQuery(std::string const& query) |
604 | { |
605 | - xpathselect::parser::xpath_grammar<std::string::const_iterator> grammar; |
606 | - QueryList query_parts; |
607 | - |
608 | - auto begin = query.cbegin(); |
609 | - auto end = query.cend(); |
610 | - if (boost::spirit::qi::parse(begin, end, grammar, query_parts) && (begin == end)) |
611 | - { |
612 | -#ifdef DEBUG |
613 | - std::cout << "Query parts are: "; |
614 | - for (auto n : query_parts) |
615 | - n.Dump(); |
616 | - std::cout << std::endl; |
617 | -#endif |
618 | - return query_parts; |
619 | - } |
620 | -#ifdef DEBUG |
621 | - std::cout << "Query failed." << std::endl; |
622 | -#endif |
623 | - return QueryList(); |
624 | + QueryList query_parts = xpathselect::parser::parse_query(query); |
625 | +#ifdef DEBUG |
626 | + ::xpathselect::debug::dump_query_list(*query_parts); |
627 | +#endif |
628 | + return query_parts; |
629 | + } |
630 | + |
631 | + class matches_visitor: public boost::static_visitor<bool> |
632 | + { |
633 | + public: |
634 | + matches_visitor(Node::Ptr const& n) |
635 | + : node_(n) {} |
636 | + |
637 | + bool operator()(XPathSpecifiedNode const& n) const |
638 | + { |
639 | + return n.Matches(node_); |
640 | + } |
641 | + |
642 | + bool operator()(XPathWildcardNode const& n) const |
643 | + { |
644 | + return n.Matches(node_); |
645 | + } |
646 | + |
647 | + // everything else: |
648 | + template <typename U> |
649 | + bool operator()(U n) const |
650 | + { |
651 | + return false; |
652 | + } |
653 | + |
654 | + Node::Ptr const& node_; |
655 | + }; |
656 | + |
657 | + // return true if 'p' is one of the matchable types, and p matches 'node' |
658 | + bool maybe_matches(Node::Ptr const& node, AnyNode const& p) |
659 | + { |
660 | + return boost::apply_visitor( matches_visitor(node), p ); |
661 | } |
662 | |
663 | // Starting at each node listed in 'start_points', search the tree for nodes that match |
664 | // 'next_match'. next_match *must* be a normal query part object, not a search token. |
665 | - NodeList SearchTreeForNode(NodeList const& start_points, XPathQueryPart const& next_match) |
666 | + NodeList SearchTreeForNode(NodeList const& start_points, AnyNode const& next_match) |
667 | { |
668 | NodeList matches; |
669 | for (auto root: start_points) |
670 | @@ -65,7 +106,8 @@ |
671 | { |
672 | Node::Ptr node = queue.front(); |
673 | queue.pop(); |
674 | - if (next_match.Matches(node)) |
675 | + |
676 | + if (maybe_matches(node, next_match)) |
677 | { |
678 | // found one. We keep going deeper, as there may be another node beneath this one |
679 | // with the same node name. |
680 | @@ -98,20 +140,16 @@ |
681 | while (query_part != query_parts.cend()) |
682 | { |
683 | // If the current query piece is a recursive search token ('//')... |
684 | - if (query_part->Type() == XPathQueryPart::QueryPartType::Search) |
685 | + if (query_part->type() == typeid(XPathSearchNode)) |
686 | { |
687 | // advance to look at the next piece. |
688 | ++query_part; |
689 | - // do some sanity checking... |
690 | - if (query_part->Type() == XPathQueryPart::QueryPartType::Search) |
691 | - // invalid query - cannot specify multiple search sequences in a row. |
692 | - return NodeVector(); |
693 | + |
694 | // then find all the nodes that match the new query part, and store them as |
695 | - // the new start nodes. We pass in 'start_nodes' rather than 'root' since |
696 | - // there's a chance we'll be doing more than one search in different parts of the tree. |
697 | + // the new start nodes. |
698 | start_nodes = SearchTreeForNode(start_nodes, *query_part); |
699 | } |
700 | - else if (query_part->Type() == XPathQueryPart::QueryPartType::Parent) |
701 | + else if (query_part->type() == typeid(XPathParentNode)) |
702 | { |
703 | // This part of the query selects the parent node. If the current node has no |
704 | // parent (i.e.- we're already at the root of the tree) then this is a no-op: |
705 | @@ -133,7 +171,7 @@ |
706 | start_nodes.begin(), |
707 | start_nodes.end(), |
708 | [query_part](Node::Ptr n) -> bool { |
709 | - return ! query_part->Matches(n); |
710 | + return ! maybe_matches(n, *query_part); |
711 | } |
712 | ), |
713 | start_nodes.end() |
714 | @@ -144,7 +182,7 @@ |
715 | // next query part is not a parent node... |
716 | auto next_query_part = query_part + 1; |
717 | if (next_query_part != query_parts.cend() |
718 | - && next_query_part->Type() != XPathQueryPart::QueryPartType::Parent) |
719 | + && next_query_part->type() != typeid(XPathParentNode)) |
720 | { |
721 | NodeList new_start_nodes; |
722 | for (auto node: start_nodes) |
723 | |
724 | === modified file 'test/test_parser.cpp' |
725 | --- test/test_parser.cpp 2014-02-19 19:16:49 +0000 |
726 | +++ test/test_parser.cpp 2014-07-28 19:46:11 +0000 |
727 | @@ -100,6 +100,83 @@ |
728 | T expected_; |
729 | }; |
730 | |
731 | +// Some helper functions to make dealing with these new node classes a little easier. |
732 | + |
733 | +void assert_is_specified_node(xpathselect::AnyNode const& node) |
734 | +{ |
735 | + ASSERT_EQ(node.type(), typeid(xpathselect::XPathSpecifiedNode)); |
736 | +} |
737 | + |
738 | +void assert_is_wildcard_node(xpathselect::AnyNode const& node) |
739 | +{ |
740 | + ASSERT_EQ(node.type(), typeid(xpathselect::XPathWildcardNode)); |
741 | +} |
742 | + |
743 | +void assert_is_parent_node(xpathselect::AnyNode const& node) |
744 | +{ |
745 | + ASSERT_EQ(node.type(), typeid(xpathselect::XPathParentNode)); |
746 | +} |
747 | + |
748 | +void assert_is_search_node(xpathselect::AnyNode const& node) |
749 | +{ |
750 | + ASSERT_EQ(node.type(), typeid(xpathselect::XPathSearchNode)); |
751 | +} |
752 | + |
753 | +// assert that the node is a specified node, and has the given name: |
754 | +void assert_node_is_specified_with_name(std::string const& name, xpathselect::AnyNode const& node) |
755 | +{ |
756 | + assert_is_specified_node(node); |
757 | + ASSERT_EQ(name, boost::get<xpathselect::XPathSpecifiedNode>(node).names.at(0)); |
758 | +} |
759 | + |
760 | +// assert that the node is a specified node and has no parameters: |
761 | +void assert_node_is_specified_with_num_parameters(int expected, xpathselect::AnyNode const& node) |
762 | +{ |
763 | + assert_is_specified_node(node); |
764 | + ASSERT_EQ(expected, boost::get<xpathselect::XPathSpecifiedNode>(node).parameters.size()); |
765 | +} |
766 | + |
767 | +void assert_node_is_wildcard_with_num_parameters(int expected, xpathselect::AnyNode const& node) |
768 | +{ |
769 | + assert_is_wildcard_node(node); |
770 | + ASSERT_EQ(expected, boost::get<xpathselect::XPathWildcardNode>(node).parameters.size()); |
771 | +} |
772 | + |
773 | +// assert that the parameter on 'node' at index 'index' has name and value equal to 'name' and 'val' |
774 | +template <typename T> |
775 | +void assert_parameter_name_and_value( |
776 | + xpathselect::AnyNode const& node, |
777 | + int index, |
778 | + std::string const& name, |
779 | + variant_equality_assertion<T> const& val |
780 | +) |
781 | +{ |
782 | + if (node.type() == typeid(xpathselect::XPathSpecifiedNode)) |
783 | + { |
784 | + auto n = boost::get<xpathselect::XPathSpecifiedNode>(node); |
785 | + ASSERT_EQ("name", n.parameters.at(index).param_name); |
786 | + boost::apply_visitor( |
787 | + val, |
788 | + n.parameters.at(0).param_value |
789 | + ); |
790 | + } |
791 | + else if (node.type() == typeid(xpathselect::XPathWildcardNode)) |
792 | + { |
793 | + auto n = boost::get<xpathselect::XPathWildcardNode>(node); |
794 | + ASSERT_EQ("name", n.parameters.at(index).param_name); |
795 | + boost::apply_visitor( |
796 | + val, |
797 | + n.parameters.at(0).param_value |
798 | + ); |
799 | + |
800 | + } |
801 | + else |
802 | + { |
803 | + FAIL() << "Node is not specified or wildcard node, and thus does not have parameters."; |
804 | + } |
805 | +} |
806 | + |
807 | + |
808 | ////////////////////////////////////// |
809 | // Tests for basic type support: |
810 | ////////////////////////////////////// |
811 | @@ -333,6 +410,55 @@ |
812 | std::pair<std::string, bool>("..", false) |
813 | )); |
814 | |
815 | +// Test node name lists. |
816 | +TEST(TestNodeNameList, test_single_name) |
817 | +{ |
818 | + xpathselect::NodeNameList result; |
819 | + parser::xpath_grammar<std::string::iterator> g; |
820 | + |
821 | + ASSERT_TRUE(test_parser_attr("someNode", g.spec_node_name_list, result)); |
822 | + ASSERT_EQ(1, result.size()); |
823 | + ASSERT_EQ("someNode", result.at(0)); |
824 | +} |
825 | + |
826 | +TEST(TestNodeNameList, test_two_nodes) |
827 | +{ |
828 | + xpathselect::NodeNameList result; |
829 | + parser::xpath_grammar<std::string::iterator> g; |
830 | + |
831 | + ASSERT_TRUE(test_parser_attr("someNode,someOtherNode", g.spec_node_name_list, result)); |
832 | + ASSERT_EQ(2, result.size()); |
833 | + ASSERT_EQ("someNode", result.at(0)); |
834 | + ASSERT_EQ("someOtherNode", result.at(1)); |
835 | +} |
836 | + |
837 | +TEST(TestNodeNameList, test_empty_string) |
838 | +{ |
839 | + xpathselect::NodeNameList result; |
840 | + parser::xpath_grammar<std::string::iterator> g; |
841 | + |
842 | + ASSERT_FALSE(test_parser_attr("", g.spec_node_name_list, result)); |
843 | +} |
844 | + |
845 | + |
846 | +TEST(TestNodeNameList, test_comma_string) |
847 | +{ |
848 | + xpathselect::NodeNameList result; |
849 | + parser::xpath_grammar<std::string::iterator> g; |
850 | + |
851 | + ASSERT_FALSE(test_parser_attr(",", g.spec_node_name_list, result)); |
852 | +} |
853 | + |
854 | + |
855 | +TEST(TestNodeNameList, test_wildcard_not_allowed_in_list) |
856 | +{ |
857 | + xpathselect::NodeNameList result; |
858 | + parser::xpath_grammar<std::string::iterator> g; |
859 | + |
860 | + ASSERT_FALSE(test_parser_attr("foo,*", g.spec_node_name_list, result)); |
861 | +} |
862 | + |
863 | + |
864 | /// Tests for parameter values. This test is much larger than it should be, since it seems to be |
865 | // impossible to parameterise tests for both type and value. The solution I use here is to have |
866 | // the actual test in a base class template method, and have several derive classes use different |
867 | @@ -480,8 +606,6 @@ |
868 | |
869 | std::string result; |
870 | ASSERT_EQ( expect_pass, test_parser_attr(input, g.wildcard_node_name, result) ); |
871 | - if (expect_pass) |
872 | - ASSERT_EQ(input, result); |
873 | } |
874 | |
875 | INSTANTIATE_TEST_CASE_P(BasicNodeNames, |
876 | @@ -597,14 +721,14 @@ |
877 | |
878 | parser::xpath_grammar<std::string::iterator> g; |
879 | |
880 | - xpathselect::XPathQueryPart result; |
881 | + xpathselect::XPathSpecifiedNode result; |
882 | ASSERT_EQ( true, test_parser_attr(input, g.spec_node, result) ); |
883 | - ASSERT_EQ("node_name", result.node_name_); |
884 | - ASSERT_FALSE(result.parameter.empty()); |
885 | - ASSERT_EQ("param_name", result.parameter.at(0).param_name); |
886 | + ASSERT_EQ("node_name", result.names.at(0)); |
887 | + ASSERT_FALSE(result.parameters.empty()); |
888 | + ASSERT_EQ("param_name", result.parameters.at(0).param_name); |
889 | boost::apply_visitor( |
890 | variant_equality_assertion<int>(123), |
891 | - result.parameter.at(0).param_value |
892 | + result.parameters.at(0).param_value |
893 | ); |
894 | } |
895 | |
896 | @@ -614,10 +738,10 @@ |
897 | |
898 | parser::xpath_grammar<std::string::iterator> g; |
899 | |
900 | - xpathselect::XPathQueryPart result; |
901 | + xpathselect::XPathSpecifiedNode result; |
902 | ASSERT_EQ( true, test_parser_attr(input, g.spec_node, result) ); |
903 | - ASSERT_EQ("node_name", result.node_name_); |
904 | - ASSERT_TRUE(result.parameter.empty()); |
905 | + ASSERT_EQ("node_name", result.names.at(0)); |
906 | + ASSERT_TRUE(result.parameters.empty()); |
907 | } |
908 | |
909 | TEST(TestXPathParser, test_wildcard_node) |
910 | @@ -626,10 +750,9 @@ |
911 | |
912 | parser::xpath_grammar<std::string::iterator> g; |
913 | |
914 | - xpathselect::XPathQueryPart result; |
915 | + xpathselect::XPathWildcardNode result; |
916 | ASSERT_EQ( true, test_parser_attr(input, g.wildcard_node, result) ); |
917 | - ASSERT_EQ("*", result.node_name_); |
918 | - ASSERT_TRUE(result.parameter.empty()); |
919 | + ASSERT_TRUE(result.parameters.empty()); |
920 | } |
921 | |
922 | TEST(TestXPathParser, test_wildcard_node_rejects_parameters) |
923 | @@ -638,7 +761,7 @@ |
924 | |
925 | parser::xpath_grammar<std::string::iterator> g; |
926 | |
927 | - xpathselect::XPathQueryPart result; |
928 | + xpathselect::XPathWildcardNode result; |
929 | ASSERT_FALSE( test_parser_attr(input, g.wildcard_node, result) ); |
930 | } |
931 | |
932 | @@ -648,15 +771,14 @@ |
933 | |
934 | parser::xpath_grammar<std::string::iterator> g; |
935 | |
936 | - xpathselect::XPathQueryPart result; |
937 | + xpathselect::XPathWildcardNode result; |
938 | ASSERT_EQ( true, test_parser_attr(input, g.wildcard_node_with_params, result) ); |
939 | - ASSERT_EQ("*", result.node_name_); |
940 | - ASSERT_FALSE(result.parameter.empty()); |
941 | - ASSERT_EQ("param_name", result.parameter.at(0).param_name); |
942 | + ASSERT_FALSE(result.parameters.empty()); |
943 | + ASSERT_EQ("param_name", result.parameters.at(0).param_name); |
944 | boost::apply_visitor( |
945 | variant_equality_assertion<int>(123), |
946 | - result.parameter.at(0).param_value |
947 | - ); |
948 | + result.parameters.at(0).param_value |
949 | + ); |
950 | } |
951 | |
952 | |
953 | @@ -666,16 +788,16 @@ |
954 | |
955 | parser::xpath_grammar<std::string::iterator> g; |
956 | |
957 | - xpathselect::XPathQueryPart result; |
958 | - ASSERT_EQ( true, test_parser_attr(input, g.node, result) ); |
959 | - ASSERT_EQ( "*", result.node_name_ ); |
960 | - ASSERT_EQ( xpathselect::XPathQueryPart::QueryPartType::Normal, result.Type() ); |
961 | - ASSERT_EQ( 1, result.parameter.size() ); |
962 | - ASSERT_EQ( "name", result.parameter.at(0).param_name ); |
963 | - boost::apply_visitor( |
964 | - variant_equality_assertion<std::string>("value"), |
965 | - result.parameter.at(0).param_value |
966 | - ); |
967 | + xpathselect::AnyNode result; |
968 | + ASSERT_EQ( true, test_parser_attr(input, g.any_node, result) ); |
969 | + |
970 | + assert_node_is_wildcard_with_num_parameters(1, result); |
971 | + assert_parameter_name_and_value( |
972 | + result, |
973 | + 0, |
974 | + "name", |
975 | + variant_equality_assertion<std::string>("value") |
976 | + ); |
977 | } |
978 | |
979 | TEST(TestXPathParser, test_node_can_be_a_wildcard_node_without_params) |
980 | @@ -684,10 +806,9 @@ |
981 | |
982 | parser::xpath_grammar<std::string::iterator> g; |
983 | |
984 | - xpathselect::XPathQueryPart result; |
985 | - ASSERT_EQ( true, test_parser_attr(input, g.node, result) ); |
986 | - ASSERT_EQ( "*", result.node_name_ ); |
987 | - ASSERT_EQ( xpathselect::XPathQueryPart::QueryPartType::Normal, result.Type() ); |
988 | + xpathselect::AnyNode result; |
989 | + ASSERT_EQ( true, test_parser_attr(input, g.any_node, result) ); |
990 | + assert_is_wildcard_node(result); |
991 | } |
992 | |
993 | TEST(TestXPathParser, test_node_can_be_a_spec_node_with_params) |
994 | @@ -696,16 +817,16 @@ |
995 | |
996 | parser::xpath_grammar<std::string::iterator> g; |
997 | |
998 | - xpathselect::XPathQueryPart result; |
999 | - ASSERT_EQ( true, test_parser_attr(input, g.node, result) ); |
1000 | - ASSERT_EQ( "foo", result.node_name_ ); |
1001 | - ASSERT_EQ( xpathselect::XPathQueryPart::QueryPartType::Normal, result.Type() ); |
1002 | - ASSERT_EQ( 1, result.parameter.size() ); |
1003 | - ASSERT_EQ( "name", result.parameter.at(0).param_name ); |
1004 | - boost::apply_visitor( |
1005 | - variant_equality_assertion<std::string>("value"), |
1006 | - result.parameter.at(0).param_value |
1007 | - ); |
1008 | + xpathselect::AnyNode result; |
1009 | + ASSERT_EQ( true, test_parser_attr(input, g.any_node, result) ); |
1010 | + |
1011 | + assert_node_is_specified_with_name("foo", result); |
1012 | + assert_parameter_name_and_value( |
1013 | + result, |
1014 | + 0, |
1015 | + "name", |
1016 | + variant_equality_assertion<std::string>("value") |
1017 | + ); |
1018 | } |
1019 | |
1020 | TEST(TestXPathParser, test_node_can_be_a_spec_node_without_params) |
1021 | @@ -714,10 +835,10 @@ |
1022 | |
1023 | parser::xpath_grammar<std::string::iterator> g; |
1024 | |
1025 | - xpathselect::XPathQueryPart result; |
1026 | - ASSERT_EQ( true, test_parser_attr(input, g.node, result) ); |
1027 | - ASSERT_EQ( "foo", result.node_name_ ); |
1028 | - ASSERT_EQ( xpathselect::XPathQueryPart::QueryPartType::Normal, result.Type() ); |
1029 | + xpathselect::AnyNode result; |
1030 | + ASSERT_EQ( true, test_parser_attr(input, g.any_node, result) ); |
1031 | + |
1032 | + assert_node_is_specified_with_name("foo", result); |
1033 | } |
1034 | |
1035 | TEST(TestXPathParser, test_node_can_be_a_parent_node) |
1036 | @@ -726,10 +847,9 @@ |
1037 | |
1038 | parser::xpath_grammar<std::string::iterator> g; |
1039 | |
1040 | - xpathselect::XPathQueryPart result; |
1041 | - ASSERT_EQ( true, test_parser_attr(input, g.node, result) ); |
1042 | - ASSERT_EQ( "..", result.node_name_ ); |
1043 | - ASSERT_EQ( xpathselect::XPathQueryPart::QueryPartType::Parent, result.Type() ); |
1044 | + xpathselect::AnyNode result; |
1045 | + ASSERT_EQ( true, test_parser_attr(input, g.any_node, result) ); |
1046 | + assert_is_parent_node(result); |
1047 | } |
1048 | |
1049 | TEST(TestXPathParser, test_search_node_followed_by_normal_node) |
1050 | @@ -738,12 +858,11 @@ |
1051 | // to give it some more data, even though we're not actually matching it. |
1052 | std::string input("//node_name"); |
1053 | parser::xpath_grammar<std::string::iterator> g; |
1054 | - xpathselect::XPathQueryPart result; |
1055 | + xpathselect::XPathSearchNode result; |
1056 | |
1057 | // however, this means we can't use the test_parser_attr function, since it |
1058 | // returns false on a partial match. Use the parse(...) function directly: |
1059 | ASSERT_TRUE( parse(input.begin(), input.end(),g.search_node, result) ); |
1060 | - ASSERT_TRUE( result.Type() == xpathselect::XPathQueryPart::QueryPartType::Search ); |
1061 | } |
1062 | |
1063 | TEST(TestXPathParser, test_search_node_followed_by_wildcard_node_with_parameters) |
1064 | @@ -752,12 +871,11 @@ |
1065 | // to give it some more data, even though we're not actually matching it. |
1066 | std::string input("//*[foo=\"bar\"]"); |
1067 | parser::xpath_grammar<std::string::iterator> g; |
1068 | - xpathselect::XPathQueryPart result; |
1069 | + xpathselect::XPathSearchNode result; |
1070 | |
1071 | // however, this means we can't use the test_parser_attr function, since it |
1072 | // returns false on a partial match. Use the parse(...) function directly: |
1073 | ASSERT_TRUE( parse(input.begin(), input.end(),g.search_node, result) ); |
1074 | - ASSERT_TRUE( result.Type() == xpathselect::XPathQueryPart::QueryPartType::Search ); |
1075 | } |
1076 | |
1077 | TEST(TestXPathParser, test_search_node_cannot_have_parameters) |
1078 | @@ -766,9 +884,20 @@ |
1079 | |
1080 | parser::xpath_grammar<std::string::iterator> g; |
1081 | |
1082 | - xpathselect::XPathQueryPart result; |
1083 | - ASSERT_FALSE( test_parser_attr(input, g.search_node, result) ); |
1084 | -} |
1085 | + xpathselect::XPathSearchNode result; |
1086 | + ASSERT_FALSE( test_parser_attr(input, g.search_node, result) ); |
1087 | +} |
1088 | + |
1089 | +TEST(TestXPathParser, test_cannot_give_two_search_nodes) |
1090 | +{ |
1091 | + std::string input("////"); |
1092 | + |
1093 | + parser::xpath_grammar<std::string::iterator> g; |
1094 | + |
1095 | + xpathselect::XPathSearchNode result; |
1096 | + ASSERT_FALSE( test_parser_attr(input, g.search_node, result) ); |
1097 | +} |
1098 | + |
1099 | |
1100 | TEST(TestXPathParser, test_parent_node) |
1101 | { |
1102 | @@ -776,9 +905,8 @@ |
1103 | |
1104 | parser::xpath_grammar<std::string::iterator> g; |
1105 | |
1106 | - xpathselect::XPathQueryPart result; |
1107 | + xpathselect::XPathParentNode result; |
1108 | ASSERT_TRUE( test_parser_attr(input, g.parent_node, result) ); |
1109 | - ASSERT_TRUE( result.Type() == xpathselect::XPathQueryPart::QueryPartType::Parent ); |
1110 | } |
1111 | |
1112 | TEST(TestXPathParser, test_normal_sep_works) |
1113 | @@ -801,123 +929,87 @@ |
1114 | |
1115 | TEST(TestXPathParser, test_can_extract_query_list) |
1116 | { |
1117 | - std::string input("/node1/node2"); |
1118 | - |
1119 | - parser::xpath_grammar<std::string::iterator> g; |
1120 | - |
1121 | - xpathselect::QueryList result; |
1122 | - ASSERT_TRUE(test_parser_attr(input, g.node_sequence, result)); |
1123 | + xpathselect::QueryList result = xpathselect::parser::parse_query("/node1/node2"); |
1124 | ASSERT_EQ(2, result.size()); |
1125 | - ASSERT_EQ("node1", result.at(0).node_name_); |
1126 | - ASSERT_TRUE(result.at(0).parameter.empty()); |
1127 | - ASSERT_EQ("node2", result.at(1).node_name_); |
1128 | - ASSERT_TRUE(result.at(1).parameter.empty()); |
1129 | + assert_node_is_specified_with_name("node1", result.at(0)); |
1130 | + assert_node_is_specified_with_num_parameters(0, result.at(0)); |
1131 | + assert_node_is_specified_with_name("node2", result.at(1)); |
1132 | + assert_node_is_specified_with_num_parameters(0, result.at(1)); |
1133 | } |
1134 | |
1135 | TEST(TestXPathParser, test_can_extract_query_list_with_search) |
1136 | { |
1137 | - std::string input("//node1"); |
1138 | - |
1139 | - parser::xpath_grammar<std::string::iterator> g; |
1140 | - |
1141 | - xpathselect::QueryList result; |
1142 | - ASSERT_TRUE(test_parser_attr(input, g.node_sequence, result)); |
1143 | + xpathselect::QueryList result = xpathselect::parser::parse_query("//node1"); |
1144 | ASSERT_EQ(2, result.size()); |
1145 | - ASSERT_TRUE(result.at(0).Type() == xpathselect::XPathQueryPart::QueryPartType::Search ); |
1146 | - ASSERT_TRUE(result.at(1).Type() == xpathselect::XPathQueryPart::QueryPartType::Normal ); |
1147 | - ASSERT_TRUE(result.at(0).parameter.empty()); |
1148 | - ASSERT_EQ("node1", result.at(1).node_name_); |
1149 | - ASSERT_TRUE(result.at(1).parameter.empty()); |
1150 | + assert_is_search_node(result.at(0)); |
1151 | + assert_node_is_specified_with_name("node1", result.at(1)); |
1152 | + assert_node_is_specified_with_num_parameters(0, result.at(1)); |
1153 | } |
1154 | |
1155 | TEST(TestXPathParser, test_mix_search_and_normal) |
1156 | { |
1157 | - std::string input("/node1//node2"); |
1158 | - |
1159 | - parser::xpath_grammar<std::string::iterator> g; |
1160 | - |
1161 | - xpathselect::QueryList result; |
1162 | - ASSERT_TRUE(test_parser_attr(input, g.node_sequence, result)); |
1163 | + xpathselect::QueryList result = xpathselect::parser::parse_query("/node1//node2"); |
1164 | |
1165 | ASSERT_EQ(3, result.size()); |
1166 | |
1167 | - ASSERT_EQ("node1", result.at(0).node_name_); |
1168 | - ASSERT_TRUE(result.at(0).Type() == xpathselect::XPathQueryPart::QueryPartType::Normal ); |
1169 | - ASSERT_TRUE(result.at(0).parameter.empty()); |
1170 | - |
1171 | - ASSERT_TRUE(result.at(1).Type() == xpathselect::XPathQueryPart::QueryPartType::Search ); |
1172 | - ASSERT_TRUE(result.at(1).parameter.empty()); |
1173 | - |
1174 | - ASSERT_EQ("node2", result.at(2).node_name_); |
1175 | - ASSERT_TRUE(result.at(2).Type() == xpathselect::XPathQueryPart::QueryPartType::Normal ); |
1176 | - ASSERT_TRUE(result.at(2).parameter.empty()); |
1177 | + assert_node_is_specified_with_name("node1", result.at(0)); |
1178 | + assert_node_is_specified_with_num_parameters(0, result.at(0)); |
1179 | + |
1180 | + assert_is_search_node(result.at(1)); |
1181 | + |
1182 | + assert_node_is_specified_with_name("node2", result.at(2)); |
1183 | + assert_node_is_specified_with_num_parameters(0, result.at(2)); |
1184 | } |
1185 | |
1186 | TEST(TestXPathParser, test_mix_search_and_long_normal) |
1187 | { |
1188 | - std::string input("/node1//node2[name=\"val\"]/node3"); |
1189 | - |
1190 | - parser::xpath_grammar<std::string::iterator> g; |
1191 | - |
1192 | - xpathselect::QueryList result; |
1193 | - ASSERT_TRUE(test_parser_attr(input, g.node_sequence, result)); |
1194 | - |
1195 | - ASSERT_EQ(4, result.size()); |
1196 | - |
1197 | - ASSERT_EQ("node1", result.at(0).node_name_); |
1198 | - ASSERT_TRUE(result.at(0).parameter.empty()); |
1199 | - |
1200 | - ASSERT_TRUE(result.at(1).Type() == xpathselect::XPathQueryPart::QueryPartType::Search ); |
1201 | - ASSERT_TRUE(result.at(1).parameter.empty()); |
1202 | - |
1203 | - ASSERT_EQ("node2", result.at(2).node_name_); |
1204 | - ASSERT_EQ(1, result.at(2).parameter.size()); |
1205 | - ASSERT_EQ("name", result.at(2).parameter.at(0).param_name); |
1206 | - boost::apply_visitor( |
1207 | - variant_equality_assertion<std::string>("val"), |
1208 | - result.at(2).parameter.at(0).param_value |
1209 | - ); |
1210 | - ASSERT_EQ("node3", result.at(3).node_name_); |
1211 | - ASSERT_TRUE(result.at(3).parameter.empty()); |
1212 | + xpathselect::QueryList result = xpathselect::parser::parse_query( |
1213 | + "/node1//node2[name=\"val\"]/node3" |
1214 | + ); |
1215 | + |
1216 | + EXPECT_EQ(4, result.size()); |
1217 | + |
1218 | + assert_node_is_specified_with_name("node1", result.at(0)); |
1219 | + assert_node_is_specified_with_num_parameters(0, result.at(0)); |
1220 | + |
1221 | + assert_is_search_node(result.at(1)); |
1222 | + |
1223 | + assert_node_is_specified_with_name("node2", result.at(2)); |
1224 | + assert_node_is_specified_with_num_parameters(1, result.at(2)); |
1225 | + assert_parameter_name_and_value( |
1226 | + result.at(2), |
1227 | + 0, |
1228 | + "name", |
1229 | + variant_equality_assertion<std::string>("val") |
1230 | + ); |
1231 | + |
1232 | + assert_node_is_specified_with_name("node3", result.at(3)); |
1233 | + assert_node_is_specified_with_num_parameters(0, result.at(3)); |
1234 | } |
1235 | |
1236 | TEST(TestXPathParser, test_mix_normal_and_parent) |
1237 | { |
1238 | - std::string input("/node1/.."); |
1239 | - |
1240 | - parser::xpath_grammar<std::string::iterator> g; |
1241 | - |
1242 | - xpathselect::QueryList result; |
1243 | - ASSERT_TRUE(test_parser_attr(input, g.node_sequence, result)); |
1244 | + xpathselect::QueryList result = xpathselect::parser::parse_query("/node1/.."); |
1245 | |
1246 | ASSERT_EQ(2, result.size()); |
1247 | |
1248 | - ASSERT_EQ("node1", result.at(0).node_name_); |
1249 | - ASSERT_TRUE(result.at(0).parameter.empty()); |
1250 | + assert_node_is_specified_with_name("node1", result.at(0)); |
1251 | + assert_node_is_specified_with_num_parameters(0, result.at(0)); |
1252 | |
1253 | - ASSERT_TRUE(result.at(1).Type() == xpathselect::XPathQueryPart::QueryPartType::Parent ); |
1254 | - ASSERT_TRUE(result.at(1).parameter.empty()); |
1255 | + assert_is_parent_node(result.at(1)); |
1256 | } |
1257 | |
1258 | TEST(TestXPathParser, test_mix_normal_and_parent_and_wildcard) |
1259 | { |
1260 | - std::string input("/node1/../*"); |
1261 | - |
1262 | - parser::xpath_grammar<std::string::iterator> g; |
1263 | - |
1264 | - xpathselect::QueryList result; |
1265 | - ASSERT_TRUE(test_parser_attr(input, g.node_sequence, result)); |
1266 | + xpathselect::QueryList result = xpathselect::parser::parse_query("/node1/../*"); |
1267 | |
1268 | ASSERT_EQ(3, result.size()); |
1269 | |
1270 | - ASSERT_EQ("node1", result.at(0).node_name_); |
1271 | - ASSERT_TRUE(result.at(0).parameter.empty()); |
1272 | - |
1273 | - ASSERT_TRUE(result.at(1).Type() == xpathselect::XPathQueryPart::QueryPartType::Parent ); |
1274 | - ASSERT_TRUE(result.at(1).parameter.empty()); |
1275 | - |
1276 | - ASSERT_TRUE(result.at(2).node_name_ == "*" ); |
1277 | - ASSERT_TRUE(result.at(2).parameter.empty()); |
1278 | + assert_node_is_specified_with_name("node1", result.at(0)); |
1279 | + assert_node_is_specified_with_num_parameters(0, result.at(0)); |
1280 | + |
1281 | + assert_is_parent_node(result.at(1)); |
1282 | + assert_is_wildcard_node(result.at(2)); |
1283 | } |
1284 | |
1285 | class TestXPathParserQueryStrings : public ::testing::TestWithParam<std::pair<std::string, bool> > |
1286 | @@ -930,10 +1022,9 @@ |
1287 | std::string input = p.first; |
1288 | bool expect_pass = p.second; |
1289 | |
1290 | - parser::xpath_grammar<std::string::iterator> g; |
1291 | - |
1292 | - xpathselect::QueryList result; |
1293 | - ASSERT_EQ( expect_pass, test_parser_attr(input, g, result) ); |
1294 | + bool actual_pass; |
1295 | + xpathselect::QueryList result = xpathselect::parser::parse_query(input, actual_pass); |
1296 | + ASSERT_EQ( expect_pass, actual_pass ); |
1297 | } |
1298 | |
1299 | INSTANTIATE_TEST_CASE_P(BasicNodeNames, |
1300 | |
1301 | === modified file 'test/test_xpath_tree.cpp' |
1302 | --- test/test_xpath_tree.cpp 2013-09-03 03:28:14 +0000 |
1303 | +++ test/test_xpath_tree.cpp 2014-07-28 19:46:11 +0000 |
1304 | @@ -47,6 +47,16 @@ |
1305 | NodePtr leaf_2_; |
1306 | }; |
1307 | |
1308 | +std::string dump_result(xpathselect::NodeVector result) |
1309 | +{ |
1310 | + // horribly inneficient, but only called when tests fail, so... *shrug* |
1311 | + std::string s; |
1312 | + for (auto r: result) |
1313 | + { |
1314 | + s += r->GetName() + " "; |
1315 | + } |
1316 | + return s; |
1317 | +} |
1318 | TEST_F(TestTreeFixture, test_simple) |
1319 | { |
1320 | xpathselect::NodeVector result = xpathselect::SelectNodes(root_, "/"); |
1321 | @@ -64,6 +74,17 @@ |
1322 | ASSERT_EQ(expected, actual); |
1323 | } |
1324 | |
1325 | +TEST_F(TestTreeFixture, test_multi_name_absolute) |
1326 | +{ |
1327 | + xpathselect::NodeVector result = xpathselect::SelectNodes(root_, "/Root/ChildLeft1,ChildRight1"); |
1328 | + ASSERT_EQ(2, result.size()); |
1329 | + |
1330 | + std::set<xpathselect::Node::Ptr> to_be_matched { child_l1_, child_r1_ }; |
1331 | + std::set<xpathselect::Node::Ptr> resultset(result.begin(), result.end()); |
1332 | + |
1333 | + ASSERT_EQ(to_be_matched, resultset); |
1334 | +} |
1335 | + |
1336 | TEST_F(TestTreeFixture, test_simple_relative) |
1337 | { |
1338 | xpathselect::NodeVector result = xpathselect::SelectNodes(root_, "//ChildRight1"); |
1339 | @@ -175,7 +196,7 @@ |
1340 | TEST_F(TestTreeFixture, test_wildcard) |
1341 | { |
1342 | xpathselect::NodeVector result = xpathselect::SelectNodes(root_, "/Root/*"); |
1343 | - ASSERT_EQ(2, result.size()); |
1344 | + ASSERT_EQ(2, result.size()) << dump_result(result); |
1345 | for(auto n : result) |
1346 | { |
1347 | ASSERT_TRUE(n == child_l1_ || n == child_r1_ ); |
Note: Please do not merge this. While this MP is considered complete, I need to write the autopilot-side of things, and I'd like to land the two at the same time to avoid back-and-forth merge proposals between the two projects.
Reviews are very welcome though :)