addressable 2.2.8 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of addressable might be problematic. Click here for more details.

@@ -22,15 +22,57 @@ require "addressable/uri"
22
22
  module Addressable
23
23
  ##
24
24
  # This is an implementation of a URI template based on
25
- # <a href="http://tinyurl.com/uritemplatedraft03">URI Template draft 03</a>.
25
+ # RFC 6570 (http://tools.ietf.org/html/rfc6570).
26
26
  class Template
27
27
  # Constants used throughout the template code.
28
28
  anything =
29
29
  Addressable::URI::CharacterClasses::RESERVED +
30
30
  Addressable::URI::CharacterClasses::UNRESERVED
31
- OPERATOR_EXPANSION =
32
- /\{-([a-zA-Z]+)\|([#{anything}]+)\|([#{anything}]+)\}/
33
- VARIABLE_EXPANSION = /\{([#{anything}]+?)(?:=([#{anything}]+))?\}/
31
+
32
+
33
+ variable_char_class =
34
+ Addressable::URI::CharacterClasses::ALPHA +
35
+ Addressable::URI::CharacterClasses::DIGIT + '_'
36
+
37
+ var_char =
38
+ "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
39
+ RESERVED =
40
+ "(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
41
+ UNRESERVED =
42
+ "(?:[#{
43
+ Addressable::URI::CharacterClasses::UNRESERVED
44
+ }]|%[a-fA-F0-9][a-fA-F0-9])"
45
+ variable =
46
+ "(?:#{var_char}(?:\\.?#{var_char})*)"
47
+ varspec =
48
+ "(?:(#{variable})(\\*|:\\d+)?)"
49
+ VARNAME =
50
+ /^#{variable}$/
51
+ VARSPEC =
52
+ /^#{varspec}$/
53
+ VARIABLE_LIST =
54
+ /^#{varspec}(?:,#{varspec})*$/
55
+ operator =
56
+ "+#./;?&=,!@|"
57
+ EXPRESSION =
58
+ /\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/
59
+
60
+
61
+ LEADERS = {
62
+ '?' => '?',
63
+ '/' => '/',
64
+ '#' => '#',
65
+ '.' => '.',
66
+ ';' => ';',
67
+ '&' => '&'
68
+ }
69
+ JOINERS = {
70
+ '?' => '&',
71
+ '.' => '.',
72
+ ';' => ';',
73
+ '&' => '&',
74
+ '/' => '/'
75
+ }
34
76
 
35
77
  ##
36
78
  # Raised if an invalid template value is supplied.
@@ -148,7 +190,7 @@ module Addressable
148
190
  #
149
191
  # @param [#restore, #match] processor
150
192
  # A template processor object may optionally be supplied.
151
- #
193
+ #
152
194
  # The object should respond to either the <tt>restore</tt> or
153
195
  # <tt>match</tt> messages or both. The <tt>restore</tt> method should
154
196
  # take two parameters: `[String] name` and `[String] value`.
@@ -210,7 +252,7 @@ module Addressable
210
252
  #
211
253
  # @param [#restore, #match] processor
212
254
  # A template processor object may optionally be supplied.
213
- #
255
+ #
214
256
  # The object should respond to either the <tt>restore</tt> or
215
257
  # <tt>match</tt> messages or both. The <tt>restore</tt> method should
216
258
  # take two parameters: `[String] name` and `[String] value`.
@@ -253,7 +295,7 @@ module Addressable
253
295
  #
254
296
  # uri = Addressable::URI.parse("http://example.com/a/b/c/")
255
297
  # match = Addressable::Template.new(
256
- # "http://example.com/{first}/{second}/"
298
+ # "http://example.com/{first}/{+second}/"
257
299
  # ).match(uri, ExampleProcessor)
258
300
  # match.variables
259
301
  # #=> ["first", "second"]
@@ -262,7 +304,7 @@ module Addressable
262
304
  #
263
305
  # uri = Addressable::URI.parse("http://example.com/a/b/c/")
264
306
  # match = Addressable::Template.new(
265
- # "http://example.com/{first}/{-list|/|second}/"
307
+ # "http://example.com/{first}{/second*}/"
266
308
  # ).match(uri)
267
309
  # match.variables
268
310
  # #=> ["first", "second"]
@@ -279,38 +321,56 @@ module Addressable
279
321
 
280
322
  if uri.to_str == pattern
281
323
  return Addressable::Template::MatchData.new(uri, self, mapping)
282
- elsif expansions.size > 0 && expansions.size == unparsed_values.size
283
- expansions.each_with_index do |expansion, index|
284
- unparsed_value = unparsed_values[index]
285
- if expansion =~ OPERATOR_EXPANSION
286
- operator, argument, variables =
287
- parse_template_expansion(expansion)
288
- extract_method = "extract_#{operator}_operator"
289
- if ([extract_method, extract_method.to_sym] &
290
- private_methods).empty?
291
- raise InvalidTemplateOperatorError,
292
- "Invalid template operator: #{operator}"
293
- else
294
- begin
295
- send(
296
- extract_method.to_sym, unparsed_value, processor,
297
- argument, variables, mapping
298
- )
299
- rescue TemplateOperatorAbortedError
300
- return nil
324
+ elsif expansions.size > 0
325
+ index = 0
326
+ expansions.each do |expansion|
327
+ _, operator, varlist = *expansion.match(EXPRESSION)
328
+ varlist.split(',').each do |varspec|
329
+ _, name, modifier = *varspec.match(VARSPEC)
330
+ case operator
331
+ when nil, '+', '#', '/', '.'
332
+ unparsed_value = unparsed_values[index]
333
+ name = varspec[VARSPEC, 1]
334
+ value = unparsed_value
335
+ value = value.split(JOINERS[operator]) if value && modifier == '*'
336
+ when ';', '?', '&'
337
+ if modifier == '*'
338
+ value = unparsed_values[index].split(JOINERS[operator])
339
+ value = value.inject({}) do |acc, v|
340
+ key, val = v.split('=')
341
+ val = "" if val.nil?
342
+ acc[key] = val
343
+ acc
344
+ end
345
+ else
346
+ if (unparsed_values[index])
347
+ name, value = unparsed_values[index].split('=')
348
+ value = "" if value.nil?
349
+ end
301
350
  end
302
351
  end
303
- else
304
- name = expansion[VARIABLE_EXPANSION, 1]
305
- value = unparsed_value
306
352
  if processor != nil && processor.respond_to?(:restore)
307
353
  value = processor.restore(name, value)
308
354
  end
355
+ if processor == nil
356
+ if value.is_a?(Hash)
357
+ value = value.inject({}){|acc, (k, v)|
358
+ acc[Addressable::URI.unencode_component(k)] =
359
+ Addressable::URI.unencode_component(v)
360
+ acc
361
+ }
362
+ elsif value.is_a?(Array)
363
+ value = value.map{|v| Addressable::URI.unencode_component(v) }
364
+ else
365
+ value = Addressable::URI.unencode_component(value)
366
+ end
367
+ end
309
368
  if mapping[name] == nil || mapping[name] == value
310
369
  mapping[name] = value
311
370
  else
312
371
  return nil
313
372
  end
373
+ index = index + 1
314
374
  end
315
375
  end
316
376
  return Addressable::Template::MatchData.new(uri, self, mapping)
@@ -324,7 +384,7 @@ module Addressable
324
384
  #
325
385
  # @param [Hash] mapping The mapping that corresponds to the pattern.
326
386
  # @param [#validate, #transform] processor
327
- # An optional processor object may be supplied.
387
+ # An optional processor object may be supplied.
328
388
  #
329
389
  # The object should respond to either the <tt>validate</tt> or
330
390
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -347,50 +407,18 @@ module Addressable
347
407
  # #=> "http://example.com/1/{two}/"
348
408
  #
349
409
  # Addressable::Template.new(
350
- # "http://example.com/search/{-list|+|query}/"
351
- # ).partial_expand(
352
- # {"query" => "an example search query".split(" ")}
353
- # ).pattern
354
- # #=> "http://example.com/search/an+example+search+query/"
355
- #
356
- # Addressable::Template.new(
357
- # "http://example.com/{-join|&|one,two}/"
410
+ # "http://example.com/{?one,two}/"
358
411
  # ).partial_expand({"one" => "1"}).pattern
359
- # #=> "http://example.com/?one=1{-prefix|&two=|two}"
412
+ # #=> "http://example.com/?one=1{&two}/"
360
413
  #
361
414
  # Addressable::Template.new(
362
- # "http://example.com/{-join|&|one,two,three}/"
415
+ # "http://example.com/{?one,two,three}/"
363
416
  # ).partial_expand({"one" => "1", "three" => 3}).pattern
364
- # #=> "http://example.com/?one=1{-prefix|&two=|two}&three=3"
417
+ # #=> "http://example.com/?one=1{&two}&three=3"
365
418
  def partial_expand(mapping, processor=nil)
366
419
  result = self.pattern.dup
367
- transformed_mapping = transform_mapping(mapping, processor)
368
- result.gsub!(
369
- /#{OPERATOR_EXPANSION}|#{VARIABLE_EXPANSION}/
370
- ) do |capture|
371
- if capture =~ OPERATOR_EXPANSION
372
- operator, argument, variables, default_mapping =
373
- parse_template_expansion(capture, transformed_mapping)
374
- expand_method = "expand_#{operator}_operator"
375
- if ([expand_method, expand_method.to_sym] & private_methods).empty?
376
- raise InvalidTemplateOperatorError,
377
- "Invalid template operator: #{operator}"
378
- else
379
- send(
380
- expand_method.to_sym, argument, variables,
381
- default_mapping, true
382
- )
383
- end
384
- else
385
- varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
386
- if transformed_mapping[varname]
387
- transformed_mapping[varname]
388
- elsif vardefault
389
- "{#{varname}=#{vardefault}}"
390
- else
391
- "{#{varname}}"
392
- end
393
- end
420
+ result.gsub!( EXPRESSION ) do |capture|
421
+ transform_partial_capture(mapping, capture, processor)
394
422
  end
395
423
  return Addressable::Template.new(result)
396
424
  end
@@ -438,11 +466,11 @@ module Addressable
438
466
  # #=> "http://example.com/search/an+example+search+query/"
439
467
  #
440
468
  # Addressable::Template.new(
441
- # "http://example.com/search/{-list|+|query}/"
469
+ # "http://example.com/search/{query}/"
442
470
  # ).expand(
443
- # {"query" => "an example search query".split(" ")}
471
+ # {"query" => "an example search query"}
444
472
  # ).to_str
445
- # #=> "http://example.com/search/an+example+search+query/"
473
+ # #=> "http://example.com/search/an%20example%20search%20query/"
446
474
  #
447
475
  # Addressable::Template.new(
448
476
  # "http://example.com/search/{query}/"
@@ -453,24 +481,9 @@ module Addressable
453
481
  # #=> Addressable::Template::InvalidTemplateValueError
454
482
  def expand(mapping, processor=nil)
455
483
  result = self.pattern.dup
456
- transformed_mapping = transform_mapping(mapping, processor)
457
- result.gsub!(
458
- /#{OPERATOR_EXPANSION}|#{VARIABLE_EXPANSION}/
459
- ) do |capture|
460
- if capture =~ OPERATOR_EXPANSION
461
- operator, argument, variables, default_mapping =
462
- parse_template_expansion(capture, transformed_mapping)
463
- expand_method = "expand_#{operator}_operator"
464
- if ([expand_method, expand_method.to_sym] & private_methods).empty?
465
- raise InvalidTemplateOperatorError,
466
- "Invalid template operator: #{operator}"
467
- else
468
- send(expand_method.to_sym, argument, variables, default_mapping)
469
- end
470
- else
471
- varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
472
- transformed_mapping[varname] || vardefault
473
- end
484
+ mapping = normalize_keys(mapping)
485
+ result.gsub!( EXPRESSION ) do |capture|
486
+ transform_capture(mapping, capture, processor)
474
487
  end
475
488
  return Addressable::URI.parse(result)
476
489
  end
@@ -499,27 +512,25 @@ module Addressable
499
512
 
500
513
  private
501
514
  def ordered_variable_defaults
502
- @ordered_variable_defaults ||= (begin
515
+ @ordered_variable_defaults ||= (
503
516
  expansions, expansion_regexp = parse_template_pattern(pattern)
504
-
505
- expansions.inject([]) do |result, expansion|
506
- case expansion
507
- when OPERATOR_EXPANSION
508
- _, _, variables, mapping = parse_template_expansion(expansion)
509
- result.concat variables.map { |var| [var, mapping[var]] }
510
- when VARIABLE_EXPANSION
511
- result << [$1, $2]
517
+ expansions.map do |capture|
518
+ _, operator, varlist = *capture.match(EXPRESSION)
519
+ varlist.split(',').map do |varspec|
520
+ name = varspec[VARSPEC, 1]
512
521
  end
513
- result
514
- end
515
- end)
522
+ end.flatten
523
+ )
516
524
  end
517
525
 
526
+
518
527
  ##
519
- # Transforms a mapping so that values can be substituted into the
520
- # template.
528
+ # Loops through each capture and expands any values available in mapping
521
529
  #
522
- # @param [Hash] mapping The mapping of variables to values.
530
+ # @param [Hash] mapping
531
+ # Set of keys to expand
532
+ # @param [String] capture
533
+ # The expression to expand
523
534
  # @param [#validate, #transform] processor
524
535
  # An optional processor object may be supplied.
525
536
  #
@@ -535,279 +546,227 @@ module Addressable
535
546
  # automatically. Unicode normalization will be performed both before and
536
547
  # after sending the value to the transform method.
537
548
  #
538
- # @return [Hash] The transformed mapping.
539
- def transform_mapping(mapping, processor=nil)
540
- return mapping.inject({}) do |accu, pair|
541
- name, value = pair
542
- value = value.to_s if Numeric === value || Symbol === value
543
-
544
- unless value.respond_to?(:to_ary) || value.respond_to?(:to_str)
545
- raise TypeError,
546
- "Can't convert #{value.class} into String or Array."
547
- end
548
-
549
- if Symbol === name
550
- name = name.to_s
551
- elsif name.respond_to?(:to_str)
552
- name = name.to_str
553
- else
554
- raise TypeError,
555
- "Can't convert #{name.class} into String."
556
- end
557
- value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
558
-
559
- # Handle unicode normalization
560
- if value.kind_of?(Array)
561
- value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
549
+ # @return [String] The expanded expression
550
+ def transform_partial_capture(mapping, capture, processor = nil)
551
+ _, operator, varlist = *capture.match(EXPRESSION)
552
+ is_first = true
553
+ varlist.split(',').inject('') do |acc, varspec|
554
+ _, name, modifier = *varspec.match(VARSPEC)
555
+ value = mapping[name]
556
+ if value
557
+ operator = '&' if !is_first && operator == '?'
558
+ acc << transform_capture(mapping, "{#{operator}#{varspec}}", processor)
562
559
  else
563
- value = Addressable::IDNA.unicode_normalize_kc(value)
560
+ operator = '&' if !is_first && operator == '?'
561
+ acc << "{#{operator}#{varspec}}"
564
562
  end
565
-
566
- if processor == nil || !processor.respond_to?(:transform)
567
- # Handle percent escaping
568
- if value.kind_of?(Array)
569
- transformed_value = value.map do |val|
570
- Addressable::URI.encode_component(
571
- val, Addressable::URI::CharacterClasses::UNRESERVED)
572
- end
573
- else
574
- transformed_value = Addressable::URI.encode_component(
575
- value, Addressable::URI::CharacterClasses::UNRESERVED)
576
- end
577
- end
578
-
579
- # Process, if we've got a processor
580
- if processor != nil
581
- if processor.respond_to?(:validate)
582
- if !processor.validate(name, value)
583
- display_value = value.kind_of?(Array) ? value.inspect : value
584
- raise InvalidTemplateValueError,
585
- "#{name}=#{display_value} is an invalid template value."
586
- end
587
- end
588
- if processor.respond_to?(:transform)
589
- transformed_value = processor.transform(name, value)
590
- if transformed_value.kind_of?(Array)
591
- transformed_value.map! do |val|
592
- Addressable::IDNA.unicode_normalize_kc(val)
593
- end
594
- else
595
- transformed_value =
596
- Addressable::IDNA.unicode_normalize_kc(transformed_value)
597
- end
598
- end
599
- end
600
-
601
- accu[name] = transformed_value
602
- accu
563
+ is_first = false
564
+ acc
603
565
  end
604
566
  end
605
567
 
606
568
  ##
607
- # Expands a URI Template opt operator.
608
- #
609
- # @param [String] argument The argument to the operator.
610
- # @param [Array] variables The variables the operator is working on.
611
- # @param [Hash] mapping The mapping of variables to values.
612
- #
613
- # @return [String] The expanded result.
614
- def expand_opt_operator(argument, variables, mapping, partial=false)
615
- variables_present = variables.any? do |variable|
616
- mapping[variable] != [] &&
617
- mapping[variable]
618
- end
619
- if partial && !variables_present
620
- "{-opt|#{argument}|#{variables.join(",")}}"
621
- elsif variables_present
622
- argument
623
- else
624
- ""
625
- end
626
- end
627
-
628
- ##
629
- # Expands a URI Template neg operator.
630
- #
631
- # @param [String] argument The argument to the operator.
632
- # @param [Array] variables The variables the operator is working on.
633
- # @param [Hash] mapping The mapping of variables to values.
569
+ # Transforms a mapped value so that values can be substituted into the
570
+ # template.
634
571
  #
635
- # @return [String] The expanded result.
636
- def expand_neg_operator(argument, variables, mapping, partial=false)
637
- variables_present = variables.any? do |variable|
638
- mapping[variable] != [] &&
639
- mapping[variable]
640
- end
641
- if partial && !variables_present
642
- "{-neg|#{argument}|#{variables.join(",")}}"
643
- elsif variables_present
644
- ""
645
- else
646
- argument
647
- end
648
- end
649
-
650
- ##
651
- # Expands a URI Template prefix operator.
572
+ # @param [Hash] mapping The mapping to replace captures
573
+ # @param [String] capture
574
+ # The expression to replace
575
+ # @param [#validate, #transform] processor
576
+ # An optional processor object may be supplied.
652
577
  #
653
- # @param [String] argument The argument to the operator.
654
- # @param [Array] variables The variables the operator is working on.
655
- # @param [Hash] mapping The mapping of variables to values.
578
+ # The object should respond to either the <tt>validate</tt> or
579
+ # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
580
+ # <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
581
+ # <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
582
+ # or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
583
+ # <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
584
+ # will be raised if the value is invalid. The <tt>transform</tt> method
585
+ # should return the transformed variable value as a <tt>String</tt>. If a
586
+ # <tt>transform</tt> method is used, the value will not be percent encoded
587
+ # automatically. Unicode normalization will be performed both before and
588
+ # after sending the value to the transform method.
656
589
  #
657
- # @return [String] The expanded result.
658
- def expand_prefix_operator(argument, variables, mapping, partial=false)
659
- if variables.size != 1
660
- raise InvalidTemplateOperatorError,
661
- "Template operator 'prefix' takes exactly one variable."
662
- end
663
- value = mapping[variables.first]
664
- if !partial || value
665
- if value.kind_of?(Array)
666
- (value.map { |list_value| argument + list_value }).join("")
667
- elsif value
668
- argument + value.to_s
669
- end
670
- else
671
- "{-prefix|#{argument}|#{variables.first}}"
672
- end
673
- end
590
+ # @return [String] The expanded expression
591
+ def transform_capture(mapping, capture, processor=nil)
592
+ _, operator, varlist = *capture.match(EXPRESSION)
593
+ return_value = varlist.split(',').inject([]) do |acc, varspec|
594
+ _, name, modifier = *varspec.match(VARSPEC)
595
+ value = mapping[name]
596
+ unless value == nil || value == {}
597
+ allow_reserved = %w(+ #).include?(operator)
598
+ value = value.to_s if Numeric === value || Symbol === value
599
+ length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
600
+
601
+ unless (Hash === value) ||
602
+ value.respond_to?(:to_ary) || value.respond_to?(:to_str)
603
+ raise TypeError,
604
+ "Can't convert #{value.class} into String or Array."
605
+ end
674
606
 
675
- ##
676
- # Expands a URI Template suffix operator.
677
- #
678
- # @param [String] argument The argument to the operator.
679
- # @param [Array] variables The variables the operator is working on.
680
- # @param [Hash] mapping The mapping of variables to values.
681
- #
682
- # @return [String] The expanded result.
683
- def expand_suffix_operator(argument, variables, mapping, partial=false)
684
- if variables.size != 1
685
- raise InvalidTemplateOperatorError,
686
- "Template operator 'suffix' takes exactly one variable."
687
- end
688
- value = mapping[variables.first]
689
- if !partial || value
690
- if value.kind_of?(Array)
691
- (value.map { |list_value| list_value + argument }).join("")
692
- elsif value
693
- value.to_s + argument
694
- end
695
- else
696
- "{-suffix|#{argument}|#{variables.first}}"
697
- end
698
- end
607
+ value = normalize_value(value)
699
608
 
700
- ##
701
- # Expands a URI Template join operator.
702
- #
703
- # @param [String] argument The argument to the operator.
704
- # @param [Array] variables The variables the operator is working on.
705
- # @param [Hash] mapping The mapping of variables to values.
706
- #
707
- # @return [String] The expanded result.
708
- def expand_join_operator(argument, variables, mapping, partial=false)
709
- if !partial
710
- variable_values = variables.inject([]) do |accu, variable|
711
- if !mapping[variable].kind_of?(Array)
712
- if mapping[variable]
713
- accu << variable + "=" + (mapping[variable])
609
+ if processor == nil || !processor.respond_to?(:transform)
610
+ # Handle percent escaping
611
+ if allow_reserved
612
+ encode_map =
613
+ Addressable::URI::CharacterClasses::RESERVED +
614
+ Addressable::URI::CharacterClasses::UNRESERVED
615
+ else
616
+ encode_map = Addressable::URI::CharacterClasses::UNRESERVED
714
617
  end
715
- else
716
- raise InvalidTemplateOperatorError,
717
- "Template operator 'join' does not accept Array values."
718
- end
719
- accu
720
- end
721
- variable_values.join(argument)
722
- else
723
- buffer = ""
724
- state = :suffix
725
- variables.each_with_index do |variable, index|
726
- if !mapping[variable].kind_of?(Array)
727
- if mapping[variable]
728
- if buffer.empty? || buffer[-1..-1] == "}"
729
- buffer << (variable + "=" + (mapping[variable]))
730
- elsif state == :suffix
731
- buffer << argument
732
- buffer << (variable + "=" + (mapping[variable]))
733
- else
734
- buffer << (variable + "=" + (mapping[variable]))
618
+ if value.kind_of?(Array)
619
+ transformed_value = value.map do |val|
620
+ if length
621
+ Addressable::URI.encode_component(val[0...length], encode_map)
622
+ else
623
+ Addressable::URI.encode_component(val, encode_map)
624
+ end
735
625
  end
736
- else
737
- if !buffer.empty? && (buffer[-1..-1] != "}" || state == :prefix)
738
- buffer << "{-opt|#{argument}|#{variable}}"
739
- state = :prefix
626
+ unless modifier == "*"
627
+ transformed_value = transformed_value.join(',')
628
+ end
629
+ elsif value.kind_of?(Hash)
630
+ transformed_value = value.map do |key, val|
631
+ if modifier == "*"
632
+ "#{
633
+ Addressable::URI.encode_component( key, encode_map)
634
+ }=#{
635
+ Addressable::URI.encode_component( val, encode_map)
636
+ }"
637
+ else
638
+ "#{
639
+ Addressable::URI.encode_component( key, encode_map)
640
+ },#{
641
+ Addressable::URI.encode_component( val, encode_map)
642
+ }"
643
+ end
740
644
  end
