microfiche 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ProjectInfo +31 -0
- data/README +24 -0
- data/Rakefile +2 -0
- data/VERSION +1 -0
- data/lib/facets/json.rb +917 -0
- data/lib/facets/xoxo.rb +541 -0
- data/test/lib/facets/test_json.rb +232 -0
- data/test/lib/facets/test_xoxo.rb +288 -0
- metadata +61 -0
data/ProjectInfo
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
PROJECT : microfiche
|
2
|
+
VERSION : '1.0.0'
|
3
|
+
DATE : '2006-11-26'
|
4
|
+
STATUS : stable
|
5
|
+
|
6
|
+
CONTACT : Trans <transfire_AT_gmail.com>
|
7
|
+
EMAIL : facets-universal@rubyforge.org
|
8
|
+
HOMEPAGE : "http://facets.rubyforge.org"
|
9
|
+
|
10
|
+
TITLE : Microfiche
|
11
|
+
SUMMARY : Collection of microformat libraries.
|
12
|
+
DESCRIPTION : >
|
13
|
+
Micorfiche is an assortment of microformat libraries.
|
14
|
+
So far only XOXO is included, but other will be added
|
15
|
+
soon. Also included in a JSON library, not strictly a
|
16
|
+
micorformat, its usage is closely paired with these
|
17
|
+
other technologies, and so is included.
|
18
|
+
|
19
|
+
DEPENDENCIES: [ corsets ]
|
20
|
+
|
21
|
+
RUBYFORGE:
|
22
|
+
project : facets
|
23
|
+
username : transami
|
24
|
+
package : microfiche
|
25
|
+
|
26
|
+
RDOC:
|
27
|
+
template : jamis
|
28
|
+
options : ['--all', '--inline-source']
|
29
|
+
|
30
|
+
TEST:
|
31
|
+
extract : true
|
data/README
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= Microfiche
|
2
|
+
|
3
|
+
Collection of Microformat libraries.
|
4
|
+
|
5
|
+
|
6
|
+
== Install
|
7
|
+
|
8
|
+
Microfiche is available in via gem or source tarball.
|
9
|
+
|
10
|
+
gem install microfiche
|
11
|
+
|
12
|
+
or
|
13
|
+
|
14
|
+
tar -xvzf microfiche-1.0.0.tar.gz
|
15
|
+
cd microfiche-1.0.0
|
16
|
+
sudo ruby setup.rb
|
17
|
+
|
18
|
+
|
19
|
+
== License
|
20
|
+
|
21
|
+
Copyright(c)2006-2007 Florian Frank, Christian Neukirchen, Thomas Sawyer
|
22
|
+
|
23
|
+
Ruby/License
|
24
|
+
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0 stable (2007-03-16)
|
data/lib/facets/json.rb
ADDED
@@ -0,0 +1,917 @@
|
|
1
|
+
# = json.rb
|
2
|
+
#
|
3
|
+
# == Copyright (c) 2006 Florian Frank
|
4
|
+
#
|
5
|
+
# GNU General Public License
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation; either version 2 of the License, or (at
|
10
|
+
# your option) any later version.
|
11
|
+
#
|
12
|
+
# This program is distributed in the hope that it will be useful, but
|
13
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
15
|
+
# General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with this program; if not, write to the Free Software
|
19
|
+
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
20
|
+
#
|
21
|
+
# == Author(s)
|
22
|
+
#
|
23
|
+
# * Florian Frank
|
24
|
+
#
|
25
|
+
# == Additional Information
|
26
|
+
#
|
27
|
+
# The latest version of this library can be downloaded at:
|
28
|
+
# * http://rubyforge.org/frs?group_id=953
|
29
|
+
#
|
30
|
+
# Online Documentation should be located at:
|
31
|
+
# * http://json.rubyforge.org
|
32
|
+
|
33
|
+
# Author:: Florian Frank <mailto:flori@ping.de>
|
34
|
+
# Copyright:: Copyright (c) 2006 Florian Frank
|
35
|
+
# License:: GNU General Public License (GPL)
|
36
|
+
|
37
|
+
# = JSON library for Ruby
|
38
|
+
#
|
39
|
+
# Ruby support of Javascript Object Notation.
|
40
|
+
#
|
41
|
+
# == Examples
|
42
|
+
#
|
43
|
+
# To create a JSON string from a ruby data structure, you
|
44
|
+
# can call JSON.unparse like that:
|
45
|
+
#
|
46
|
+
# json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
47
|
+
# # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
|
48
|
+
#
|
49
|
+
# It's also possible to call the #to_json method directly.
|
50
|
+
#
|
51
|
+
# json = [1, 2, {"a"=>3.141}, false, true, nil, 4..10].to_json
|
52
|
+
# # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
|
53
|
+
#
|
54
|
+
# To get back a ruby data structure, you have to call
|
55
|
+
# JSON.parse on the JSON string:
|
56
|
+
#
|
57
|
+
# JSON.parse json
|
58
|
+
# # => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"]
|
59
|
+
#
|
60
|
+
# Note, that the range from the original data structure is a simple
|
61
|
+
# string now. The reason for this is, that JSON doesn't support ranges
|
62
|
+
# or arbitrary classes. In this case the json library falls back to call
|
63
|
+
# Object#to_json, which is the same as #to_s.to_json.
|
64
|
+
#
|
65
|
+
# It's possible to extend JSON to support serialization of arbitray classes by
|
66
|
+
# simply implementing a more specialized version of the #to_json method, that
|
67
|
+
# should return a JSON object (a hash converted to JSON with #to_json)
|
68
|
+
# like this (don't forget the *a for all the arguments):
|
69
|
+
#
|
70
|
+
# class Range
|
71
|
+
# def to_json(*a)
|
72
|
+
# {
|
73
|
+
# 'json_class' => self.class.name,
|
74
|
+
# 'data' => [ first, last, exclude_end? ]
|
75
|
+
# }.to_json(*a)
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# The hash key 'json_class' is the class, that will be asked to deserialize the
|
80
|
+
# JSON representation later. In this case it's 'Range', but any namespace of
|
81
|
+
# the form 'A::B' or '::A::B' will do. All other keys are arbitrary and can be
|
82
|
+
# used to store the necessary data to configure the object to be deserialized.
|
83
|
+
#
|
84
|
+
# If a the key 'json_class' is found in a JSON object, the JSON parser checks
|
85
|
+
# if the given class responds to the json_create class method. If so, it is
|
86
|
+
# called with the JSON object converted to a Ruby hash. So a range can
|
87
|
+
# be deserialized by implementing Range.json_create like this:
|
88
|
+
#
|
89
|
+
# class Range
|
90
|
+
# def self.json_create(o)
|
91
|
+
# new(*o['data'])
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# Now it possible to serialize/deserialize ranges as well:
|
96
|
+
#
|
97
|
+
# json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
98
|
+
# # => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]"
|
99
|
+
# JSON.parse json
|
100
|
+
# # => [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
101
|
+
#
|
102
|
+
# JSON.unparse always creates the shortes possible string representation of a
|
103
|
+
# ruby data structure in one line. This good for data storage or network
|
104
|
+
# protocols, but not so good for humans to read. Fortunately there's
|
105
|
+
# also JSON.pretty_unparse that creates a more readable output:
|
106
|
+
#
|
107
|
+
# puts JSON.pretty_unparse([1, 2, {"a"=>3.141}, false, true, nil, 4..10])
|
108
|
+
# [
|
109
|
+
# 1,
|
110
|
+
# 2,
|
111
|
+
# {
|
112
|
+
# "a": 3.141
|
113
|
+
# },
|
114
|
+
# false,
|
115
|
+
# true,
|
116
|
+
# null,
|
117
|
+
# {
|
118
|
+
# "json_class": "Range",
|
119
|
+
# "data": [
|
120
|
+
# 4,
|
121
|
+
# 10,
|
122
|
+
# false
|
123
|
+
# ]
|
124
|
+
# }
|
125
|
+
# ]
|
126
|
+
#
|
127
|
+
# There are also the methods Kernel#j for unparse, and Kernel#jj for
|
128
|
+
# pretty_unparse output to the console, that work analogous to Kernel#p and
|
129
|
+
# Kernel#pp.
|
130
|
+
#
|
131
|
+
|
132
|
+
require 'strscan'
|
133
|
+
|
134
|
+
# This module is the namespace for all the JSON related classes. It also
|
135
|
+
# defines some module functions to expose a nicer API to users, instead
|
136
|
+
# of using the parser and other classes directly.
|
137
|
+
|
138
|
+
module JSON
|
139
|
+
|
140
|
+
# The base exception for JSON errors.
|
141
|
+
JSONError = Class.new StandardError
|
142
|
+
|
143
|
+
# This exception is raise, if a parser error occurs.
|
144
|
+
ParserError = Class.new JSONError
|
145
|
+
|
146
|
+
# This exception is raise, if a unparser error occurs.
|
147
|
+
UnparserError = Class.new JSONError
|
148
|
+
|
149
|
+
# If a circular data structure is encountered while unparsing
|
150
|
+
# this exception is raised.
|
151
|
+
CircularDatastructure = Class.new UnparserError
|
152
|
+
|
153
|
+
class << self
|
154
|
+
# Switches on Unicode support, if _enable_ is _true_. Otherwise switches
|
155
|
+
# Unicode support off.
|
156
|
+
def support_unicode=(enable)
|
157
|
+
@support_unicode = enable
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns _true_ if JSON supports unicode, otherwise _false_ is returned.
|
161
|
+
def support_unicode?
|
162
|
+
!!@support_unicode
|
163
|
+
end
|
164
|
+
end
|
165
|
+
JSON.support_unicode = true # default, hower it's possible to switch off full
|
166
|
+
# unicode support, if non-ascii bytes should be
|
167
|
+
# just passed through.
|
168
|
+
|
169
|
+
begin
|
170
|
+
require 'iconv'
|
171
|
+
# An iconv instance to convert from UTF8 to UTF16 Big Endian.
|
172
|
+
UTF16toUTF8 = Iconv.new('utf-8', 'utf-16be')
|
173
|
+
# An iconv instance to convert from UTF16 Big Endian to UTF8.
|
174
|
+
UTF8toUTF16 = Iconv.new('utf-16be', 'utf-8'); UTF8toUTF16.iconv('no bom')
|
175
|
+
rescue LoadError
|
176
|
+
JSON.support_unicode = false # enforce disabling of unicode support
|
177
|
+
end
|
178
|
+
|
179
|
+
# This class implements the JSON parser that is used to parse a JSON string
|
180
|
+
# into a Ruby data structure.
|
181
|
+
class Parser < StringScanner
|
182
|
+
STRING = /"((?:[^"\\]|\\.)*)"/
|
183
|
+
INTEGER = /-?\d+/
|
184
|
+
FLOAT = /-?\d+\.(\d*)(?i:e[+-]?\d+)?/
|
185
|
+
OBJECT_OPEN = /\{/
|
186
|
+
OBJECT_CLOSE = /\}/
|
187
|
+
ARRAY_OPEN = /\[/
|
188
|
+
ARRAY_CLOSE = /\]/
|
189
|
+
PAIR_DELIMITER = /:/
|
190
|
+
COLLECTION_DELIMITER = /,/
|
191
|
+
TRUE = /true/
|
192
|
+
FALSE = /false/
|
193
|
+
NULL = /null/
|
194
|
+
IGNORE = %r(
|
195
|
+
(?:
|
196
|
+
//[^\n\r]*[\n\r]| # line comments
|
197
|
+
/\* # c-style comments
|
198
|
+
(?:
|
199
|
+
[^*/]| # normal chars
|
200
|
+
/[^*]| # slashes that do not start a nested comment
|
201
|
+
\*[^/]| # asterisks that do not end this comment
|
202
|
+
/(?=\*/) # single slash before this comment's end
|
203
|
+
)*
|
204
|
+
\*/ # the end of this comment
|
205
|
+
|\s+ # whitespaces
|
206
|
+
)+
|
207
|
+
)mx
|
208
|
+
|
209
|
+
UNPARSED = Object.new
|
210
|
+
|
211
|
+
# Parses the current JSON string and returns the complete data structure
|
212
|
+
# as a result.
|
213
|
+
def parse
|
214
|
+
reset
|
215
|
+
until eos?
|
216
|
+
case
|
217
|
+
when scan(ARRAY_OPEN)
|
218
|
+
return parse_array
|
219
|
+
when scan(OBJECT_OPEN)
|
220
|
+
return parse_object
|
221
|
+
when skip(IGNORE)
|
222
|
+
;
|
223
|
+
when !((value = parse_value).equal? UNPARSED)
|
224
|
+
return value
|
225
|
+
else
|
226
|
+
raise ParserError, "source '#{peek(20)}' not in JSON!"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def parse_string
|
234
|
+
if scan(STRING)
|
235
|
+
return '' if self[1].empty?
|
236
|
+
self[1].gsub(/\\(?:[\\bfnrt"]|u([A-Fa-f\d]{4}))/) do
|
237
|
+
case $~[0]
|
238
|
+
when '\\\\' then '\\'
|
239
|
+
when '\\b' then "\b"
|
240
|
+
when '\\f' then "\f"
|
241
|
+
when '\\n' then "\n"
|
242
|
+
when '\\r' then "\r"
|
243
|
+
when '\\t' then "\t"
|
244
|
+
when '\\"' then '"'
|
245
|
+
else
|
246
|
+
if JSON.support_unicode? and $KCODE == 'UTF8'
|
247
|
+
JSON.utf16_to_utf8($~[1])
|
248
|
+
else
|
249
|
+
# if utf8 mode is switched off or unicode not supported, try to
|
250
|
+
# transform unicode \u-notation to bytes directly:
|
251
|
+
$~[1].to_i(16).chr
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
else
|
256
|
+
UNPARSED
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def parse_value
|
261
|
+
case
|
262
|
+
when scan(FLOAT)
|
263
|
+
Float(self[0])
|
264
|
+
when scan(INTEGER)
|
265
|
+
Integer(self[0])
|
266
|
+
when scan(TRUE)
|
267
|
+
true
|
268
|
+
when scan(FALSE)
|
269
|
+
false
|
270
|
+
when scan(NULL)
|
271
|
+
nil
|
272
|
+
when (string = parse_string) != UNPARSED
|
273
|
+
string
|
274
|
+
when scan(ARRAY_OPEN)
|
275
|
+
parse_array
|
276
|
+
when scan(OBJECT_OPEN)
|
277
|
+
parse_object
|
278
|
+
else
|
279
|
+
UNPARSED
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def parse_array
|
284
|
+
result = []
|
285
|
+
until eos?
|
286
|
+
case
|
287
|
+
when (value = parse_value) != UNPARSED
|
288
|
+
result << value
|
289
|
+
skip(IGNORE)
|
290
|
+
unless scan(COLLECTION_DELIMITER) or match?(ARRAY_CLOSE)
|
291
|
+
raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
|
292
|
+
end
|
293
|
+
when scan(ARRAY_CLOSE)
|
294
|
+
break
|
295
|
+
when skip(IGNORE)
|
296
|
+
;
|
297
|
+
else
|
298
|
+
raise ParserError, "unexpected token in array at '#{peek(20)}'!"
|
299
|
+
end
|
300
|
+
end
|
301
|
+
result
|
302
|
+
end
|
303
|
+
|
304
|
+
def parse_object
|
305
|
+
result = {}
|
306
|
+
until eos?
|
307
|
+
case
|
308
|
+
when (string = parse_string) != UNPARSED
|
309
|
+
skip(IGNORE)
|
310
|
+
unless scan(PAIR_DELIMITER)
|
311
|
+
raise ParserError, "expected ':' in object at '#{peek(20)}'!"
|
312
|
+
end
|
313
|
+
skip(IGNORE)
|
314
|
+
unless (value = parse_value).equal? UNPARSED
|
315
|
+
result[string] = value
|
316
|
+
skip(IGNORE)
|
317
|
+
unless scan(COLLECTION_DELIMITER) or match?(OBJECT_CLOSE)
|
318
|
+
raise ParserError,
|
319
|
+
"expected ',' or '}' in object at '#{peek(20)}'!"
|
320
|
+
end
|
321
|
+
else
|
322
|
+
raise ParserError, "expected value in object at '#{peek(20)}'!"
|
323
|
+
end
|
324
|
+
when scan(OBJECT_CLOSE)
|
325
|
+
if klassname = result['json_class']
|
326
|
+
klass = klassname.sub(/^:+/, '').split(/::/).inject(Object) do |p,k|
|
327
|
+
p.const_get(k) rescue nil
|
328
|
+
end
|
329
|
+
break unless klass and klass.json_creatable?
|
330
|
+
result = klass.json_create(result)
|
331
|
+
end
|
332
|
+
break
|
333
|
+
when skip(IGNORE)
|
334
|
+
;
|
335
|
+
else
|
336
|
+
raise ParserError, "unexpected token in object at '#{peek(20)}'!"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
result
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# This class is used to create State instances, that are use to hold data
|
344
|
+
# while unparsing a Ruby data structure into a JSON string.
|
345
|
+
class State
|
346
|
+
# Creates a State object from _opts_, which ought to be Hash to create a
|
347
|
+
# new State instance configured by opts, something else to create an
|
348
|
+
# unconfigured instance. If _opts_ is a State object, it is just returned.
|
349
|
+
def self.from_state(opts)
|
350
|
+
case opts
|
351
|
+
when self
|
352
|
+
opts
|
353
|
+
when Hash
|
354
|
+
new(opts)
|
355
|
+
else
|
356
|
+
new
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
# Instantiates a new State object, configured by _opts_.
|
361
|
+
def initialize(opts = {})
|
362
|
+
@indent = opts[:indent] || ''
|
363
|
+
@space = opts[:space] || ''
|
364
|
+
@object_nl = opts[:object_nl] || ''
|
365
|
+
@array_nl = opts[:array_nl] || ''
|
366
|
+
@seen = {}
|
367
|
+
end
|
368
|
+
|
369
|
+
# This string is used to indent levels in the JSON string.
|
370
|
+
attr_accessor :indent
|
371
|
+
|
372
|
+
# This string is used to include a space between the tokens in a JSON
|
373
|
+
# string.
|
374
|
+
attr_accessor :space
|
375
|
+
|
376
|
+
# This string is put at the end of a line that holds a JSON object (or
|
377
|
+
# Hash).
|
378
|
+
attr_accessor :object_nl
|
379
|
+
|
380
|
+
# This string is put at the end of a line that holds a JSON array.
|
381
|
+
attr_accessor :array_nl
|
382
|
+
|
383
|
+
# Returns _true_, if _object_ was already seen during this Unparsing run.
|
384
|
+
def seen?(object)
|
385
|
+
@seen.key?(object.__id__)
|
386
|
+
end
|
387
|
+
|
388
|
+
# Remember _object_, to find out if it was already encountered (to find out
|
389
|
+
# if a cyclic data structure is unparsed).
|
390
|
+
def remember(object)
|
391
|
+
@seen[object.__id__] = true
|
392
|
+
end
|
393
|
+
|
394
|
+
# Forget _object_ for this Unparsing run.
|
395
|
+
def forget(object)
|
396
|
+
@seen.delete object.__id__
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
module_function
|
401
|
+
|
402
|
+
# Convert _string_ from UTF8 encoding to UTF16 (big endian) encoding and
|
403
|
+
# return it.
|
404
|
+
def utf8_to_utf16(string)
|
405
|
+
JSON::UTF8toUTF16.iconv(string).unpack('H*')[0]
|
406
|
+
end
|
407
|
+
|
408
|
+
# Convert _string_ from UTF16 (big endian) encoding to UTF8 encoding and
|
409
|
+
# return it.
|
410
|
+
def utf16_to_utf8(string)
|
411
|
+
bytes = '' << string[0, 2].to_i(16) << string[2, 2].to_i(16)
|
412
|
+
JSON::UTF16toUTF8.iconv(bytes)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
|
416
|
+
# UTF16 big endian characters as \u????, and return it.
|
417
|
+
def utf8_to_json(string)
|
418
|
+
i, n, result = 0, string.size, ''
|
419
|
+
while i < n
|
420
|
+
char = string[i]
|
421
|
+
case
|
422
|
+
when char == ?\b then result << '\b'
|
423
|
+
when char == ?\t then result << '\t'
|
424
|
+
when char == ?\n then result << '\n'
|
425
|
+
when char == ?\f then result << '\f'
|
426
|
+
when char == ?\r then result << '\r'
|
427
|
+
when char == ?" then result << '\"'
|
428
|
+
when char == ?\\ then result << '\\\\'
|
429
|
+
when char.between?(0x0, 0x1f) then result << "\\u%04x" % char
|
430
|
+
when char.between?(0x20, 0x7f) then result << char
|
431
|
+
when !(JSON.support_unicode? && $KCODE == 'UTF8')
|
432
|
+
# if utf8 mode is switched off or unicode not supported, just pass
|
433
|
+
# bytes through:
|
434
|
+
result << char
|
435
|
+
when char & 0xe0 == 0xc0
|
436
|
+
result << '\u' << utf8_to_utf16(string[i, 2])
|
437
|
+
i += 1
|
438
|
+
when char & 0xf0 == 0xe0
|
439
|
+
result << '\u' << utf8_to_utf16(string[i, 3])
|
440
|
+
i += 2
|
441
|
+
when char & 0xf8 == 0xf0
|
442
|
+
result << '\u' << utf8_to_utf16(string[i, 4])
|
443
|
+
i += 3
|
444
|
+
when char & 0xfc == 0xf8
|
445
|
+
result << '\u' << utf8_to_utf16(string[i, 5])
|
446
|
+
i += 4
|
447
|
+
when char & 0xfe == 0xfc
|
448
|
+
result << '\u' << utf8_to_utf16(string[i, 6])
|
449
|
+
i += 5
|
450
|
+
else
|
451
|
+
raise JSON::UnparserError, "Encountered unknown UTF-8 byte: %x!" % char
|
452
|
+
end
|
453
|
+
i += 1
|
454
|
+
end
|
455
|
+
result
|
456
|
+
end
|
457
|
+
|
458
|
+
# Parse the JSON string _source_ into a Ruby data structure and return it.
|
459
|
+
def parse(source)
|
460
|
+
Parser.new(source).parse
|
461
|
+
end
|
462
|
+
|
463
|
+
# Unparse the Ruby data structure _obj_ into a single line JSON string and
|
464
|
+
# return it. _state_ is a JSON::State object, that can be used to configure
|
465
|
+
# the output further.
|
466
|
+
def unparse(obj, state = nil)
|
467
|
+
obj.to_json(JSON::State.from_state(state))
|
468
|
+
end
|
469
|
+
|
470
|
+
# Unparse the Ruby data structure _obj_ into a JSON string and return it.
|
471
|
+
# The returned string is a prettier form of the string returned by #unparse.
|
472
|
+
def pretty_unparse(obj)
|
473
|
+
state = JSON::State.new(
|
474
|
+
:indent => ' ',
|
475
|
+
:space => ' ',
|
476
|
+
:object_nl => "\n",
|
477
|
+
:array_nl => "\n"
|
478
|
+
)
|
479
|
+
obj.to_json(state)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
require 'yaml'
|
484
|
+
|
485
|
+
class Object
|
486
|
+
#--
|
487
|
+
# Converts this object to a string (calling #to_s), converts
|
488
|
+
# it to a JSON string, and returns the result. This is a fallback, if no
|
489
|
+
# special method #to_json was defined for some object.
|
490
|
+
# _state_ is a JSON::State object, that can also be used
|
491
|
+
# to configure the produced JSON string output further.
|
492
|
+
#
|
493
|
+
#def to_json(*) to_s.to_json end
|
494
|
+
#++
|
495
|
+
|
496
|
+
# Fallback to_json method uses to_yaml, strips the type header and then
|
497
|
+
# reloads as a pure hash, which is then converted to JSON.
|
498
|
+
def to_json(*)
|
499
|
+
str = to_yaml
|
500
|
+
str.sub!(/^---.*?$/, '---')
|
501
|
+
YAML.load(str).to_json
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
class Module
|
506
|
+
def to_json(*) to_s.to_json end
|
507
|
+
end
|
508
|
+
|
509
|
+
class Hash
|
510
|
+
# Returns a JSON string containing a JSON object, that is unparsed from
|
511
|
+
# this Hash instance.
|
512
|
+
# _state_ is a JSON::State object, that can also be used to configure the
|
513
|
+
# produced JSON string output further.
|
514
|
+
# _depth_ is used to find out nesting depth, to indent accordingly.
|
515
|
+
def to_json(state = nil, depth = 0)
|
516
|
+
state = JSON::State.from_state(state)
|
517
|
+
json_check_circular(state) { json_transform(state, depth) }
|
518
|
+
end
|
519
|
+
|
520
|
+
private
|
521
|
+
|
522
|
+
def json_check_circular(state)
|
523
|
+
if state
|
524
|
+
state.seen?(self) and raise JSON::CircularDatastructure,
|
525
|
+
"circular data structures not supported!"
|
526
|
+
state.remember self
|
527
|
+
end
|
528
|
+
yield
|
529
|
+
ensure
|
530
|
+
state and state.forget self
|
531
|
+
end
|
532
|
+
|
533
|
+
def json_shift(state, depth)
|
534
|
+
state and not state.object_nl.empty? or return ''
|
535
|
+
state.indent * depth
|
536
|
+
end
|
537
|
+
|
538
|
+
def json_transform(state, depth)
|
539
|
+
delim = ','
|
540
|
+
delim << state.object_nl if state
|
541
|
+
result = '{'
|
542
|
+
result << state.object_nl if state
|
543
|
+
result << map { |key,value|
|
544
|
+
json_shift(state, depth + 1) <<
|
545
|
+
key.to_s.to_json(state, depth + 1) <<
|
546
|
+
':' << state.space << value.to_json(state, depth + 1)
|
547
|
+
}.join(delim)
|
548
|
+
result << state.object_nl if state
|
549
|
+
result << json_shift(state, depth)
|
550
|
+
result << '}'
|
551
|
+
result
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
class Array
|
556
|
+
# Returns a JSON string containing a JSON array, that is unparsed from
|
557
|
+
# this Array instance.
|
558
|
+
# _state_ is a JSON::State object, that can also be used to configure the
|
559
|
+
# produced JSON string output further.
|
560
|
+
# _depth_ is used to find out nesting depth, to indent accordingly.
|
561
|
+
def to_json(state = nil, depth = 0)
|
562
|
+
state = JSON::State.from_state(state)
|
563
|
+
json_check_circular(state) { json_transform(state, depth) }
|
564
|
+
end
|
565
|
+
|
566
|
+
private
|
567
|
+
|
568
|
+
def json_check_circular(state)
|
569
|
+
if state
|
570
|
+
state.seen?(self) and raise JSON::CircularDatastructure,
|
571
|
+
"circular data structures not supported!"
|
572
|
+
state.remember self
|
573
|
+
end
|
574
|
+
yield
|
575
|
+
ensure
|
576
|
+
state and state.forget self
|
577
|
+
end
|
578
|
+
|
579
|
+
def json_shift(state, depth)
|
580
|
+
state and not state.array_nl.empty? or return ''
|
581
|
+
state.indent * depth
|
582
|
+
end
|
583
|
+
|
584
|
+
def json_transform(state, depth)
|
585
|
+
delim = ','
|
586
|
+
delim << state.array_nl if state
|
587
|
+
result = '['
|
588
|
+
result << state.array_nl if state
|
589
|
+
result << map { |value|
|
590
|
+
json_shift(state, depth + 1) << value.to_json(state, depth + 1)
|
591
|
+
}.join(delim)
|
592
|
+
result << state.array_nl if state
|
593
|
+
result << json_shift(state, depth)
|
594
|
+
result << ']'
|
595
|
+
result
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
class Integer #:nodoc:
|
600
|
+
# Returns a JSON string representation for this Integer number.
|
601
|
+
def to_json(*) to_s end
|
602
|
+
end
|
603
|
+
|
604
|
+
class Float #:nodoc:
|
605
|
+
# Returns a JSON string representation for this Float number.
|
606
|
+
def to_json(*) to_s end
|
607
|
+
end
|
608
|
+
|
609
|
+
class String
|
610
|
+
# This string should be encoded with UTF-8 (if JSON unicode support is
|
611
|
+
# enabled). A call to this method returns a JSON string
|
612
|
+
# encoded with UTF16 big endian characters as \u????. If
|
613
|
+
# JSON.support_unicode? is false only control characters are encoded this
|
614
|
+
# way, all 8-bit bytes are just passed through.
|
615
|
+
def to_json(*)
|
616
|
+
'"' << JSON::utf8_to_json(self) << '"'
|
617
|
+
end
|
618
|
+
|
619
|
+
# Raw Strings are JSON Objects (the raw bytes are stored in an array for the
|
620
|
+
# key "raw"). The Ruby String can be created by this class method.
|
621
|
+
def self.json_create(o)
|
622
|
+
o['raw'].pack('C*')
|
623
|
+
end
|
624
|
+
|
625
|
+
# This method creates a raw object, that can be nested into other data
|
626
|
+
# structures and will be unparsed as a raw string.
|
627
|
+
def to_json_raw_object
|
628
|
+
{
|
629
|
+
'json_class' => self.class.name,
|
630
|
+
'raw' => self.unpack('C*'),
|
631
|
+
}
|
632
|
+
end
|
633
|
+
|
634
|
+
# This method should be used, if you want to convert raw strings to JSON
|
635
|
+
# instead of UTF-8 strings, e. g. binary data (and JSON Unicode support is
|
636
|
+
# enabled).
|
637
|
+
def to_json_raw(*args)
|
638
|
+
to_json_raw_object.to_json(*args)
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
class TrueClass #:nodoc:
|
643
|
+
# Returns a JSON string for true: 'true'.
|
644
|
+
def to_json(*) to_s end
|
645
|
+
end
|
646
|
+
|
647
|
+
class FalseClass #:nodoc:
|
648
|
+
# Returns a JSON string for false: 'false'.
|
649
|
+
def to_json(*) to_s end
|
650
|
+
end
|
651
|
+
|
652
|
+
class NilClass #:nodoc:
|
653
|
+
# Returns a JSON string for nil: 'null'.
|
654
|
+
def to_json(*) 'null' end
|
655
|
+
end
|
656
|
+
|
657
|
+
module Kernel
|
658
|
+
# Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
|
659
|
+
# one line.
|
660
|
+
def j(*objs)
|
661
|
+
objs.each do |obj|
|
662
|
+
puts JSON::unparse(obj)
|
663
|
+
end
|
664
|
+
nil
|
665
|
+
end
|
666
|
+
|
667
|
+
# Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with
|
668
|
+
# indentation and over many lines.
|
669
|
+
def jj(*objs)
|
670
|
+
objs.each do |obj|
|
671
|
+
puts JSON::pretty_unparse(obj)
|
672
|
+
end
|
673
|
+
nil
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
class Class
|
678
|
+
# Returns true, if this class can be used to create an instance
|
679
|
+
# from a serialised JSON string. The class has to implement a class
|
680
|
+
# method _json_create_ that expects a hash as first parameter, which includes
|
681
|
+
# the required data.
|
682
|
+
def json_creatable?
|
683
|
+
respond_to?(:json_create)
|
684
|
+
end
|
685
|
+
end
|
686
|
+
# vim: set et sw=2 ts=2:
|
687
|
+
|
688
|
+
|
689
|
+
|
690
|
+
|
691
|
+
# _____ _
|
692
|
+
# |_ _|__ ___| |_
|
693
|
+
# | |/ _ \/ __| __|
|
694
|
+
# | | __/\__ \ |_
|
695
|
+
# |_|\___||___/\__|
|
696
|
+
#
|
697
|
+
=begin test
|
698
|
+
|
699
|
+
require 'test/unit'
|
700
|
+
|
701
|
+
class TC_JSON < Test::Unit::TestCase
|
702
|
+
|
703
|
+
include JSON
|
704
|
+
|
705
|
+
class A
|
706
|
+
def initialize(a)
|
707
|
+
@a = a
|
708
|
+
end
|
709
|
+
|
710
|
+
attr_reader :a
|
711
|
+
|
712
|
+
def ==(other)
|
713
|
+
a == other.a
|
714
|
+
end
|
715
|
+
|
716
|
+
def self.json_create(object)
|
717
|
+
new(*object['args'])
|
718
|
+
end
|
719
|
+
|
720
|
+
def to_json(*args)
|
721
|
+
{
|
722
|
+
'json_class' => self.class,
|
723
|
+
'args' => [ @a ],
|
724
|
+
}.to_json(*args)
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
def setup
|
729
|
+
$KCODE = 'UTF8'
|
730
|
+
@ary = [1, "foo", 3.14, 4711.0, 2.718, nil, [1,-2,3], false, true]
|
731
|
+
@ary_to_parse = ["1", '"foo"', "3.14", "4711.0", "2.718", "null",
|
732
|
+
"[1,-2,3]", "false", "true"]
|
733
|
+
@hash = {
|
734
|
+
'a' => 2,
|
735
|
+
'b' => 3.141,
|
736
|
+
'c' => 'c',
|
737
|
+
'd' => [ 1, "b", 3.14 ],
|
738
|
+
'e' => { 'foo' => 'bar' },
|
739
|
+
'g' => "\"\0\037",
|
740
|
+
'h' => 1000.0,
|
741
|
+
'i' => 0.001
|
742
|
+
}
|
743
|
+
@json = '{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
|
744
|
+
'"g":"\\"\\u0000\\u001f","h":1.0E3,"i":1.0E-3}'
|
745
|
+
@json2 = '{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
|
746
|
+
'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}'
|
747
|
+
end
|
748
|
+
|
749
|
+
def test_parse_value
|
750
|
+
assert_equal("", parse('""'))
|
751
|
+
assert_equal("\\", parse('"\\\\"'))
|
752
|
+
assert_equal('"', parse('"\""'))
|
753
|
+
assert_equal('\\"\\', parse('"\\\\\\"\\\\"'))
|
754
|
+
assert_equal("\\a\"\b\f\n\r\t\0\037",
|
755
|
+
parse('"\\a\"\b\f\n\r\t\u0000\u001f"'))
|
756
|
+
for i in 0 ... @ary.size
|
757
|
+
assert_equal(@ary[i], parse(@ary_to_parse[i]))
|
758
|
+
end
|
759
|
+
end
|
760
|
+
|
761
|
+
def test_parse_array
|
762
|
+
assert_equal([], parse('[]'))
|
763
|
+
assert_equal([], parse(' [ ] '))
|
764
|
+
assert_equal([1], parse('[1]'))
|
765
|
+
assert_equal([1], parse(' [ 1 ] '))
|
766
|
+
assert_equal(@ary,
|
767
|
+
parse('[1,"foo",3.14,47.11e+2,2718.E-3,null,[1,-2,3],false,true]'))
|
768
|
+
assert_equal(@ary, parse(%Q{ [ 1 , "foo" , 3.14 \t , 47.11e+2
|
769
|
+
, 2718.E-3 ,\n null , [1, -2, 3 ], false , true\n ] }))
|
770
|
+
end
|
771
|
+
|
772
|
+
def test_parse_object
|
773
|
+
assert_equal({}, parse('{}'))
|
774
|
+
assert_equal({}, parse(' { } '))
|
775
|
+
assert_equal({'foo'=>'bar'}, parse('{"foo":"bar"}'))
|
776
|
+
assert_equal({'foo'=>'bar'}, parse(' { "foo" : "bar" } '))
|
777
|
+
end
|
778
|
+
|
779
|
+
def test_unparse
|
780
|
+
json = unparse(@hash)
|
781
|
+
assert_equal(@json2, json)
|
782
|
+
parsed_json = parse(json)
|
783
|
+
assert_equal(@hash, parsed_json)
|
784
|
+
json = unparse({1=>2})
|
785
|
+
assert_equal('{"1":2}', json)
|
786
|
+
parsed_json = parse(json)
|
787
|
+
assert_equal({"1"=>2}, parsed_json)
|
788
|
+
end
|
789
|
+
|
790
|
+
def test_parser_reset
|
791
|
+
parser = Parser.new(@json)
|
792
|
+
assert_equal(@hash, parser.parse)
|
793
|
+
assert_equal(@hash, parser.parse)
|
794
|
+
end
|
795
|
+
|
796
|
+
def test_unicode
|
797
|
+
assert_equal '""', ''.to_json
|
798
|
+
assert_equal '"\\b"', "\b".to_json
|
799
|
+
assert_equal '"\u0001"', 0x1.chr.to_json
|
800
|
+
assert_equal '"\u001f"', 0x1f.chr.to_json
|
801
|
+
assert_equal '" "', ' '.to_json
|
802
|
+
assert_equal "\"#{0x7f.chr}\"", 0x7f.chr.to_json
|
803
|
+
utf8 = '© ≠ €!'
|
804
|
+
json = '"\u00a9 \u2260 \u20ac!"'
|
805
|
+
assert_equal json, utf8.to_json
|
806
|
+
assert_equal utf8, parse(json)
|
807
|
+
utf8 = "\343\201\202\343\201\204\343\201\206\343\201\210\343\201\212"
|
808
|
+
json = '"\u3042\u3044\u3046\u3048\u304a"'
|
809
|
+
assert_equal json, utf8.to_json
|
810
|
+
assert_equal utf8, parse(json)
|
811
|
+
utf8 = 'საქართველო'
|
812
|
+
json = '"\u10e1\u10d0\u10e5\u10d0\u10e0\u10d7\u10d5\u10d4\u10da\u10dd"'
|
813
|
+
assert_equal json, utf8.to_json
|
814
|
+
assert_equal utf8, parse(json)
|
815
|
+
end
|
816
|
+
|
817
|
+
def test_comments
|
818
|
+
json = <<EOT
|
819
|
+
{
|
820
|
+
"key1":"value1", // eol comment
|
821
|
+
"key2":"value2" /* multi line
|
822
|
+
* comment */,
|
823
|
+
"key3":"value3" /* multi line
|
824
|
+
// nested eol comment
|
825
|
+
* comment */
|
826
|
+
}
|
827
|
+
EOT
|
828
|
+
assert_equal(
|
829
|
+
{ "key1" => "value1", "key2" => "value2", "key3" => "value3" },
|
830
|
+
parse(json))
|
831
|
+
json = <<EOT
|
832
|
+
{
|
833
|
+
"key1":"value1" /* multi line
|
834
|
+
// nested eol comment
|
835
|
+
/* illegal nested multi line comment */
|
836
|
+
* comment */
|
837
|
+
}
|
838
|
+
EOT
|
839
|
+
assert_raises(ParserError) { parse(json) }
|
840
|
+
json = <<EOT
|
841
|
+
{
|
842
|
+
"key1":"value1" /* multi line
|
843
|
+
// nested eol comment
|
844
|
+
closed multi comment */
|
845
|
+
and again, throw an Error */
|
846
|
+
}
|
847
|
+
EOT
|
848
|
+
assert_raises(ParserError) { parse(json) }
|
849
|
+
json = <<EOT
|
850
|
+
{
|
851
|
+
"key1":"value1" /*/*/
|
852
|
+
}
|
853
|
+
EOT
|
854
|
+
assert_equal({ "key1" => "value1" }, parse(json))
|
855
|
+
end
|
856
|
+
|
857
|
+
def test_extended_json
|
858
|
+
a = A.new(666)
|
859
|
+
json = a.to_json
|
860
|
+
a_again = JSON.parse(json)
|
861
|
+
assert_kind_of a.class, a_again
|
862
|
+
assert_equal a, a_again
|
863
|
+
end
|
864
|
+
|
865
|
+
def test_raw_strings
|
866
|
+
raw = ''
|
867
|
+
raw_array = []
|
868
|
+
for i in 0..255
|
869
|
+
raw << i
|
870
|
+
raw_array << i
|
871
|
+
end
|
872
|
+
json = raw.to_json_raw
|
873
|
+
json_raw_object = raw.to_json_raw_object
|
874
|
+
hash = { 'json_class' => 'String', 'raw'=> raw_array }
|
875
|
+
assert_equal hash, json_raw_object
|
876
|
+
json_raw = <<EOT.chomp
|
877
|
+
{\"json_class\":\"String\",\"raw\":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255]}
|
878
|
+
EOT
|
879
|
+
# "
|
880
|
+
assert_equal json_raw, json
|
881
|
+
raw_again = JSON.parse(json)
|
882
|
+
assert_equal raw, raw_again
|
883
|
+
end
|
884
|
+
|
885
|
+
def test_utf8_mode
|
886
|
+
$KCODE = 'NONE'
|
887
|
+
utf8 = "© ≠ €! - \001"
|
888
|
+
json = "\"© ≠ €! - \\u0001\""
|
889
|
+
assert_equal json, utf8.to_json
|
890
|
+
assert_equal utf8, parse(json)
|
891
|
+
assert JSON.support_unicode?
|
892
|
+
$KCODE = 'UTF8'
|
893
|
+
utf8 = '© ≠ €!'
|
894
|
+
json = '"\u00a9 \u2260 \u20ac!"'
|
895
|
+
assert_equal json, utf8.to_json
|
896
|
+
assert_equal utf8, parse(json)
|
897
|
+
JSON.support_unicode = false
|
898
|
+
assert !JSON.support_unicode?
|
899
|
+
utf8 = "© ≠ €! - \001"
|
900
|
+
json = "\"© ≠ €! - \\u0001\""
|
901
|
+
assert_equal json, utf8.to_json
|
902
|
+
assert_equal utf8, parse(json)
|
903
|
+
end
|
904
|
+
|
905
|
+
def test_backslash
|
906
|
+
json = '"\\\\.(?i:gif|jpe?g|png)$"'
|
907
|
+
data = JSON.parse(json)
|
908
|
+
assert_equal json, JSON.unparse(data)
|
909
|
+
json = '"\\""'
|
910
|
+
data = JSON.parse(json)
|
911
|
+
assert_equal json, JSON.unparse(data)
|
912
|
+
end
|
913
|
+
end
|
914
|
+
|
915
|
+
=end
|
916
|
+
|
917
|
+
# vim: set et sw=2 ts=2:
|