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-2021 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  static bool findInPatterns
40  (
41  const bool patternMatch,
42  const Foam::word& keyword,
43  WcIterator& wcIter,
44  ReIterator& reIter
45  )
46  {
47  while (wcIter.good())
48  {
49  if
50  (
51  patternMatch
52  ? reIter()->match(keyword)
53  : wcIter()->keyword() == 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 == 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 == 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 == 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 using regular expressions only
286  if (findInPatterns(true, 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.find('/') != string::npos)
331  {
332  return csearchSlashScoped(keyword, matchOpt);
333  }
334 
335  if (keyword[0] == ':' || keyword[0] == '^')
336  {
337  // It is ':' scoped - force non-recusive 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  const wordList dictCmpts(path.components()); // Split on '/'
401 
402  for (const word& 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  // -> can do direct lookup, without csearch(cmpt, false, false);
429 
430  auto iter = dictPtr->hashedEntries_.cfind(cmpt);
431 
432  if (iter.good())
433  {
434  const entry *eptr = iter.val();
435 
436  if (eptr->isDict())
437  {
438  dictPtr = eptr->dictPtr();
439  }
440  else
441  {
442  FatalIOErrorInFunction(*dictPtr)
443  << "Found entry '" << cmpt
444  << "' but not a dictionary, while searching scoped"
445  << nl
446  << " " << path
447  << exit(FatalIOError);
448 
449  return nullptr;
450  }
451  }
452  else
453  {
454  return nullptr;
455  }
456  }
457  }
458 
459  return dictPtr;
460 }
461 
462 
464 (
465  const fileName& dictPath
466 ) const
467 {
468  return cfindScopedDict(dictPath);
469 }
470 
471 
473 (
474  const fileName& dictPath
475 )
476 {
477  const dictionary* ptr = cfindScopedDict(dictPath);
478  return const_cast<dictionary*>(ptr);
479 }
480 
481 
483 {
484  // Or warning
485  if (dictPath.empty())
486  {
487  return nullptr;
488  }
489 
490  dictionary* dictPtr = this;
491  if (dictPath[0] == '/')
492  {
493  // isAbsolute:
494  // Ascend to top-level
495  while (&dictPtr->parent_ != &dictionary::null)
496  {
497  dictPtr = const_cast<dictionary*>(&dictPtr->parent_);
498  }
499  }
500 
501  std::string path(dictPath); // Work on a copy
502  fileName::clean(path); // Remove unneeded ".."
503  auto dictCmpts = stringOps::split(path, '/'); // Split on '/'
504 
505  for (const auto& cmpt : dictCmpts)
506  {
507  if (cmpt == ".")
508  {
509  // "." - ignore
510  }
511  else if (cmpt == "..")
512  {
513  // ".." - go to parent
514  if (&dictPtr->parent_ != &dictionary::null)
515  {
516  dictPtr = const_cast<dictionary*>(&dictPtr->parent_);
517  }
518  else
519  {
520  FatalIOErrorInFunction(*dictPtr)
521  << "No parent for dictionary while searching "
522  << path
523  << exit(FatalIOError);
524 
525  return nullptr;
526  }
527  }
528  else
529  {
530  // Non-recursive, no patternMatch
531  // -> can do direct lookup,
532  // without csearch(cmptName, keyType::LITERAL);
533  const word cmptName(cmpt.str(), false);
534 
535  auto iter = dictPtr->hashedEntries_.find(cmptName);
536 
537  if (iter.good())
538  {
539  entry *eptr = iter.val();
540 
541  if (eptr->isDict())
542  {
543  dictPtr = eptr->dictPtr();
544  }
545  else
546  {
547  FatalIOErrorInFunction(*dictPtr)
548  << "Cannot create sub-dictionary entry '" << cmptName
549  << "' - a non-dictionary entry is in the way"
550  << nl << "Encountered in scope" << nl
551  << " " << path
552  << exit(FatalIOError);
553 
554  return nullptr;
555  }
556  }
557  else
558  {
559  dictionaryEntry *eptr =
560  new dictionaryEntry(cmptName, *dictPtr, dictionary());
561 
562  // Add *without* merging, since we just checked that the entry
563  // doesn't exist and to ensure that the pointer remains valid.
564 
565  if (dictPtr->add(eptr, false)) // NO merge
566  {
567  dictPtr = eptr;
568  }
569  else
570  {
571  // Note: a failed add() deletes the eptr passed
572  return nullptr;
573  }
574  }
575  }
576  }
577 
578  return dictPtr;
579 }
580 
581 
582 bool Foam::dictionary::remove(const word& keyword)
583 {
584  auto iter = hashedEntries_.find(keyword);
585 
586  if (iter.good())
587  {
588  // Delete from patterns
589  auto wcLink = patterns_.begin();
590  auto reLink = regexps_.begin();
591 
592  // Find in pattern using exact match only
593  if (findInPatterns(false, keyword, wcLink, reLink))
594  {
595  patterns_.remove(wcLink);
596  regexps_.remove(reLink);
597  }
598 
599  parent_type::remove(iter());
600  delete iter();
601  hashedEntries_.erase(iter);
602 
603  return true;
604  }
605 
606  return false;
607 }
608 
609 
611 (
612  const keyType& oldKeyword,
613  const keyType& newKeyword,
614  bool overwrite
615 )
616 {
617  // No change
618  if (oldKeyword == newKeyword)
619  {
620  return false;
621  }
622 
623  // Check that oldKeyword exists and can be changed
624  auto iter = hashedEntries_.find(oldKeyword);
625 
626  if (!iter.good())
627  {
628  return false;
629  }
630 
631  if (iter()->keyword().isPattern())
632  {
634  << "Old keyword " << oldKeyword << " is a pattern." << nl
635  << "Pattern replacement is not supported." << nl
636  << exit(FatalIOError);
637  }
638 
639 
640  auto iter2 = hashedEntries_.find(newKeyword);
641 
642  // newKeyword already exists
643  if (iter2.good())
644  {
645  if (overwrite)
646  {
647  if (iter2()->keyword().isPattern())
648  {
649  // Delete from patterns
650  auto wcLink = patterns_.begin();
651  auto reLink = regexps_.begin();
652 
653  // Find in patterns using exact match only
654  if (findInPatterns(false, iter2()->keyword(), wcLink, reLink))
655  {
656  patterns_.remove(wcLink);
657  regexps_.remove(reLink);
658  }
659  }
660 
661  parent_type::replace(iter2(), iter());
662  delete iter2();
663  hashedEntries_.erase(iter2);
664  }
665  else
666  {
667  IOWarningInFunction(*this)
668  << "Cannot rename keyword " << oldKeyword
669  << " to existing keyword " << newKeyword
670  << " in dictionary " << name() << endl;
671  return false;
672  }
673  }
674 
675  // Change name and HashTable, but leave DL-List untouched
676  iter()->keyword() = newKeyword;
677  iter()->name() = name() + '.' + newKeyword;
678  hashedEntries_.erase(oldKeyword);
679  hashedEntries_.insert(newKeyword, iter());
680 
681  if (newKeyword.isPattern())
682  {
683  patterns_.insert(iter());
684  regexps_.insert(autoPtr<regExp>::New(newKeyword));
685  }
686 
687  return true;
688 }
689 
690 
691 // ************************************************************************* //
Foam::entry
A keyword and a list of tokens is an 'entry'.
Definition: entry.H:67
Foam::dictionaryEntry
A keyword and a list of tokens is a 'dictionaryEntry'.
Definition: dictionaryEntry.H:65
Foam::dictionary::changeKeyword
bool changeKeyword(const keyType &oldKeyword, const keyType &newKeyword, bool overwrite=false)
Change the keyword for an entry,.
Definition: dictionarySearch.C:611
Foam::fileName::components
wordList components(const char delim='/') const
Return path components as wordList.
Definition: fileName.C:461
Foam::word
A class for handling words, derived from Foam::string.
Definition: word.H:65
Foam::keyType::isPattern
bool isPattern() const noexcept
The keyType is treated as a pattern, not as literal string.
Definition: keyTypeI.H:104
Foam::fileName
A class for handling file names.
Definition: fileName.H:73
Foam::dictionary::dictionary
dictionary()
Default construct, a top-level empty dictionary.
Definition: dictionary.C:75
Foam::stringOps::split
Foam::SubStrings< StringType > split(const StringType &str, const char delim, const bool keepEmpty=false)
Split string into sub-strings at the delimiter character.
Definition: stringOpsTemplates.C:98
Foam::entry::isDict
virtual bool isDict() const noexcept
Return true if this entry is a dictionary.
Definition: entry.H:233
Foam::entry::dictPtr
virtual const dictionary * dictPtr() const noexcept
Return pointer to dictionary, if entry is a dictionary.
Definition: entry.H:240
Foam::glTF::key
auto key(const Type &t) -> typename std::enable_if< std::is_enum< Type >::value, typename std::underlying_type< Type >::type >::type
Definition: foamGltfBase.H:108
Foam::dictionary::searchScoped
const_searcher searchScoped(const word &keyword, enum keyType::option) const
Search using dot or slash scoping.
Definition: dictionarySearch.C:355
Foam::word::validate
static word validate(const std::string &s, const bool prefix=false)
Construct validated word (no invalid characters).
Definition: word.C:45
Foam::dictionary::cfindScopedDict
const dictionary * cfindScopedDict(const fileName &dictPath) const
Locate a sub-dictionary using slash-scoping.
Definition: dictionarySearch.C:377
Foam::FatalIOError
IOerror FatalIOError
Foam::endl
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:369
Foam::dictionary::null
static const dictionary null
An empty dictionary, which is also the parent for all dictionaries.
Definition: dictionary.H:392
Foam::dictionary::Searcher
Generic const/non-const dictionary entry searcher.
Definition: dictionary.H:140
Foam::keyType
A class for handling keywords in dictionaries.
Definition: keyType.H:68
Foam::dictionary::findScopedDict
const dictionary * findScopedDict(const fileName &dictPath) const
Locate a sub-dictionary using slash-scoping.
Definition: dictionarySearch.C:464
Foam::keyType::RECURSIVE
Recursive search (eg, in dictionary)
Definition: keyType.H:85
Foam::dictionary::csearchScoped
const_searcher csearchScoped(const word &keyword, enum keyType::option) const
Search using scoping.
Definition: dictionarySearch.C:325
Foam::dictionary::makeScopedDict
dictionary * makeScopedDict(const fileName &dictPath)
Locate existing or create sub-dictionary using slash-scoping.
Definition: dictionarySearch.C:482
Foam::dictionary::csearch
const_searcher csearch(const word &keyword, enum keyType::option=keyType::REGEX) const
Search dictionary for given keyword.
Definition: dictionarySearch.C:265
Foam::dictionary::const_searcher
Searcher< true > const_searcher
Searcher with const access.
Definition: dictionary.H:270
Foam::dictionary::Searcher::dict
dict_reference dict() const
Reference the found entry as a dictionary.
Definition: dictionary.H:244
Foam::dictionary
A list of keyword definitions, which are a keyword followed by a number of values (eg,...
Definition: dictionary.H:123
dictionaryEntry.H
Foam::exit
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:130
Foam::autoPtr
Pointer management similar to std::unique_ptr, with some additional methods and type checking.
Definition: HashPtrTable.H:53
Foam::nl
constexpr char nl
Definition: Ostream.H:404
Foam::dictionary::remove
bool remove(const word &keyword)
Remove an entry specified by keyword.
Definition: dictionarySearch.C:582
Foam::List< word >
Foam::keyType::REGEX
Regular expression.
Definition: keyType.H:82
dictionary.H
path
fileName path(UMean.rootPath()/UMean.caseName()/"graphs"/UMean.instance())
Foam::dictionary::search
const_searcher search(const word &keyword, enum keyType::option=keyType::REGEX) const
Search dictionary for given keyword.
Definition: dictionarySearch.C:303
Foam::name
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:59
Foam::fileName::clean
static bool clean(std::string &str)
Definition: fileName.C:199
Foam::dictionary::Searcher::set
void set(pointer eptr)
Assign the entry.
Definition: dictionary.H:184
Foam::dictionary::add
entry * add(entry *entryPtr, bool mergeEntry=false)
Add a new entry.
Definition: dictionary.C:640
FatalIOErrorInFunction
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:473
Foam::fileName::clean
bool clean()
Cleanup filename (inplace)
Definition: fileName.C:390
IOWarningInFunction
#define IOWarningInFunction(ios)
Report an IO warning using Foam::Warning.
Definition: messageStream.H:340
stringOps.H
Foam::keyType::option
option
Enumeration for the data type and search/match modes (bitmask)
Definition: keyType.H:78