741
- if buffer.empty? && variables.size == 1
742
- # Evaluates back to itself
743
- buffer << "{-join|#{argument}|#{variable}}"
645
+ unless modifier == "*"
646
+ transformed_value = transformed_value.join(',')
647
+ end
648
+ else
649
+ if length
650
+ transformed_value = Addressable::URI.encode_component(
651
+ value[0...length], encode_map)
744
652
  else
745
- buffer << "{-prefix|#{variable}=|#{variable}}"
653
+ transformed_value = Addressable::URI.encode_component(
654
+ value, encode_map)
746
655
  end
747
- if (index != (variables.size - 1) && state == :suffix)
748
- buffer << "{-opt|#{argument}|#{variable}}"
749
- elsif index != (variables.size - 1) &&
750
- mapping[variables[index + 1]]
751
- buffer << argument
752
- state = :prefix
656
+ end
657
+ end
658
+
659
+ # Process, if we've got a processor
660
+ if processor != nil
661
+ if processor.respond_to?(:validate)
662
+ if !processor.validate(name, value)
663
+ display_value = value.kind_of?(Array) ? value.inspect : value
664
+ raise InvalidTemplateValueError,
665
+ "#{name}=#{display_value} is an invalid template value."
753
666
  end
754
667
  end
755
- else
756
- raise InvalidTemplateOperatorError,
757
- "Template operator 'join' does not accept Array values."
668
+ if processor.respond_to?(:transform)
669
+ transformed_value = processor.transform(name, value)
670
+ transformed_value = normalize_value(transformed_value)
671
+ end
758
672
  end
