microfiche 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,2 @@
1
+ require 'autorake'
2
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0 stable (2007-03-16)
@@ -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: