2121 */
2222class Google_Utils_URITemplate
2323{
24+ const TYPE_MAP = "1 " ;
25+ const TYPE_LIST = "2 " ;
26+ const TYPE_SCALAR = "4 " ;
27+
2428 /**
2529 * @var $operators array
2630 * These are valid at the start of a template block to
@@ -86,36 +90,53 @@ private function replace($string, $start, $end, $parameters)
8690 if (isset ($ this ->operators [$ data [0 ]])) {
8791 $ op = $ this ->operators [$ data [0 ]];
8892 $ data = substr ($ data , 1 );
93+ $ prefix = "" ;
94+ $ prefix_on_missing = false ;
95+
8996 switch ($ op ) {
9097 case "reserved " :
9198 // Reserved means certain characters should not be URL encoded
9299 $ data = $ this ->replaceVars ($ data , $ parameters , ", " , null , true );
93100 break ;
94101 case "fragment " :
95102 // Comma separated with fragment prefix. Bare values only.
96- $ data = "# " . $ this ->replaceVars ($ data , $ parameters , ", " , null , true );
103+ $ prefix = "# " ;
104+ $ prefix_on_missing = true ;
105+ $ data = $ this ->replaceVars ($ data , $ parameters , ", " , null , true );
97106 break ;
98107 case "segments " :
99108 // Slash separated data. Bare values only.
100- $ data = "/ " . $ this ->replaceVars ($ data , $ parameters , "/ " );
109+ $ prefix = "/ " ;
110+ $ data =$ this ->replaceVars ($ data , $ parameters , "/ " );
101111 break ;
102112 case "dotprefix " :
103113 // Dot separated data. Bare values only.
104- $ data = ". " . $ this ->replaceVars ($ data , $ parameters , ". " );
114+ $ prefix = ". " ;
115+ $ prefix_on_missing = true ;
116+ $ data = $ this ->replaceVars ($ data , $ parameters , ". " );
105117 break ;
106118 case "semicolon " :
107119 // Semicolon prefixed and separated. Uses the key name
108- $ data = "; " . $ this ->replaceVars ($ data , $ parameters , "; " , "= " , false , true );
120+ $ prefix = "; " ;
121+ $ data = $ this ->replaceVars ($ data , $ parameters , "; " , "= " , false , true , false );
109122 break ;
110123 case "form " :
111124 // Standard URL format. Uses the key name
112- $ data = "? " . $ this ->replaceVars ($ data , $ parameters , "& " , "= " );
125+ $ prefix = "? " ;
126+ $ data = $ this ->replaceVars ($ data , $ parameters , "& " , "= " );
113127 break ;
114128 case "continuation " :
115129 // Standard URL, but with leading ampersand. Uses key name.
116- $ data = "& " . $ this ->replaceVars ($ data , $ parameters , "& " , "= " );
130+ $ prefix = "& " ;
131+ $ data = $ this ->replaceVars ($ data , $ parameters , "& " , "= " );
117132 break ;
118133 }
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+
119140 } else {
120141 // If no operator we replace with the defaults.
121142 $ data = $ this ->replaceVars ($ data , $ parameters );
@@ -130,95 +151,177 @@ private function replaceVars(
130151 $ sep = ", " ,
131152 $ combine = null ,
132153 $ reserved = false ,
133- $ tag_empty = false
154+ $ tag_empty = false ,
155+ $ combine_on_empty = true
134156 ) {
135157 if (strpos ($ section , ", " ) === false ) {
136158 // If we only have a single value, we can immediately process.
137- return $ this ->combine ($ section , $ parameters , $ sep , $ combine , $ reserved , $ tag_empty );
159+ return $ this ->combine (
160+ $ section ,
161+ $ parameters ,
162+ $ sep ,
163+ $ combine ,
164+ $ reserved ,
165+ $ tag_empty ,
166+ $ combine_on_empty
167+ );
138168 } else {
139169 // If we have multiple values, we need to split and loop over them.
140170 // Each is treated individually, then glued together with the
141171 // separator character.
142172 $ vars = explode (", " , $ section );
143- return $ this ->combineList ($ vars , $ sep , $ parameters , $ combine , $ reserved , $ tag_empty );
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+ );
144182 }
145183 }
146184
147- public function combine ($ key , $ parameters , $ sep , $ combine , $ reserved , $ tag_empty )
148- {
185+ public function combine (
186+ $ key ,
187+ $ parameters ,
188+ $ sep ,
189+ $ combine ,
190+ $ reserved ,
191+ $ tag_empty ,
192+ $ combine_on_empty
193+ ) {
149194 $ length = false ;
150195 $ explode = false ;
151196 $ skip_final_combine = false ;
197+ $ value = false ;
152198
199+ // Check for length restriction.
153200 if (strpos ($ key , ": " ) !== false ) {
154201 list ($ key , $ length ) = explode (": " , $ key );
155202 }
156203
204+ // Check for explode parameter.
157205 if ($ key [strlen ($ key ) - 1 ] == "* " ) {
158206 $ explode = true ;
159207 $ key = substr ($ key , 0 , -1 );
208+ $ skip_final_combine = true ;
160209 }
161210
162- if (!empty ($ parameters [$ key ])) {
163- if (is_array ($ parameters [$ key ])) {
164- $ values = array ();
165- $ use_keys = false ;
166- if (!is_numeric (key ($ parameters [$ key ]))) {
167- $ use_keys = true ;
168- }
169- foreach ($ parameters [$ key ] as $ pkey => $ pvalue ) {
170- $ pvalue = $ this ->getValue ($ pvalue , $ length );
171- if ($ use_keys ) {
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 );
172239 if ($ explode ) {
173- $ skip_final_combine = true ;
240+ $ pkey = $ this -> getValue ( $ pkey , $ length ) ;
174241 $ values [] = $ pkey . "= " . $ pvalue ; // Explode triggers = combine.
175242 } else {
176243 $ values [] = $ pkey ;
177244 $ values [] = $ pvalue ;
178245 }
179- } else {
180- if ($ combine && $ explode ) {
181- $ skip_final_combine = true ;
182- $ values [$ pkey ] = $ key . $ combine . $ pvalue ;
183- } else {
184- $ values [$ pkey ] = $ pvalue ;
185- }
186246 }
187- }
188- $ list_sep = $ explode ? $ sep : ", " ;
189- $ value = implode ($ list_sep , $ values );
190- } else {
191- // For an individual value, we need to URL encode the data.
192- $ value = $ this ->getValue ($ parameters [$ key ], $ length );
247+ $ value = implode ($ list_sep , $ values );
248+ if ($ value == '' ) {
249+ return false ;
250+ }
251+ break ;
193252 }
194253 } else if ($ tag_empty ) {
195254 // If we are just indicating empty values with their key name, return that.
196255 return $ key ;
197256 } else {
198- // Otherwise we can return empty string .
199- $ value = "" ;
257+ // Otherwise we can skip this variable due to not being defined .
258+ return false ;
200259 }
260+
201261 if ($ reserved ) {
202262 $ value = str_replace ($ this ->reservedEncoded , $ this ->reserved , $ value );
203263 }
264+
204265 // If we do not need to include the key name, we just return the raw
205266 // value.
206267 if (!$ combine || $ skip_final_combine ) {
207268 return $ value ;
208269 }
209- // Else we combine the key name: foo=bar
210- return $ key . $ combine . $ value ;
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 : '' );
211273 }
212274
213- private function combineList ($ vars , $ sep , $ parameters , $ combine , $ reserved , $ tag_empty )
275+ /**
276+ * Return the type of a passed in value
277+ */
278+ private function getDataType ($ data )
214279 {
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+ ) {
215303 $ ret = array ();
216304 foreach ($ vars as $ var ) {
217- $ ret [] = $ this ->combine ($ var , $ parameters , $ sep , $ combine , $ reserved , $ tag_empty );
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 ;
218318 }
219319 return implode ($ sep , $ ret );
220320 }
221321
322+ /**
323+ * Utility function to encode and trim values
324+ */
222325 private function getValue ($ value , $ length )
223326 {
224327 if ($ length ) {
0 commit comments