673
+ acc << [name, transformed_value]
759
674
  end
760
- buffer
675
+ acc
676
+ end
677
+ return "" if return_value.empty?
678
+ join_values(operator, return_value)
679
+ end
680
+
681
+ ##
682
+ # Takes a set of values, and joins them together based on the
683
+ # operator.
684
+ #
685
+ # @param [String, Nil] operator One of the operators from the set
686
+ # (?,&,+,#,;,/,.), or nil if there wasn't one.
687
+ # @param [Array] return_value
688
+ # The set of return values (as [variable_name, value] tuples) that will
689
+ # be joined together.
690
+ #
691
+ # @return [String] The transformed mapped value
692
+ def join_values(operator, return_value)
693
+ leader = LEADERS.fetch(operator, '')
694
+ joiner = JOINERS.fetch(operator, ',')
695
+ case operator
696
+ when '&', '?'
697
+ leader + return_value.map{|k,v|
698
+ if v.is_a?(Array) && v.first =~ /=/
699
+ v.join(joiner)
700
+ elsif v.is_a?(Array)
701
+ v.map{|v| "#{k}=#{v}"}.join(joiner)
702
+ else
703
+ "#{k}=#{v}"
704
+ end
705
+ }.join(joiner)
706
+ when ';'
707
+ return_value.map{|k,v|
708
+ if v.is_a?(Array) && v.first =~ /=/
709
+ ';' + v.join(";")
710
+ elsif v.is_a?(Array)
711
+ ';' + v.map{|v| "#{k}=#{v}"}.join(";")
712
+ else
713
+ v && v != '' ? ";#{k}=#{v}" : ";#{k}"
714
+ end
715
+ }.join
716
+ else
717
+ leader + return_value.map{|k,v| v}.join(joiner)
761
718
  end
