1 /******************************************************************************
2 3 URI query parameter parser
4 5 - QueryParams splits an URI query parameter list into key/value pairs.
6 - QueryParamSet parses an URI query parameter list and memorizes the values
7 corresponding to keys in a list provided at instantiation.
8 9 TODO: The QueryParams class may be moved to ocean.text.
10 11 Copyright:
12 Copyright (c) 2009-2016 dunnhumby Germany GmbH.
13 All rights reserved.
14 15 License:
16 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
17 Alternatively, this file may be distributed under the terms of the Tango
18 3-Clause BSD License (see LICENSE_BSD.txt for details).
19 20 ******************************************************************************/21 22 moduleocean.net.util.QueryParams;
23 24 25 importocean.meta.types.Qualifiers;
26 27 importocean.core.Verify;
28 29 importocean.net.util.ParamSet;
30 31 importocean.text.util.SplitIterator: ChrSplitIterator;
32 33 importocean.util.container.AppendBuffer: AppendBuffer, IAppendBufferReader;
34 35 version (unittest) importocean.core.Test;
36 37 /******************************************************************************
38 39 The QueryParams class is memory-friendly and therefore suitable for stack
40 allocated 'scope' instances.
41 42 ******************************************************************************/43 44 classQueryParams45 {
46 /**************************************************************************
47 48 Delimiter of elements, where each element is a key/value pair, and
49 between key and value of an element.
50 51 Treatment of special cases:
52 53 - If an element does not contain a keyval_delim character, it is treated
54 as a key without a value; a null value is then reported.
55 - If an element contains more than one keyval_delim character, the first
56 occurrence is used as delimiter so that the value contains
57 keyval_delim characters but not the key.
58 - If the last character of an element is a keyval_delim character and
59 this is the only occurrence, the value is a non-null empty string.
60 61 Must be specified in the constructor but may be modified at any time.
62 63 Note that changing the delimiters during an iteration becomes effective
64 when the next iteration is started.
65 66 **************************************************************************/67 68 publiccharelement_delim, keyval_delim;
69 70 /**************************************************************************
71 72 Option to trim whitespace from keys and values, enabled by default.
73 74 Note that changing this option during an iteration becomes effective
75 when the next iteration is started.
76 77 **************************************************************************/78 79 publicbooltrim_whitespace = true;
80 81 /**************************************************************************
82 83 Current query string to parse and iterate over
84 85 **************************************************************************/86 87 privatecstringquery;
88 89 /**************************************************************************
90 91 Debug flag to prevent calling any method during a 'foreach' iteration.
92 93 **************************************************************************/94 95 privatebooliterating = false;
96 97 invariant ( )
98 {
99 assert (!this.iterating, typeof (this).stringof ~
100 " method called during 'foreach' iteration");
101 }
102 103 /**************************************************************************
104 105 Constructor
106 107 Params:
108 element_delim = delimiter between elements
109 keyval_delim = delimiter between key and value of an element
110 111 **************************************************************************/112 113 publicthis ( charelement_delim, charkeyval_delim )
114 {
115 this.element_delim = element_delim;
116 this.keyval_delim = keyval_delim;
117 }
118 119 /**************************************************************************
120 121 Sets the URI query string to parse
122 123 Params:
124 query = query string to parse
125 126 Returns:
127 this instance
128 129 **************************************************************************/130 131 publictypeof (this) set ( cstringquery )
132 {
133 this.query = query;
134 135 returnthis;
136 }
137 138 /**************************************************************************
139 140 'foreach' iteration over the URI query parameter list items, each one
141 split into a key/value pair. key and value slice the string passed to
142 query() so DO NOT MODIFY THEM. (You may, however, modify their content;
143 this will modify the string passed to query() in-place.)
144 145 Note that during iteration, no public method may be called nor may a
146 nested iteration be started.
147 148 **************************************************************************/149 150 publicintopApply ( scopeintdelegate ( refcstringkey, refcstringvalue ) ext_dg )
151 {
152 this.iterating = true;
153 154 scope (exit) this.iterating = false;
155 156 scopetrim_dg = (refcstringkey, refcstringvalue)
157 {
158 autotkey = ChrSplitIterator.trim(key),
159 tval = ChrSplitIterator.trim(value);
160 returnext_dg(tkey, tval);
161 };
162 163 scopedg = this.trim_whitespace ? trim_dg : ext_dg;
164 165 scopesplit_paramlist = newChrSplitIterator(this.element_delim),
166 split_param = newChrSplitIterator(this.keyval_delim);
167 168 split_paramlist.collapse = true;
169 170 split_param.include_remaining = false;
171 172 split_paramlist.reset(this.query);
173 174 returnsplit_paramlist.opApply((refcstringparam)
175 {
176 cstringvalue = null;
177 178 foreach (key; split_param.reset(param))
179 {
180 value = split_param.remaining;
181 182 returndg(key, value);
183 }
184 185 verify(!split_param.n);
186 187 returndg(param, value);
188 });
189 }
190 }
191 192 /******************************************************************************/193 194 classQueryParamSet: ParamSet195 {
196 /**************************************************************************
197 198 Delimiter of elements, where each element is a key/value pair, and
199 between key and value of an element.
200 201 Treatment of special cases:
202 203 - If an element does not contain a keyval_delim character, it is treated
204 as a key without a value; a null value is then reported.
205 - If an element contains more than one keyval_delim character, the first
206 occurrence is used as delimiter so that the value contains
207 keyval_delim characters but not the key.
208 - If the last character of an element is a keyval_delim character and
209 this is the only occurrence, the value is a non-null empty string.
210 211 Must be specified in the constructor but may be modified at any time.
212 213 **************************************************************************/214 215 publiccharelement_delim, keyval_delim;
216 217 /**************************************************************************
218 219 Constructor
220 221 Params:
222 element_delim = delimiter between elements
223 keyval_delim = delimiter between key and value of an element
224 keys = parameter keys of interest (case-insensitive)
225 226 **************************************************************************/227 228 publicthis ( charelement_delim, charkeyval_delim, inistring[] keys ... )
229 {
230 this.addKeys(keys);
231 232 this.rehash();
233 234 this.element_delim = element_delim;
235 this.keyval_delim = keyval_delim;
236 }
237 238 /**************************************************************************
239 240 Parses query and memorizes the values corresponding to the keys provided
241 to the constructor. query will be sliced.
242 243 Params:
244 query = query string to parse
245 246 **************************************************************************/247 248 publicvoidparse ( cstringquery )
249 {
250 this.reset();
251 252 scopequery_params = newQueryParams(this.element_delim, this.keyval_delim);
253 254 foreach (key, val; query_params.set(query))
255 {
256 this.set(key, val);
257 }
258 }
259 }
260 261 unittest262 {
263 scopeqp = newQueryParams(';', '=');
264 265 test (qp.trim_whitespace);
266 267 {
268 uinti = 0;
269 270 foreach (key, val; qp.set(" Die Katze = tritt ;\n\tdie= Treppe;krumm.= "))
271 {
272 switch (i++)
273 {
274 case0:
275 test (key == "Die Katze");
276 test (val == "tritt");
277 break;
278 case1:
279 test (key == "die");
280 test (val == "Treppe");
281 break;
282 case2:
283 test (key == "krumm.");
284 test (!val.length);
285 break;
286 default:
287 test(0);
288 }
289 }
290 }
291 292 {
293 qp.trim_whitespace = false;
294 295 uinti = 0;
296 297 foreach (key, val; qp.set(" Die Katze = tritt ;\n\tdie= Treppe;krumm.= "))
298 {
299 switch (i++)
300 {
301 case0:
302 test (key == " Die Katze ");
303 test (val == " tritt ");
304 break;
305 case1:
306 test (key == "\n\tdie");
307 test (val == " Treppe");
308 break;
309 case2:
310 test (key == "krumm.");
311 test (val == " ");
312 break;
313 default:
314 test(0);
315 }
316 }
317 }
318 }