1 //
2 // System.IO.SearchPattern.cs: Filename glob support.
3 //
4 // Author:
5 //   Dan Lewis (dihlewis@yahoo.co.uk)
6 //
7 // (C) 2002
8 //
9 
10 //
11 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 //
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 //
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32 
33 using System;
34 
35 namespace System.Web.Util {
36 
37 	// FIXME: there's a complication with this algorithm under windows.
38 	// the pattern '*.*' matches all files (i think . matches the extension),
39 	// whereas under UNIX it should only match files containing the '.' character.
40 	class SearchPattern
41 	{
SearchPattern(string pattern)42 		public SearchPattern (string pattern) : this (pattern, false) { }
43 
SearchPattern(string pattern, bool ignore)44 		public SearchPattern (string pattern, bool ignore)
45 		{
46 			SetPattern (pattern, ignore);
47 		}
48 
SetPattern(string pattern, bool ignore)49 		public void SetPattern (string pattern, bool ignore)
50 		{
51 			this.ignore = ignore;
52 			Compile (pattern);
53 		}
54 
IsMatch(string text)55 		public bool IsMatch (string text)
56 		{
57 			return Match (ops, text, 0);
58 		}
59 
60 		// private
61 
62 		private Op ops;		// the compiled pattern
63 		private bool ignore;	// ignore case
64 
Compile(string pattern)65 		private void Compile (string pattern)
66 		{
67 			if (pattern == null)
68 				throw new ArgumentException ("Invalid search pattern.");
69 
70 			if (pattern == "*") {	// common case
71 				ops = new Op (OpCode.True);
72 				return;
73 			}
74 
75 			ops = null;
76 
77 			int ptr = 0;
78 			Op last_op = null;
79 			while (ptr < pattern.Length) {
80 				Op op;
81 
82 				switch (pattern [ptr]) {
83 				case '?':
84 					op = new Op (OpCode.AnyChar);
85 					++ ptr;
86 					break;
87 
88 				case '*':
89 					op = new Op (OpCode.AnyString);
90 					++ ptr;
91 					break;
92 
93 				default:
94 					op = new Op (OpCode.ExactString);
95 					int end = pattern.IndexOfAny (WildcardChars, ptr);
96 					if (end < 0)
97 						end = pattern.Length;
98 
99 					op.Argument = pattern.Substring (ptr, end - ptr);
100 					if (ignore)
101 						op.Argument = op.Argument.ToLower ();
102 
103 					ptr = end;
104 					break;
105 				}
106 
107 				if (last_op == null)
108 					ops = op;
109 				else
110 					last_op.Next = op;
111 
112 				last_op = op;
113 			}
114 
115 			if (last_op == null)
116 				ops = new Op (OpCode.End);
117 			else
118 				last_op.Next = new Op (OpCode.End);
119 		}
120 
Match(Op op, string text, int ptr)121 		private bool Match (Op op, string text, int ptr)
122 		{
123 			while (op != null) {
124 				switch (op.Code) {
125 				case OpCode.True:
126 					return true;
127 
128 				case OpCode.End:
129 					if (ptr == text.Length)
130 						return true;
131 
132 					return false;
133 
134 				case OpCode.ExactString:
135 					int length = op.Argument.Length;
136 					if (ptr + length > text.Length)
137 						return false;
138 
139 					string str = text.Substring (ptr, length);
140 					if (ignore)
141 						str = str.ToLower ();
142 
143 					if (str != op.Argument)
144 						return false;
145 
146 					ptr += length;
147 					break;
148 
149 				case OpCode.AnyChar:
150 					if (++ ptr > text.Length)
151 						return false;
152 					break;
153 
154 				case OpCode.AnyString:
155 					while (ptr <= text.Length) {
156 						if (Match (op.Next, text, ptr))
157 							return true;
158 
159 						++ ptr;
160 					}
161 
162 					return false;
163 				}
164 
165 				op = op.Next;
166 			}
167 
168 			return true;
169 		}
170 
171 		// private static
172 
173 		internal static readonly char [] WildcardChars = { '*', '?' };
174 
175 		private class Op {
Op(OpCode code)176 			public Op (OpCode code)
177 			{
178 				this.Code = code;
179 				this.Argument = null;
180 				this.Next = null;
181 			}
182 
183 			public OpCode Code;
184 			public string Argument;
185 			public Op Next;
186 		}
187 
188 		private enum OpCode {
189 			ExactString,		// literal
190 			AnyChar,		// ?
191 			AnyString,		// *
192 			End,			// end of pattern
193 			True			// always succeeds
194 		};
195 	}
196 }
197