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 module ocean.net.util.QueryParams; 23 24 25 import ocean.meta.types.Qualifiers; 26 27 import ocean.core.Verify; 28 29 import ocean.net.util.ParamSet; 30 31 import ocean.text.util.SplitIterator: ChrSplitIterator; 32 33 import ocean.util.container.AppendBuffer: AppendBuffer, IAppendBufferReader; 34 35 version (unittest) import ocean.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 class QueryParams 45 { 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 public char element_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 public bool trim_whitespace = true; 80 81 /************************************************************************** 82 83 Current query string to parse and iterate over 84 85 **************************************************************************/ 86 87 private cstring query; 88 89 /************************************************************************** 90 91 Debug flag to prevent calling any method during a 'foreach' iteration. 92 93 **************************************************************************/ 94 95 private bool iterating = 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 public this ( char element_delim, char keyval_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 public typeof (this) set ( cstring query ) 132 { 133 this.query = query; 134 135 return this; 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 public int opApply ( scope int delegate ( ref cstring key, ref cstring value ) ext_dg ) 151 { 152 this.iterating = true; 153 154 scope (exit) this.iterating = false; 155 156 scope trim_dg = (ref cstring key, ref cstring value) 157 { 158 auto tkey = ChrSplitIterator.trim(key), 159 tval = ChrSplitIterator.trim(value); 160 return ext_dg(tkey, tval); 161 }; 162 163 scope dg = this.trim_whitespace ? trim_dg : ext_dg; 164 165 scope split_paramlist = new ChrSplitIterator(this.element_delim), 166 split_param = new ChrSplitIterator(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 return split_paramlist.opApply((ref cstring param) 175 { 176 cstring value = null; 177 178 foreach (key; split_param.reset(param)) 179 { 180 value = split_param.remaining; 181 182 return dg(key, value); 183 } 184 185 verify(!split_param.n); 186 187 return dg(param, value); 188 }); 189 } 190 } 191 192 /******************************************************************************/ 193 194 class QueryParamSet: ParamSet 195 { 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 public char element_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 public this ( char element_delim, char keyval_delim, in istring[] 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 public void parse ( cstring query ) 249 { 250 this.reset(); 251 252 scope query_params = new QueryParams(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 unittest 262 { 263 scope qp = new QueryParams(';', '='); 264 265 test (qp.trim_whitespace); 266 267 { 268 uint i = 0; 269 270 foreach (key, val; qp.set(" Die Katze = tritt ;\n\tdie= Treppe;krumm.= ")) 271 { 272 switch (i++) 273 { 274 case 0: 275 test (key == "Die Katze"); 276 test (val == "tritt"); 277 break; 278 case 1: 279 test (key == "die"); 280 test (val == "Treppe"); 281 break; 282 case 2: 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 uint i = 0; 296 297 foreach (key, val; qp.set(" Die Katze = tritt ;\n\tdie= Treppe;krumm.= ")) 298 { 299 switch (i++) 300 { 301 case 0: 302 test (key == " Die Katze "); 303 test (val == " tritt "); 304 break; 305 case 1: 306 test (key == "\n\tdie"); 307 test (val == " Treppe"); 308 break; 309 case 2: 310 test (key == "krumm."); 311 test (val == " "); 312 break; 313 default: 314 test(0); 315 } 316 } 317 } 318 }