ALib C++ Library
Library Version: 2510 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
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{
31 // Note: The following invariants will be true in the command vector:
32 // - A String-entry will be a non-empty string
33 // - No two * follow each other"
34 // - A ? will not follow an asterisk (combinations of ? and ' will be sorted to "?(n) *"
35
36 commands.clear();
37
38 TSubstring<TChar> parser= pattern;
39 while( parser.IsNotEmpty() )
40 {
41 // *
42 if(parser.CharAtStart() == '*' )
43 {
44 // add if empty or last is not * already.
45 if( commands.size() == 0
46 || commands.back().first != ASTERISK ) commands.emplace_back( ASTERISK, nullptr );
47
48 parser.ConsumeChar();
49 continue;
50 }
52 // ?
53 int qtyQ= 0;
54 while( parser.ConsumeChar('?') )
55 ++qtyQ;
56
57 if( qtyQ )
58 {
59 // previous is "*" ?
60 if( commands.size() > 1
61 && (commands.end()-1)->first == ASTERISK )
62 {
64 // before * we already have Q? -> add to the Q
65 if( commands.size() >= 2
66 && (commands.end()-2)->first > 0 )
67 {
68 (commands.end()-2)->first+= qtyQ;
69 continue;
70 }
71
72 commands.emplace( commands.end() - 1, qtyQ, nullptr );
73 continue;
74 }
75
76 // Just add it
77 commands.emplace_back( qtyQ, nullptr );
78 continue;
79 }
80
81 // strings
82 integer idx= 1;
83 while ( idx < parser.Length() && parser[idx] != '*' && parser[idx] != '?' )
84 ++idx;
85
86 commands.emplace_back( STRING, TString<TChar>(parser.Buffer(), idx ) );
87 parser.ConsumeChars( idx );
88 continue;
89 }
90
91}
92
93template<typename TChar>
94bool TWildcardMatcher<TChar>::Match( const TString<TChar>& pHaystack, lang::Case sensitivity )
95{
96 if( commands.size() == 0 )
97 return true;
98
99 if( pHaystack.IsNull() )
100 return false;
101
102 TSubstring<TChar> haystack= pHaystack;
103 size_t actCmd = 0;
104 size_t lastCmd= size_t( commands.size() - 1 );
105 bool lastWasAsterisk= false;
106
107 for( actCmd= 0; actCmd <= lastCmd ; ++actCmd )
108 {
109 auto& cmd= commands[actCmd];
110
111 // ?
112 if( cmd.first > 0 )
113 {
114 if ( haystack.Length() < cmd.first )
115 return false;
116 haystack.template ConsumeChars<NC>( cmd.first );
117 continue;
118 }
119
120 // *
121 if( cmd.first == ASTERISK )
122 {
123 if ( actCmd == lastCmd )
124 return true;
125
126 lastWasAsterisk= true;
127 continue;
128 }
129
130 // string
131 if( cmd.second.Length() > haystack.Length() )
132 return false;
133
134 if( lastWasAsterisk )
135 {
136 integer idx= sensitivity==lang::Case::Sensitive ? haystack.template IndexOf<NC, lang::Case::Sensitive>( cmd.second, 0, haystack.Length() - cmd.second.Length() + 1 )
137 : haystack.template IndexOf<NC, lang::Case::Ignore >( cmd.second, 0, haystack.Length() - cmd.second.Length() + 1 );
138 if( idx < 0 )
139 return false;
140 haystack.ConsumeChars( idx + cmd.second.Length() );
141 lastWasAsterisk= false;
142 continue;
143 }
144
145 if( sensitivity==lang::Case::Sensitive ? (!haystack.template StartsWith<NC, lang::Case::Sensitive>( cmd.second ) )
146 : (!haystack.template StartsWith<NC, lang::Case::Ignore >( cmd.second ) ) )
147 return false;
148
149 haystack.template ConsumeChars<NC>( cmd.second.Length() );
150 }
151
152 return haystack.IsEmpty() || lastWasAsterisk;
153}
154
155template void TWildcardMatcher<nchar>::Compile( const TString<nchar>& pattern );
156template bool TWildcardMatcher<nchar>::Match ( const TString<nchar>& haystack, lang::Case sensitivity );
157template void TWildcardMatcher<wchar>::Compile( const TString<wchar>& pattern );
158template bool TWildcardMatcher<wchar>::Match ( const TString<wchar>& haystack, lang::Case sensitivity );
159
160#undef STRING
161#undef ASTERISK
162}}} // namespace [alib::strings::util]
163
constexpr integer Length() const
Definition string.inl:318
constexpr bool IsEmpty() const
Definition string.inl:367
TChar CharAtStart() const
Definition string.inl:440
constexpr bool IsNotEmpty() const
Definition string.inl:371
constexpr const TChar * Buffer() const
Definition string.inl:313
constexpr bool IsNull() const
Definition string.inl:352
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