Skip to content

Commit d7d5d1b

Browse files
committed
More work on DataTree query
1 parent 38f8b46 commit d7d5d1b

File tree

3 files changed

+151
-19
lines changed

3 files changed

+151
-19
lines changed

modules/yup_data_model/tree/yup_DataTreeQuery.cpp

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ struct DataTreeQuery::XPathParser
3939
AtSign, // @
4040
Equal, // =
4141
NotEqual, // !=
42+
Greater, // >
43+
Less, // <
44+
GreaterEqual, // >=
45+
LessEqual, // <=
4246
String, // 'value' or "value"
4347
Number, // 123, 45.67
4448
And, // and
@@ -83,6 +87,10 @@ struct DataTreeQuery::XPathParser
8387
HasProperty, // [@prop]
8488
PropertyEquals, // [@prop='value']
8589
PropertyNotEquals, // [@prop!='value']
90+
PropertyGreater, // [@prop > value]
91+
PropertyLess, // [@prop < value]
92+
PropertyGreaterEqual, // [@prop >= value]
93+
PropertyLessEqual, // [@prop <= value]
8694
Position, // [1], [2], etc.
8795
First, // [first()]
8896
Last, // [last()]
@@ -246,6 +254,22 @@ struct DataTreeQuery::XPathParser
246254

247255
return; // Property selection terminates node traversal
248256
}
257+
else if (token.type == Token::Type::Function && token.value == "text")
258+
{
259+
// text() function - select text property
260+
++currentToken;
261+
262+
// Skip parentheses for text()
263+
if (currentToken < static_cast<int> (tokens.size()) && tokens[currentToken].type == Token::Type::OpenParen)
264+
{
265+
++currentToken;
266+
if (currentToken < static_cast<int> (tokens.size()) && tokens[currentToken].type == Token::Type::CloseParen)
267+
++currentToken;
268+
}
269+
270+
operations.emplace_back (QueryOperation::Property, "text");
271+
return; // text() selection terminates node traversal
272+
}
249273
else if (token.type == Token::Type::OpenBracket)
250274
{
251275
// Unexpected bracket without node test
@@ -424,6 +448,50 @@ struct DataTreeQuery::XPathParser
424448
}
425449
return std::make_unique<Predicate> (Predicate::PropertyNotEquals, propertyName, value);
426450
}
451+
else if (tokens[currentToken].type == Token::Type::Greater)
452+
{
453+
++currentToken;
454+
auto value = parseValue();
455+
if (!isValidValue(value))
456+
{
457+
parseResult = Result::fail("Expected value after comparison operator");
458+
return nullptr;
459+
}
460+
return std::make_unique<Predicate> (Predicate::PropertyGreater, propertyName, value);
461+
}
462+
else if (tokens[currentToken].type == Token::Type::Less)
463+
{
464+
++currentToken;
465+
auto value = parseValue();
466+
if (!isValidValue(value))
467+
{
468+
parseResult = Result::fail("Expected value after comparison operator");
469+
return nullptr;
470+
}
471+
return std::make_unique<Predicate> (Predicate::PropertyLess, propertyName, value);
472+
}
473+
else if (tokens[currentToken].type == Token::Type::GreaterEqual)
474+
{
475+
++currentToken;
476+
auto value = parseValue();
477+
if (!isValidValue(value))
478+
{
479+
parseResult = Result::fail("Expected value after comparison operator");
480+
return nullptr;
481+
}
482+
return std::make_unique<Predicate> (Predicate::PropertyGreaterEqual, propertyName, value);
483+
}
484+
else if (tokens[currentToken].type == Token::Type::LessEqual)
485+
{
486+
++currentToken;
487+
auto value = parseValue();
488+
if (!isValidValue(value))
489+
{
490+
parseResult = Result::fail("Expected value after comparison operator");
491+
return nullptr;
492+
}
493+
return std::make_unique<Predicate> (Predicate::PropertyLessEqual, propertyName, value);
494+
}
427495
}
428496

429497
// Just checking for property existence
@@ -489,13 +557,15 @@ struct DataTreeQuery::XPathParser
489557
QueryOperation op (QueryOperation::Where);
490558

491559
auto predicatePtr = std::shared_ptr<Predicate> (std::move (predicate));
492-
op.predicate = [predicatePtr] (const DataTree& node) -> bool
493-
{
494-
return evaluatePredicate (*predicatePtr, node, 0, 1); // position and size will be set during execution
495-
};
560+
561+
// Store the predicate for position-aware evaluation
562+
op.xpathPredicate = predicatePtr;
563+
496564
operations.push_back (std::move (op));
497565
}
498566

567+
568+
public:
499569
static bool evaluatePredicate (const Predicate& predicate, const DataTree& node, int position, int totalCount)
500570
{
501571
switch (predicate.type)
@@ -509,6 +579,26 @@ struct DataTreeQuery::XPathParser
509579
case Predicate::PropertyNotEquals:
510580
return ! node.hasProperty (predicate.property) || node.getProperty (predicate.property) != predicate.value;
511581

582+
case Predicate::PropertyGreater:
583+
if (!node.hasProperty (predicate.property))
584+
return false;
585+
return node.getProperty (predicate.property) > predicate.value;
586+
587+
case Predicate::PropertyLess:
588+
if (!node.hasProperty (predicate.property))
589+
return false;
590+
return node.getProperty (predicate.property) < predicate.value;
591+
592+
case Predicate::PropertyGreaterEqual:
593+
if (!node.hasProperty (predicate.property))
594+
return false;
595+
return node.getProperty (predicate.property) >= predicate.value;
596+
597+
case Predicate::PropertyLessEqual:
598+
if (!node.hasProperty (predicate.property))
599+
return false;
600+
return node.getProperty (predicate.property) <= predicate.value;
601+
512602
case Predicate::Position:
513603
return position == predicate.position - 1; // XPath is 1-indexed
514604

@@ -531,6 +621,7 @@ struct DataTreeQuery::XPathParser
531621
return false;
532622
}
533623

