Merged updates from trunk into stable branch
[feedcatcher.git] / vendor / rails / activesupport / lib / active_support / json / decoding.rb
1 require 'yaml'
2 require 'strscan'
3
4 module ActiveSupport
5 module JSON
6 class ParseError < StandardError
7 end
8
9 class << self
10 # Converts a JSON string into a Ruby object.
11 def decode(json)
12 YAML.load(convert_json_to_yaml(json))
13 rescue ArgumentError => e
14 raise ParseError, "Invalid JSON string"
15 end
16
17 protected
18 # matches YAML-formatted dates
19 DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)$/
20
21 # Ensure that ":" and "," are always followed by a space
22 def convert_json_to_yaml(json) #:nodoc:
23 scanner, quoting, marks, pos, times = StringScanner.new(json), false, [], nil, []
24 while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
25 case char = scanner[1]
26 when '"', "'"
27 if !quoting
28 quoting = char
29 pos = scanner.pos
30 elsif quoting == char
31 if json[pos..scanner.pos-2] =~ DATE_REGEX
32 # found a date, track the exact positions of the quotes so we can remove them later.
33 # oh, and increment them for each current mark, each one is an extra padded space that bumps
34 # the position in the final YAML output
35 total_marks = marks.size
36 times << pos+total_marks << scanner.pos+total_marks
37 end
38 quoting = false
39 end
40 when ":",","
41 marks << scanner.pos - 1 unless quoting
42 end
43 end
44
45 if marks.empty?
46 json.gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do
47 ustr = $1
48 if ustr.starts_with?('u')
49 [ustr[1..-1].to_i(16)].pack("U")
50 elsif ustr == '\\'
51 '\\\\'
52 else
53 ustr
54 end
55 end
56 else
57 left_pos = [-1].push(*marks)
58 right_pos = marks << scanner.pos + scanner.rest_size
59 output = []
60 left_pos.each_with_index do |left, i|
61 scanner.pos = left.succ
62 output << scanner.peek(right_pos[i] - scanner.pos + 1).gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do
63 ustr = $1
64 if ustr.starts_with?('u')
65 [ustr[1..-1].to_i(16)].pack("U")
66 elsif ustr == '\\'
67 '\\\\'
68 else
69 ustr
70 end
71 end
72 end
73 output = output * " "
74
75 times.each { |i| output[i-1] = ' ' }
76 output.gsub!(/\\\//, '/')
77 output
78 end
79 end
80 end
81 end
82 end