762
719
  end
763
720
 
764
721
  ##
765
- # Expands a URI Template list operator.
722
+ # Takes a set of values, and joins them together based on the
723
+ # operator.
766
724
  #
767
- # @param [String] argument The argument to the operator.
768
- # @param [Array] variables The variables the operator is working on.
769
- # @param [Hash] mapping The mapping of variables to values.
725
+ # @param [Hash, Array, String] value
726
+ # Normalizes keys and values with IDNA#unicode_normalize_kc
770
727
  #
771
- # @return [String] The expanded result.
772
- def expand_list_operator(argument, variables, mapping, partial=false)
773
- if variables.size != 1
774
- raise InvalidTemplateOperatorError,
775
- "Template operator 'list' takes exactly one variable."
728
+ # @return [Hash, Array, String] The normalized values
729
+ def normalize_value(value)
730
+ unless value.is_a?(Hash)
731
+ value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
776
732
  end
777
- if !partial || mapping[variables.first]
778
- values = mapping[variables.first]
779
- if values
780
- if values.kind_of?(Array)
781
- values.join(argument)
782
- else
783
- raise InvalidTemplateOperatorError,
784
- "Template operator 'list' only accepts Array values."
785
- end
786
- end
733
+
734
+ # Handle unicode normalization
735
+ if value.kind_of?(Array)
736
+ value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
737
+ elsif value.kind_of?(Hash)
738
+ value = value.inject({}) { |acc, (k, v)|
739
+ acc[Addressable::IDNA.unicode_normalize_kc(k)] =
740
+ Addressable::IDNA.unicode_normalize_kc(v)
741
+ acc
742
+ }
787
743
  else