624+
private:
534625
void tokenize()
535626
{
536627
pos = 0;
@@ -598,6 +689,32 @@ struct DataTreeQuery::XPathParser
598689
}
599690
break;
600691

692+
case '>':
693+
if (pos + 1 < input.length() && input[pos + 1] == '=')
694+
{
695+
tokens.emplace_back (Token::Type::GreaterEqual, tokenStart);
696+
pos += 2;
697+
}
698+
else
699+
{
700+
tokens.emplace_back (Token::Type::Greater, tokenStart);
701+
++pos;
702+
}
703+
break;
704+
705+
case '<':
706+
if (pos + 1 < input.length() && input[pos + 1] == '=')
707+
{
708+
tokens.emplace_back (Token::Type::LessEqual, tokenStart);
709+
pos += 2;
710+
}
711+
else
712+
{
713+
tokens.emplace_back (Token::Type::Less, tokenStart);
714+
++pos;
715+
}
716+
break;
717+
601718
case '(':
602719
tokens.emplace_back (Token::Type::OpenParen, tokenStart);
603720
++pos;
@@ -683,7 +800,7 @@ struct DataTreeQuery::XPathParser
683800
else if (identifier == "not")
684801
tokens.emplace_back (Token::Type::Not, start);
685802

686-
else if (identifier == "first" || identifier == "last" || identifier == "position" || identifier == "count")
803+
else if (identifier == "first" || identifier == "last" || identifier == "position" || identifier == "count" || identifier == "text")
687804
tokens.emplace_back (Token::Type::Function, identifier, start);
688805

689806
else
@@ -1135,8 +1252,21 @@ std::vector<DataTree> DataTreeQuery::applyOperation (const QueryOperation& op, c
11351252

11361253
case QueryOperation::Where:
11371254
{
1138-
if (op.predicate)
1255+
if (op.xpathPredicate)
1256+
{
1257+
// XPath predicate with position information
1258+
auto predicate = std::static_pointer_cast<XPathParser::Predicate>(op.xpathPredicate);
1259+
int totalCount = static_cast<int>(input.size());
1260+
for (int i = 0; i < static_cast<int>(input.size()); ++i)
1261+
{
1262+
const auto& node = input[i];
1263+
if (XPathParser::evaluatePredicate(*predicate, node, i, totalCount))
1264+
result.push_back (node);
1265+
}
1266+
}
1267+
else if (op.predicate)
11391268
{
1269+
// Regular predicate (from fluent API)
11401270
for (const auto& node : input)
11411271
{
11421272
if (op.predicate (node))

modules/yup_data_model/tree/yup_DataTreeQuery.h

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,6 @@
2222
namespace yup
2323
{
2424

25-
//==============================================================================
26-
// Forward declaration for VarHasher
27-
struct VarHasher
28-
{
29-
std::size_t operator() (const var& v) const
30-
{
31-
return std::hash<String>() (v.toString());
32-
}
33-
};
34-
3525
//==============================================================================
3626
/**
3727
A powerful query system for extracting data from DataTree hierarchies using both fluent API and XPath-like syntax.
@@ -111,6 +101,15 @@ struct VarHasher
111101
*/
112102
class YUP_API DataTreeQuery
113103
{
104+
//==============================================================================
105+
struct VarHasher
106+
{
107+
std::size_t operator() (const var& v) const
108+
{
109+
return std::hash<String>() (v.toString());
110+
}
111+
};
112+
114113
public:
115114
//==============================================================================
116115
/**
@@ -428,6 +427,9 @@ class YUP_API DataTreeQuery
428427
var parameter2;
429428
std::function<bool (const DataTree&)> predicate;
430429
std::function<var (const DataTree&)> transformer;
430+
431+
// For XPath predicates that need position information
432+
std::shared_ptr<void> xpathPredicate;
431433

432434
QueryOperation (Type t)
433435
: type (t)
@@ -539,7 +541,7 @@ DataTreeQuery& DataTreeQuery::orderBy (KeySelector keySelector)
539541
}
540542

541543
template <typename KeySelector>
542-
std::unordered_map<var, std::vector<DataTree>, VarHasher> DataTreeQuery::groupBy (KeySelector keySelector) const
544+
std::unordered_map<var, std::vector<DataTree>, DataTreeQuery::VarHasher> DataTreeQuery::groupBy (KeySelector keySelector) const
543545
{
544546
auto results = nodes();
545547
std::unordered_map<var, std::vector<DataTree>, VarHasher> groups;

tests/yup_data_model/yup_DataTreeQuery.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -917,11 +917,11 @@ TEST_F (DataTreeQueryTests, XPathInvalidSyntax)
917917

918918
TEST_F (DataTreeQueryTests, XPathComplexExpressions)
919919
{
920-
// Complex boolean expressions
920+
// Complex boolean expressions with AND and comparison operators
921921
auto result = DataTreeQuery::xpath (testTree, "//Button[@enabled='true' and @width > 50]").nodes();
922922
EXPECT_GT (static_cast<int> (result.size()), 0);
923923

924-
// OR expressions
924+
// OR expressions with comparison operators
925925
auto result2 = DataTreeQuery::xpath (testTree, "//Button[@width > 100 or @enabled='false']").nodes();
926926
EXPECT_GT (static_cast<int> (result2.size()), 0);
927927

0 commit comments

Comments
 (0)