ALib C++ Library
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
wildcardmatcher.cpp
1//##################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2025 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6//##################################################################################################
7#include "alib_precompile.hpp"
8#if !defined(ALIB_C20_MODULES) || ((ALIB_C20_MODULES != 0) && (ALIB_C20_MODULES != 1))
9# error "Symbol ALIB_C20_MODULES has to be given to the compiler as either 0 or 1"
10#endif
11#if ALIB_C20_MODULES
12 module;
13#endif
14//========================================= Global Fragment ========================================
16//============================================== Module ============================================
17#if ALIB_C20_MODULES
18 module ALib.Strings.Search;
19#else
20# include "ALib.Strings.Search.H"
21#endif
22//========================================== Implementation ========================================
23namespace alib { namespace strings { namespace util {
24
25#define STRING -1
26#define ASTERISK -2
27
28template<typename TChar>
30 // Note: The following invariants will be true in the command vector:
31 // - A String-entry will be a non-empty string
32 // - No two * follow each other"
33 // - A ? will not follow an asterisk (combinations of ? and ' will be sorted to "?(n) *"
34
35 commands.clear();
36
37 TSubstring<TChar> parser= pattern;
38 while( parser.IsNotEmpty() ) {
39 // *
40 if(parser.CharAtStart() == '*' ) {
41 // add if empty or last is not * already.
42 if( commands.size() == 0
43 || commands.back().first != ASTERISK ) commands.emplace_back( ASTERISK, nullptr );
45 parser.ConsumeChar();
46 continue;
47 }
48
49 // ?
50 int qtyQ= 0;
51 while( parser.ConsumeChar('?') )
52 ++qtyQ;
53
54 if( qtyQ ) {
55 // previous is "*" ?
56 if( commands.size() > 1
57 && (commands.end()-1)->first == ASTERISK )
58 {
59
60 // before * we already have Q? -> add to the Q
61 if( commands.size() >= 2
62 && (commands.end()-2)->first > 0 )
63 {
64 (commands.end()-2)->first+= qtyQ;
65 continue;
66 }
67
68 commands.emplace( commands.end() - 1, qtyQ, nullptr );
69 continue;
70 }
71
72 // Just add it
73 commands.emplace_back( qtyQ, nullptr );
74 continue;
75 }
76
77 // strings
78 integer idx= 1;
79 while ( idx < parser.Length() && parser[idx] != '*' && parser[idx] != '?' )
80 ++idx;
81
82 commands.emplace_back( STRING, TString<TChar>(parser.Buffer(), idx ) );
83 parser.ConsumeChars( idx );
84 continue;
85 }
86
87}
88
89template<typename TChar>
90bool TWildcardMatcher<TChar>::Match( const TString<TChar>& pHaystack, lang::Case sensitivity ) {
91 if( commands.size() == 0 )
92 return true;
93
94 if( pHaystack.IsNull() )
95 return false;
96
97 TSubstring<TChar> haystack= pHaystack;
98 size_t actCmd = 0;
99 size_t lastCmd= size_t( commands.size() - 1 );
100 bool lastWasAsterisk= false;
101
102 for( actCmd= 0; actCmd <= lastCmd ; ++actCmd ) {
103 auto& cmd= commands[actCmd];
104
105 // ?
106 if( cmd.first > 0 ) {
107 if ( haystack.Length() < cmd.first )
108 return false;
109 haystack.template ConsumeChars<NC>( cmd.first );
110 continue;
111 }
112
113 // *
114 if( cmd.first == ASTERISK ) {
115 if ( actCmd == lastCmd )
116 return true;
117
118 lastWasAsterisk= true;
119 continue;
120 }
121
122 // string
123 if( cmd.second.Length() > haystack.Length() )
124 return false;
125
126 if( lastWasAsterisk ) {
127 integer idx= sensitivity==lang::Case::Sensitive ? haystack.template IndexOf<NC, lang::Case::Sensitive>( cmd.second, 0, haystack.Length() - cmd.second.Length() + 1 )
128 : haystack.template IndexOf<NC, lang::Case::Ignore >( cmd.second, 0, haystack.Length() - cmd.second.Length() + 1 );
129 if( idx < 0 )
130 return false;
131 haystack.ConsumeChars( idx + cmd.second.Length() );
132 lastWasAsterisk= false;
133 continue;
134 }
135
136 if( sensitivity==lang::Case::Sensitive ? (!haystack.template StartsWith<NC, lang::Case::Sensitive>( cmd.second ) )
137 : (!haystack.template StartsWith<NC, lang::Case::Ignore >( cmd.second ) ) )
138 return false;
139
140 haystack.template ConsumeChars<NC>( cmd.second.Length() );
141 }
142
143 return haystack.IsEmpty() || lastWasAsterisk;
144}
145
146template void TWildcardMatcher<nchar>::Compile( const TString<nchar>& pattern );
147template bool TWildcardMatcher<nchar>::Match ( const TString<nchar>& haystack, lang::Case sensitivity );
148template void TWildcardMatcher<wchar>::Compile( const TString<wchar>& pattern );
149template bool TWildcardMatcher<wchar>::Match ( const TString<wchar>& haystack, lang::Case sensitivity );
150
151#undef STRING
152#undef ASTERISK
153}}} // namespace [alib::strings::util]
constexpr integer Length() const
Definition string.inl:316
constexpr bool IsEmpty() const
Definition string.inl:365
TChar CharAtStart() const
Definition string.inl:433
constexpr bool IsNotEmpty() const
Definition string.inl:369
constexpr const TChar * Buffer() const
Definition string.inl:311
constexpr bool IsNull() const
Definition string.inl:350
integer ConsumeChars(integer regionLength, TSubstring *target=nullptr)
ALIB_DLL bool Match(const TString< TChar > &haystack, lang::Case sensitivity=lang::Case::Sensitive)
ALIB_DLL void Compile(const TString< TChar > &pattern)
std::vector< std::pair< int, TString< TChar > > > commands
The result list of commands created with Compile and executed with Match.
Case
Denotes upper and lower case character treatment.
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149