Loading...
Searching...
No Matches
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-------------------------------------------------------------------------------
10License
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
34namespace
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
71Foam::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
165Foam::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)
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)
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 }
458 }
459
460 return dictPtr;
461}
462
463
464const Foam::dictionary* Foam::dictionary::findScopedDict
465(
474(
475 const fileName& dictPath
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 }
577 }
578
579 return dictPtr;
580}
581
582
583bool 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 {
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// ************************************************************************* //
link * replace(link *oldLink, link *newLink)
Replace oldLink with newLink and return element.
Definition DLListBase.C:214
static autoPtr< T > New(Args &&... args)
Construct autoPtr with forwarding arguments.
Definition autoPtr.H:178
A keyword and a list of tokens is a 'dictionaryEntry'.
void set(pointer eptr) noexcept
Assign the entry.
Definition dictionary.H:213
A list of keyword definitions, which are a keyword followed by a number of values (eg,...
Definition dictionary.H:133
bool changeKeyword(const keyType &oldKeyword, const keyType &newKeyword, bool overwrite=false)
Change the keyword for an entry,.
dictionary()
Default construct, a top-level empty dictionary.
Definition dictionary.C:68
const_searcher csearchScoped(const word &keyword, enum keyType::option matchOpt) const
Search using scoping.
const dictionary * findScopedDict(const fileName &dictPath) const
Locate a sub-dictionary using slash-scoping.
bool remove(const word &keyword)
Remove an entry specified by keyword.
friend class entry
Declare friendship with the entry class for IO.
Definition dictionary.H:338
dictionary * makeScopedDict(const fileName &dictPath)
Locate existing or create sub-dictionary using slash-scoping.
const_searcher search(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Search dictionary for given keyword.
const dictionary * cfindScopedDict(const fileName &dictPath) const
Locate a sub-dictionary using slash-scoping.
const_searcher csearch(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Search dictionary for given keyword.
Searcher< false > searcher
Searcher with non-const access.
Definition dictionary.H:330
Searcher< true > const_searcher
Searcher with const access.
Definition dictionary.H:325
entry * add(entry *entryPtr, bool mergeEntry=false)
Add a new entry.
Definition dictionary.C:625
static const dictionary null
An empty dictionary, which is also the parent for all dictionaries.
Definition dictionary.H:487
const_searcher searchScoped(const word &keyword, enum keyType::option matchOpt) const
Search using dot or slash scoping.
A keyword and a list of tokens is an 'entry'.
Definition entry.H:66
virtual bool isDict() const noexcept
True if this entry is a dictionary.
Definition entry.H:284
virtual const dictionary * dictPtr() const noexcept
Return pointer to dictionary, if entry is a dictionary, otherwise return nullptr.
Definition entry.H:290
A class for handling file names.
Definition fileName.H:75
bool clean()
Cleanup filename (inplace).
Definition fileName.C:385
static bool clean(std::string &str)
Cleanup filename string, possibly applies other transformations such as changing the path separator e...
Definition fileName.C:192
static fileName concat(const std::string &s1, const std::string &s2, const char delim='/')
Join two strings with a path separator ('/' by default).
Definition fileName.C:211
A class for handling keywords in dictionaries.
Definition keyType.H:69
option
Enumeration for the data type and search/match modes (bitmask).
Definition keyType.H:80
@ REGEX
Regular expression.
Definition keyType.H:83
@ RECURSIVE
Recursive search (eg, in dictionary).
Definition keyType.H:86
bool isPattern() const noexcept
The keyType is treated as a pattern, not as literal string.
Definition keyTypeI.H:97
bool contains(char c) const noexcept
True if string contains given character (cf. C++23).
Definition string.H:412
A class for handling words, derived from Foam::string.
Definition word.H:66
static word validate(const std::string &s, const bool prefix=false)
Construct validated word (no invalid characters).
Definition word.C:39
fileName path(UMean.rootPath()/UMean.caseName()/"graphs"/UMean.instance())
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition error.H:629
fileName dictPath(runTime.system()/regionDir/faMesh::prefix()/areaRegionDir/dictName)
auto & name
#define IOWarningInFunction(ios)
Report an IO warning using Foam::Warning.
constexpr auto key(const Type &t) noexcept
Helper function to return the enum value.
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.
bool match(const UList< wordRe > &selectors, const std::string &text)
True if text matches one of the selector expressions.
Definition stringOps.H:79
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition Ostream.H:519
IOerror FatalIOError
Error stream (stdout output on all processes), with additional 'FOAM FATAL IO ERROR' header text and ...
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition errorManip.H:125
constexpr char nl
The newline '\n' character (0x0a).
Definition Ostream.H:50