788
- "{-list|#{argument}|#{variables.first}}"
744
+ value = Addressable::IDNA.unicode_normalize_kc(value)
789
745
  end
746
+ value
790
747
  end
791
748
 
792
749
  ##
793
- # Parses a URI template expansion <tt>String</tt>.
750
+ # Generates a hash with string keys
794
751
  #
795
- # @param [String] expansion The operator <tt>String</tt>.
796
- # @param [Hash] mapping An optional mapping to merge defaults into.
752
+ # @param [Hash] mapping A mapping hash to normalize
797
753
  #
798
- # @return [Array]
799
- # A tuple of the operator, argument, variables, and mapping.
800
- def parse_template_expansion(capture, mapping={})
801
- operator, argument, variables = capture[1...-1].split("|", -1)
802
- operator.gsub!(/^\-/, "")
803
- variables = variables.split(",", -1)
804
- mapping = (variables.inject({}) do |accu, var|
805
- varname, _, vardefault = var.scan(/^(.+?)(=(.*))?$/)[0]
806
- accu[varname] = vardefault
754
+ # @return [Hash]
755
+ # A hash with stringified keys
756
+ def normalize_keys(mapping)
757
+ return mapping.inject({}) do |accu, pair|
758
+ name, value = pair
759
+ if Symbol === name
760
+ name = name.to_s
761
+ elsif name.respond_to?(:to_str)
762
+ name = name.to_str
763
+ else
764
+ raise TypeError,
765
+ "Can't convert #{name.class} into String."
766
+ end
767
+ accu[name] = value
807
768
  accu
808
- end).merge(mapping)
809
- variables = variables.map { |var| var.gsub(/=.*$/, "") }
810
- return operator, argument, variables, mapping
769
+ end
811
770
  end
