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-2023 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  // Split on '/'
196  auto cmpts = stringOps::split<std::string>(keyword, '/');
197  auto remaining = cmpts.size();
198 
199  for (const auto& cmpt : cmpts)
200  {
201  --remaining; // Decrement now so we can check (remaining == 0)
202 
203  if (cmpt == ".")
204  {
205  // "." - ignore
206  }
207  else if (cmpt == "..")
208  {
209  // ".." - go to parent
210  if (&dictPtr->parent_ != &dictionary::null)
211  {
212  dictPtr = &dictPtr->parent_;
213  }
214  else
215  {
216  FatalIOErrorInFunction(*dictPtr)
217  << "No parent of current dictionary when searching for "
218  << keyword << " at " << cmpt
219  << exit(FatalIOError);
220  break;
221  }
222  }
223  else
224  {
225  // Find entry
226  const word key = word::validate(cmpt);
227 
228  auto finder = dictPtr->csearch(key, matchOpt);
229 
230  if (finder.good())
231  {
232  if (remaining)
233  {
234  // Intermediate must be a dictionary
235  if (finder.isDict())
236  {
237  dictPtr = finder.dictPtr();
238  }
239  else
240  {
241  return const_searcher(dictPtr);
242  }
243  }
244  else
245  {
246  // Last entry - done
247  return finder;
248  }
249  }
250  else
251  {
252  break;
253  }
254  }
255  }
256 
257  // Failed at this dictionary level
258  return const_searcher(dictPtr);
259 }
260 
261 
262 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
263 
265 (
266  const word& keyword,
267  enum keyType::option matchOpt
268 ) const
269 {
270  const_searcher finder(this);
271 
272  auto iter = hashedEntries_.cfind(keyword);
273 
274  if (iter.good())
275  {
276  finder.set(iter.val());
277  return finder;
278  }
279 
280  if ((matchOpt & keyType::REGEX) && patterns_.size())
281  {
282  auto wcLink = patterns_.cbegin();
283  auto reLink = regexps_.cbegin();
284 
285  // Find in patterns : non-literal matching
286  if (findInPatterns(false, keyword, wcLink, reLink))
287  {
288  finder.set(*wcLink);
289  return finder;
290  }
291  }
292 
293  if ((matchOpt & keyType::RECURSIVE) && &parent_ != &dictionary::null)
294  {
295  return parent_.csearch(keyword, matchOpt);
296  }
297 
298  return finder;
299 }
300 
301 
303 (
304  const word& keyword,
305  enum keyType::option matchOpt
306 ) const
307 {
308  return csearch(keyword, matchOpt);
309 }
310 
311 
313 (
314  const word& keyword,
315  enum keyType::option matchOpt
316 )
317 {
318  const_searcher finder = csearch(keyword, matchOpt);
319 
320  return static_cast<const searcher&>(finder);
321 }
322 
323 
325 (
326  const word& keyword,
327  enum keyType::option matchOpt
328 ) const
329 {
330  if (keyword.contains('/'))
331  {
332  return csearchSlashScoped(keyword, matchOpt);
333  }
334 
335  if (keyword[0] == ':' || keyword[0] == '^')
336  {
337  // It is ':' scoped - force non-recursive searching
338  matchOpt = keyType::option(matchOpt & ~(keyType::RECURSIVE));
339 
340  // Ascend to top-level
341  const dictionary* dictPtr = this;
342  while (&dictPtr->parent_ != &dictionary::null)
343  {
344  dictPtr = &dictPtr->parent_;
345  }
346 
347  return dictPtr->csearchDotScoped(keyword.substr(1), matchOpt);
348  }
349 
350  return csearchDotScoped(keyword, matchOpt);
351 }
352 
353 
355 (
356  const word& keyword,
357  enum keyType::option matchOpt
358 ) const
359 {
360  return csearchScoped(keyword, matchOpt);
361 }
362 
363 
365 (
366  const word& keyword,
367  enum keyType::option matchOpt
368 )
369 {
370  const_searcher finder = csearchScoped(keyword, matchOpt);
371 
372  return static_cast<const searcher&>(finder);
373 }
374 
375 
377 (
378  const fileName& dictPath
379 ) const
380 {
381  // Or warning
382  if (dictPath.empty())
383  {
384  return nullptr;
385  }
386 
387  const dictionary* dictPtr = this;
388  if (dictPath[0] == '/')
389  {
390  // isAbsolute:
391  // Ascend to top-level
392  while (&dictPtr->parent_ != &dictionary::null)
393  {
394  dictPtr = &dictPtr->parent_;
395  }
396  }
397 
398  fileName path(dictPath); // Work on copy
399  path.clean(); // Remove unneeded ".."
400  auto dictCmpts = stringOps::split(path, '/'); // Split on '/'
401 
402  for (const auto& cmpt : dictCmpts)
403  {
404  if (cmpt == ".")
405  {
406  // "." - ignore
407  }
408  else if (cmpt == "..")
409  {
410  // ".." - go to parent
411  if (&dictPtr->parent_ != &dictionary::null)
412  {
413  dictPtr = &dictPtr->parent_;
414  }
415  else
416  {
417  FatalIOErrorInFunction(*dictPtr)
418  << "No parent for dictionary while searching "
419  << path
420  << exit(FatalIOError);
421 
422  return nullptr;
423  }
424  }
425  else
426  {
427  // Non-recursive, no patternMatch:
428  // do direct lookup, without csearch(cmpt, keyType::LITERAL)
429 
430  const word cmptName(cmpt.str(), false);
431 
432  auto iter = dictPtr->hashedEntries_.cfind(cmptName);
433 
434  if (iter.good())
435  {
436  const entry *eptr = iter.val();
437 
438  if (eptr->isDict())
439  {
440  dictPtr = eptr->dictPtr();
441  }
442  else
443  {
444  FatalIOErrorInFunction(*dictPtr)
445  << "Found entry '" << cmptName
446  << "' but not a dictionary, while searching scoped"
447  << nl
448  << " " << path
449  << exit(FatalIOError);
450 
451  return nullptr;
452  }
453  }
454  else
455  {
456  return nullptr;
457  }
458  }
459  }
460 
461  return dictPtr;
462 }
463 
464 
466 (
467  const fileName& dictPath
468 ) const
469 {
470  return cfindScopedDict(dictPath);
471 }
472 
473 
475 (
476  const fileName& dictPath
477 )
478 {
479  const dictionary* ptr = cfindScopedDict(dictPath);
480  return const_cast<dictionary*>(ptr);
481 }
482 
483 
485 {
486  // Or warning
487  if (dictPath.empty())
488  {
489  return nullptr;
490  }
491 
492  dictionary* dictPtr = this;
493  if (dictPath[0] == '/')
494  {
495  // isAbsolute:
496  // Ascend to top-level
497  while (&dictPtr->parent_ != &dictionary::null)
498  {
499  dictPtr = const_cast<dictionary*>(&dictPtr->parent_);
500  }
501  }
502 
503  std::string path(dictPath); // Work on a copy
504  fileName::clean(path); // Remove unneeded ".."
505  auto dictCmpts = stringOps::split(path, '/'); // Split on '/'
506 
507  for (const auto& cmpt : dictCmpts)
508  {
509  if (cmpt == ".")
510  {
511  // "." - ignore
512  }
513  else if (cmpt == "..")
514  {
515  // ".." - go to parent
516  if (&dictPtr->parent_ != &dictionary::null)
517  {
518  dictPtr = const_cast<dictionary*>(&dictPtr->parent_);
519  }
520  else
521  {
522  FatalIOErrorInFunction(*dictPtr)
523  << "No parent for dictionary while searching "
524  << path
525  << exit(FatalIOError);
526 
527  return nullptr;
528  }
529  }
530  else
531  {
532  // Non-recursive, no patternMatch:
533  // do direct lookup, without csearch(cmptName, keyType::LITERAL)
534 
535  const word cmptName(cmpt.str(), false);
536 
537  auto iter = dictPtr->hashedEntries_.find(cmptName);
538 
539  if (iter.good())
540  {
541  entry *eptr = iter.val();
542 
543  if (eptr->isDict())
544  {
545  dictPtr = eptr->dictPtr();
546  }
547  else
548  {
549  FatalIOErrorInFunction(*dictPtr)
550  << "Cannot create sub-dictionary entry '" << cmptName
551  << "' - a non-dictionary entry is in the way"
552  << nl << "Encountered in scope" << nl
553  << " " << path
554  << exit(FatalIOError);
555 
556  return nullptr;
557  }
558  }
559  else
560  {
561  dictionaryEntry *eptr =
562  new dictionaryEntry(cmptName, *dictPtr, dictionary());
563 
564  // Add *without* merging, since we just checked that the entry
565  // doesn't exist and to ensure that the pointer remains valid.
566 
567  if (dictPtr->add(eptr, false)) // NO merge
568  {
569  dictPtr = eptr;
570  }
571  else
572  {
573  // Note: a failed add() deletes the eptr passed
574  return nullptr;
575  }
576  }
577  }
578  }
579 
580  return dictPtr;
581 }
582 
583 
584 bool Foam::dictionary::remove(const word& keyword)
585 {
586  auto iter = hashedEntries_.find(keyword);
587 
588  if (iter.good())
589  {
590  // Delete from patterns
591  auto wcLink = patterns_.begin();
592  auto reLink = regexps_.begin();
593 
594  // Find in patterns : literal matching
595  if (findInPatterns(true, keyword, wcLink, reLink))
596  {
597  patterns_.remove(wcLink);
598  regexps_.remove(reLink);
599  }
600 
601  parent_type::remove(iter());
602  delete iter();
603  hashedEntries_.erase(iter);
604 
605  return true;
606  }
607 
608  return false;
609 }
610 
611 
613 (
614  const keyType& oldKeyword,
615  const keyType& newKeyword,
616  bool overwrite
617 )
618 {
619  // No change
620  if (oldKeyword == newKeyword)
621  {
622  return false;
623  }
624 
625  // Check that oldKeyword exists and can be changed
626  auto iter = hashedEntries_.find(oldKeyword);
627 
628  if (!iter.good())
629  {
630  return false;
631  }
632 
633  if (iter()->keyword().isPattern())
634  {
636  << "Old keyword " << oldKeyword << " is a pattern." << nl
637  << "Pattern replacement is not supported." << nl
638  << exit(FatalIOError);
639  }
640 
641 
642  auto iter2 = hashedEntries_.find(newKeyword);
643 
644  // newKeyword already exists
645  if (iter2.good())
646  {
647  if (overwrite)
648  {
649  if (iter2()->keyword().isPattern())
650  {
651  // Delete from patterns
652  auto wcLink = patterns_.begin();
653  auto reLink = regexps_.begin();
654 
655  // Find in patterns : literal matching
656  if (findInPatterns(true, iter2()->keyword(), wcLink, reLink))
657  {
658  patterns_.remove(wcLink);
659  regexps_.remove(reLink);
660  }
661  }
662 
663  parent_type::replace(iter2(), iter());
664  delete iter2();
665  hashedEntries_.erase(iter2);
666  }
667  else
668  {
669  IOWarningInFunction(*this)
670  << "Cannot rename keyword " << oldKeyword
671  << " to existing keyword " << newKeyword
672  << " in dictionary " << name() << endl;
673  return false;
674  }
675  }
676 
677  // Change name and HashTable, but leave DL-List untouched
678  iter()->keyword() = newKeyword;
679  iter()->name() = fileName::concat(name(), newKeyword, '/');
680  hashedEntries_.erase(oldKeyword);
681  hashedEntries_.insert(newKeyword, iter());
682 
683  if (newKeyword.isPattern())
684  {
685  patterns_.push_front(iter());
686  regexps_.push_front(autoPtr<regExp>::New(newKeyword));
687  }
688 
689  return true;
690 }
691 
692 
693 // ************************************************************************* //
dict_reference dict() const
Reference the found entry as a dictionary.
Definition: dictionary.H:278
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
Foam::SubStrings< StringType > split(const StringType &str, const char delim, const bool keepEmpty=false)
Split string into sub-strings at the delimiter character.
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
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
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:474
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:312
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:627
auto key(const Type &t) -> typename std::enable_if< std::is_enum< Type >::value, typename std::underlying_type< Type >::type >::type
Definition: foamGltfBase.H:103
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 ...