2 // QueryStringParser.cs
4 // Copyright (C) 2004-2005 Novell, Inc.
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
28 using System
.Collections
;
32 namespace Beagle
.Daemon
{
34 public class QueryStringParser
{
36 private QueryStringParser () { }
// a static class
38 // Returns an ICollection of QueryPart objects.
39 static public ICollection
Parse (string query_string
)
42 token_list
= new ArrayList ();
45 Token token
= ExtractToken (ref query_string
);
48 token_list
.Add (token
);
51 return TokensToQueryParts (token_list
);
54 /////////////////////////////////////////////////////////
56 private enum TokenType
{
66 public TokenType Type
= TokenType
.Unknown
;
71 // Our tiny query language:
72 // prefix terms by + or - to require or prohibit them
75 static private Token
ExtractToken (ref string in_string
)
77 if (in_string
== null || in_string
.Length
== 0)
80 // Find the first non-whitespace character.
83 bool first_is_singleton
= false;
84 for (int i
= 0; i
< in_string
.Length
; ++i
) {
85 first
= in_string
[i
];
86 if (! Char
.IsWhiteSpace (first
)) {
88 if (i
== in_string
.Length
- 1)
89 first_is_singleton
= true;
94 // This is only whitespace, and thus doesn't
95 // contain any tokens.
100 token
= new Token ();
102 // Based on the first character, decide what kind of a
103 // token this is. Advance first_pos as necessary to
104 // skip special characters (like + and -)
108 if (first_is_singleton
) {
109 token
.Type
= TokenType
.Meta
;
110 token
.Text
= "DanglingPlus";
115 token
.Type
= TokenType
.Plus
;
120 if (first_is_singleton
) {
121 token
.Type
= TokenType
.Meta
;
122 token
.Text
= "DanglingMinus";
127 token
.Type
= TokenType
.Minus
;
132 if (first_is_singleton
) {
133 token
.Type
= TokenType
.Meta
;
134 token
.Text
= "DanglingQuote";
139 token
.Type
= TokenType
.StandAlone
;
140 token
.IsQuoted
= true;
145 token
.Type
= TokenType
.StandAlone
;
150 last
= token
.IsQuoted
? '"' : ' ';
153 last_pos
= in_string
.IndexOf (last
, first_pos
);
155 if (last_pos
== -1) {
156 // We don't worry about missing close-quotes.
157 // FIXME: Maybe we should, or at least return a meta-token
158 token
.Text
= in_string
.Substring (first_pos
);
161 token
.Text
= in_string
.Substring (first_pos
, last_pos
- first_pos
);
162 if (last_pos
== in_string
.Length
-1)
165 in_string
= in_string
.Substring (last_pos
+1);
168 // Trap the OR operator
169 if (token
.Type
== TokenType
.StandAlone
&& token
.Text
== "OR") {
170 token
.Type
= TokenType
.Operator
;
174 // Ah, the dreaded "internal error".
175 if (token
.Type
== TokenType
.Unknown
)
176 throw new Exception ("Internal QueryStringParser.ExtractToken Error");
181 static private ICollection
TokensToQueryParts (ArrayList token_list
)
184 parts
= new ArrayList ();
187 ArrayList or_list
= null;
189 while (i
< token_list
.Count
) {
191 token
= token_list
[i
] as Token
;
193 if (token
.Type
== TokenType
.Meta
) {
198 // Skip any extra operators
199 if (token
.Type
== TokenType
.Operator
) {
204 // Assemble a part for this token.
206 QueryPart_Text text_part
;
207 text_part
= new QueryPart_Text ();
208 text_part
.Text
= token
.Text
;
210 if (token
.Type
== TokenType
.Minus
)
211 text_part
.Logic
= QueryPartLogic
.Prohibited
;
213 text_part
.Logic
= QueryPartLogic
.Required
;
215 if (or_list
!= null) {
216 or_list
.Add (text_part
);
220 Token next_token
= null;
221 if (i
< token_list
.Count
- 1)
222 next_token
= token_list
[i
+1] as Token
;
225 // If the next token is an or, start an or_list
226 // (if we don't have one already) and skip
227 // ahead to the next part.
228 if (next_token
!= null
229 && next_token
.Type
== TokenType
.Operator
230 && next_token
.Text
== "Or") {
231 if (or_list
== null) {
232 or_list
= new ArrayList ();
233 or_list
.Add (text_part
);
239 // If we have a non-empty or-list going,
240 // Create the appropriate QueryPart and add it
242 if (or_list
!= null) {
243 QueryPart_Or or_part
;
244 or_part
= new QueryPart_Or ();
245 or_part
.Logic
= QueryPartLogic
.Required
;
246 foreach (QueryPart sub_part
in or_list
)
247 or_part
.Add (sub_part
);
252 // Add the next text part
253 if (text_part
!= null)
254 parts
.Add (text_part
);
259 // If we ended with an or_parts list, do the right thing.
260 if (or_list
!= null) {
261 QueryPart_Or or_part
;
262 or_part
= new QueryPart_Or ();
263 or_part
.Logic
= QueryPartLogic
.Required
;
264 foreach (QueryPart sub_part
in or_list
)
265 or_part
.Add (sub_part
);