812
771
 
813
772
  ##
@@ -832,216 +791,48 @@ module Addressable
832
791
 
833
792
  # Create a regular expression that captures the values of the
834
793
  # variables in the URI.
835
- regexp_string = escaped_pattern.gsub(
836
- /#{OPERATOR_EXPANSION}|#{VARIABLE_EXPANSION}/
837
- ) do |expansion|
794
+ regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
795
+
838
796
  expansions << expansion
839
- if expansion =~ OPERATOR_EXPANSION
840
- capture_group = "(.*)"
841
- operator, argument, names, _ =
842
- parse_template_expansion(expansion)
797
+ _, operator, varlist = *expansion.match(EXPRESSION)
798
+ leader = Regexp.escape(LEADERS.fetch(operator, ''))
799
+ joiner = Regexp.escape(JOINERS.fetch(operator, ','))
800
+ leader + varlist.split(',').map do |varspec|
801
+ _, name, modifier = *varspec.match(VARSPEC)
843
802
  if processor != nil && processor.respond_to?(:match)
844
- # We can only lookup the match values for single variable
845
- # operator expansions. Besides, ".*" is usually the only
846
- # reasonable value for multivariate operators anyways.
847
- if ["prefix", "suffix", "list"].include?(operator)
848
- capture_group = "(#{processor.match(names.first)})"
803
+ "(#{ processor.match(name) })"
804
+ else
805
+ group = case operator
806
+ when '+'
807
+ "#{ RESERVED }*?"
808
+ when '#'
809
+ "#{ RESERVED }*?"
810
+ when '/'
811
+ "#{ UNRESERVED }*?"
812
+ when '.'
813
+ "#{ UNRESERVED.gsub('\.', '') }*?"
814
+ when ';'
815
+ "#{ UNRESERVED }*=?#{ UNRESERVED }*?"
816
+ when '?'
817
+ "#{ UNRESERVED }*=#{ UNRESERVED }*?"
818
+ when '&'
819
+ "#{ UNRESERVED }*=#{ UNRESERVED }*?"
820
+ else
821
+ "#{ UNRESERVED }*?"
822
+ end
823
+ if modifier == '*'
824
+ "(#{group}(?:#{joiner}?#{group})*)?"
825
+ else
826
+ "(#{group})?"
849
827
  end
