Differences Between: [Versions 311 and 402] [Versions 311 and 403]
1 <?php 2 /* 3 * Copyright 2013 Google Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /** 19 * Implementation of levels 1-3 of the URI Template spec. 20 * @see http://tools.ietf.org/html/rfc6570 21 */ 22 class Google_Utils_URITemplate 23 { 24 const TYPE_MAP = "1"; 25 const TYPE_LIST = "2"; 26 const TYPE_SCALAR = "4"; 27 28 /** 29 * @var $operators array 30 * These are valid at the start of a template block to 31 * modify the way in which the variables inside are 32 * processed. 33 */ 34 private $operators = array( 35 "+" => "reserved", 36 "/" => "segments", 37 "." => "dotprefix", 38 "#" => "fragment", 39 ";" => "semicolon", 40 "?" => "form", 41 "&" => "continuation" 42 ); 43 44 /** 45 * @var reserved array 46 * These are the characters which should not be URL encoded in reserved 47 * strings. 48 */ 49 private $reserved = array( 50 "=", ",", "!", "@", "|", ":", "/", "?", "#", 51 "[", "]",'$', "&", "'", "(", ")", "*", "+", ";" 52 ); 53 private $reservedEncoded = array( 54 "%3D", "%2C", "%21", "%40", "%7C", "%3A", "%2F", "%3F", 55 "%23", "%5B", "%5D", "%24", "%26", "%27", "%28", "%29", 56 "%2A", "%2B", "%3B" 57 ); 58 59 public function parse($string, array $parameters) 60 { 61 return $this->resolveNextSection($string, $parameters); 62 } 63 64 /** 65 * This function finds the first matching {...} block and 66 * executes the replacement. It then calls itself to find 67 * subsequent blocks, if any. 68 */ 69 private function resolveNextSection($string, $parameters) 70 { 71 $start = strpos($string, "{"); 72 if ($start === false) { 73 return $string; 74 } 75 $end = strpos($string, "}"); 76 if ($end === false) { 77 return $string; 78 } 79 $string = $this->replace($string, $start, $end, $parameters); 80 return $this->resolveNextSection($string, $parameters); 81 } 82 83 private function replace($string, $start, $end, $parameters) 84 { 85 // We know a data block will have {} round it, so we can strip that. 86 $data = substr($string, $start + 1, $end - $start - 1); 87 88 // If the first character is one of the reserved operators, it effects 89 // the processing of the stream. 90 if (isset($this->operators[$data[0]])) { 91 $op = $this->operators[$data[0]]; 92 $data = substr($data, 1); 93 $prefix = ""; 94 $prefix_on_missing = false; 95 96 switch ($op) { 97 case "reserved": 98 // Reserved means certain characters should not be URL encoded 99 $data = $this->replaceVars($data, $parameters, ",", null, true); 100 break; 101 case "fragment": 102 // Comma separated with fragment prefix. Bare values only. 103 $prefix = "#"; 104 $prefix_on_missing = true; 105 $data = $this->replaceVars($data, $parameters, ",", null, true); 106 break; 107 case "segments": 108 // Slash separated data. Bare values only. 109 $prefix = "/"; 110 $data =$this->replaceVars($data, $parameters, "/"); 111 break; 112 case "dotprefix": 113 // Dot separated data. Bare values only. 114 $prefix = "."; 115 $prefix_on_missing = true; 116 $data = $this->replaceVars($data, $parameters, "."); 117 break; 118 case "semicolon": 119 // Semicolon prefixed and separated. Uses the key name 120 $prefix = ";"; 121 $data = $this->replaceVars($data, $parameters, ";", "=", false, true, false); 122 break; 123 case "form": 124 // Standard URL format. Uses the key name 125 $prefix = "?"; 126 $data = $this->replaceVars($data, $parameters, "&", "="); 127 break; 128 case "continuation": 129 // Standard URL, but with leading ampersand. Uses key name. 130 $prefix = "&"; 131 $data = $this->replaceVars($data, $parameters, "&", "="); 132 break; 133 } 134 135 // Add the initial prefix character if data is valid. 136 if ($data || ($data !== false && $prefix_on_missing)) { 137 $data = $prefix . $data; 138 } 139 140 } else { 141 // If no operator we replace with the defaults. 142 $data = $this->replaceVars($data, $parameters); 143 } 144 // This is chops out the {...} and replaces with the new section. 145 return substr($string, 0, $start) . $data . substr($string, $end + 1); 146 } 147 148 private function replaceVars( 149 $section, 150 $parameters, 151 $sep = ",", 152 $combine = null, 153 $reserved = false, 154 $tag_empty = false, 155 $combine_on_empty = true 156 ) { 157 if (strpos($section, ",") === false) { 158 // If we only have a single value, we can immediately process. 159 return $this->combine( 160 $section, 161 $parameters, 162 $sep, 163 $combine, 164 $reserved, 165 $tag_empty, 166 $combine_on_empty 167 ); 168 } else { 169 // If we have multiple values, we need to split and loop over them. 170 // Each is treated individually, then glued together with the 171 // separator character. 172 $vars = explode(",", $section); 173 return $this->combineList( 174 $vars, 175 $sep, 176 $parameters, 177 $combine, 178 $reserved, 179 false, // Never emit empty strings in multi-param replacements 180 $combine_on_empty 181 ); 182 } 183 } 184 185 public function combine( 186 $key, 187 $parameters, 188 $sep, 189 $combine, 190 $reserved, 191 $tag_empty, 192 $combine_on_empty 193 ) { 194 $length = false; 195 $explode = false; 196 $skip_final_combine = false; 197 $value = false; 198 199 // Check for length restriction. 200 if (strpos($key, ":") !== false) { 201 list($key, $length) = explode(":", $key); 202 } 203 204 // Check for explode parameter. 205 if ($key[strlen($key) - 1] == "*") { 206 $explode = true; 207 $key = substr($key, 0, -1); 208 $skip_final_combine = true; 209 } 210 211 // Define the list separator. 212 $list_sep = $explode ? $sep : ","; 213 214 if (isset($parameters[$key])) { 215 $data_type = $this->getDataType($parameters[$key]); 216 switch ($data_type) { 217 case self::TYPE_SCALAR: 218 $value = $this->getValue($parameters[$key], $length); 219 break; 220 case self::TYPE_LIST: 221 $values = array(); 222 foreach ($parameters[$key] as $pkey => $pvalue) { 223 $pvalue = $this->getValue($pvalue, $length); 224 if ($combine && $explode) { 225 $values[$pkey] = $key . $combine . $pvalue; 226 } else { 227 $values[$pkey] = $pvalue; 228 } 229 } 230 $value = implode($list_sep, $values); 231 if ($value == '') { 232 return ''; 233 } 234 break; 235 case self::TYPE_MAP: 236 $values = array(); 237 foreach ($parameters[$key] as $pkey => $pvalue) { 238 $pvalue = $this->getValue($pvalue, $length); 239 if ($explode) { 240 $pkey = $this->getValue($pkey, $length); 241 $values[] = $pkey . "=" . $pvalue; // Explode triggers = combine. 242 } else { 243 $values[] = $pkey; 244 $values[] = $pvalue; 245 } 246 } 247 $value = implode($list_sep, $values); 248 if ($value == '') { 249 return false; 250 } 251 break; 252 } 253 } else if ($tag_empty) { 254 // If we are just indicating empty values with their key name, return that. 255 return $key; 256 } else { 257 // Otherwise we can skip this variable due to not being defined. 258 return false; 259 } 260 261 if ($reserved) { 262 $value = str_replace($this->reservedEncoded, $this->reserved, $value); 263 } 264 265 // If we do not need to include the key name, we just return the raw 266 // value. 267 if (!$combine || $skip_final_combine) { 268 return $value; 269 } 270 271 // Else we combine the key name: foo=bar, if value is not the empty string. 272 return $key . ($value != '' || $combine_on_empty ? $combine . $value : ''); 273 } 274 275 /** 276 * Return the type of a passed in value 277 */ 278 private function getDataType($data) 279 { 280 if (is_array($data)) { 281 reset($data); 282 if (key($data) !== 0) { 283 return self::TYPE_MAP; 284 } 285 return self::TYPE_LIST; 286 } 287 return self::TYPE_SCALAR; 288 } 289 290 /** 291 * Utility function that merges multiple combine calls 292 * for multi-key templates. 293 */ 294 private function combineList( 295 $vars, 296 $sep, 297 $parameters, 298 $combine, 299 $reserved, 300 $tag_empty, 301 $combine_on_empty 302 ) { 303 $ret = array(); 304 foreach ($vars as $var) { 305 $response = $this->combine( 306 $var, 307 $parameters, 308 $sep, 309 $combine, 310 $reserved, 311 $tag_empty, 312 $combine_on_empty 313 ); 314 if ($response === false) { 315 continue; 316 } 317 $ret[] = $response; 318 } 319 return implode($sep, $ret); 320 } 321 322 /** 323 * Utility function to encode and trim values 324 */ 325 private function getValue($value, $length) 326 { 327 if ($length) { 328 $value = substr($value, 0, $length); 329 } 330 $value = rawurlencode($value); 331 return $value; 332 } 333 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body