dictionarySearch.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2017-2025 OpenCFD Ltd.
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 \*---------------------------------------------------------------------------*/
27 
28 #include "dictionary.H"
29 #include "dictionaryEntry.H"
30 #include "stringOps.H"
31 
32 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
33 
34 namespace
35 {
36  // Walk lists of patterns and regexps for an exact match
37  // or a regular expression match
38  template<class WcIterator, class ReIterator>
39  bool findInPatterns
40  (
41  const bool literal,
42  const Foam::word& keyword,
43  WcIterator& wcIter,
44  ReIterator& reIter
45  )
46  {
47  while (wcIter.good())
48  {
49  if
50  (
51  literal
52  ? wcIter()->keyword() == keyword
53  : reIter()->match(keyword)
54  )
55  {
56  return true;
57  }
58 
59  ++reIter;
60  ++wcIter;
61  }
62 
63  return false;
64  }
65 
66 } // End anonymous namespace
67 
68 
69 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
70 
71 Foam::dictionary::const_searcher Foam::dictionary::csearchDotScoped
72 (
73  const word& keyword,
74  enum keyType::option matchOpt
75 ) const
76 {
77  auto scopePos = keyword.find('.');
78 
79  if (scopePos == std::string::npos)
80  {
81  // Normal, non-scoped search
82  return csearch(keyword, matchOpt);
83  }
84 
85  // It is '.' scoped - force non-recusive searching
86  matchOpt = keyType::option(matchOpt & ~(keyType::RECURSIVE));
87 
88  if (scopePos == 0)
89  {
90  // Starting with a '.' -> go up for every further '.' found
91  ++scopePos;
92 
93  const dictionary* dictPtr = this;
94  for
95  (
96  string::const_iterator it = keyword.begin()+1;
97  it != keyword.end() && *it == '.';
98  ++scopePos, ++it
99  )
100  {
101  // Go to parent
102  if (&dictPtr->parent_ != &dictionary::null)
103  {
104  dictPtr = &dictPtr->parent_;
105  }
106  else
107  {
109  << "No parent of current dictionary when searching for "
110  << keyword.substr(1)
111  << exit(FatalIOError);
112 
113  return nullptr;
114  }
115  }
116 
117  return dictPtr->csearchDotScoped
118  (
119  keyword.substr(scopePos),
120  matchOpt
121  );
122  }
123 
124  // The first word
125  const_searcher finder = csearchDotScoped
126  (
127  keyword.substr(0, scopePos),
128  matchOpt
129  );
130 
131  // Fall back to finding key with '.' so e.g. if keyword is
132  // a.b.c.d it would try
133  // a.b, a.b.c, a.b.c.d
134 
135  if (!finder.good())
136  {
137  while (!finder.isDict())
138  {
139  scopePos = keyword.find('.', scopePos+1);
140 
141  // Local entry:
142  finder = csearch(keyword.substr(0, scopePos), matchOpt);
143 
144  if (scopePos == std::string::npos)
145  {
146  // Parsed the whole word. Return entry or null.
147  return finder;
148  }
149  }
150  }
151 
152  if (finder.isDict())
153  {
154  return finder.dict().csearchDotScoped
155  (
156  keyword.substr(scopePos),
157  matchOpt
158  );
159  }
160 
161  return finder;
162 }
163 
164 
165 Foam::dictionary::const_searcher Foam::dictionary::csearchSlashScoped
166 (
167  const word& keyword,
168  enum keyType::option matchOpt
169 ) const
170 {
171 
172  // With '/' scoping - recursive is never allowed
173  matchOpt = keyType::option(matchOpt & ~(keyType::RECURSIVE));
174 
175  const dictionary* dictPtr = this;
176 
177  const auto slash = keyword.find('/');
178 
179  if (slash == std::string::npos)
180  {
181  // No slashes:
182  // Can use normal (non-scoped) search at the current dictionary level
183  return csearch(keyword, matchOpt);
184  }
185  else if (slash == 0)
186  {
187  // isAbsolute:
188  // Ascend to top-level
189  while (&dictPtr->parent_ != &dictionary::null)
190  {
191  dictPtr = &dictPtr->parent_;
192  }
193  }
194 
195  auto cmpts = stringOps::split(keyword, '/');
196  auto remaining = cmpts.size();
197 
198  for (const auto& cmpt : cmpts)
199  {
200  --remaining; // Decrement now so we can check (remaining == 0)
201 
202  if (cmpt == ".")
203  {
204  // "." - ignore
205  }
206  else if (cmpt == "..")
207  {
208  // ".." - go to parent
209  if (&dictPtr->parent_ != &dictionary::null)
210  {
211  dictPtr = &dictPtr->parent_;
212  }
213  else
214  {
215  FatalIOErrorInFunction(*dictPtr)
216  << "No parent of current dictionary when searching for "
217  << keyword << " at " << cmpt
218  << exit(FatalIOError);
219  break;
220  }
221  }
222  else
223  {
224  // Find entry
225  const word key = word::validate(cmpt);
226 
227  auto finder = dictPtr->csearch(key, matchOpt);
228 
229  if (finder.good())
230  {
231  if (remaining)
232  {
233  // Intermediate must be a dictionary
234  if (finder.isDict())
235  {
236  dictPtr = finder.dictPtr();
237  }
238  else
239  {
240  return const_searcher(dictPtr);
241  }
242  }
243  else
244  {
245  // Last entry - done
246  return finder;
247  }
248  }
249  else
250  {
251  break;
252  }
253  }
254  }
255 
256  // Failed at this dictionary level
257  return const_searcher(dictPtr);
258 }
259 
260 
261 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
262 
264 (
265  const word& keyword,
266  enum keyType::option matchOpt
267 ) const
268 {
269  const_searcher finder(this);
270 
271  auto iter = hashedEntries_.cfind(keyword);
272 
273  if (iter.good())
274  {
275  finder.set(iter.val());
276  return finder;
277  }
278 
279  if ((matchOpt & keyType::REGEX) && patterns_.size())
280  {
281  auto wcLink = patterns_.cbegin();
282  auto reLink = regexps_.cbegin();
283 
284  // Find in patterns : non-literal matching
285  if (findInPatterns(false, keyword, wcLink, reLink))
286  {
287  finder.set(*wcLink);
288  return finder;
289  }
290  }
291 
292  if ((matchOpt & keyType::RECURSIVE) && &parent_ != &dictionary::null)
293  {
294  return parent_.csearch(keyword, matchOpt);
295  }
296 
297  return finder;
298 }
299 
300 
302 (
303  const word& keyword,
304  enum keyType::option matchOpt
305 ) const
306 {
307  return csearch(keyword, matchOpt);
308 }
309 
310 
312 (
313  const word& keyword,
314  enum keyType::option matchOpt
315 )
316 {
317  const_searcher finder = csearch(keyword, matchOpt);
318 
319  return static_cast<const searcher&>(finder);
320 }
321 
322 
324 (
325  const word& keyword,
326  enum keyType::option matchOpt
327 ) const
328 {
329  if (keyword.contains('/'))
330  {
331  return csearchSlashScoped(keyword, matchOpt);
332  }
333 
334  if (keyword[0] == ':' || keyword[0] == '^')
335  {
336  // It is ':' scoped - force non-recursive searching
337  matchOpt = keyType::option(matchOpt & ~(keyType::RECURSIVE));
338 
339  // Ascend to top-level
340  const dictionary* dictPtr = this;
341  while (&dictPtr->parent_ != &dictionary::null)
342  {
343  dictPtr = &dictPtr->parent_;
344  }
345 
346  return dictPtr->csearchDotScoped(keyword.substr(1), matchOpt);
347  }
348 
349  return csearchDotScoped(keyword, matchOpt);
350 }
351 
352 
354 (
355  const word& keyword,
356  enum keyType::option matchOpt
357 ) const
358 {
359  return csearchScoped(keyword, matchOpt);
360 }
361 
362 
364 (
365  const word& keyword,
366  enum keyType::option matchOpt
367 )
368 {
369  const_searcher finder = csearchScoped(keyword, matchOpt);
370 
371  return static_cast<const searcher&>(finder);
372 }
373 
374 
376 (
377  const fileName& dictPath
378 ) const
379 {
380  // Or warning
381  if (dictPath.empty())
382  {
383  return nullptr;
384  }
385 
386  const dictionary* dictPtr = this;
387  if (dictPath[0] == '/')
388  {
389  // isAbsolute:
390  // Ascend to top-level
391  while (&dictPtr->parent_ != &dictionary::null)
392  {
393  dictPtr = &dictPtr->parent_;
394  }
395  }
396 
397  fileName path(dictPath); // Work on copy
398  path.clean(); // Remove unneeded ".."
399  auto dictCmpts = stringOps::split(path, '/');
400 
401  for (const auto& cmpt : dictCmpts)
402  {
403  if (cmpt == ".")
404  {
405  // "." - ignore
406  }
407  else if (cmpt == "..")
408  {
409  // ".." - go to parent
410  if (&dictPtr->parent_ != &dictionary::null)
411  {
412  dictPtr = &dictPtr->parent_;
413  }
414  else
415  {
416  FatalIOErrorInFunction(*dictPtr)
417  << "No parent for dictionary while searching "
418  << path
419  << exit(FatalIOError);
420 
421  return nullptr;
422  }
423  }
424  else
425  {
426  // Non-recursive, no patternMatch:
427  // do direct lookup, without csearch(cmpt, keyType::LITERAL)
428 
429  const word cmptName(cmpt.str(), false);
430 
431  auto iter = dictPtr->hashedEntries_.cfind(cmptName);
432 
433  if (iter.good())
434  {
435  const entry *eptr = iter.val();
436 
437  if (eptr->isDict())
438  {
439  dictPtr = eptr->dictPtr();
440  }
441  else
442  {
443  FatalIOErrorInFunction(*dictPtr)
444  << "Found entry '" << cmptName
445  << "' but not a dictionary, while searching scoped"
446  << nl
447  << " " << path
448  << exit(FatalIOError);
449 
450  return nullptr;
451  }
452  }
453  else
454  {
455  return nullptr;
456  }
457  }
458  }
459 
460  return dictPtr;
461 }
462 
463 
465 (
466  const fileName& dictPath
467 ) const
468 {
469  return cfindScopedDict(dictPath);
470 }
471 
472 
474 (
475  const fileName& dictPath
476 )
477 {
478  const dictionary* ptr = cfindScopedDict(dictPath);
479  return const_cast<dictionary*>(ptr);
480 }
481 
482 
484 {
485  // Or warning
486  if (dictPath.empty())
487  {
488  return nullptr;
489  }
490 
491  dictionary* dictPtr = this;
492  if (dictPath[0] == '/')
493  {
494  // isAbsolute:
495  // Ascend to top-level
496  while (&dictPtr->parent_ != &dictionary::null)
497  {
498  dictPtr = const_cast<dictionary*>(&dictPtr->parent_);
499  }
500  }
501 
502  std::string path(dictPath); // Work on a copy
503  fileName::clean(path); // Remove unneeded ".."
504  auto dictCmpts = stringOps::split(path, '/');
505 
506  for (const auto& cmpt : dictCmpts)
507  {
508  if (cmpt == ".")
509  {
510  // "." - ignore
511  }
512  else if (cmpt == "..")
513  {
514  // ".." - go to parent
515  if (&dictPtr->parent_ != &dictionary::null)
516  {
517  dictPtr = const_cast<dictionary*>(&dictPtr->parent_);
518  }
519  else
520  {
521  FatalIOErrorInFunction(*dictPtr)
522  << "No parent for dictionary while searching "
523  << path
524  << exit(FatalIOError);
525 
526  return nullptr;
527  }
528  }
529  else
530  {
531  // Non-recursive, no patternMatch:
532  // do direct lookup, without csearch(cmptName, keyType::LITERAL)
533 
534  const word cmptName(cmpt.str(), false);
535 
536  auto iter = dictPtr->hashedEntries_.find(cmptName);
537 
538  if (iter.good())
539  {
540  entry *eptr = iter.val();
541 
542  if (eptr->isDict())
543  {
544  dictPtr = eptr->dictPtr();
545  }
546  else
547  {
548  FatalIOErrorInFunction(*dictPtr)
549  << "Cannot create sub-dictionary entry '" << cmptName
550  << "' - a non-dictionary entry is in the way"
551  << nl << "Encountered in scope" << nl
552  << " " << path
553  << exit(FatalIOError);
554 
555  return nullptr;
556  }
557  }
558  else
559  {
560  dictionaryEntry *eptr =
561  new dictionaryEntry(cmptName, *dictPtr, dictionary());
562 
563  // Add *without* merging, since we just checked that the entry
564  // doesn't exist and to ensure that the pointer remains valid.
565 
566  if (dictPtr->add(eptr, false)) // NO merge
567  {
568  dictPtr = eptr;
569  }
570  else
571  {
572  // Note: a failed add() deletes the eptr passed
573  return nullptr;
574  }
575  }
576  }
577  }
578 
579  return dictPtr;
580 }
581 
582 
583 bool Foam::dictionary::remove(const word& keyword)
584 {
585  auto iter = hashedEntries_.find(keyword);
586 
587  if (iter.good())
588  {
589  // Delete from patterns
590  auto wcLink = patterns_.begin();
591  auto reLink = regexps_.begin();
592 
593  // Find in patterns : literal matching
594  if (findInPatterns(true, keyword, wcLink, reLink))
595  {
596  patterns_.remove(wcLink);
597  regexps_.remove(reLink);
598  }
599 
600  parent_type::remove(iter());
601  delete iter();
602  hashedEntries_.erase(iter);
603 
604  return true;
605  }
606 
607  return false;
608 }
609 
610 
612 (
613  const keyType& oldKeyword,
614  const keyType& newKeyword,
615  bool overwrite
616 )
617 {
618  // No change
619  if (oldKeyword == newKeyword)
620  {
621  return false;
622  }
623 
624  // Check that oldKeyword exists and can be changed
625  auto iter = hashedEntries_.find(oldKeyword);
626 
627  if (!iter.good())
628  {
629  return false;
630  }
631 
632  if (iter()->keyword().isPattern())
633  {
635  << "Old keyword " << oldKeyword << " is a pattern." << nl
636  << "Pattern replacement is not supported." << nl
637  << exit(FatalIOError);
638  }
639 
640 
641  auto iter2 = hashedEntries_.find(newKeyword);
642 
643  // newKeyword already exists
644  if (iter2.good())
645  {
646  if (overwrite)
647  {
648  if (iter2()->keyword().isPattern())
649  {
650  // Delete from patterns
651  auto wcLink = patterns_.begin();
652  auto reLink = regexps_.begin();
653 
654  // Find in patterns : literal matching
655  if (findInPatterns(true, iter2()->keyword(), wcLink, reLink))
656  {
657  patterns_.remove(wcLink);
658  regexps_.remove(reLink);
659  }
660  }
661 
662  parent_type::replace(iter2(), iter());
663  delete iter2();
664  hashedEntries_.erase(iter2);
665  }
666  else
667  {
668  IOWarningInFunction(*this)
669  << "Cannot rename keyword " << oldKeyword
670  << " to existing keyword " << newKeyword
671  << " in dictionary " << name() << endl;
672  return false;
673  }
674  }
675 
676  // Change name and HashTable, but leave DL-List untouched
677  iter()->keyword() = newKeyword;
678  iter()->name() = fileName::concat(name(), newKeyword, '/');
679  hashedEntries_.erase(oldKeyword);
680  hashedEntries_.insert(newKeyword, iter());
681 
682  if (newKeyword.isPattern())
683  {
684  patterns_.push_front(iter());
685  regexps_.push_front(autoPtr<regExp>::New(newKeyword));
686  }
687 
688  return true;
689 }
690 
691 
692 // ************************************************************************* //
dict_reference dict() const
Return the found entry as a dictionary. Error if not found, or not a dictionary.
Definition: dictionary.H:293
static word validate(const std::string &s, const bool prefix=false)
Construct validated word (no invalid characters).
Definition: word.C:39
A class for handling file names.
Definition: fileName.H:72
virtual const fileName & name() const
The name of the stream.
Definition: IOstream.C:33
bool contains(char c) const noexcept
True if string contains given character (cf. C++23)
Definition: string.H:411
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:129
bool clean()
Cleanup filename (inplace)
Definition: fileName.C:385
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
Foam::SubStrings split(const std::string &str, const char delim, std::string::size_type pos=0, const bool keepEmpty=false)
Split string into sub-strings at the delimiter character.
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:529
bool match(const std::string &text) const
Test for equality.
Definition: stringI.H:217
Recursive search (eg, in dictionary)
Definition: keyType.H:86
bool remove(const word &keyword)
Remove an entry specified by keyword.
const_searcher search(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Search dictionary for given keyword.
dictionary()
Default construct, a top-level empty dictionary.
Definition: dictionary.C:68
const dictionary * findScopedDict(const fileName &dictPath) const
Locate a sub-dictionary using slash-scoping.
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for expressions::valueTypeCode::INVALID.
Definition: exprTraits.C:127
Generic const/non-const dictionary entry searcher.
Definition: dictionary.H:149
A class for handling words, derived from Foam::string.
Definition: word.H:63
static const dictionary null
An empty dictionary, which is also the parent for all dictionaries.
Definition: dictionary.H:486
auto key(const Type &t) -> std::enable_if_t< std::is_enum_v< Type >, std::underlying_type_t< Type > >
Definition: foamGltfBase.H:103
fileName path(UMean.rootPath()/UMean.caseName()/"graphs"/UMean.instance())
const dictionary * cfindScopedDict(const fileName &dictPath) const
Locate a sub-dictionary using slash-scoping.
const_searcher csearchScoped(const word &keyword, enum keyType::option matchOpt) const
Search using scoping.
static bool clean(std::string &str)
Cleanup filename string, possibly applies other transformations such as changing the path separator e...
Definition: fileName.C:192
Searcher< true > const_searcher
Searcher with const access.
Definition: dictionary.H:324
static fileName concat(const std::string &s1, const std::string &s2, const char delim='/')
Join two strings with a path separator (&#39;/&#39; by default).
Definition: fileName.C:211
const_searcher searchScoped(const word &keyword, enum keyType::option matchOpt) const
Search using dot or slash scoping.
dictionary * makeScopedDict(const fileName &dictPath)
Locate existing or create sub-dictionary using slash-scoping.
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:629
option
Enumeration for the data type and search/match modes (bitmask)
Definition: keyType.H:79
#define IOWarningInFunction(ios)
Report an IO warning using Foam::Warning.
const_searcher csearch(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Search dictionary for given keyword.
static autoPtr< T > New(Args &&... args)
Construct autoPtr with forwarding arguments.
Definition: autoPtr.H:178
bool changeKeyword(const keyType &oldKeyword, const keyType &newKeyword, bool overwrite=false)
Change the keyword for an entry,.
Regular expression.
Definition: keyType.H:83
IOerror FatalIOError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL IO ERROR&#39; header text and ...