850
- elsif operator == "prefix"
851
- capture_group = "(#{Regexp.escape(argument)}.*?)"
852
- elsif operator == "suffix"
853
- capture_group = "(.*?#{Regexp.escape(argument)})"
854
- end
855
- capture_group
856
- else
857
- capture_group = "(.*?)"
858
- if processor != nil && processor.respond_to?(:match)
859
- name = expansion[/\{([^\}=]+)(=[^\}]+)?\}/, 1]
860
- capture_group = "(#{processor.match(name)})"
861
828
  end
862
- capture_group
863
- end
829
+ end.join("#{joiner}?")
864
830
  end
865
831
 
866
832
  # Ensure that the regular expression matches the whole URI.
867
833
  regexp_string = "^#{regexp_string}$"
868
-
869
834
  return expansions, Regexp.new(regexp_string)
870
835
  end
871
836
 
872
- ##
873
- # Extracts a URI Template opt operator.
874
- #
875
- # @param [String] value The unparsed value to extract from.
876
- # @param [#restore] processor The processor object.
877
- # @param [String] argument The argument to the operator.
878
- # @param [Array] variables The variables the operator is working on.
879
- # @param [Hash] mapping The mapping of variables to values.
880
- #
881
- # @return [String] The extracted result.
882
- def extract_opt_operator(
883
- value, processor, argument, variables, mapping)
884
- if value != "" && value != argument
885
- raise TemplateOperatorAbortedError,
886
- "Value for template operator 'opt' was unexpected."
887
- end
888
- end
889
-
890
- ##
891
- # Extracts a URI Template neg operator.
892
- #
893
- # @param [String] value The unparsed value to extract from.
894
- # @param [#restore] processor The processor object.
895
- # @param [String] argument The argument to the operator.
896
- # @param [Array] variables The variables the operator is working on.
897
- # @param [Hash] mapping The mapping of variables to values.
898
- #
899
- # @return [String] The extracted result.
900
- def extract_neg_operator(
901
- value, processor, argument, variables, mapping)
902
- if value != "" && value != argument
903
- raise TemplateOperatorAbortedError,
904
- "Value for template operator 'neg' was unexpected."
905
- end
906
- end
907
-
908
- ##
909
- # Extracts a URI Template prefix operator.
910
- #
911
- # @param [String] value The unparsed value to extract from.
912
- # @param [#restore] processor The processor object.
913
- # @param [String] argument The argument to the operator.
914
- # @param [Array] variables The variables the operator is working on.
915
- # @param [Hash] mapping The mapping of variables to values.
916
- #
917
- # @return [String] The extracted result.
918
- def extract_prefix_operator(
919
- value, processor, argument, variables, mapping)
920
- if variables.size != 1
921
- raise InvalidTemplateOperatorError,
922
- "Template operator 'prefix' takes exactly one variable."
923
- end
924
- if value[0...argument.size] != argument
925
- raise TemplateOperatorAbortedError,
926
- "Value for template operator 'prefix' missing expected prefix."
927
- end
928
- values = value.split(argument, -1)
929
- values << "" if value[-argument.size..-1] == argument
930
- values.shift if values[0] == ""
931
- values.pop if values[-1] == ""
932
-
933
- if processor && processor.respond_to?(:restore)
934
- values.map! { |val| processor.restore(variables.first, val) }
935
- end
936
- values = values.first if values.size == 1
937
- if mapping[variables.first] == nil || mapping[variables.first] == values
938
- mapping[variables.first] = values
939
- else
940
- raise TemplateOperatorAbortedError,
941
- "Value mismatch for repeated variable."
942
- end
943
- end
944
-
945
- ##
946
- # Extracts a URI Template suffix operator.
947
- #
948
- # @param [String] value The unparsed value to extract from.
949
- # @param [#restore] processor The processor object.
950
- # @param [String] argument The argument to the operator.
951
- # @param [Array] variables The variables the operator is working on.
952
- # @param [Hash] mapping The mapping of variables to values.
953
- #
954
- # @return [String] The extracted result.
955
- def extract_suffix_operator(
956
- value, processor, argument, variables, mapping)
957
- if variables.size != 1
958
- raise InvalidTemplateOperatorError,
959
- "Template operator 'suffix' takes exactly one variable."
960
- end
961
- if value[-argument.size..-1] != argument
962
- raise TemplateOperatorAbortedError,
963
- "Value for template operator 'suffix' missing expected suffix."
964
- end
965
- values = value.split(argument, -1)
966
- values.pop if values[-1] == ""
967
- if processor && processor.respond_to?(:restore)
968
- values.map! { |val| processor.restore(variables.first, val) }
969
- end
970
- values = values.first if values.size == 1
971
- if mapping[variables.first] == nil || mapping[variables.first] == values
972
- mapping[variables.first] = values
973
- else
974
- raise TemplateOperatorAbortedError,
975
- "Value mismatch for repeated variable."
976
- end
977
- end
978
-
979
- ##
980
- # Extracts a URI Template join operator.
981
- #
982
- # @param [String] value The unparsed value to extract from.
983
- # @param [#restore] processor The processor object.
984
- # @param [String] argument The argument to the operator.
985
- # @param [Array] variables The variables the operator is working on.
986
- # @param [Hash] mapping The mapping of variables to values.
987
- #
988
- # @return [String] The extracted result.
989
- def extract_join_operator(value, processor, argument, variables, mapping)
990
- unparsed_values = value.split(argument)
991
- parsed_variables = []
992
- for unparsed_value in unparsed_values
993
- name = unparsed_value[/^(.+?)=(.+)$/, 1]
994
- parsed_variables << name
995
- parsed_value = unparsed_value[/^(.+?)=(.+)$/, 2]
996
- if processor && processor.respond_to?(:restore)
997
- parsed_value = processor.restore(name, parsed_value)
998
- end
999
- if mapping[name] == nil || mapping[name] == parsed_value
1000
- mapping[name] = parsed_value
1001
- else
1002
- raise TemplateOperatorAbortedError,
1003
- "Value mismatch for repeated variable."
1004
- end
1005
- end
1006
- for variable in variables
1007
- if !parsed_variables.include?(variable) && mapping[variable] != nil
1008
- raise TemplateOperatorAbortedError,
1009
- "Value mismatch for repeated variable."
1010
- end
1011
- end
1012
- if (parsed_variables & variables) != parsed_variables
1013
- raise TemplateOperatorAbortedError,
1014
- "Template operator 'join' variable mismatch: " +
1015
- "#{parsed_variables.inspect}, #{variables.inspect}"
1016
- end
1017
- end
1018
-
1019
- ##
1020
- # Extracts a URI Template list operator.
1021
- #
1022
- # @param [String] value The unparsed value to extract from.
1023
- # @param [#restore] processor The processor object.
1024
- # @param [String] argument The argument to the operator.
1025
- # @param [Array] variables The variables the operator is working on.
1026
- # @param [Hash] mapping The mapping of variables to values.
1027
- #
1028
- # @return [String] The extracted result.
1029
- def extract_list_operator(value, processor, argument, variables, mapping)
1030
- if variables.size != 1
1031
- raise InvalidTemplateOperatorError,
1032
- "Template operator 'list' takes exactly one variable."
1033
- end
1034
- values = value.split(argument, -1)
1035
- values.pop if values[-1] == ""
1036
- if processor && processor.respond_to?(:restore)
1037
- values.map! { |val| processor.restore(variables.first, val) }
1038
- end
1039
- if mapping[variables.first] == nil || mapping[variables.first] == values
1040
- mapping[variables.first] = values
1041
- else
1042
- raise TemplateOperatorAbortedError,
1043
- "Value mismatch for repeated variable."
1044
- end
1045
- end
1046
837
  end
1047
838
  end