11/*
2- * Copyright 2025 Matthias Kühlewein
2+ * Copyright 2026 Matthias Kühlewein
33 *
44 * This file is part of OpenOrienteering.
55 *
2424
2525#include < Qt>
2626#include < QLatin1Char>
27+ #include < QLatin1String>
2728
2829#include " core/map.h"
30+ #include " core/map_part.h"
2931#include " core/objects/object.h"
3032#include " core/symbols/symbol.h"
3133
@@ -86,13 +88,13 @@ bool EvaluatePaperReal(const QString& element, int& type)
8688
8789bool EvaluateSymbolType (QString element, int symbol_type, bool & result)
8890{
89- QStringList symbol_types = {QLatin1String (" Point" ), QLatin1String (" Line" ), QLatin1String (" Area" ), QLatin1String (" Text" ), QLatin1String (" Combined" )};
91+ const QStringList symbol_types = {QLatin1String (" Point" ), QLatin1String (" Line" ), QLatin1String (" Area" ), QLatin1String (" Text" ), QLatin1String (" Combined" )};
9092 int operation;
9193
9294 if (element.startsWith (QLatin1String (" ==" )))
93- operation = 0 ;
94- else if (element.startsWith (QLatin1String (" !=" )))
9595 operation = 1 ;
96+ else if (element.startsWith (QLatin1String (" !=" )))
97+ operation = 0 ;
9698 else
9799 return false ;
98100
@@ -104,17 +106,60 @@ bool EvaluateSymbolType(QString element, int symbol_type, bool& result)
104106 if (match != symbol_types.end ())
105107 {
106108 auto index = match - symbol_types.begin ();
107- result = operation ? (1 <<index != symbol_type) : (1 <<index = = symbol_type);
109+ result = operation ? (1 <<index == symbol_type) : (1 <<index ! = symbol_type);
108110 return true ;
109111 }
110112 return false ;
111113}
112114
115+ bool EvaluateSymbolId (QString element, QString symbol_id, bool & result)
116+ {
117+ int operation;
118+
119+ if (element.startsWith (QLatin1String (" ==" )))
120+ operation = 1 ;
121+ else if (element.startsWith (QLatin1String (" !=" )))
122+ operation = 0 ;
123+ else
124+ return false ;
125+
126+ const auto symbol_id_elements = symbol_id.split (QLatin1Char (' .' ));
127+ const auto valuestr = element.remove (0 , 2 ).trimmed ();
128+ if (valuestr.isEmpty ())
129+ return false ;
130+ const auto value_elements = valuestr.split (QLatin1Char (' .' ));
131+ if (value_elements.size () > 3 )
132+ return false ;
133+
134+ for (auto i = 0 ; i < value_elements.size (); ++i)
135+ {
136+ if (i >= symbol_id_elements.size ())
137+ {
138+ result = !(bool )operation;
139+ return true ;
140+ }
141+ if (value_elements.at (i) == QLatin1Char (' *' ))
142+ {
143+ result = (bool )operation;
144+ return true ;
145+ }
146+ if (value_elements.at (i) != symbol_id_elements.at (i))
147+ {
148+ result = !(bool )operation;
149+ return true ;
150+ }
151+ }
152+ result = (bool )operation;
153+ return true ;
154+ }
155+
113156} // namespace
114157
115158
116159namespace OpenOrienteering {
117160
161+ const QStringList DynamicObjectQueryManager::keywords = {QLatin1String{" LINE" }, QLatin1String{" AREA" }, QLatin1String{" SYMBOL" }, QLatin1String{" OBJECT" }};
162+
118163DynamicObjectQueryManager::DynamicObjectQueryManager ()
119164{
120165 // nothing else
@@ -124,25 +169,23 @@ DynamicObjectQuery::~DynamicObjectQuery()= default;
124169
125170DynamicObjectQuery* DynamicObjectQueryManager::parse (QStringRef token_text, QStringRef token_attributes_text)
126171{
127- if (AreaObjectQuery::getKeyword () == token_text )
172+ if (token_text == keywords[DynamicObjectQuery::AreaObjectQuery] )
128173 {
129174 return new AreaObjectQuery (token_attributes_text);
130175 }
131- else if (LineObjectQuery::getKeyword () == token_text )
176+ else if (token_text == keywords[DynamicObjectQuery::LineObjectQuery] )
132177 {
133178 return new LineObjectQuery (token_attributes_text);
134179 }
135- else if (SymbolQuery::getKeyword () == token_text )
180+ else if (token_text == keywords[DynamicObjectQuery::SymbolQuery] )
136181 {
137182 return new SymbolQuery (token_attributes_text);
138183 }
139- /*
140- else if (ObjectQuery::getKeyword() == token_text)
184+ else if (token_text == keywords[DynamicObjectQuery::GeneralObjectQuery])
141185 {
142- return new ObjectQuery (token_attributes_text);
186+ return new GeneralObjectQuery (token_attributes_text);
143187 }
144- */
145- return nullptr ; // we should not get here
188+ return nullptr ;
146189}
147190
148191bool DynamicObjectQueryManager::performDynamicQuery (const Object* object, const DynamicObjectQuery* dynamic_query)
@@ -171,26 +214,94 @@ bool DynamicObjectQueryManager::performDynamicQuery(const Object* object, const
171214 const auto map = object->getMap ();
172215 return symbol && map ? static_cast <const SymbolQuery*>(dynamic_query)->performQuery (map, symbol) : false ;
173216 }
174- /*
175- case DynamicObjectQuery::ObjectQuery:
176- return static_cast<const ObjectQuery*>(dynamic_query)->performQuery(object);
177- */
217+ case DynamicObjectQuery::GeneralObjectQuery:
218+ {
219+ const auto map = object->getMap ();
220+ return map ? static_cast <const GeneralObjectQuery*>(dynamic_query)->performQuery (map, object) : false ;
221+ }
178222 default :
179223 return false ; // we should not get here
180224 }
181225
182226 Q_UNREACHABLE ();
183227}
184228
229+ const QStringList DynamicObjectQueryManager::getContextKeywords (const QString& text, int position, bool & append)
230+ {
231+ int keyword_found = -1 ;
232+ int i = position - 1 ;
233+ for ( ; i > 3 ; --i) // consider shortest keyword
234+ {
235+ if (text.at (i) == QLatin1Char (' )' ))
236+ {
237+ break ;
238+ }
239+ else if (text.at (i) == QLatin1Char (' (' ))
240+ {
241+ for (auto j = 0 ; j < keywords.size (); ++j)
242+ {
243+ auto match = text.lastIndexOf (keywords[j], i-1 );
244+ if (match != -1 && match == i - keywords[j].length ())
245+ {
246+ keyword_found = j;
247+ break ;
248+ }
249+ }
250+ }
251+ }
252+
253+ if (keyword_found == -1 )
254+ {
255+ QStringList context_keywords;
256+ for (auto keyword : keywords)
257+ context_keywords.append (keyword.append (QLatin1String (" ()" )));
258+ append = true ;
259+ return context_keywords;
260+ }
261+
262+ auto parameters = text.mid (i, position - i).trimmed ();
263+
264+ switch (keyword_found)
265+ {
266+ case DynamicObjectQuery::LineObjectQuery:
267+ case DynamicObjectQuery::AreaObjectQuery:
268+ for (const auto & comp : numerical_compare_operations)
269+ {
270+ if (parameters.endsWith (comp.op ))
271+ return QStringList ();
272+ }
273+ {
274+ QStringList keywords = {keyword_found == DynamicObjectQuery::LineObjectQuery ? QLatin1String (" ISTOOSHORT;" ) : QLatin1String (" ISTOOSMALL;" )};
275+ keywords += QString (QLatin1String (" PAPER; REAL; AND; OR;" )).split (QLatin1Char (' ' ));
276+ for (const auto & comp : numerical_compare_operations)
277+ keywords += comp.op ;
278+ return keywords;
279+ }
280+
281+ case DynamicObjectQuery::SymbolQuery:
282+ if (parameters.endsWith (QLatin1String (" ==" )) || parameters.endsWith (QLatin1String (" !=" )))
283+ {
284+ const auto find_type_keyword = parameters.lastIndexOf (QLatin1String (" TYPE" ));
285+ const auto find_id_keyword = parameters.lastIndexOf (QLatin1String (" ID" ));
286+ if (find_type_keyword >= find_id_keyword) // if both keywords are not found the '=' part is needed for the -1 values
287+ {
288+ return QString (QLatin1String (" Point; Line; Area; Text; Combined;" )).split (QLatin1Char (' ' ));
289+ }
290+ return QStringList ();
291+ }
292+ return QString (QLatin1String (" ISUNDEFINED; TYPE; ID; == != AND; OR;" )).split (QLatin1Char (' ' ));
293+
294+ case DynamicObjectQuery::GeneralObjectQuery:
295+ return QStringList{QLatin1String (" IGNORESYMBOL;" ), QLatin1String (" ISDUPLICATE;" )};
296+
297+ default :
298+ return QStringList (); // we should not get here
299+ }
300+
301+ Q_UNREACHABLE ();
302+ }
303+
185304
186- /* Bsp. und Ideen:
187- * AREA(ISTOOSMALL)
188- * AREA(REAL;<= 20.5;OR;>= 30.2)
189- * SYMBOL(ISUNDEFINED) bzw. SYMBOL(TYPE;== UNDEFINED)
190- * SYMBOL(TYPE;== AREA)
191- * SYMBOL(TYPE;== ISHELPER)
192- * SYMBOL(ID;== 102.1)
193- * */
194305AreaObjectQuery::AreaObjectQuery (QStringRef token_attributes_text)
195306: DynamicObjectQuery(DynamicObjectQuery::AreaObjectQuery)
196307{
@@ -307,7 +418,7 @@ bool SymbolQuery::performQuery(const Map* map, const Symbol* symbol) const
307418 int symbol_property = 0 ; // 0 = TYPE, 1 = ID
308419
309420 bool result = false ;
310- bool symbol_type_result ;
421+ bool eval_symbol_result ;
311422 for (auto & element : attributes)
312423 {
313424 if (EvaluateAndOrOperation (element, and_or_operation))
@@ -322,13 +433,17 @@ bool SymbolQuery::performQuery(const Map* map, const Symbol* symbol) const
322433 result = and_or_operation ? (result || is_undefined) : (result && is_undefined);
323434 and_or_operation = 0 ; // default after first operation is AND operation
324435 }
325- else if (symbol_property == 0 && EvaluateSymbolType (element, (symbol ? symbol->getType () : 0 ), symbol_type_result))
436+ else if (symbol_property == 0 && EvaluateSymbolType (element, (symbol ? symbol->getType () : 0 ), eval_symbol_result))
437+ {
438+ result = and_or_operation ? (result || eval_symbol_result) : (result && eval_symbol_result);
439+ and_or_operation = 0 ; // default after first operation is AND operation
440+ }
441+ else if (symbol_property == 1 && EvaluateSymbolId (element, (symbol ? symbol->getNumberAsString () : QLatin1String (" 1" )), eval_symbol_result))
326442 {
327- result = and_or_operation ? (result || symbol_type_result ) : (result && symbol_type_result );
443+ result = and_or_operation ? (result || eval_symbol_result ) : (result && eval_symbol_result );
328444 and_or_operation = 0 ; // default after first operation is AND operation
329445 }
330- // else if (symbol_property == 1 && ....)
331- else // unknown element or failure in EvaluateSymbolType()
446+ else // unknown element or failure in EvaluateSymbolType() or EvaluateSymbolId()
332447 {
333448 if (!symbol)
334449 return false ; // dry run failed
@@ -340,14 +455,48 @@ bool SymbolQuery::performQuery(const Map* map, const Symbol* symbol) const
340455 return true ; // dry run was successful
341456}
342457
343- /*
344- ObjectQuery::ObjectQuery (QStringRef token_attributes_text)
345- : DynamicObjectQuery(DynamicObjectQuery::ObjectQuery )
458+
459+ GeneralObjectQuery::GeneralObjectQuery (QStringRef token_attributes_text)
460+ : DynamicObjectQuery(DynamicObjectQuery::GeneralObjectQuery )
346461{
347462 parseTokenAttributes (token_attributes_text);
348- valid = performQuery(nullptr); // dry run
463+ valid = performQuery (nullptr , nullptr ); // dry run
349464}
350- */
465+
466+ bool GeneralObjectQuery::performQuery (const Map* map, const Object* object) const
467+ {
468+ int and_or_operation = 1 ; // 0 = AND, 1 = OR
469+ bool ignore_symbols = false ;
470+
471+ bool result = false ;
472+ for (auto & element : attributes)
473+ {
474+ if (EvaluateAndOrOperation (element, and_or_operation))
475+ continue ;
476+ else if (element == QLatin1String (" IGNORESYMBOL" ))
477+ ignore_symbols = true ;
478+ else if (element == QLatin1String (" ISDUPLICATE" ))
479+ {
480+ if (!map || !object)
481+ return true ;
482+ const bool isduplicate_result = map->getCurrentPart ()->existsObject ([object, ignore_symbols](auto const * o)
483+ { return object != o && object->equals (o, !ignore_symbols); }
484+ );
485+ result = and_or_operation ? (result || isduplicate_result) : (result && isduplicate_result);
486+ and_or_operation = 0 ; // default after first operation is AND operation
487+ }
488+ else // unknown element
489+ {
490+ if (!object)
491+ return false ; // dry run failed
492+ break ;
493+ }
494+ }
495+ if (object)
496+ return result;
497+ return true ; // dry run was successful
498+ }
499+
351500
352501DynamicObjectQuery::DynamicObjectQuery (Type type) noexcept
353502: type { type }
0 commit comments