From 825ce503c0e52edb944def195be0d3d55e3e71b3 Mon Sep 17 00:00:00 2001 From: naruse Date: Tue, 4 Dec 2007 08:09:44 +0000 Subject: * lib/json.rb, lib/json/*: moved to ext/json/lib. -- M trunk/ChangeLog D trunk/lib/json D trunk/lib/json.rb A trunk/ext/json/lib A trunk/ext/json/lib/json A trunk/ext/json/lib/json.rb A trunk/ext/json/extconf.rb git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@14100 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 4 + ext/json/extconf.rb | 3 + ext/json/lib/json.rb | 235 ++++++ ext/json/lib/json/Array.xpm | 21 + ext/json/lib/json/FalseClass.xpm | 21 + ext/json/lib/json/Hash.xpm | 21 + ext/json/lib/json/Key.xpm | 73 ++ ext/json/lib/json/NilClass.xpm | 21 + ext/json/lib/json/Numeric.xpm | 28 + ext/json/lib/json/String.xpm | 96 +++ ext/json/lib/json/TrueClass.xpm | 21 + ext/json/lib/json/add/core.rb | 120 +++ ext/json/lib/json/add/rails.rb | 58 ++ ext/json/lib/json/common.rb | 354 +++++++++ ext/json/lib/json/editor.rb | 1363 +++++++++++++++++++++++++++++++ ext/json/lib/json/ext.rb | 13 + ext/json/lib/json/json.xpm | 1499 +++++++++++++++++++++++++++++++++++ ext/json/lib/json/pure.rb | 75 ++ ext/json/lib/json/pure/generator.rb | 394 +++++++++ ext/json/lib/json/pure/parser.rb | 259 ++++++ ext/json/lib/json/version.rb | 9 + lib/json.rb | 235 ------ lib/json/Array.xpm | 21 - lib/json/FalseClass.xpm | 21 - lib/json/Hash.xpm | 21 - lib/json/Key.xpm | 73 -- lib/json/NilClass.xpm | 21 - lib/json/Numeric.xpm | 28 - lib/json/String.xpm | 96 --- lib/json/TrueClass.xpm | 21 - lib/json/add/core.rb | 120 --- lib/json/add/rails.rb | 58 -- lib/json/common.rb | 354 --------- lib/json/editor.rb | 1363 ------------------------------- lib/json/ext.rb | 13 - lib/json/json.xpm | 1499 ----------------------------------- lib/json/pure.rb | 75 -- lib/json/pure/generator.rb | 394 --------- lib/json/pure/parser.rb | 259 ------ lib/json/version.rb | 9 - 40 files changed, 4688 insertions(+), 4681 deletions(-) create mode 100644 ext/json/extconf.rb create mode 100644 ext/json/lib/json.rb create mode 100644 ext/json/lib/json/Array.xpm create mode 100644 ext/json/lib/json/FalseClass.xpm create mode 100644 ext/json/lib/json/Hash.xpm create mode 100644 ext/json/lib/json/Key.xpm create mode 100644 ext/json/lib/json/NilClass.xpm create mode 100644 ext/json/lib/json/Numeric.xpm create mode 100644 ext/json/lib/json/String.xpm create mode 100644 ext/json/lib/json/TrueClass.xpm create mode 100644 ext/json/lib/json/add/core.rb create mode 100644 ext/json/lib/json/add/rails.rb create mode 100644 ext/json/lib/json/common.rb create mode 100644 ext/json/lib/json/editor.rb create mode 100644 ext/json/lib/json/ext.rb create mode 100644 ext/json/lib/json/json.xpm create mode 100644 ext/json/lib/json/pure.rb create mode 100644 ext/json/lib/json/pure/generator.rb create mode 100644 ext/json/lib/json/pure/parser.rb create mode 100644 ext/json/lib/json/version.rb delete mode 100644 lib/json.rb delete mode 100644 lib/json/Array.xpm delete mode 100644 lib/json/FalseClass.xpm delete mode 100644 lib/json/Hash.xpm delete mode 100644 lib/json/Key.xpm delete mode 100644 lib/json/NilClass.xpm delete mode 100644 lib/json/Numeric.xpm delete mode 100644 lib/json/String.xpm delete mode 100644 lib/json/TrueClass.xpm delete mode 100644 lib/json/add/core.rb delete mode 100644 lib/json/add/rails.rb delete mode 100644 lib/json/common.rb delete mode 100644 lib/json/editor.rb delete mode 100644 lib/json/ext.rb delete mode 100644 lib/json/json.xpm delete mode 100644 lib/json/pure.rb delete mode 100644 lib/json/pure/generator.rb delete mode 100644 lib/json/pure/parser.rb delete mode 100644 lib/json/version.rb diff --git a/ChangeLog b/ChangeLog index 5f3730e452..e774963915 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Tue Dec 4 17:07:25 2007 NARUSE, Yui + + * lib/json.rb, lib/json/*: moved to ext/json/lib. + Tue Dec 4 16:34:46 2007 Nobuyoshi Nakada * ext/iconv/iconv.c (iconv_create): achieve target encoding. diff --git a/ext/json/extconf.rb b/ext/json/extconf.rb new file mode 100644 index 0000000000..850798c643 --- /dev/null +++ b/ext/json/extconf.rb @@ -0,0 +1,3 @@ +require 'mkmf' +create_makefile('json') + diff --git a/ext/json/lib/json.rb b/ext/json/lib/json.rb new file mode 100644 index 0000000000..3b0b711550 --- /dev/null +++ b/ext/json/lib/json.rb @@ -0,0 +1,235 @@ +require 'json/common' +# = json - JSON for Ruby +# +# == Description +# +# This is a implementation of the JSON specification according to RFC 4627 +# (http://www.ietf.org/rfc/rfc4627.txt). Starting from version 1.0.0 on there +# will be two variants available: +# +# * A pure ruby variant, that relies on the iconv and the stringscan +# extensions, which are both part of the ruby standard library. +# * The quite a bit faster C extension variant, which is in parts implemented +# in C and comes with its own unicode conversion functions and a parser +# generated by the ragel state machine compiler +# (http://www.cs.queensu.ca/~thurston/ragel). +# +# Both variants of the JSON generator escape all non-ASCII an control +# characters with \uXXXX escape sequences, and support UTF-16 surrogate pairs +# in order to be able to generate the whole range of unicode code points. This +# means that generated JSON text is encoded as UTF-8 (because ASCII is a subset +# of UTF-8) and at the same time avoids decoding problems for receiving +# endpoints, that don't expect UTF-8 encoded texts. On the negative side this +# may lead to a bit longer strings than necessarry. +# +# All strings, that are to be encoded as JSON strings, should be UTF-8 byte +# sequences on the Ruby side. To encode raw binary strings, that aren't UTF-8 +# encoded, please use the to_json_raw_object method of String (which produces +# an object, that contains a byte array) and decode the result on the receiving +# endpoint. +# +# == Author +# +# Florian Frank +# +# == License +# +# This software is distributed under the same license as Ruby itself, see +# http://www.ruby-lang.org/en/LICENSE.txt. +# +# == Download +# +# The latest version of this library can be downloaded at +# +# * http://rubyforge.org/frs?group_id=953 +# +# Online Documentation should be located at +# +# * http://json.rubyforge.org +# +# == Usage +# +# To use JSON you can +# require 'json' +# to load the installed variant (either the extension 'json' or the pure +# variant 'json_pure'). If you have installed the extension variant, you can +# pick either the extension variant or the pure variant by typing +# require 'json/ext' +# or +# require 'json/pure' +# +# You can choose to load a set of common additions to ruby core's objects if +# you +# require 'json/add/core' +# +# After requiring this you can, e. g., serialise/deserialise Ruby ranges: +# +# JSON JSON(1..10) # => 1..10 +# +# To find out how to add JSON support to other or your own classes, read the +# Examples section below. +# +# To get the best compatibility to rails' JSON implementation, you can +# require 'json/add/rails' +# +# Both of the additions attempt to require 'json' (like above) first, if it has +# not been required yet. +# +# == Speed Comparisons +# +# I have created some benchmark results (see the benchmarks subdir of the +# package) for the JSON-Parser to estimate the speed up in the C extension: +# +# JSON::Pure::Parser:: 28.90 calls/second +# JSON::Ext::Parser:: 505.50 calls/second +# +# This is ca. 17.5 times the speed of the pure Ruby implementation. +# +# I have benchmarked the JSON-Generator as well. This generates a few more +# values, because there are different modes, that also influence the achieved +# speed: +# +# * JSON::Pure::Generator: +# generate:: 35.06 calls/second +# pretty_generate:: 34.00 calls/second +# fast_generate:: 41.06 calls/second +# +# * JSON::Ext::Generator: +# generate:: 492.11 calls/second +# pretty_generate:: 348.85 calls/second +# fast_generate:: 541.60 calls/second +# +# * Speedup Ext/Pure: +# generate safe:: 14.0 times +# generate pretty:: 10.3 times +# generate fast:: 13.2 times +# +# The rails framework includes a generator as well, also it seems to be rather +# slow: I measured only 23.87 calls/second which is slower than any of my pure +# generator results. Here a comparison of the different speedups with the Rails +# measurement as the divisor: +# +# * Speedup Pure/Rails: +# generate safe:: 1.5 times +# generate pretty:: 1.4 times +# generate fast:: 1.7 times +# +# * Speedup Ext/Rails: +# generate safe:: 20.6 times +# generate pretty:: 14.6 times +# generate fast:: 22.7 times +# +# To achieve the fastest JSON text output, you can use the +# fast_generate/fast_unparse methods. Beware, that this will disable the +# checking for circular Ruby data structures, which may cause JSON to go into +# an infinite loop. +# +# == Examples +# +# To create a JSON text from a ruby data structure, you +# can call JSON.generate (or JSON.unparse) like that: +# +# json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] +# # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]" +# +# To create a valid JSON text you have to make sure, that the output is +# embedded in either a JSON array [] or a JSON object {}. The easiest way to do +# this, is by putting your values in a Ruby Array or Hash instance. +# +# To get back a ruby data structure from a JSON text, you have to call +# JSON.parse on it: +# +# JSON.parse json +# # => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"] +# +# Note, that the range from the original data structure is a simple +# string now. The reason for this is, that JSON doesn't support ranges +# or arbitrary classes. In this case the json library falls back to call +# Object#to_json, which is the same as #to_s.to_json. +# +# It's possible to add JSON support serialization to arbitrary classes by +# simply implementing a more specialized version of the #to_json method, that +# should return a JSON object (a hash converted to JSON with #to_json) like +# this (don't forget the *a for all the arguments): +# +# class Range +# def to_json(*a) +# { +# 'json_class' => self.class.name, # = 'Range' +# 'data' => [ first, last, exclude_end? ] +# }.to_json(*a) +# end +# end +# +# The hash key 'json_class' is the class, that will be asked to deserialise the +# JSON representation later. In this case it's 'Range', but any namespace of +# the form 'A::B' or '::A::B' will do. All other keys are arbitrary and can be +# used to store the necessary data to configure the object to be deserialised. +# +# If a the key 'json_class' is found in a JSON object, the JSON parser checks +# if the given class responds to the json_create class method. If so, it is +# called with the JSON object converted to a Ruby hash. So a range can +# be deserialised by implementing Range.json_create like this: +# +# class Range +# def self.json_create(o) +# new(*o['data']) +# end +# end +# +# Now it possible to serialise/deserialise ranges as well: +# +# json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] +# # => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]" +# JSON.parse json +# # => [1, 2, {"a"=>3.141}, false, true, nil, 4..10] +# +# JSON.generate always creates the shortest possible string representation of a +# ruby data structure in one line. This good for data storage or network +# protocols, but not so good for humans to read. Fortunately there's also +# JSON.pretty_generate (or JSON.pretty_generate) that creates a more +# readable output: +# +# puts JSON.pretty_generate([1, 2, {"a"=>3.141}, false, true, nil, 4..10]) +# [ +# 1, +# 2, +# { +# "a": 3.141 +# }, +# false, +# true, +# null, +# { +# "json_class": "Range", +# "data": [ +# 4, +# 10, +# false +# ] +# } +# ] +# +# There are also the methods Kernel#j for unparse, and Kernel#jj for +# pretty_unparse output to the console, that work analogous to Core Ruby's p +# and the pp library's pp methods. +# +# The script tools/server.rb contains a small example if you want to test, how +# receiving a JSON object from a webrick server in your browser with the +# javasript prototype library (http://www.prototypejs.org) works. +# +module JSON + require 'json/version' + + if VARIANT_BINARY + require 'json/ext' + else + begin + require 'json/ext' + rescue LoadError + require 'json/pure' + end + end + + JSON_LOADED = true +end diff --git a/ext/json/lib/json/Array.xpm b/ext/json/lib/json/Array.xpm new file mode 100644 index 0000000000..27c48011f9 --- /dev/null +++ b/ext/json/lib/json/Array.xpm @@ -0,0 +1,21 @@ +/* XPM */ +static char * Array_xpm[] = { +"16 16 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" .......... ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" .......... ", +" ", +" ", +" "}; diff --git a/ext/json/lib/json/FalseClass.xpm b/ext/json/lib/json/FalseClass.xpm new file mode 100644 index 0000000000..25ce60832d --- /dev/null +++ b/ext/json/lib/json/FalseClass.xpm @@ -0,0 +1,21 @@ +/* XPM */ +static char * False_xpm[] = { +"16 16 2 1", +" c None", +". c #FF0000", +" ", +" ", +" ", +" ...... ", +" . ", +" . ", +" . ", +" ...... ", +" . ", +" . ", +" . ", +" . ", +" . ", +" ", +" ", +" "}; diff --git a/ext/json/lib/json/Hash.xpm b/ext/json/lib/json/Hash.xpm new file mode 100644 index 0000000000..cd8f6f7b53 --- /dev/null +++ b/ext/json/lib/json/Hash.xpm @@ -0,0 +1,21 @@ +/* XPM */ +static char * Hash_xpm[] = { +"16 16 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" . . ", +" . . ", +" . . ", +" ......... ", +" . . ", +" . . ", +" ......... ", +" . . ", +" . . ", +" . . ", +" ", +" ", +" "}; diff --git a/ext/json/lib/json/Key.xpm b/ext/json/lib/json/Key.xpm new file mode 100644 index 0000000000..9fd7281388 --- /dev/null +++ b/ext/json/lib/json/Key.xpm @@ -0,0 +1,73 @@ +/* XPM */ +static char * Key_xpm[] = { +"16 16 54 1", +" c None", +". c #110007", +"+ c #0E0900", +"@ c #000013", +"# c #070600", +"$ c #F6F006", +"% c #ECE711", +"& c #E5EE00", +"* c #16021E", +"= c #120900", +"- c #EDF12B", +"; c #000033", +"> c #0F0000", +", c #FFFE03", +"' c #E6E500", +") c #16021B", +"! c #F7F502", +"~ c #000E00", +"{ c #130000", +"] c #FFF000", +"^ c #FFE711", +"/ c #140005", +"( c #190025", +"_ c #E9DD27", +": c #E7DC04", +"< c #FFEC09", +"[ c #FFE707", +"} c #FFDE10", +"| c #150021", +"1 c #160700", +"2 c #FAF60E", +"3 c #EFE301", +"4 c #FEF300", +"5 c #E7E000", +"6 c #FFFF08", +"7 c #0E0206", +"8 c #040000", +"9 c #03052E", +"0 c #041212", +"a c #070300", +"b c #F2E713", +"c c #F9DE13", +"d c #36091E", +"e c #00001C", +"f c #1F0010", +"g c #FFF500", +"h c #DEDE00", +"i c #050A00", +"j c #FAF14A", +"k c #F5F200", +"l c #040404", +"m c #1A0D00", +"n c #EDE43D", +"o c #ECE007", +" ", +" ", +" .+@ ", +" #$%&* ", +" =-;>,') ", +" >!~{]^/ ", +" (_:<[}| ", +" 1234567 ", +" 890abcd ", +" efghi ", +" >jkl ", +" mnol ", +" >kl ", +" ll ", +" ", +" "}; diff --git a/ext/json/lib/json/NilClass.xpm b/ext/json/lib/json/NilClass.xpm new file mode 100644 index 0000000000..3509f06c99 --- /dev/null +++ b/ext/json/lib/json/NilClass.xpm @@ -0,0 +1,21 @@ +/* XPM */ +static char * False_xpm[] = { +"16 16 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" ... ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" ... ", +" ", +" ", +" "}; diff --git a/ext/json/lib/json/Numeric.xpm b/ext/json/lib/json/Numeric.xpm new file mode 100644 index 0000000000..e071e2ee9c --- /dev/null +++ b/ext/json/lib/json/Numeric.xpm @@ -0,0 +1,28 @@ +/* XPM */ +static char * Numeric_xpm[] = { +"16 16 9 1", +" c None", +". c #FF0000", +"+ c #0000FF", +"@ c #0023DB", +"# c #00EA14", +"$ c #00FF00", +"% c #004FAF", +"& c #0028D6", +"* c #00F20C", +" ", +" ", +" ", +" ... +++@#$$$$ ", +" .+ %& $$ ", +" . + $ ", +" . + $$ ", +" . ++$$$$ ", +" . + $$ ", +" . + $ ", +" . + $ ", +" . + $ $$ ", +" .....++++*$$ ", +" ", +" ", +" "}; diff --git a/ext/json/lib/json/String.xpm b/ext/json/lib/json/String.xpm new file mode 100644 index 0000000000..f79a89cdc1 --- /dev/null +++ b/ext/json/lib/json/String.xpm @@ -0,0 +1,96 @@ +/* XPM */ +static char * String_xpm[] = { +"16 16 77 1", +" c None", +". c #000000", +"+ c #040404", +"@ c #080806", +"# c #090606", +"$ c #EEEAE1", +"% c #E7E3DA", +"& c #E0DBD1", +"* c #D4B46F", +"= c #0C0906", +"- c #E3C072", +"; c #E4C072", +"> c #060505", +", c #0B0A08", +"' c #D5B264", +") c #D3AF5A", +"! c #080602", +"~ c #E1B863", +"{ c #DDB151", +"] c #DBAE4A", +"^ c #DDB152", +"/ c #DDB252", +"( c #070705", +"_ c #0C0A07", +": c #D3A33B", +"< c #020201", +"[ c #DAAA41", +"} c #040302", +"| c #E4D9BF", +"1 c #0B0907", +"2 c #030201", +"3 c #020200", +"4 c #C99115", +"5 c #080704", +"6 c #DBC8A2", +"7 c #E7D7B4", +"8 c #E0CD9E", +"9 c #080601", +"0 c #040400", +"a c #010100", +"b c #0B0B08", +"c c #DCBF83", +"d c #DCBC75", +"e c #DEB559", +"f c #040301", +"g c #BC8815", +"h c #120E07", +"i c #060402", +"j c #0A0804", +"k c #D4A747", +"l c #D6A12F", +"m c #0E0C05", +"n c #C8C1B0", +"o c #1D1B15", +"p c #D7AD51", +"q c #070502", +"r c #080804", +"s c #BC953B", +"t c #C4BDAD", +"u c #0B0807", +"v c #DBAC47", +"w c #1B150A", +"x c #B78A2C", +"y c #D8A83C", +"z c #D4A338", +"A c #0F0B03", +"B c #181105", +"C c #C59325", +"D c #C18E1F", +"E c #060600", +"F c #CC992D", +"G c #B98B25", +"H c #B3831F", +"I c #C08C1C", +"J c #060500", +"K c #0E0C03", +"L c #0D0A00", +" ", +" .+@# ", +" .$%&*= ", +" .-;>,')! ", +" .~. .{]. ", +" .^/. (_:< ", +" .[.}|$12 ", +" 345678}90 ", +" a2bcdefgh ", +" ijkl.mno ", +" self.class.name.to_s, + 's' => tv_sec, + 'u' => tv_usec, + }.to_json(*args) + end +end + +class Date + def self.json_create(object) + civil(*object.values_at('y', 'm', 'd', 'sg')) + end + + def to_json(*args) + { + 'json_class' => self.class.name.to_s, + 'y' => year, + 'm' => month, + 'd' => day, + 'sg' => @sg, + }.to_json(*args) + end +end + +class DateTime + def self.json_create(object) + args = object.values_at('y', 'm', 'd', 'H', 'M', 'S') + of_a, of_b = object['of'].split('/') + args << Rational(of_a.to_i, of_b.to_i) + args << object['sg'] + civil(*args) + end + + def to_json(*args) + { + 'json_class' => self.class.name.to_s, + 'y' => year, + 'm' => month, + 'd' => day, + 'H' => hour, + 'M' => min, + 'S' => sec, + 'of' => offset.to_s, + 'sg' => @sg, + }.to_json(*args) + end +end + +class Range + def self.json_create(object) + new(*object['a']) + end + + def to_json(*args) + { + 'json_class' => self.class.name.to_s, + 'a' => [ first, last, exclude_end? ] + }.to_json(*args) + end +end + +class Struct + def self.json_create(object) + new(*object['v']) + end + + def to_json(*args) + klass = self.class.name.to_s + klass.empty? and raise JSON::JSONError, "Only named structs are supported!" + { + 'json_class' => klass, + 'v' => values, + }.to_json(*args) + end +end + +class Exception + def self.json_create(object) + result = new(object['m']) + result.set_backtrace object['b'] + result + end + + def to_json(*args) + { + 'json_class' => self.class.name.to_s, + 'm' => message, + 'b' => backtrace, + }.to_json(*args) + end +end + +class Regexp + def self.json_create(object) + new(object['s'], object['o']) + end + + def to_json(*) + { + 'json_class' => self.class.name.to_s, + 'o' => options, + 's' => source, + }.to_json + end +end diff --git a/ext/json/lib/json/add/rails.rb b/ext/json/lib/json/add/rails.rb new file mode 100644 index 0000000000..e86ed1aab9 --- /dev/null +++ b/ext/json/lib/json/add/rails.rb @@ -0,0 +1,58 @@ +# This file contains implementations of rails custom objects for +# serialisation/deserialisation. + +unless Object.const_defined?(:JSON) and ::JSON.const_defined?(:JSON_LOADED) and + ::JSON::JSON_LOADED + require 'json' +end + +class Object + def self.json_create(object) + obj = new + for key, value in object + next if key == 'json_class' + instance_variable_set "@#{key}", value + end + obj + end + + def to_json(*a) + result = { + 'json_class' => self.class.name + } + instance_variables.inject(result) do |r, name| + r[name[1..-1]] = instance_variable_get name + r + end + result.to_json(*a) + end +end + +class Symbol + def to_json(*a) + to_s.to_json(*a) + end +end + +module Enumerable + def to_json(*a) + to_a.to_json(*a) + end +end + +# class Regexp +# def to_json(*) +# inspect +# end +# end +# +# The above rails definition has some problems: +# +# 1. { 'foo' => /bar/ }.to_json # => "{foo: /bar/}" +# This isn't valid JSON, because the regular expression syntax is not +# defined in RFC 4627. (And unquoted strings are disallowed there, too.) +# Though it is valid Javascript. +# +# 2. { 'foo' => /bar/mix }.to_json # => "{foo: /bar/mix}" +# This isn't even valid Javascript. + diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb new file mode 100644 index 0000000000..7bc5ae0656 --- /dev/null +++ b/ext/json/lib/json/common.rb @@ -0,0 +1,354 @@ +require 'json/version' + +module JSON + class << self + # If _object_ is string like parse the string and return the parsed result + # as a Ruby data structure. Otherwise generate a JSON text from the Ruby + # data structure object and return it. + # + # The _opts_ argument is passed through to generate/parse respectively, see + # generate and parse for their documentation. + def [](object, opts = {}) + if object.respond_to? :to_str + JSON.parse(object.to_str, opts => {}) + else + JSON.generate(object, opts => {}) + end + end + + # Returns the JSON parser class, that is used by JSON. This might be either + # JSON::Ext::Parser or JSON::Pure::Parser. + attr_reader :parser + + # Set the JSON parser class _parser_ to be used by JSON. + def parser=(parser) # :nodoc: + @parser = parser + remove_const :Parser if const_defined? :Parser + const_set :Parser, parser + end + + # Return the constant located at _path_. The format of _path_ has to be + # either ::A::B::C or A::B::C. In any case A has to be located at the top + # level (absolute namespace path?). If there doesn't exist a constant at + # the given path, an ArgumentError is raised. + def deep_const_get(path) # :nodoc: + path = path.to_s + path.split(/::/).inject(Object) do |p, c| + case + when c.empty? then p + when p.const_defined?(c) then p.const_get(c) + else raise ArgumentError, "can't find const #{path}" + end + end + end + + # Set the module _generator_ to be used by JSON. + def generator=(generator) # :nodoc: + @generator = generator + generator_methods = generator::GeneratorMethods + for const in generator_methods.constants + klass = deep_const_get(const) + modul = generator_methods.const_get(const) + klass.class_eval do + instance_methods(false).each do |m| + m.to_s == 'to_json' and remove_method m + end + include modul + end + end + self.state = generator::State + const_set :State, self.state + end + + # Returns the JSON generator modul, that is used by JSON. This might be + # either JSON::Ext::Generator or JSON::Pure::Generator. + attr_reader :generator + + # Returns the JSON generator state class, that is used by JSON. This might + # be either JSON::Ext::Generator::State or JSON::Pure::Generator::State. + attr_accessor :state + + # This is create identifier, that is used to decide, if the _json_create_ + # hook of a class should be called. It defaults to 'json_class'. + attr_accessor :create_id + end + self.create_id = 'json_class' + + NaN = (-1.0) ** 0.5 + + Infinity = 1.0/0 + + MinusInfinity = -Infinity + + # The base exception for JSON errors. + class JSONError < StandardError; end + + # This exception is raised, if a parser error occurs. + class ParserError < JSONError; end + + # This exception is raised, if the nesting of parsed datastructures is too + # deep. + class NestingError < ParserError; end + + # This exception is raised, if a generator or unparser error occurs. + class GeneratorError < JSONError; end + # For backwards compatibility + UnparserError = GeneratorError + + # If a circular data structure is encountered while unparsing + # this exception is raised. + class CircularDatastructure < GeneratorError; end + + # This exception is raised, if the required unicode support is missing on the + # system. Usually this means, that the iconv library is not installed. + class MissingUnicodeSupport < JSONError; end + + module_function + + # Parse the JSON string _source_ into a Ruby data structure and return it. + # + # _opts_ can have the following + # keys: + # * *max_nesting*: The maximum depth of nesting allowed in the parsed data + # structures. Disable depth checking with :max_nesting => false, it defaults + # to 19. + # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in + # defiance of RFC 4627 to be parsed by the Parser. This option defaults + # to false. + # * *create_additions*: If set to false, the Parser doesn't create + # additions even if a matchin class and create_id was found. This option + # defaults to true. + def parse(source, opts = {}) + JSON.parser.new(source, opts).parse + end + + # Parse the JSON string _source_ into a Ruby data structure and return it. + # The bang version of the parse method, defaults to the more dangerous values + # for the _opts_ hash, so be sure only to parse trusted _source_ strings. + # + # _opts_ can have the following keys: + # * *max_nesting*: The maximum depth of nesting allowed in the parsed data + # structures. Enable depth checking with :max_nesting => anInteger. The parse! + # methods defaults to not doing max depth checking: This can be dangerous, + # if someone wants to fill up your stack. + # * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in + # defiance of RFC 4627 to be parsed by the Parser. This option defaults + # to true. + # * *create_additions*: If set to false, the Parser doesn't create + # additions even if a matchin class and create_id was found. This option + # defaults to true. + def parse!(source, opts = {}) + opts = { + :max_nesting => false, + :allow_nan => true + }.update(opts) + JSON.parser.new(source, opts).parse + end + + # Unparse the Ruby data structure _obj_ into a single line JSON string and + # return it. _state_ is + # * a JSON::State object, + # * or a Hash like object (responding to to_hash), + # * an object convertible into a hash by a to_h method, + # that is used as or to configure a State object. + # + # It defaults to a state object, that creates the shortest possible JSON text + # in one line, checks for circular data structures and doesn't allow NaN, + # Infinity, and -Infinity. + # + # A _state_ hash can have the following keys: + # * *indent*: a string used to indent levels (default: ''), + # * *space*: a string that is put after, a : or , delimiter (default: ''), + # * *space_before*: a string that is put before a : pair delimiter (default: ''), + # * *object_nl*: a string that is put at the end of a JSON object (default: ''), + # * *array_nl*: a string that is put at the end of a JSON array (default: ''), + # * *check_circular*: true if checking for circular data structures + # should be done (the default), false otherwise. + # * *allow_nan*: true if NaN, Infinity, and -Infinity should be + # generated, otherwise an exception is thrown, if these values are + # encountered. This options defaults to false. + # * *max_nesting*: The maximum depth of nesting allowed in the data + # structures from which JSON is to be generated. Disable depth checking + # with :max_nesting => false, it defaults to 19. + # + # See also the fast_generate for the fastest creation method with the least + # amount of sanity checks, and the pretty_generate method for some + # defaults for a pretty output. + def generate(obj, state = nil) + if state + state = State.from_state(state) + else + state = State.new + end + obj.to_json(state) + end + + # :stopdoc: + # I want to deprecate these later, so I'll first be silent about them, and later delete them. + alias unparse generate + module_function :unparse + # :startdoc: + + # Unparse the Ruby data structure _obj_ into a single line JSON string and + # return it. This method disables the checks for circles in Ruby objects, and + # also generates NaN, Infinity, and, -Infinity float values. + # + # *WARNING*: Be careful not to pass any Ruby data structures with circles as + # _obj_ argument, because this will cause JSON to go into an infinite loop. + def fast_generate(obj) + obj.to_json(nil) + end + + # :stopdoc: + # I want to deprecate these later, so I'll first be silent about them, and later delete them. + alias fast_unparse fast_generate + module_function :fast_unparse + # :startdoc: + + # Unparse the Ruby data structure _obj_ into a JSON string and return it. The + # returned string is a prettier form of the string returned by #unparse. + # + # The _opts_ argument can be used to configure the generator, see the + # generate method for a more detailed explanation. + def pretty_generate(obj, opts = nil) + state = JSON.state.new( + :indent => ' ', + :space => ' ', + :object_nl => "\n", + :array_nl => "\n", + :check_circular => true + ) + if opts + if opts.respond_to? :to_hash + opts = opts.to_hash + elsif opts.respond_to? :to_h + opts = opts.to_h + else + raise TypeError, "can't convert #{opts.class} into Hash" + end + state.configure(opts) + end + obj.to_json(state) + end + + # :stopdoc: + # I want to deprecate these later, so I'll first be silent about them, and later delete them. + alias pretty_unparse pretty_generate + module_function :pretty_unparse + # :startdoc: + + # Load a ruby data structure from a JSON _source_ and return it. A source can + # either be a string like object, an IO like object, or an object responding + # to the read method. If _proc_ was given, it will be called with any nested + # Ruby object as an argument recursively in depth first order. + # + # This method is part of the implementation of the load/dump interface of + # Marshal and YAML. + def load(source, proc = nil) + if source.respond_to? :to_str + source = source.to_str + elsif source.respond_to? :to_io + source = source.to_io.read + else + source = source.read + end + result = parse(source, :max_nesting => false, :allow_nan => true) + recurse_proc(result, &proc) if proc + result + end + + def recurse_proc(result, &proc) + case result + when Array + result.each { |x| recurse_proc x, &proc } + proc.call result + when Hash + result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc } + proc.call result + else + proc.call result + end + end + private :recurse_proc + module_function :recurse_proc + + alias restore load + module_function :restore + + # Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns + # the result. + # + # If anIO (an IO like object or an object that responds to the write method) + # was given, the resulting JSON is written to it. + # + # If the number of nested arrays or objects exceeds _limit_ an ArgumentError + # exception is raised. This argument is similar (but not exactly the + # same!) to the _limit_ argument in Marshal.dump. + # + # This method is part of the implementation of the load/dump interface of + # Marshal and YAML. + def dump(obj, anIO = nil, limit = nil) + if anIO and limit.nil? + anIO = anIO.to_io if anIO.respond_to?(:to_io) + unless anIO.respond_to?(:write) + limit = anIO + anIO = nil + end + end + limit ||= 0 + result = generate(obj, :allow_nan => true, :max_nesting => limit) + if anIO + anIO.write result + anIO + else + result + end + rescue JSON::NestingError + raise ArgumentError, "exceed depth limit" + end +end + +module ::Kernel + # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in + # one line. + def j(*objs) + objs.each do |obj| + puts JSON::generate(obj, :allow_nan => true, :max_nesting => false) + end + nil + end + + # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with + # indentation and over many lines. + def jj(*objs) + objs.each do |obj| + puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false) + end + nil + end + + # If _object_ is string like parse the string and return the parsed result as + # a Ruby data structure. Otherwise generate a JSON text from the Ruby data + # structure object and return it. + # + # The _opts_ argument is passed through to generate/parse respectively, see + # generate and parse for their documentation. + def JSON(object, opts = {}) + if object.respond_to? :to_str + JSON.parse(object.to_str, opts) + else + JSON.generate(object, opts) + end + end +end + +class ::Class + # Returns true, if this class can be used to create an instance + # from a serialised JSON string. The class has to implement a class + # method _json_create_ that expects a hash as first parameter, which includes + # the required data. + def json_creatable? + respond_to?(:json_create) + end +end + # vim: set et sw=2 ts=2: diff --git a/ext/json/lib/json/editor.rb b/ext/json/lib/json/editor.rb new file mode 100644 index 0000000000..9a65400622 --- /dev/null +++ b/ext/json/lib/json/editor.rb @@ -0,0 +1,1363 @@ +# To use the GUI JSON editor, start the edit_json.rb executable script. It +# requires ruby-gtk to be installed. + +require 'gtk2' +require 'iconv' +require 'json' +require 'rbconfig' +require 'open-uri' + +module JSON + module Editor + include Gtk + + # Beginning of the editor window title + TITLE = 'JSON Editor'.freeze + + # Columns constants + ICON_COL, TYPE_COL, CONTENT_COL = 0, 1, 2 + + # JSON primitive types (Containers) + CONTAINER_TYPES = %w[Array Hash].sort + # All JSON primitive types + ALL_TYPES = (%w[TrueClass FalseClass Numeric String NilClass] + + CONTAINER_TYPES).sort + + # The Nodes necessary for the tree representation of a JSON document + ALL_NODES = (ALL_TYPES + %w[Key]).sort + + DEFAULT_DIALOG_KEY_PRESS_HANDLER = lambda do |dialog, event| + case event.keyval + when Gdk::Keyval::GDK_Return + dialog.response Dialog::RESPONSE_ACCEPT + when Gdk::Keyval::GDK_Escape + dialog.response Dialog::RESPONSE_REJECT + end + end + + # Returns the Gdk::Pixbuf of the icon named _name_ from the icon cache. + def Editor.fetch_icon(name) + @icon_cache ||= {} + unless @icon_cache.key?(name) + path = File.dirname(__FILE__) + @icon_cache[name] = Gdk::Pixbuf.new(File.join(path, name + '.xpm')) + end + @icon_cache[name] + end + + # Opens an error dialog on top of _window_ showing the error message + # _text_. + def Editor.error_dialog(window, text) + dialog = MessageDialog.new(window, Dialog::MODAL, + MessageDialog::ERROR, + MessageDialog::BUTTONS_CLOSE, text) + dialog.show_all + dialog.run + rescue TypeError + dialog = MessageDialog.new(Editor.window, Dialog::MODAL, + MessageDialog::ERROR, + MessageDialog::BUTTONS_CLOSE, text) + dialog.show_all + dialog.run + ensure + dialog.destroy if dialog + end + + # Opens a yes/no question dialog on top of _window_ showing the error + # message _text_. If yes was answered _true_ is returned, otherwise + # _false_. + def Editor.question_dialog(window, text) + dialog = MessageDialog.new(window, Dialog::MODAL, + MessageDialog::QUESTION, + MessageDialog::BUTTONS_YES_NO, text) + dialog.show_all + dialog.run do |response| + return Gtk::Dialog::RESPONSE_YES === response + end + ensure + dialog.destroy if dialog + end + + # Convert the tree model starting from Gtk::TreeIter _iter_ into a Ruby + # data structure and return it. + def Editor.model2data(iter) + return nil if iter.nil? + case iter.type + when 'Hash' + hash = {} + iter.each { |c| hash[c.content] = Editor.model2data(c.first_child) } + hash + when 'Array' + array = Array.new(iter.n_children) + iter.each_with_index { |c, i| array[i] = Editor.model2data(c) } + array + when 'Key' + iter.content + when 'String' + iter.content + when 'Numeric' + content = iter.content + if /\./.match(content) + content.to_f + else + content.to_i + end + when 'TrueClass' + true + when 'FalseClass' + false + when 'NilClass' + nil + else + fail "Unknown type found in model: #{iter.type}" + end + end + + # Convert the Ruby data structure _data_ into tree model data for Gtk and + # returns the whole model. If the parameter _model_ wasn't given a new + # Gtk::TreeStore is created as the model. The _parent_ parameter specifies + # the parent node (iter, Gtk:TreeIter instance) to which the data is + # appended, alternativeley the result of the yielded block is used as iter. + def Editor.data2model(data, model = nil, parent = nil) + model ||= TreeStore.new(Gdk::Pixbuf, String, String) + iter = if block_given? + yield model + else + model.append(parent) + end + case data + when Hash + iter.type = 'Hash' + data.sort.each do |key, value| + pair_iter = model.append(iter) + pair_iter.type = 'Key' + pair_iter.content = key.to_s + Editor.data2model(value, model, pair_iter) + end + when Array + iter.type = 'Array' + data.each do |value| + Editor.data2model(value, model, iter) + end + when Numeric + iter.type = 'Numeric' + iter.content = data.to_s + when String, true, false, nil + iter.type = data.class.name + iter.content = data.nil? ? 'null' : data.to_s + else + iter.type = 'String' + iter.content = data.to_s + end + model + end + + # The Gtk::TreeIter class is reopened and some auxiliary methods are added. + class Gtk::TreeIter + include Enumerable + + # Traverse each of this Gtk::TreeIter instance's children + # and yield to them. + def each + n_children.times { |i| yield nth_child(i) } + end + + # Recursively traverse all nodes of this Gtk::TreeIter's subtree + # (including self) and yield to them. + def recursive_each(&block) + yield self + each do |i| + i.recursive_each(&block) + end + end + + # Remove the subtree of this Gtk::TreeIter instance from the + # model _model_. + def remove_subtree(model) + while current = first_child + model.remove(current) + end + end + + # Returns the type of this node. + def type + self[TYPE_COL] + end + + # Sets the type of this node to _value_. This implies setting + # the respective icon accordingly. + def type=(value) + self[TYPE_COL] = value + self[ICON_COL] = Editor.fetch_icon(value) + end + + # Returns the content of this node. + def content + self[CONTENT_COL] + end + + # Sets the content of this node to _value_. + def content=(value) + self[CONTENT_COL] = value + end + end + + # This module bundles some method, that can be used to create a menu. It + # should be included into the class in question. + module MenuExtension + include Gtk + + # Creates a Menu, that includes MenuExtension. _treeview_ is the + # Gtk::TreeView, on which it operates. + def initialize(treeview) + @treeview = treeview + @menu = Menu.new + end + + # Returns the Gtk::TreeView of this menu. + attr_reader :treeview + + # Returns the menu. + attr_reader :menu + + # Adds a Gtk::SeparatorMenuItem to this instance's #menu. + def add_separator + menu.append SeparatorMenuItem.new + end + + # Adds a Gtk::MenuItem to this instance's #menu. _label_ is the label + # string, _klass_ is the item type, and _callback_ is the procedure, that + # is called if the _item_ is activated. + def add_item(label, keyval = nil, klass = MenuItem, &callback) + label = "#{label} (C-#{keyval.chr})" if keyval + item = klass.new(label) + item.signal_connect(:activate, &callback) + if keyval + self.signal_connect(:'key-press-event') do |item, event| + if event.state & Gdk::Window::ModifierType::CONTROL_MASK != 0 and + event.keyval == keyval + callback.call item + end + end + end + menu.append item + item + end + + # This method should be implemented in subclasses to create the #menu of + # this instance. It has to be called after an instance of this class is + # created, to build the menu. + def create + raise NotImplementedError + end + + def method_missing(*a, &b) + treeview.__send__(*a, &b) + end + end + + # This class creates the popup menu, that opens when clicking onto the + # treeview. + class PopUpMenu + include MenuExtension + + # Change the type or content of the selected node. + def change_node(item) + if current = selection.selected + parent = current.parent + old_type, old_content = current.type, current.content + if ALL_TYPES.include?(old_type) + @clipboard_data = Editor.model2data(current) + type, content = ask_for_element(parent, current.type, + current.content) + if type + current.type, current.content = type, content + current.remove_subtree(model) + toplevel.display_status("Changed a node in tree.") + window.change + end + else + toplevel.display_status( + "Cannot change node of type #{old_type} in tree!") + end + end + end + + # Cut the selected node and its subtree, and save it into the + # clipboard. + def cut_node(item) + if current = selection.selected + if current and current.type == 'Key' + @clipboard_data = { + current.content => Editor.model2data(current.first_child) + } + else + @clipboard_data = Editor.model2data(current) + end + model.remove(current) + window.change + toplevel.display_status("Cut a node from tree.") + end + end + + # Copy the selected node and its subtree, and save it into the + # clipboard. + def copy_node(item) + if current = selection.selected + if current and current.type == 'Key' + @clipboard_data = { + current.content => Editor.model2data(current.first_child) + } + else + @clipboard_data = Editor.model2data(current) + end + window.change + toplevel.display_status("Copied a node from tree.") + end + end + + # Paste the data in the clipboard into the selected Array or Hash by + # appending it. + def paste_node_appending(item) + if current = selection.selected + if @clipboard_data + case current.type + when 'Array' + Editor.data2model(@clipboard_data, model, current) + expand_collapse(current) + when 'Hash' + if @clipboard_data.is_a? Hash + parent = current.parent + hash = Editor.model2data(current) + model.remove(current) + hash.update(@clipboard_data) + Editor.data2model(hash, model, parent) + if parent + expand_collapse(parent) + elsif @expanded + expand_all + end + window.change + else + toplevel.display_status( + "Cannot paste non-#{current.type} data into '#{current.type}'!") + end + else + toplevel.display_status( + "Cannot paste node below '#{current.type}'!") + end + else + toplevel.display_status("Nothing to paste in clipboard!") + end + else + toplevel.display_status("Append a node into the root first!") + end + end + + # Paste the data in the clipboard into the selected Array inserting it + # before the selected element. + def paste_node_inserting_before(item) + if current = selection.selected + if @clipboard_data + parent = current.parent or return + parent_type = parent.type + if parent_type == 'Array' + selected_index = parent.each_with_index do |c, i| + break i if c == current + end + Editor.data2model(@clipboard_data, model, parent) do |m| + m.insert_before(parent, current) + end + expand_collapse(current) + toplevel.display_status("Inserted an element to " + + "'#{parent_type}' before index #{selected_index}.") + window.change + else + toplevel.display_status( + "Cannot insert node below '#{parent_type}'!") + end + else + toplevel.display_status("Nothing to paste in clipboard!") + end + else + toplevel.display_status("Append a node into the root first!") + end + end + + # Append a new node to the selected Hash or Array. + def append_new_node(item) + if parent = selection.selected + parent_type = parent.type + case parent_type + when 'Hash' + key, type, content = ask_for_hash_pair(parent) + key or return + iter = create_node(parent, 'Key', key) + iter = create_node(iter, type, content) + toplevel.display_status( + "Added a (key, value)-pair to '#{parent_type}'.") + window.change + when 'Array' + type, content = ask_for_element(parent) + type or return + iter = create_node(parent, type, content) + window.change + toplevel.display_status("Appendend an element to '#{parent_type}'.") + else + toplevel.display_status("Cannot append to '#{parent_type}'!") + end + else + type, content = ask_for_element + type or return + iter = create_node(nil, type, content) + window.change + end + end + + # Insert a new node into an Array before the selected element. + def insert_new_node(item) + if current = selection.selected + parent = current.parent or return + parent_parent = parent.parent + parent_type = parent.type + if parent_type == 'Array' + selected_index = parent.each_with_index do |c, i| + break i if c == current + end + type, content = ask_for_element(parent) + type or return + iter = model.insert_before(parent, current) + iter.type, iter.content = type, content + toplevel.display_status("Inserted an element to " + + "'#{parent_type}' before index #{selected_index}.") + window.change + else + toplevel.display_status( + "Cannot insert node below '#{parent_type}'!") + end + else + toplevel.display_status("Append a node into the root first!") + end + end + + # Recursively collapse/expand a subtree starting from the selected node. + def collapse_expand(item) + if current = selection.selected + if row_expanded?(current.path) + collapse_row(current.path) + else + expand_row(current.path, true) + end + else + toplevel.display_status("Append a node into the root first!") + end + end + + # Create the menu. + def create + add_item("Change node", ?n, &method(:change_node)) + add_separator + add_item("Cut node", ?X, &method(:cut_node)) + add_item("Copy node", ?C, &method(:copy_node)) + add_item("Paste node (appending)", ?A, &method(:paste_node_appending)) + add_item("Paste node (inserting before)", ?I, + &method(:paste_node_inserting_before)) + add_separator + add_item("Append new node", ?a, &method(:append_new_node)) + add_item("Insert new node before", ?i, &method(:insert_new_node)) + add_separator + add_item("Collapse/Expand node (recursively)", ?e, + &method(:collapse_expand)) + + menu.show_all + signal_connect(:button_press_event) do |widget, event| + if event.kind_of? Gdk::EventButton and event.button == 3 + menu.popup(nil, nil, event.button, event.time) + end + end + signal_connect(:popup_menu) do + menu.popup(nil, nil, 0, Gdk::Event::CURRENT_TIME) + end + end + end + + # This class creates the File pulldown menu. + class FileMenu + include MenuExtension + + # Clear the model and filename, but ask to save the JSON document, if + # unsaved changes have occured. + def new(item) + window.clear + end + + # Open a file and load it into the editor. Ask to save the JSON document + # first, if unsaved changes have occured. + def open(item) + window.file_open + end + + def open_location(item) + window.location_open + end + + # Revert the current JSON document in the editor to the saved version. + def revert(item) + window.instance_eval do + @filename and file_open(@filename) + end + end + + # Save the current JSON document. + def save(item) + window.file_save + end + + # Save the current JSON document under the given filename. + def save_as(item) + window.file_save_as + end + + # Quit the editor, after asking to save any unsaved changes first. + def quit(item) + window.quit + end + + # Create the menu. + def create + title = MenuItem.new('File') + title.submenu = menu + add_item('New', &method(:new)) + add_item('Open', ?o, &method(:open)) + add_item('Open location', ?l, &method(:open_location)) + add_item('Revert', &method(:revert)) + add_separator + add_item('Save', ?s, &method(:save)) + add_item('Save As', ?S, &method(:save_as)) + add_separator + add_item('Quit', ?q, &method(:quit)) + title + end + end + + # This class creates the Edit pulldown menu. + class EditMenu + include MenuExtension + + # Copy data from model into primary clipboard. + def copy(item) + data = Editor.model2data(model.iter_first) + json = JSON.pretty_generate(data, :max_nesting => false) + c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY) + c.text = json + end + + # Copy json text from primary clipboard into model. + def paste(item) + c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY) + if json = c.wait_for_text + window.ask_save if @changed + begin + window.edit json + rescue JSON::ParserError + window.clear + end + end + end + + # Find a string in all nodes' contents and select the found node in the + # treeview. + def find(item) + @search = ask_for_find_term(@search) or return + iter = model.get_iter('0') or return + iter.recursive_each do |i| + if @iter + if @iter != i + next + else + @iter = nil + next + end + elsif @search.match(i[CONTENT_COL]) + set_cursor(i.path, nil, false) + @iter = i + break + end + end + end + + # Repeat the last search given by #find. + def find_again(item) + @search or return + iter = model.get_iter('0') + iter.recursive_each do |i| + if @iter + if @iter != i + next + else + @iter = nil + next + end + elsif @search.match(i[CONTENT_COL]) + set_cursor(i.path, nil, false) + @iter = i + break + end + end + end + + # Sort (Reverse sort) all elements of the selected array by the given + # expression. _x_ is the element in question. + def sort(item) + if current = selection.selected + if current.type == 'Array' + parent = current.parent + ary = Editor.model2data(current) + order, reverse = ask_for_order + order or return + begin + block = eval "lambda { |x| #{order} }" + if reverse + ary.sort! { |a,b| block[b] <=> block[a] } + else + ary.sort! { |a,b| block[a] <=> block[b] } + end + rescue => e + Editor.error_dialog(self, "Failed to sort Array with #{order}: #{e}!") + else + Editor.data2model(ary, model, parent) do |m| + m.insert_before(parent, current) + end + model.remove(current) + expand_collapse(parent) + window.change + toplevel.display_status("Array has been sorted.") + end + else + toplevel.display_status("Only Array nodes can be sorted!") + end + else + toplevel.display_status("Select an Array to sort first!") + end + end + + # Create the menu. + def create + title = MenuItem.new('Edit') + title.submenu = menu + add_item('Copy', ?c, &method(:copy)) + add_item('Paste', ?v, &method(:paste)) + add_separator + add_item('Find', ?f, &method(:find)) + add_item('Find Again', ?g, &method(:find_again)) + add_separator + add_item('Sort', ?S, &method(:sort)) + title + end + end + + class OptionsMenu + include MenuExtension + + # Collapse/Expand all nodes by default. + def collapsed_nodes(item) + if expanded + self.expanded = false + collapse_all + else + self.expanded = true + expand_all + end + end + + # Toggle pretty saving mode on/off. + def pretty_saving(item) + @pretty_item.toggled + window.change + end + + attr_reader :pretty_item + + # Create the menu. + def create + title = MenuItem.new('Options') + title.submenu = menu + add_item('Collapsed nodes', nil, CheckMenuItem, &method(:collapsed_nodes)) + @pretty_item = add_item('Pretty saving', nil, CheckMenuItem, + &method(:pretty_saving)) + @pretty_item.active = true + window.unchange + title + end + end + + # This class inherits from Gtk::TreeView, to configure it and to add a lot + # of behaviour to it. + class JSONTreeView < Gtk::TreeView + include Gtk + + # Creates a JSONTreeView instance, the parameter _window_ is + # a MainWindow instance and used for self delegation. + def initialize(window) + @window = window + super(TreeStore.new(Gdk::Pixbuf, String, String)) + self.selection.mode = SELECTION_BROWSE + + @expanded = false + self.headers_visible = false + add_columns + add_popup_menu + end + + # Returns the MainWindow instance of this JSONTreeView. + attr_reader :window + + # Returns true, if nodes are autoexpanding, false otherwise. + attr_accessor :expanded + + private + + def add_columns + cell = CellRendererPixbuf.new + column = TreeViewColumn.new('Icon', cell, + 'pixbuf' => ICON_COL + ) + append_column(column) + + cell = CellRendererText.new + column = TreeViewColumn.new('Type', cell, + 'text' => TYPE_COL + ) + append_column(column) + + cell = CellRendererText.new + cell.editable = true + column = TreeViewColumn.new('Content', cell, + 'text' => CONTENT_COL + ) + cell.signal_connect(:edited, &method(:cell_edited)) + append_column(column) + end + + def unify_key(iter, key) + return unless iter.type == 'Key' + parent = iter.parent + if parent.any? { |c| c != iter and c.content == key } + old_key = key + i = 0 + begin + key = sprintf("%s.%d", old_key, i += 1) + end while parent.any? { |c| c != iter and c.content == key } + end + iter.content = key + end + + def cell_edited(cell, path, value) + iter = model.get_iter(path) + case iter.type + when 'Key' + unify_key(iter, value) + toplevel.display_status('Key has been changed.') + when 'FalseClass' + value.downcase! + if value == 'true' + iter.type, iter.content = 'TrueClass', 'true' + end + when 'TrueClass' + value.downcase! + if value == 'false' + iter.type, iter.content = 'FalseClass', 'false' + end + when 'Numeric' + iter.content = (Integer(value) rescue Float(value) rescue 0).to_s + when 'String' + iter.content = value + when 'Hash', 'Array' + return + else + fail "Unknown type found in model: #{iter.type}" + end + window.change + end + + def configure_value(value, type) + value.editable = false + case type + when 'Array', 'Hash' + value.text = '' + when 'TrueClass' + value.text = 'true' + when 'FalseClass' + value.text = 'false' + when 'NilClass' + value.text = 'null' + when 'Numeric', 'String' + value.text ||= '' + value.editable = true + else + raise ArgumentError, "unknown type '#{type}' encountered" + end + end + + def add_popup_menu + menu = PopUpMenu.new(self) + menu.create + end + + public + + # Create a _type_ node with content _content_, and add it to _parent_ + # in the model. If _parent_ is nil, create a new model and put it into + # the editor treeview. + def create_node(parent, type, content) + iter = if parent + model.append(parent) + else + new_model = Editor.data2model(nil) + toplevel.view_new_model(new_model) + new_model.iter_first + end + iter.type, iter.content = type, content + expand_collapse(parent) if parent + iter + end + + # Ask for a hash key, value pair to be added to the Hash node _parent_. + def ask_for_hash_pair(parent) + key_input = type_input = value_input = nil + + dialog = Dialog.new("New (key, value) pair for Hash", nil, nil, + [ Stock::OK, Dialog::RESPONSE_ACCEPT ], + [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] + ) + dialog.width_request = 640 + + hbox = HBox.new(false, 5) + hbox.pack_start(Label.new("Key:"), false) + hbox.pack_start(key_input = Entry.new) + key_input.text = @key || '' + dialog.vbox.pack_start(hbox, false) + key_input.signal_connect(:activate) do + if parent.any? { |c| c.content == key_input.text } + toplevel.display_status('Key already exists in Hash!') + key_input.text = '' + else + toplevel.display_status('Key has been changed.') + end + end + + hbox = HBox.new(false, 5) + hbox.pack_start(Label.new("Type:"), false) + hbox.pack_start(type_input = ComboBox.new(true)) + ALL_TYPES.each { |t| type_input.append_text(t) } + type_input.active = @type || 0 + dialog.vbox.pack_start(hbox, false) + + type_input.signal_connect(:changed) do + value_input.editable = false + case ALL_TYPES[type_input.active] + when 'Array', 'Hash' + value_input.text = '' + when 'TrueClass' + value_input.text = 'true' + when 'FalseClass' + value_input.text = 'false' + when 'NilClass' + value_input.text = 'null' + else + value_input.text = '' + value_input.editable = true + end + end + + hbox = HBox.new(false, 5) + hbox.pack_start(Label.new("Value:"), false) + hbox.pack_start(value_input = Entry.new) + value_input.width_chars = 60 + value_input.text = @value || '' + dialog.vbox.pack_start(hbox, false) + + dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) + dialog.show_all + self.focus = dialog + dialog.run do |response| + if response == Dialog::RESPONSE_ACCEPT + @key = key_input.text + type = ALL_TYPES[@type = type_input.active] + content = value_input.text + return @key, type, content + end + end + return + ensure + dialog.destroy + end + + # Ask for an element to be appended _parent_. + def ask_for_element(parent = nil, default_type = nil, value_text = @content) + type_input = value_input = nil + + dialog = Dialog.new( + "New element into #{parent ? parent.type : 'root'}", + nil, nil, + [ Stock::OK, Dialog::RESPONSE_ACCEPT ], + [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] + ) + hbox = HBox.new(false, 5) + hbox.pack_start(Label.new("Type:"), false) + hbox.pack_start(type_input = ComboBox.new(true)) + default_active = 0 + types = parent ? ALL_TYPES : CONTAINER_TYPES + types.each_with_index do |t, i| + type_input.append_text(t) + if t == default_type + default_active = i + end + end + type_input.active = default_active + dialog.vbox.pack_start(hbox, false) + type_input.signal_connect(:changed) do + configure_value(value_input, types[type_input.active]) + end + + hbox = HBox.new(false, 5) + hbox.pack_start(Label.new("Value:"), false) + hbox.pack_start(value_input = Entry.new) + value_input.width_chars = 60 + value_input.text = value_text if value_text + configure_value(value_input, types[type_input.active]) + + dialog.vbox.pack_start(hbox, false) + + dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) + dialog.show_all + self.focus = dialog + dialog.run do |response| + if response == Dialog::RESPONSE_ACCEPT + type = types[type_input.active] + @content = case type + when 'Numeric' + Integer(value_input.text) rescue Float(value_input.text) rescue 0 + else + value_input.text + end.to_s + return type, @content + end + end + return + ensure + dialog.destroy if dialog + end + + # Ask for an order criteria for sorting, using _x_ for the element in + # question. Returns the order criterium, and true/false for reverse + # sorting. + def ask_for_order + dialog = Dialog.new( + "Give an order criterium for 'x'.", + nil, nil, + [ Stock::OK, Dialog::RESPONSE_ACCEPT ], + [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] + ) + hbox = HBox.new(false, 5) + + hbox.pack_start(Label.new("Order:"), false) + hbox.pack_start(order_input = Entry.new) + order_input.text = @order || 'x' + order_input.width_chars = 60 + + hbox.pack_start(reverse_checkbox = CheckButton.new('Reverse'), false) + + dialog.vbox.pack_start(hbox, false) + + dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) + dialog.show_all + self.focus = dialog + dialog.run do |response| + if response == Dialog::RESPONSE_ACCEPT + return @order = order_input.text, reverse_checkbox.active? + end + end + return + ensure + dialog.destroy if dialog + end + + # Ask for a find term to search for in the tree. Returns the term as a + # string. + def ask_for_find_term(search = nil) + dialog = Dialog.new( + "Find a node matching regex in tree.", + nil, nil, + [ Stock::OK, Dialog::RESPONSE_ACCEPT ], + [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] + ) + hbox = HBox.new(false, 5) + + hbox.pack_start(Label.new("Regex:"), false) + hbox.pack_start(regex_input = Entry.new) + hbox.pack_start(icase_checkbox = CheckButton.new('Icase'), false) + regex_input.width_chars = 60 + if search + regex_input.text = search.source + icase_checkbox.active = search.casefold? + end + + dialog.vbox.pack_start(hbox, false) + + dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) + dialog.show_all + self.focus = dialog + dialog.run do |response| + if response == Dialog::RESPONSE_ACCEPT + begin + return Regexp.new(regex_input.text, icase_checkbox.active? ? Regexp::IGNORECASE : 0) + rescue => e + Editor.error_dialog(self, "Evaluation of regex /#{regex_input.text}/ failed: #{e}!") + return + end + end + end + return + ensure + dialog.destroy if dialog + end + + # Expand or collapse row pointed to by _iter_ according + # to the #expanded attribute. + def expand_collapse(iter) + if expanded + expand_row(iter.path, true) + else + collapse_row(iter.path) + end + end + end + + # The editor main window + class MainWindow < Gtk::Window + include Gtk + + def initialize(encoding) + @changed = false + @encoding = encoding + super(TOPLEVEL) + display_title + set_default_size(800, 600) + signal_connect(:delete_event) { quit } + + vbox = VBox.new(false, 0) + add(vbox) + #vbox.border_width = 0 + + @treeview = JSONTreeView.new(self) + @treeview.signal_connect(:'cursor-changed') do + display_status('') + end + + menu_bar = create_menu_bar + vbox.pack_start(menu_bar, false, false, 0) + + sw = ScrolledWindow.new(nil, nil) + sw.shadow_type = SHADOW_ETCHED_IN + sw.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) + vbox.pack_start(sw, true, true, 0) + sw.add(@treeview) + + @status_bar = Statusbar.new + vbox.pack_start(@status_bar, false, false, 0) + + @filename ||= nil + if @filename + data = read_data(@filename) + view_new_model Editor.data2model(data) + end + + signal_connect(:button_release_event) do |_,event| + if event.button == 2 + c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY) + if url = c.wait_for_text + location_open url + end + false + else + true + end + end + end + + # Creates the menu bar with the pulldown menus and returns it. + def create_menu_bar + menu_bar = MenuBar.new + @file_menu = FileMenu.new(@treeview) + menu_bar.append @file_menu.create + @edit_menu = EditMenu.new(@treeview) + menu_bar.append @edit_menu.create + @options_menu = OptionsMenu.new(@treeview) + menu_bar.append @options_menu.create + menu_bar + end + + # Sets editor status to changed, to indicate that the edited data + # containts unsaved changes. + def change + @changed = true + display_title + end + + # Sets editor status to unchanged, to indicate that the edited data + # doesn't containt unsaved changes. + def unchange + @changed = false + display_title + end + + # Puts a new model _model_ into the Gtk::TreeView to be edited. + def view_new_model(model) + @treeview.model = model + @treeview.expanded = true + @treeview.expand_all + unchange + end + + # Displays _text_ in the status bar. + def display_status(text) + @cid ||= nil + @status_bar.pop(@cid) if @cid + @cid = @status_bar.get_context_id('dummy') + @status_bar.push(@cid, text) + end + + # Opens a dialog, asking, if changes should be saved to a file. + def ask_save + if Editor.question_dialog(self, + "Unsaved changes to JSON model. Save?") + if @filename + file_save + else + file_save_as + end + end + end + + # Quit this editor, that is, leave this editor's main loop. + def quit + ask_save if @changed + if Gtk.main_level > 0 + destroy + Gtk.main_quit + end + nil + end + + # Display the new title according to the editor's current state. + def display_title + title = TITLE.dup + title << ": #@filename" if @filename + title << " *" if @changed + self.title = title + end + + # Clear the current model, after asking to save all unsaved changes. + def clear + ask_save if @changed + @filename = nil + self.view_new_model nil + end + + def check_pretty_printed(json) + pretty = !!((nl_index = json.index("\n")) && nl_index != json.size - 1) + @options_menu.pretty_item.active = pretty + end + private :check_pretty_printed + + # Open the data at the location _uri_, if given. Otherwise open a dialog + # to ask for the _uri_. + def location_open(uri = nil) + uri = ask_for_location unless uri + uri or return + ask_save if @changed + data = load_location(uri) or return + view_new_model Editor.data2model(data) + end + + # Open the file _filename_ or call the #select_file method to ask for a + # filename. + def file_open(filename = nil) + filename = select_file('Open as a JSON file') unless filename + data = load_file(filename) or return + view_new_model Editor.data2model(data) + end + + # Edit the string _json_ in the editor. + def edit(json) + if json.respond_to? :read + json = json.read + end + data = parse_json json + view_new_model Editor.data2model(data) + end + + # Save the current file. + def file_save + if @filename + store_file(@filename) + else + file_save_as + end + end + + # Save the current file as the filename + def file_save_as + filename = select_file('Save as a JSON file') + store_file(filename) + end + + # Store the current JSON document to _path_. + def store_file(path) + if path + data = Editor.model2data(@treeview.model.iter_first) + File.open(path + '.tmp', 'wb') do |output| + data or break + if @options_menu.pretty_item.active? + output.puts JSON.pretty_generate(data, :max_nesting => false) + else + output.write JSON.generate(data, :max_nesting => false) + end + end + File.rename path + '.tmp', path + @filename = path + toplevel.display_status("Saved data to '#@filename'.") + unchange + end + rescue SystemCallError => e + Editor.error_dialog(self, "Failed to store JSON file: #{e}!") + end + + # Load the file named _filename_ into the editor as a JSON document. + def load_file(filename) + if filename + if File.directory?(filename) + Editor.error_dialog(self, "Try to select a JSON file!") + nil + else + @filename = filename + if data = read_data(filename) + toplevel.display_status("Loaded data from '#@filename'.") + end + display_title + data + end + end + end + + # Load the data at location _uri_ into the editor as a JSON document. + def load_location(uri) + data = read_data(uri) or return + @filename = nil + toplevel.display_status("Loaded data from '#{uri}'.") + display_title + data + end + + def parse_json(json) + check_pretty_printed(json) + if @encoding && !/^utf8$/i.match(@encoding) + iconverter = Iconv.new('utf8', @encoding) + json = iconverter.iconv(json) + end + JSON::parse(json, :max_nesting => false, :create_additions => false) + end + private :parse_json + + # Read a JSON document from the file named _filename_, parse it into a + # ruby data structure, and return the data. + def read_data(filename) + open(filename) do |f| + json = f.read + return parse_json(json) + end + rescue => e + Editor.error_dialog(self, "Failed to parse JSON file: #{e}!") + return + end + + # Open a file selecton dialog, displaying _message_, and return the + # selected filename or nil, if no file was selected. + def select_file(message) + filename = nil + fs = FileSelection.new(message) + fs.set_modal(true) + @default_dir = File.join(Dir.pwd, '') unless @default_dir + fs.set_filename(@default_dir) + fs.set_transient_for(self) + fs.signal_connect(:destroy) { Gtk.main_quit } + fs.ok_button.signal_connect(:clicked) do + filename = fs.filename + @default_dir = File.join(File.dirname(filename), '') + fs.destroy + Gtk.main_quit + end + fs.cancel_button.signal_connect(:clicked) do + fs.destroy + Gtk.main_quit + end + fs.show_all + Gtk.main + filename + end + + # Ask for location URI a to load data from. Returns the URI as a string. + def ask_for_location + dialog = Dialog.new( + "Load data from location...", + nil, nil, + [ Stock::OK, Dialog::RESPONSE_ACCEPT ], + [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] + ) + hbox = HBox.new(false, 5) + + hbox.pack_start(Label.new("Location:"), false) + hbox.pack_start(location_input = Entry.new) + location_input.width_chars = 60 + location_input.text = @location || '' + + dialog.vbox.pack_start(hbox, false) + + dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) + dialog.show_all + dialog.run do |response| + if response == Dialog::RESPONSE_ACCEPT + return @location = location_input.text + end + end + return + ensure + dialog.destroy if dialog + end + end + + class << self + # Starts a JSON Editor. If a block was given, it yields + # to the JSON::Editor::MainWindow instance. + def start(encoding = 'utf8') # :yield: window + Gtk.init + @window = Editor::MainWindow.new(encoding) + @window.icon_list = [ Editor.fetch_icon('json') ] + yield @window if block_given? + @window.show_all + Gtk.main + end + + # Edit the string _json_ with encoding _encoding_ in the editor. + def edit(json, encoding = 'utf8') + start(encoding) do |window| + window.edit json + end + end + + attr_reader :window + end + end +end + # vim: set et sw=2 ts=2: diff --git a/ext/json/lib/json/ext.rb b/ext/json/lib/json/ext.rb new file mode 100644 index 0000000000..ff4fa42329 --- /dev/null +++ b/ext/json/lib/json/ext.rb @@ -0,0 +1,13 @@ +require 'json/common' + +module JSON + # This module holds all the modules/classes that implement JSON's + # functionality as C extensions. + module Ext + require 'json/ext/parser' + require 'json/ext/generator' + $DEBUG and warn "Using c extension for JSON." + JSON.parser = Parser + JSON.generator = Generator + end +end diff --git a/ext/json/lib/json/json.xpm b/ext/json/lib/json/json.xpm new file mode 100644 index 0000000000..2cb626bb05 --- /dev/null +++ b/ext/json/lib/json/json.xpm @@ -0,0 +1,1499 @@ +/* XPM */ +static char * json_xpm[] = { +"64 64 1432 2", +" c None", +". c #641839", +"+ c #CF163C", +"@ c #D31C3B", +"# c #E11A38", +"$ c #5F242D", +"% c #320C22", +"& c #9B532D", +"* c #F32E34", +"= c #820F33", +"- c #4B0F34", +"; c #8E1237", +"> c #944029", +", c #961325", +"' c #A00C24", +") c #872C23", +"! c #694021", +"~ c #590D1F", +"{ c #420528", +"] c #D85A2D", +"^ c #7E092B", +"/ c #0E0925", +"( c #0D081F", +"_ c #0F081E", +": c #12071F", +"< c #360620", +"[ c #682A21", +"} c #673F21", +"| c #780E21", +"1 c #A82320", +"2 c #8D1D1F", +"3 c #970127", +"4 c #0D0123", +"5 c #0D0324", +"6 c #3B1E28", +"7 c #C28429", +"8 c #0C0523", +"9 c #0C041E", +"0 c #0E031A", +"a c #11031A", +"b c #13031B", +"c c #13031C", +"d c #11031D", +"e c #19051E", +"f c #390E20", +"g c #9C0C20", +"h c #C00721", +"i c #980320", +"j c #14031E", +"k c #CD9F32", +"l c #C29F2E", +"m c #0F0325", +"n c #0D0321", +"o c #0E0324", +"p c #D08329", +"q c #9D1B27", +"r c #1C0320", +"s c #0D011A", +"t c #120117", +"u c #130017", +"v c #150018", +"w c #160119", +"x c #17021A", +"y c #15021B", +"z c #11021E", +"A c #0F021F", +"B c #8C1821", +"C c #CF4522", +"D c #831821", +"E c #BA7033", +"F c #EDB339", +"G c #C89733", +"H c #280727", +"I c #0F051F", +"J c #0E0420", +"K c #591F27", +"L c #E47129", +"M c #612224", +"N c #0C021D", +"O c #120018", +"P c #140017", +"Q c #170017", +"R c #190018", +"S c #1B0019", +"T c #1B011A", +"U c #18011B", +"V c #15011C", +"W c #12031E", +"X c #460A21", +"Y c #A13823", +"Z c #784323", +"` c #5A0C21", +" . c #BC4530", +".. c #EB5B38", +"+. c #CE4E3B", +"@. c #DD9334", +"#. c #751A27", +"$. c #11071E", +"%. c #0F041C", +"&. c #1E0824", +"*. c #955A28", +"=. c #9A5027", +"-. c #1E0321", +";. c #11011A", +">. c #140018", +",. c #180018", +"'. c #1F001A", +"). c #20001B", +"!. c #1E001A", +"~. c #1B001A", +"{. c #16021B", +"]. c #16041E", +"^. c #220622", +"/. c #5F3525", +"(. c #DE5724", +"_. c #611021", +":. c #0F0925", +"<. c #D1892E", +"[. c #F27036", +"}. c #EC633B", +"|. c #DA293C", +"1. c #E64833", +"2. c #912226", +"3. c #11081C", +"4. c #110419", +"5. c #0F041E", +"6. c #451425", +"7. c #BF6F28", +"8. c #332225", +"9. c #0E021E", +"0. c #13001B", +"a. c #17001A", +"b. c #1C001B", +"c. c #21001C", +"d. c #23001C", +"e. c #21001B", +"f. c #19021A", +"g. c #17041E", +"h. c #150721", +"i. c #602424", +"j. c #D51223", +"k. c #540820", +"l. c #D04D2D", +"m. c #EA8933", +"n. c #875637", +"o. c #88543A", +"p. c #E5923A", +"q. c #891931", +"r. c #130B25", +"s. c #10051B", +"t. c #110217", +"u. c #12021A", +"v. c #761826", +"w. c #E2A728", +"x. c #300224", +"y. c #10011E", +"z. c #16001B", +"A. c #1B001B", +"B. c #21001A", +"C. c #1E0019", +"D. c #1D0019", +"E. c #1A011A", +"F. c #17031C", +"G. c #120720", +"H. c #4E0822", +"I. c #670721", +"J. c #C07630", +"K. c #F59734", +"L. c #BE1B35", +"M. c #0E1435", +"N. c #522037", +"O. c #DB8039", +"P. c #D45933", +"Q. c #420927", +"R. c #0F041D", +"S. c #140118", +"T. c #13021D", +"U. c #100423", +"V. c #7B6227", +"W. c #C04326", +"X. c #0E0020", +"Y. c #13001D", +"Z. c #18001B", +"`. c #1E001B", +" + c #22001C", +".+ c #22001B", +"++ c #1B011B", +"@+ c #16041D", +"#+ c #130520", +"$+ c #860521", +"%+ c #710520", +"&+ c #670A2A", +"*+ c #A66431", +"=+ c #E97536", +"-+ c #F8833A", +";+ c #F77A3A", +">+ c #C45337", +",+ c #0A1C35", +"'+ c #993638", +")+ c #F7863B", +"!+ c #F49736", +"~+ c #94462B", +"{+ c #0E031F", +"]+ c #130119", +"^+ c #160018", +"/+ c #16011B", +"(+ c #15021F", +"_+ c #120123", +":+ c #A65C28", +"<+ c #5C4D23", +"[+ c #0F001F", +"}+ c #14001D", +"|+ c #1A001B", +"1+ c #1F001B", +"2+ c #24001D", +"3+ c #25001D", +"4+ c #24001C", +"5+ c #1F001C", +"6+ c #1A011C", +"7+ c #16021E", +"8+ c #3F0421", +"9+ c #BC0522", +"0+ c #1C041E", +"a+ c #7F5531", +"b+ c #E68A38", +"c+ c #F8933E", +"d+ c #FA7942", +"e+ c #FB7543", +"f+ c #FA6F41", +"g+ c #F1793D", +"h+ c #7D3B3A", +"i+ c #28263B", +"j+ c #D45441", +"k+ c #F8A238", +"l+ c #996B2D", +"m+ c #0E0421", +"n+ c #12011A", +"o+ c #180019", +"p+ c #17001C", +"q+ c #12001F", +"r+ c #4C2B2A", +"s+ c #DB8130", +"t+ c #540023", +"u+ c #0F0120", +"v+ c #16011C", +"w+ c #22001D", +"x+ c #25001F", +"y+ c #26001F", +"z+ c #25001E", +"A+ c #24001E", +"B+ c #1D001C", +"C+ c #18011D", +"D+ c #16031F", +"E+ c #3C0522", +"F+ c #9B0821", +"G+ c #13041E", +"H+ c #F6462E", +"I+ c #E6AB37", +"J+ c #E7A03E", +"K+ c #FA9F44", +"L+ c #FB8A48", +"M+ c #FD7A4A", +"N+ c #FD794A", +"O+ c #FD7748", +"P+ c #FD7E45", +"Q+ c #FD8343", +"R+ c #FB5D42", +"S+ c #6E3A40", +"T+ c #EE8A37", +"U+ c #7E252B", +"V+ c #100520", +"W+ c #13011A", +"X+ c #170019", +"Y+ c #15001C", +"Z+ c #0F0020", +"`+ c #564427", +" @ c #E0BA29", +".@ c #5E2B25", +"+@ c #10011F", +"@@ c #17011C", +"#@ c #1E001D", +"$@ c #23001F", +"%@ c #250020", +"&@ c #24001F", +"*@ c #23001E", +"=@ c #21001E", +"-@ c #1B001C", +";@ c #17021D", +">@ c #14041E", +",@ c #AC0B25", +"'@ c #5E1420", +")@ c #F28635", +"!@ c #C2733E", +"~@ c #984C44", +"{@ c #EA9148", +"]@ c #FB844B", +"^@ c #FD7E4C", +"/@ c #FE7E4C", +"(@ c #FE7E4B", +"_@ c #FE7749", +":@ c #FD7148", +"<@ c #FB7D46", +"[@ c #F89641", +"}@ c #B95634", +"|@ c #0D0927", +"1@ c #11041D", +"2@ c #150119", +"3@ c #180017", +"4@ c #16001A", +"5@ c #13001E", +"6@ c #110023", +"7@ c #944C29", +"8@ c #EE6229", +"9@ c #3D0324", +"0@ c #12021F", +"a@ c #19011D", +"b@ c #21001F", +"c@ c #22001F", +"d@ c #20001E", +"e@ c #1F001D", +"f@ c #1C001C", +"g@ c #19011C", +"h@ c #3D1621", +"i@ c #B53622", +"j@ c #31061F", +"k@ c #841D34", +"l@ c #F2703F", +"m@ c #C14445", +"n@ c #E67349", +"o@ c #FB8E4B", +"p@ c #FD834C", +"q@ c #FE834D", +"r@ c #FE834C", +"s@ c #FE804C", +"t@ c #FD814B", +"u@ c #FB7D49", +"v@ c #F79B43", +"w@ c #AF1234", +"x@ c #0D0625", +"y@ c #13021C", +"z@ c #1A0019", +"A@ c #190019", +"B@ c #410225", +"C@ c #D39729", +"D@ c #AA5927", +"E@ c #0E0422", +"F@ c #15021E", +"G@ c #1A011D", +"H@ c #1D001D", +"I@ c #15031D", +"J@ c #240820", +"K@ c #A01023", +"L@ c #670B21", +"M@ c #3D0D33", +"N@ c #E63C3E", +"O@ c #EF7C45", +"P@ c #F59048", +"Q@ c #FB944A", +"R@ c #FD904A", +"S@ c #FE8E4B", +"T@ c #FE854A", +"U@ c #FE854B", +"V@ c #FE884C", +"W@ c #FC954B", +"X@ c #F8AB45", +"Y@ c #C37A35", +"Z@ c #0D0425", +"`@ c #13011B", +" # c #170018", +".# c #1A0018", +"+# c #1C0019", +"@# c #15001B", +"## c #100120", +"$# c #311F25", +"%# c #E68E28", +"&# c #7A1425", +"*# c #130321", +"=# c #17011E", +"-# c #1A001D", +";# c #19001B", +"># c #16021C", +",# c #130521", +"'# c #6F3123", +")# c #6D3022", +"!# c #C89433", +"~# c #EA7E3E", +"{# c #DB2943", +"]# c #EF7745", +"^# c #FB8544", +"/# c #FD9A43", +"(# c #FE9941", +"_# c #FE9D43", +":# c #FEA548", +"<# c #FEAE49", +"[# c #FCB944", +"}# c #CA9F35", +"|# c #0E0225", +"1# c #11001B", +"2# c #160019", +"3# c #12011B", +"4# c #0F0220", +"5# c #351D26", +"6# c #D85B28", +"7# c #6C0F26", +"8# c #190121", +"9# c #1B001E", +"0# c #1A001C", +"a# c #1D001B", +"b# c #130220", +"c# c #703A23", +"d# c #713A23", +"e# c #140327", +"f# c #411B36", +"g# c #C8713E", +"h# c #7A3A3F", +"i# c #CE2C3C", +"j# c #E77338", +"k# c #9C6535", +"l# c #9C6233", +"m# c #9C6332", +"n# c #9C6A35", +"o# c #C37D3C", +"p# c #FEAC41", +"q# c #FEC23E", +"r# c #826330", +"s# c #100122", +"t# c #120019", +"u# c #150017", +"v# c #190017", +"w# c #1B0018", +"x# c #12001A", +"y# c #10021F", +"z# c #1A0326", +"A# c #5F292A", +"B# c #7B4E29", +"C# c #3C0E25", +"D# c #1A0020", +"E# c #14021F", +"F# c #723B23", +"G# c #14001A", +"H# c #58042A", +"I# c #A28337", +"J# c #C8813B", +"K# c #B14B38", +"L# c #761231", +"M# c #5A132A", +"N# c #0D0726", +"O# c #0C0623", +"P# c #0B0723", +"Q# c #0B0A26", +"R# c #321C2D", +"S# c #C45B33", +"T# c #FEBB33", +"U# c #13052A", +"V# c #13011F", +"W# c #160017", +"X# c #15001A", +"Y# c #12001D", +"Z# c #94062A", +"`# c #630D2C", +" $ c #85292B", +".$ c #AA5E29", +"+$ c #1F0123", +"@$ c #19011F", +"#$ c #1E001C", +"$$ c #15031F", +"%$ c #712122", +"&$ c #712223", +"*$ c #14011B", +"=$ c #110321", +"-$ c #AF0C2B", +";$ c #E7D534", +">$ c #EAC934", +",$ c #84582D", +"'$ c #1B0824", +")$ c #11041E", +"!$ c #10021B", +"~$ c #100119", +"{$ c #100218", +"]$ c #0F041A", +"^$ c #0E0720", +"/$ c #2C1026", +"($ c #D8A328", +"_$ c #140322", +":$ c #160016", +"<$ c #14001F", +"[$ c #120024", +"}$ c #100128", +"|$ c #3C032F", +"1$ c #2C062E", +"2$ c #29022B", +"3$ c #A31D29", +"4$ c #976A25", +"5$ c #1A0321", +"6$ c #17031E", +"7$ c #1B021D", +"8$ c #20001C", +"9$ c #14041F", +"0$ c #703422", +"a$ c #6F3522", +"b$ c #8D0328", +"c$ c #920329", +"d$ c #0F0326", +"e$ c #100321", +"f$ c #11021B", +"g$ c #130117", +"h$ c #140016", +"i$ c #150015", +"j$ c #140015", +"k$ c #130116", +"l$ c #120219", +"m$ c #11031C", +"n$ c #12031D", +"o$ c #170016", +"p$ c #160020", +"q$ c #250029", +"r$ c #670033", +"s$ c #DCA238", +"t$ c #F5C736", +"u$ c #9A732E", +"v$ c #110227", +"w$ c #110324", +"x$ c #811924", +"y$ c #A04323", +"z$ c #250721", +"A$ c #1A041F", +"B$ c #1E011D", +"C$ c #1C011C", +"D$ c #18031D", +"E$ c #130721", +"F$ c #6F3623", +"G$ c #6B3622", +"H$ c #1A001A", +"I$ c #14011F", +"J$ c #12011E", +"K$ c #11011C", +"L$ c #140117", +"M$ c #170015", +"N$ c #150016", +"O$ c #120119", +"P$ c #11011B", +"Q$ c #11001A", +"R$ c #130018", +"S$ c #170118", +"T$ c #170119", +"U$ c #18021E", +"V$ c #1A0126", +"W$ c #6F2332", +"X$ c #E5563B", +"Y$ c #F1B83F", +"Z$ c #F6CC38", +"`$ c #9D7A2D", +" % c #130123", +".% c #130320", +"+% c #2A0721", +"@% c #B00E24", +"#% c #7D0B23", +"$% c #1F0522", +"%% c #1E0220", +"&% c #1D011E", +"*% c #1A031E", +"=% c #15051F", +"-% c #241322", +";% c #A32F23", +">% c #670E21", +",% c #1C001A", +"'% c #19001A", +")% c #180016", +"!% c #160118", +"~% c #140219", +"{% c #11021C", +"]% c #10021E", +"^% c #0F011D", +"/% c #170117", +"(% c #160219", +"_% c #17041D", +":% c #190523", +"<% c #8C042E", +"[% c #B65838", +"}% c #E9D73F", +"|% c #EED43E", +"1% c #D85538", +"2% c #493129", +"3% c #130120", +"4% c #15021D", +"5% c #330822", +"6% c #8A0825", +"7% c #3C0424", +"8% c #1E0322", +"9% c #1C0321", +"0% c #180421", +"a% c #130822", +"b% c #AF2D24", +"c% c #BC5623", +"d% c #2F071F", +"e% c #1A041C", +"f% c #1C031C", +"g% c #1D011C", +"h% c #160117", +"i% c #150419", +"j% c #12081D", +"k% c #0F0923", +"l% c #A77027", +"m% c #A60525", +"n% c #11021A", +"o% c #130218", +"p% c #150319", +"q% c #16061D", +"r% c #180923", +"s% c #9C1D2B", +"t% c #A32636", +"u% c #A66E3B", +"v% c #4B2E3C", +"w% c #412C36", +"x% c #36012D", +"y% c #140123", +"z% c #17001E", +"A% c #19011B", +"B% c #1A0421", +"C% c #340425", +"D% c #9E0326", +"E% c #1F0424", +"F% c #1C0524", +"G% c #180724", +"H% c #A91024", +"I% c #D55D24", +"J% c #90071E", +"K% c #3C051D", +"L% c #1C021C", +"M% c #1C011A", +"N% c #1D001A", +"O% c #160116", +"P% c #150216", +"Q% c #140217", +"R% c #140618", +"S% c #120D1D", +"T% c #231925", +"U% c #B16A2E", +"V% c #FDAC34", +"W% c #D58631", +"X% c #280E2A", +"Y% c #0D0A23", +"Z% c #0F0920", +"`% c #120C21", +" & c #1F1026", +".& c #A3352E", +"+& c #EE9F36", +"@& c #5D2A3C", +"#& c #960D3C", +"$& c #970638", +"%& c #A00330", +"&& c #4D0126", +"*& c #1C001F", +"=& c #280120", +"-& c #290223", +";& c #1F0425", +">& c #260726", +",& c #340A26", +"'& c #850925", +")& c #3A0823", +"!& c #82071D", +"~& c #5E071D", +"{& c #18051C", +"]& c #18021A", +"^& c #190118", +"/& c #160217", +"(& c #150418", +"_& c #130618", +":& c #110718", +"<& c #10081A", +"[& c #110D1D", +"}& c #291C24", +"|& c #A73B2D", +"1& c #FD6B36", +"2& c #FD853C", +"3& c #FD863B", +"4& c #C24A35", +"5& c #6B442F", +"6& c #6D302D", +"7& c #6E252E", +"8& c #8E3B32", +"9& c #DE7739", +"0& c #F48E3F", +"a& c #DD8D41", +"b& c #854F3D", +"c& c #7E2D35", +"d& c #33082B", +"e& c #1C0222", +"f& c #20001F", +"g& c #1F0222", +"h& c #1A0524", +"i& c #440C27", +"j& c #BC1427", +"k& c #20041B", +"l& c #53061C", +"m& c #25071B", +"n& c #11061A", +"o& c #130418", +"p& c #140317", +"q& c #150217", +"r& c #160318", +"s& c #12051B", +"t& c #100C1D", +"u& c #0E101E", +"v& c #0C121F", +"w& c #0C1321", +"x& c #781725", +"y& c #B25D2C", +"z& c #FA6335", +"A& c #FD633C", +"B& c #FE6D42", +"C& c #FE7C42", +"D& c #FE813F", +"E& c #FE873C", +"F& c #FD743B", +"G& c #FB683B", +"H& c #FA7A3E", +"I& c #F98242", +"J& c #F97844", +"K& c #F98943", +"L& c #F79C3D", +"M& c #A25133", +"N& c #280B28", +"O& c #1D021F", +"P& c #1F011C", +"Q& c #280321", +"R& c #1C0724", +"S& c #3F1C27", +"T& c #D33C27", +"U& c #0E061B", +"V& c #0C091C", +"W& c #0C0A1B", +"X& c #0E091A", +"Y& c #11081B", +"Z& c #100A20", +"`& c #0E0D23", +" * c #551227", +".* c #B21829", +"+* c #C42329", +"@* c #C62C29", +"#* c #C55429", +"$* c #E76F2B", +"%* c #F14232", +"&* c #F95E3A", +"** c #FC6740", +"=* c #FE6E45", +"-* c #FE7246", +";* c #FE7545", +">* c #FE7744", +",* c #FD7745", +"'* c #FD7845", +")* c #FD7847", +"!* c #FD7948", +"~* c #FD7B44", +"{* c #FC7C3B", +"]* c #6F3130", +"^* c #140B24", +"/* c #19031D", +"(* c #1C011B", +"_* c #5A011F", +":* c #B70421", +"<* c #380824", +"[* c #3E2626", +"}* c #9F5626", +"|* c #13051E", +"1* c #360A21", +"2* c #361223", +"3* c #371724", +"4* c #381824", +"5* c #3B1524", +"6* c #3E1E26", +"7* c #471A29", +"8* c #DB252E", +"9* c #ED2733", +"0* c #EE5436", +"a* c #F04237", +"b* c #F33934", +"c* c #F53D2F", +"d* c #D7312B", +"e* c #AF212B", +"f* c #3A2C31", +"g* c #F65F39", +"h* c #FB6F41", +"i* c #FD6D45", +"j* c #FE7047", +"k* c #FE7647", +"l* c #FE7847", +"m* c #FE7848", +"n* c #FE7748", +"o* c #FE7948", +"p* c #FE7C48", +"q* c #FE7C47", +"r* c #FE7642", +"s* c #FE7439", +"t* c #6D332C", +"u* c #100B21", +"v* c #16031B", +"w* c #2B001B", +"x* c #22011F", +"y* c #220521", +"z* c #1B0A23", +"A* c #421425", +"B* c #951924", +"C* c #381023", +"D* c #E94028", +"E* c #E7302B", +"F* c #EF432D", +"G* c #F4302E", +"H* c #F32C30", +"I* c #CB4432", +"J* c #DD3235", +"K* c #EF4B3A", +"L* c #F0333E", +"M* c #CC3D3F", +"N* c #E4313C", +"O* c #F34834", +"P* c #D13E2C", +"Q* c #431825", +"R* c #0E1424", +"S* c #3C202C", +"T* c #F15537", +"U* c #F97140", +"V* c #FC6E45", +"W* c #FE7547", +"X* c #FE7947", +"Y* c #FE7B48", +"Z* c #FE7D48", +"`* c #FE8047", +" = c #FE7A42", +".= c #FE7A38", +"+= c #6D442B", +"@= c #0F0B21", +"#= c #15031A", +"$= c #49001B", +"%= c #2F001C", +"&= c #21021E", +"*= c #220620", +"== c #1B0D23", +"-= c #641625", +";= c #951823", +">= c #390F25", +",= c #AC3A2A", +"'= c #B6492E", +")= c #ED7531", +"!= c #F45A34", +"~= c #F54C36", +"{= c #C72D39", +"]= c #DE283C", +"^= c #F33B40", +"/= c #F34142", +"(= c #D0393F", +"_= c #E72E39", +":= c #DB3C2E", +"<= c #461724", +"[= c #0F0D1E", +"}= c #140B1E", +"|= c #341427", +"1= c #CB4834", +"2= c #F7743F", +"3= c #FB7145", +"4= c #FE7747", +"5= c #FE7A47", +"6= c #FF7B48", +"7= c #FF7C48", +"8= c #FE7F47", +"9= c #FE8247", +"0= c #FE8642", +"a= c #FE8439", +"b= c #6D442D", +"c= c #0F0A21", +"d= c #14031A", +"e= c #20031D", +"f= c #210821", +"g= c #191024", +"h= c #CC1C25", +"i= c #961423", +"j= c #2C162C", +"k= c #BD242E", +"l= c #EF2C31", +"m= c #F54C34", +"n= c #F34037", +"o= c #F5353A", +"p= c #F7413D", +"q= c #F8423D", +"r= c #F93A39", +"s= c #F95731", +"t= c #341425", +"u= c #110A1D", +"v= c #140619", +"w= c #18051B", +"x= c #200F26", +"y= c #864833", +"z= c #F8773F", +"A= c #FC7445", +"B= c #FF7E48", +"C= c #FF7E49", +"D= c #FF7D49", +"E= c #FF7D48", +"F= c #FE8347", +"G= c #FE8743", +"H= c #FE893B", +"I= c #6E452F", +"J= c #100E23", +"K= c #14041A", +"L= c #55041D", +"M= c #540921", +"N= c #161124", +"O= c #CE6A25", +"P= c #3F1129", +"Q= c #170A29", +"R= c #0F0F29", +"S= c #15132B", +"T= c #1E182D", +"U= c #A82B3D", +"V= c #CB6633", +"W= c #CC6932", +"X= c #CC3D2D", +"Y= c #331225", +"Z= c #0F091C", +"`= c #120417", +" - c #160216", +".- c #190419", +"+- c #210F26", +"@- c #8C4934", +"#- c #F97A40", +"$- c #FC7545", +"%- c #FF7B49", +"&- c #FE7D46", +"*- c #FE7E43", +"=- c #FD7B3E", +"-- c #FA6934", +";- c #532328", +">- c #130B1D", +",- c #150519", +"'- c #14041C", +")- c #120920", +"!- c #C43624", +"~- c #A21E23", +"{- c #F87C30", +"]- c #C9302D", +"^- c #300F2A", +"/- c #591129", +"(- c #171328", +"_- c #171628", +":- c #141829", +"<- c #101A2B", +"[- c #0F172B", +"}- c #0F1226", +"|- c #0E0C20", +"1- c #100619", +"2- c #140316", +"3- c #19051B", +"4- c #3C1428", +"5- c #E04B36", +"6- c #FA7B41", +"7- c #FD7346", +"8- c #FE7548", +"9- c #FF7849", +"0- c #FF7749", +"a- c #FE7B47", +"b- c #FE7945", +"c- c #FC7740", +"d- c #FA7E39", +"e- c #C1432F", +"f- c #131523", +"g- c #130A1C", +"h- c #420621", +"i- c #D08423", +"j- c #F87739", +"k- c #C03D37", +"l- c #962B34", +"m- c #A14332", +"n- c #E54B30", +"o- c #9E3E2F", +"p- c #7F262E", +"q- c #922D2E", +"r- c #9C4B2E", +"s- c #65212C", +"t- c #101628", +"u- c #101022", +"v- c #11091C", +"w- c #130619", +"x- c #160A1E", +"y- c #43252C", +"z- c #F66439", +"A- c #FA6942", +"B- c #FD6C47", +"C- c #FE6E48", +"D- c #FE6F48", +"E- c #FE7049", +"F- c #FE714A", +"G- c #FE744A", +"H- c #FE7846", +"I- c #FD7243", +"J- c #FC703E", +"K- c #FA6C37", +"L- c #81312B", +"M- c #121123", +"N- c #15071D", +"O- c #16031A", +"P- c #17021B", +"Q- c #8F3D22", +"R- c #F8393E", +"S- c #E42A3D", +"T- c #E7473B", +"U- c #FB503B", +"V- c #FB4F3A", +"W- c #F95439", +"X- c #ED4C38", +"Y- c #F45938", +"Z- c #FB6537", +"`- c #EA5236", +" ; c #CE6232", +".; c #CD392C", +"+; c #181425", +"@; c #120F21", +"#; c #130D20", +"$; c #151225", +"%; c #903431", +"&; c #F8703D", +"*; c #FB6344", +"=; c #FD6748", +"-; c #FE6849", +";; c #FE6949", +">; c #FE6A49", +",; c #FE6C4A", +"'; c #FE704A", +"); c #FE734A", +"!; c #FE7449", +"~; c #FE7347", +"{; c #FE7145", +"]; c #FD6C42", +"^; c #FD753D", +"/; c #F36E35", +"(; c #CB452C", +"_; c #600D24", +":; c #1C061F", +"<; c #1E031F", +"[; c #5B3821", +"}; c #CE9822", +"|; c #FA4341", +"1; c #FB4341", +"2; c #FC4541", +"3; c #FC4542", +"4; c #FC4143", +"5; c #FC4D42", +"6; c #FB5042", +"7; c #FB5342", +"8; c #FC5242", +"9; c #FD4F40", +"0; c #FD503E", +"a; c #FB6339", +"b; c #F45E33", +"c; c #A12A2E", +"d; c #401E2C", +"e; c #452D2F", +"f; c #F74F38", +"g; c #FA5940", +"h; c #FC6245", +"i; c #FE6447", +"j; c #FE6449", +"k; c #FE6549", +"l; c #FE6749", +"m; c #FE6B49", +"n; c #FE6D49", +"o; c #FE6D48", +"p; c #FE6D47", +"q; c #FE6D45", +"r; c #FE6C44", +"s; c #FE6A42", +"t; c #FE663C", +"u; c #FC6233", +"v; c #752129", +"w; c #1F0922", +"x; c #750520", +"y; c #81061F", +"z; c #FA3D42", +"A; c #FB4142", +"B; c #FD4543", +"C; c #FD4844", +"D; c #FD4A45", +"E; c #FD4D45", +"F; c #FD5045", +"G; c #FD5345", +"H; c #FE5346", +"I; c #FE5445", +"J; c #FD5444", +"K; c #FC4F41", +"L; c #FA513D", +"M; c #F95339", +"N; c #F63736", +"O; c #F75737", +"P; c #F95F3B", +"Q; c #FB5840", +"R; c #FD5F43", +"S; c #FE6345", +"T; c #FE6547", +"U; c #FE6548", +"V; c #FE6448", +"W; c #FE6248", +"X; c #FE6348", +"Y; c #FE6748", +"Z; c #FE6848", +"`; c #FE6846", +" > c #FE6A45", +".> c #FE6D43", +"+> c #FE703F", +"@> c #FC6F36", +"#> c #6F302B", +"$> c #140A22", +"%> c #FA3B42", +"&> c #FC4243", +"*> c #FD4744", +"=> c #FE4A45", +"-> c #FE4C47", +";> c #FE4D47", +">> c #FE5047", +",> c #FE5347", +"'> c #FE5447", +")> c #FD5246", +"!> c #FB503F", +"~> c #FA543D", +"{> c #9B3D3B", +"]> c #A3433B", +"^> c #F9683D", +"/> c #FC6940", +"(> c #FE6342", +"_> c #FE6645", +":> c #FE6646", +"<> c #FE6147", +"[> c #FE6048", +"}> c #FE6148", +"|> c #FE6746", +"1> c #FE6A46", +"2> c #FE6F45", +"3> c #FE7441", +"4> c #FC7D39", +"5> c #6C422E", +"6> c #0F0F23", +"7> c #FA4142", +"8> c #FC4643", +"9> c #FE4D46", +"0> c #FE4E47", +"a> c #FE4F48", +"b> c #FE5148", +"c> c #FE5348", +"d> c #FE5548", +"e> c #FE5247", +"f> c #FD5445", +"g> c #FC5544", +"h> c #F96041", +"i> c #D33F3D", +"j> c #392D39", +"k> c #973C38", +"l> c #F94E3A", +"m> c #FD693E", +"n> c #FE6C43", +"o> c #FE6047", +"p> c #FE5D47", +"q> c #FE5E48", +"r> c #FE6948", +"s> c #FE6947", +"t> c #FE6B47", +"u> c #FE6E46", +"v> c #FD6D43", +"w> c #FB723D", +"x> c #D54A33", +"y> c #301C29", +"z> c #FB4A42", +"A> c #FD4B44", +"B> c #FE4F47", +"C> c #FE5048", +"D> c #FE5648", +"E> c #FE5848", +"F> c #FE5747", +"G> c #FE5547", +"H> c #FC5945", +"I> c #F95742", +"J> c #F3543D", +"K> c #A33336", +"L> c #302032", +"M> c #152433", +"N> c #CD3E38", +"O> c #FD5A3F", +"P> c #FE6343", +"Q> c #FE6446", +"R> c #FE6247", +"S> c #FE6A47", +"T> c #FC6542", +"U> c #FB6A3B", +"V> c #FA6D34", +"W> c #D73C2D", +"X> c #442428", +"Y> c #281323", +"Z> c #FD4E42", +"`> c #FD4D43", +" , c #FE4D45", +"., c #FE5248", +"+, c #FE5947", +"@, c #FE5C47", +"#, c #FE5B47", +"$, c #FE5A47", +"%, c #FE5847", +"&, c #FC5C45", +"*, c #F95B43", +"=, c #F3613F", +"-, c #E74F37", +";, c #8C2431", +">, c #161E2F", +",, c #CD4E33", +"', c #FD503A", +"), c #FE5D40", +"!, c #FE6445", +"~, c #FE6946", +"{, c #FE6847", +"], c #FE6747", +"^, c #FD6644", +"/, c #FD6241", +"(, c #FD5B3D", +"_, c #FE6739", +":, c #FE6135", +"<, c #AB4830", +"[, c #733E2A", +"}, c #161224", +"|, c #FC4E42", +"1, c #FE4D44", +"2, c #FE4E46", +"3, c #FE5147", +"4, c #FE5E47", +"5, c #FD5C46", +"6, c #FA5B44", +"7, c #F45441", +"8, c #EB393A", +"9, c #CC3433", +"0, c #47212F", +"a, c #59242F", +"b, c #FC6734", +"c, c #FC6F3A", +"d, c #FC723E", +"e, c #FD6540", +"f, c #FE6442", +"g, c #FE6643", +"h, c #FE6944", +"i, c #FE6546", +"j, c #FE6444", +"k, c #FE6143", +"l, c #FE5E41", +"m, c #FE613F", +"n, c #FE683C", +"o, c #FE7937", +"p, c #A25030", +"q, c #692629", +"r, c #151122", +"s, c #FA573F", +"t, c #FB4D40", +"u, c #FC4F43", +"v, c #FE5246", +"w, c #FF6347", +"x, c #FE5F48", +"y, c #F65942", +"z, c #F0493D", +"A, c #ED3736", +"B, c #73262F", +"C, c #10152C", +"D, c #3B292F", +"E, c #363034", +"F, c #AC3938", +"G, c #FC6B3B", +"H, c #FD763C", +"I, c #FE6D3F", +"J, c #FE6341", +"K, c #FE6642", +"L, c #FE6745", +"M, c #FE6245", +"N, c #FE6244", +"O, c #FE6841", +"P, c #FF683B", +"Q, c #EC7035", +"R, c #D0412D", +"S, c #3A1627", +"T, c #CF3938", +"U, c #F6543C", +"V, c #FB5040", +"W, c #FD5544", +"X, c #FE5A48", +"Y, c #FE5D48", +"Z, c #FE5F47", +"`, c #FF6147", +" ' c #FD5C45", +".' c #FB5B43", +"+' c #FA5A42", +"@' c #F76040", +"#' c #F4623D", +"$' c #F26D38", +"%' c #EC4130", +"&' c #380E2B", +"*' c #13122C", +"=' c #362D31", +"-' c #353435", +";' c #352E37", +">' c #2D3337", +",' c #CC5838", +"'' c #CD6F3A", +")' c #CE6E3D", +"!' c #FE793F", +"~' c #FD7541", +"{' c #FD6243", +"]' c #FE6545", +"^' c #FF6543", +"/' c #FF6240", +"(' c #FE723B", +"_' c #FE8034", +":' c #442D2C", +"<' c #311725", +"[' c #222830", +"}' c #B73B36", +"|' c #F94C3D", +"1' c #FD5543", +"2' c #FE5B48", +"3' c #FF5E47", +"4' c #FE5C48", +"5' c #FC5B44", +"6' c #F95640", +"7' c #C34E3D", +"8' c #A45A3A", +"9' c #F37438", +"0' c #F28935", +"a' c #AF422F", +"b' c #240D2B", +"c' c #88292F", +"d' c #FA8E34", +"e' c #FC7E38", +"f' c #FC5939", +"g' c #694A37", +"h' c #693437", +"i' c #382638", +"j' c #142439", +"k' c #9F483A", +"l' c #C45E3C", +"m' c #FD7240", +"n' c #FF6645", +"o' c #FF6245", +"p' c #FF6045", +"q' c #FF6146", +"r' c #FF6246", +"s' c #FF6446", +"t' c #FF6545", +"u' c #FE763F", +"v' c #FE7237", +"w' c #C65331", +"x' c #3D272A", +"y' c #0D1E2B", +"z' c #683032", +"A' c #F9453A", +"B' c #FD5341", +"C' c #FE5A46", +"D' c #FF5A48", +"E' c #FE5948", +"F' c #FD5A47", +"G' c #FC5D43", +"H' c #F95B3D", +"I' c #713F37", +"J' c #1E2D32", +"K' c #C44531", +"L' c #EF7A2F", +"M' c #6B2E2C", +"N' c #0F0E2C", +"O' c #F56633", +"P' c #FA803A", +"Q' c #FC673E", +"R' c #FD673E", +"S' c #FC6F3C", +"T' c #FA6E3B", +"U' c #C6633A", +"V' c #A06739", +"W' c #835638", +"X' c #381F38", +"Y' c #713B38", +"Z' c #7B503C", +"`' c #FE7741", +" ) c #FE7344", +".) c #FE6D46", +"+) c #FF6946", +"@) c #FF5E46", +"#) c #FF5D46", +"$) c #FF5D47", +"%) c #FF5F48", +"&) c #FF6248", +"*) c #FE6941", +"=) c #FC783C", +"-) c #C46B35", +";) c #892730", +">) c #111629", +",) c #1F2630", +"') c #AD3939", +")) c #FC5D41", +"!) c #FE5946", +"~) c #FF5848", +"{) c #FE5549", +"]) c #FC5E42", +"^) c #FA673B", +"/) c #DB7033", +"() c #392E2B", +"_) c #311A28", +":) c #3C2127", +"<) c #1D1027", +"[) c #92102C", +"}) c #F58336", +"|) c #FA673E", +"1) c #FD6642", +"2) c #FD5A41", +"3) c #FC6D41", +"4) c #FC6D3F", +"5) c #FD683E", +"6) c #F38C39", +"7) c #CE6535", +"8) c #612E34", +"9) c #1D2637", +"0) c #71513E", +"a) c #FF6847", +"b) c #FF5F47", +"c) c #FF5A46", +"d) c #FF5847", +"e) c #FF5748", +"f) c #FF594A", +"g) c #FF5E4B", +"h) c #FE654C", +"i) c #FE694B", +"j) c #FE6B48", +"k) c #FC6A43", +"l) c #F7683E", +"m) c #EC6E39", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" . + @ # $ % ", +" & * = - ; > , ' ) ! ~ ", +" { ] ^ / ( _ : < [ } | 1 2 ", +" 3 4 5 6 7 8 9 0 a b c d e f g h i j ", +" k l m n o p q r s t u v w x y z A B C D ", +" E F G H I J K L M N O P Q R S T U V W X Y Z ` ", +" ...+.@.#.$.%.&.*.=.-.;.>.,.S '.).!.~.{.].^./.(._. ", +" :.<.[.}.|.1.2.3.4.5.6.7.8.9.0.a.b.c.d.e.!.S f.g.h.i.j.k. ", +" l.m.n.o.p.q.r.s.t.u.J v.w.x.y.z.A.c.d.d.B.C.D.E.F.G.H.I. ", +" J.K.L.M.N.O.P.Q.R.t S.T.U.V.W.X.Y.Z.`. +d.d..+B.'.++@+#+$+%+ ", +" &+*+=+-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+[+}+|+1+d.2+3+4+d.5+6+7+8+9+0+ ", +" a+b+c+d+e+f+g+h+i+j+k+l+m+n+^+o+p+q+r+s+t+u+v+b.w+x+y+z+A+w+B+C+D+E+F+G+ ", +" H+I+J+K+L+M+N+O+P+Q+R+S+T+U+V+W+Q ,.X+Y+Z+`+ @.@+@@@#@$@%@&@*@=@#@-@;@>@,@'@ ", +" )@!@~@{@]@^@/@(@_@:@<@[@}@|@1@2@3@R ,.4@5@6@7@8@9@0@a@#@b@c@=@d@e@f@g@>@h@i@j@ ", +" k@l@m@n@o@p@q@r@s@t@u@v@w@x@y@^+R S z@A@z.q+B@C@D@E@F@G@H@#@e@#@#@f@g@I@J@K@L@ ", +" M@N@O@P@Q@R@S@T@U@V@W@X@Y@Z@`@ #.#+#+#S A@@###$#%#&#*#=#-#f@B+B+B+f@;#>#,#'#)# ", +" !#~#{#]#^#/#(#(#_#:#<#[#}#|#1#^+.#S +#+#z@2#3#4#5#6#7#8#9#0#A.B+B+a#A.@@b#c#d# ", +" e#f#g#h#i#j#k#l#m#n#o#p#q#r#s#t#u#v#.#w#S R ^+x#y#z#A#B#C#D#-#A.a#`.`.b.g@E#d#F# ", +" G#0@H#I#J#K#L#M#N#O#P#Q#R#S#T#U#V#>.W#3@v#R R X+X#Y#s#Z#`# $.$+$@$g@f@5+5+#$6+$$%$&$ ", +" *$=$-$;$>$,$'$)$!$~${$]$^$/$($_$*$u#:$Q 3@,.X+z.<$[$}$|$1$2$3$4$5$6$7$e@8$#$G@9$0$a$ ", +" ,.4@E#b$c$d$e$f$g$h$i$j$k$l$m$n$`@>.:$o$3@,. #a.p$q$r$s$t$u$v$w$x$y$z$A$B$#@C$D$E$F$G$ ", +" R S H$v+I$J$K$n+L$:$o$o$M$N$L$O$P$Q$R$N$o$3@S$T$U$V$W$X$Y$Z$`$ %.%+%@%#%$%%%&%*%=%-%;%>% ", +" E.,%~.'%Z.4@v W#o$)%)%)%Q !%~%{%]%^%Q$u u#/%(%_%:%<%[%}%|%1%2%3%4%=%5%6%7%8%9%0%a%b%c%d% ", +" e%f%g%a#,%,%z@R 3@3@3@)%Q h%i%j%k%l%m%{+n%o%p%q%r%s%t%u%v%w%x%y%z%A%*%B%C%D%E%F%G%H%I% ", +" J%K%L%M%N%D.S v#)%)%O%P%Q%R%S%T%U%V%W%X%Y%Z%`% &.&+&@&#&$&%&&&*&f@a##@=&-&;&>&,&'&)& ", +" !&~&{&]&^&.#w#^&/%/&(&_&:&<&[&}&|&1&2&3&4&5&6&7&8&9&0&a&b&c&d&e&e@1+5+e@f&g&h&i&j& ", +" k&l&m&n&o&p&q&r&i%s&3.t&u&v&w&x&y&z&A&B&C&D&E&F&G&H&I&J&K&L&M&N&O&P&1+`.e@f&Q&R&S&T& ", +" 0 U&V&W&X&<&Y&j%Z&`& *.*+*@*#*$*%*&***=*-*;*>*>*,*'*)*!*~*{*]*^*/*(*a#B+#@_*:*<*[*}* ", +" |*1*2*3*4*5*6*7*8*9*0*a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*E.w*d.e@x*y*z*A*B* ", +" C*D*E*F*G*H*I*J*K*L*M*N*O*P*Q*R*S*T*U*V*W*l*X*o*o*Y*Z*`* =.=+=@=#='%$=%=e@&=*===-=;= ", +" >=,='=)=!=~={=]=^=/=(=_=:=<=[=}=|=1=2=3=4=5=p*6=6=7=8=9=0=a=b=c=d=A@~.b.B+e=f=g=h=i= ", +" j=k=l=m=n=o=p=q=r=s=t=u=v=w=x=y=z=A=5=Z*B=C=D=E=8=F=G=H=I=J=K=S$R z@'%L=M=N=O= ", +" P=Q=R=S=T=U=V=W=X=Y=Z=`= -.-+-@-#-$-5=p*E=D=%-%-q*&-*-=---;->-,-/%3@^+'-)-!-~- ", +" {-]-^-/-(-_-:-<-[-}-|-1-2- -3-4-5-6-7-8-n*m*9-0-9-o*a-b-c-d-e-f-g-(&h%w c h-i- ", +" j-k-l-m-n-o-p-q-r-s-t-u-v-w-,-x-y-z-A-B-C-D-E-E-F-G-_@m*H-I-J-K-L-M-N-O-P-(+Q- ", +" R-S-T-U-V-W-X-Y-Z-`- ;.;+;@;#;$;%;&;*;=;-;-;;;>;,;';);!;~;{;];^;/;(;_;:;<;[;}; ", +" |;1;2;3;4;5;6;7;8;9;0;a;b;c;d;e;f;g;h;i;j;j;k;k;l;m;n;o;p;q;r;s;t;u;v;w;x;y; ", +" z;A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;k;Y;Z;`; >r;.>+>@>#>$> ", +" %>&>*>=>->;>>>,>'>,>)>F;8;!>~>{>]>^>/>(>_>:>i;<>[>X;}>i;|>1>q;2>3>4>5>6> ", +" 7>8>=>9>0>a>b>c>d>,>e>e>f>g>h>i>j>k>l>m>n>:>i;o>p>q>W;r>s>t>p;u>v>w>x>y> ", +" z>A>9>0>B>C>c>D>E>F>G>G>F>H>I>J>K>L>M>N>O>P>Q>R>o>R>T;s>S>S>S>t>1>T>U>V>W>X>Y> ", +" Z>`> ,9>B>.,D>+,@,#,$,%,$,&,*,=,-,;,>,,,',),P>!,!,_>~,t>s>{,],{,],^,/,(,_,:,<,[,}, ", +" |,`>1,2,3,G>+,4,o>o>4,@,@,5,6,7,8,9,0,a,b,c,d,e,f,g,h, >~,|>T;T;T;i,j,k,l,m,n,o,p,q,r, ", +" s,t,u,v,G>%,@,o>w,R>x,p>@,5,6,y,z,A,B,C,D,E,F,G,H,I,J,K,L,L,i,i;i;i;Q>S;M,N,P>O,P,Q,R,S, ", +" T,U,V,W,%,X,Y,Z,`,[>q>@, '.'+'@'#'$'%'&'*'='-';'>',''')'!'~'{'N,i,:>_>]'M,M,Q>_>^'/'('_':'<' ", +" ['}'|'1'$,X,2'p>3'4'2'@,5'6'7'8'9'0'a'b'c'd'e'f'g'h'i'j'k'l'd,m'g, > >n'o'p'q'r's't'.>u'v'w'x' ", +" y'z'A'B'C'X,X,2'D'E'E'F'G'H'I'J'K'L'M'N'O'P'Q'R'S'T'U'V'W'X'Y'Z'`' ).)+)r'@)#)$)%)&)l;1>*)=)-);) ", +" >),)')))!)X,E'X,~){)d>!)])^)/)()_):)<)[)})|)1)f,2)3)4)5)6)7)8)9)0)*--*a)b)c)d)e)f)g)h)i)j)k)l)m) ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/ext/json/lib/json/pure.rb b/ext/json/lib/json/pure.rb new file mode 100644 index 0000000000..b86d905523 --- /dev/null +++ b/ext/json/lib/json/pure.rb @@ -0,0 +1,75 @@ +require 'json/common' +require 'json/pure/parser' +require 'json/pure/generator' + +module JSON + begin + require 'iconv' + # An iconv instance to convert from UTF8 to UTF16 Big Endian. + UTF16toUTF8 = Iconv.new('utf-8', 'utf-16be') # :nodoc: + # An iconv instance to convert from UTF16 Big Endian to UTF8. + UTF8toUTF16 = Iconv.new('utf-16be', 'utf-8') # :nodoc: + UTF8toUTF16.iconv('no bom') + rescue Errno::EINVAL, Iconv::InvalidEncoding + # Iconv doesn't support big endian utf-16. Let's try to hack this manually + # into the converters. + begin + old_verbose, $VERBSOSE = $VERBOSE, nil + # An iconv instance to convert from UTF8 to UTF16 Big Endian. + UTF16toUTF8 = Iconv.new('utf-8', 'utf-16') # :nodoc: + # An iconv instance to convert from UTF16 Big Endian to UTF8. + UTF8toUTF16 = Iconv.new('utf-16', 'utf-8') # :nodoc: + UTF8toUTF16.iconv('no bom') + if UTF8toUTF16.iconv("\xe2\x82\xac") == "\xac\x20" + swapper = Class.new do + def initialize(iconv) # :nodoc: + @iconv = iconv + end + + def iconv(string) # :nodoc: + result = @iconv.iconv(string) + JSON.swap!(result) + end + end + UTF8toUTF16 = swapper.new(UTF8toUTF16) # :nodoc: + end + if UTF16toUTF8.iconv("\xac\x20") == "\xe2\x82\xac" + swapper = Class.new do + def initialize(iconv) # :nodoc: + @iconv = iconv + end + + def iconv(string) # :nodoc: + string = JSON.swap!(string.dup) + @iconv.iconv(string) + end + end + UTF16toUTF8 = swapper.new(UTF16toUTF8) # :nodoc: + end + rescue Errno::EINVAL, Iconv::InvalidEncoding + raise MissingUnicodeSupport, "iconv doesn't seem to support UTF-8/UTF-16 conversions" + ensure + $VERBOSE = old_verbose + end + rescue LoadError + raise MissingUnicodeSupport, + "iconv couldn't be loaded, which is required for UTF-8/UTF-16 conversions" + end + + # Swap consecutive bytes of _string_ in place. + def self.swap!(string) # :nodoc: + 0.upto(string.size / 2) do |i| + break unless string[2 * i + 1] + string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i] + end + string + end + + # This module holds all the modules/classes that implement JSON's + # functionality in pure ruby. + module Pure + $DEBUG and warn "Using pure library for JSON." + JSON.parser = Parser + JSON.generator = Generator + end +end diff --git a/ext/json/lib/json/pure/generator.rb b/ext/json/lib/json/pure/generator.rb new file mode 100644 index 0000000000..0fe73be41a --- /dev/null +++ b/ext/json/lib/json/pure/generator.rb @@ -0,0 +1,394 @@ +module JSON + MAP = { + "\x0" => '\u0000', + "\x1" => '\u0001', + "\x2" => '\u0002', + "\x3" => '\u0003', + "\x4" => '\u0004', + "\x5" => '\u0005', + "\x6" => '\u0006', + "\x7" => '\u0007', + "\b" => '\b', + "\t" => '\t', + "\n" => '\n', + "\xb" => '\u000b', + "\f" => '\f', + "\r" => '\r', + "\xe" => '\u000e', + "\xf" => '\u000f', + "\x10" => '\u0010', + "\x11" => '\u0011', + "\x12" => '\u0012', + "\x13" => '\u0013', + "\x14" => '\u0014', + "\x15" => '\u0015', + "\x16" => '\u0016', + "\x17" => '\u0017', + "\x18" => '\u0018', + "\x19" => '\u0019', + "\x1a" => '\u001a', + "\x1b" => '\u001b', + "\x1c" => '\u001c', + "\x1d" => '\u001d', + "\x1e" => '\u001e', + "\x1f" => '\u001f', + '"' => '\"', + '\\' => '\\\\', + '/' => '\/', + } # :nodoc: + + # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with + # UTF16 big endian characters as \u????, and return it. + def utf8_to_json(string) # :nodoc: + string = string.gsub(/["\\\/\x0-\x1f]/) { |c| MAP[c] } + string.gsub!(/( + (?: + [\xc2-\xdf][\x80-\xbf] | + [\xe0-\xef][\x80-\xbf]{2} | + [\xf0-\xf4][\x80-\xbf]{3} + )+ | + [\x80-\xc1\xf5-\xff] # invalid + )/nx) { |c| + c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'" + s = JSON::UTF8toUTF16.iconv(c).unpack('H*')[0] + s.gsub!(/.{4}/n, '\\\\u\&') + } + string + rescue Iconv::Failure => e + raise GeneratorError, "Caught #{e.class}: #{e}" + end + module_function :utf8_to_json + + module Pure + module Generator + # This class is used to create State instances, that are use to hold data + # while generating a JSON text from a a Ruby data structure. + class State + # Creates a State object from _opts_, which ought to be Hash to create + # a new State instance configured by _opts_, something else to create + # an unconfigured instance. If _opts_ is a State object, it is just + # returned. + def self.from_state(opts) + case opts + when self + opts + when Hash + new(opts) + else + new + end + end + + # Instantiates a new State object, configured by _opts_. + # + # _opts_ can have the following keys: + # + # * *indent*: a string used to indent levels (default: ''), + # * *space*: a string that is put after, a : or , delimiter (default: ''), + # * *space_before*: a string that is put before a : pair delimiter (default: ''), + # * *object_nl*: a string that is put at the end of a JSON object (default: ''), + # * *array_nl*: a string that is put at the end of a JSON array (default: ''), + # * *check_circular*: true if checking for circular data structures + # should be done (the default), false otherwise. + # * *check_circular*: true if checking for circular data structures + # should be done, false (the default) otherwise. + # * *allow_nan*: true if NaN, Infinity, and -Infinity should be + # generated, otherwise an exception is thrown, if these values are + # encountered. This options defaults to false. + def initialize(opts = {}) + @seen = {} + @indent = '' + @space = '' + @space_before = '' + @object_nl = '' + @array_nl = '' + @check_circular = true + @allow_nan = false + configure opts + end + + # This string is used to indent levels in the JSON text. + attr_accessor :indent + + # This string is used to insert a space between the tokens in a JSON + # string. + attr_accessor :space + + # This string is used to insert a space before the ':' in JSON objects. + attr_accessor :space_before + + # This string is put at the end of a line that holds a JSON object (or + # Hash). + attr_accessor :object_nl + + # This string is put at the end of a line that holds a JSON array. + attr_accessor :array_nl + + # This integer returns the maximum level of data structure nesting in + # the generated JSON, max_nesting = 0 if no maximum is checked. + attr_accessor :max_nesting + + def check_max_nesting(depth) # :nodoc: + return if @max_nesting.zero? + current_nesting = depth + 1 + current_nesting > @max_nesting and + raise NestingError, "nesting of #{current_nesting} is too deep" + end + + # Returns true, if circular data structures should be checked, + # otherwise returns false. + def check_circular? + @check_circular + end + + # Returns true if NaN, Infinity, and -Infinity should be considered as + # valid JSON and output. + def allow_nan? + @allow_nan + end + + # Returns _true_, if _object_ was already seen during this generating + # run. + def seen?(object) + @seen.key?(object.__id__) + end + + # Remember _object_, to find out if it was already encountered (if a + # cyclic data structure is if a cyclic data structure is rendered). + def remember(object) + @seen[object.__id__] = true + end + + # Forget _object_ for this generating run. + def forget(object) + @seen.delete object.__id__ + end + + # Configure this State instance with the Hash _opts_, and return + # itself. + def configure(opts) + @indent = opts[:indent] if opts.key?(:indent) + @space = opts[:space] if opts.key?(:space) + @space_before = opts[:space_before] if opts.key?(:space_before) + @object_nl = opts[:object_nl] if opts.key?(:object_nl) + @array_nl = opts[:array_nl] if opts.key?(:array_nl) + @check_circular = !!opts[:check_circular] if opts.key?(:check_circular) + @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan) + if !opts.key?(:max_nesting) # defaults to 19 + @max_nesting = 19 + elsif opts[:max_nesting] + @max_nesting = opts[:max_nesting] + else + @max_nesting = 0 + end + self + end + + # Returns the configuration instance variables as a hash, that can be + # passed to the configure method. + def to_h + result = {} + for iv in %w[indent space space_before object_nl array_nl check_circular allow_nan max_nesting] + result[iv.intern] = instance_variable_get("@#{iv}") + end + result + end + end + + module GeneratorMethods + module Object + # Converts this object to a string (calling #to_s), converts + # it to a JSON string, and returns the result. This is a fallback, if no + # special method #to_json was defined for some object. + def to_json(*) to_s.to_json end + end + + module Hash + # Returns a JSON string containing a JSON object, that is unparsed from + # this Hash instance. + # _state_ is a JSON::State object, that can also be used to configure the + # produced JSON string output further. + # _depth_ is used to find out nesting depth, to indent accordingly. + def to_json(state = nil, depth = 0, *) + if state + state = JSON.state.from_state(state) + state.check_max_nesting(depth) + json_check_circular(state) { json_transform(state, depth) } + else + json_transform(state, depth) + end + end + + private + + def json_check_circular(state) + if state and state.check_circular? + state.seen?(self) and raise JSON::CircularDatastructure, + "circular data structures not supported!" + state.remember self + end + yield + ensure + state and state.forget self + end + + def json_shift(state, depth) + state and not state.object_nl.empty? or return '' + state.indent * depth + end + + def json_transform(state, depth) + delim = ',' + delim << state.object_nl if state + result = '{' + result << state.object_nl if state + result << map { |key,value| + s = json_shift(state, depth + 1) + s << key.to_s.to_json(state, depth + 1) + s << state.space_before if state + s << ':' + s << state.space if state + s << value.to_json(state, depth + 1) + }.join(delim) + result << state.object_nl if state + result << json_shift(state, depth) + result << '}' + result + end + end + + module Array + # Returns a JSON string containing a JSON array, that is unparsed from + # this Array instance. + # _state_ is a JSON::State object, that can also be used to configure the + # produced JSON string output further. + # _depth_ is used to find out nesting depth, to indent accordingly. + def to_json(state = nil, depth = 0, *) + if state + state = JSON.state.from_state(state) + state.check_max_nesting(depth) + json_check_circular(state) { json_transform(state, depth) } + else + json_transform(state, depth) + end + end + + private + + def json_check_circular(state) + if state and state.check_circular? + state.seen?(self) and raise JSON::CircularDatastructure, + "circular data structures not supported!" + state.remember self + end + yield + ensure + state and state.forget self + end + + def json_shift(state, depth) + state and not state.array_nl.empty? or return '' + state.indent * depth + end + + def json_transform(state, depth) + delim = ',' + delim << state.array_nl if state + result = '[' + result << state.array_nl if state + result << map { |value| + json_shift(state, depth + 1) << value.to_json(state, depth + 1) + }.join(delim) + result << state.array_nl if state + result << json_shift(state, depth) + result << ']' + result + end + end + + module Integer + # Returns a JSON string representation for this Integer number. + def to_json(*) to_s end + end + + module Float + # Returns a JSON string representation for this Float number. + def to_json(state = nil, *) + case + when infinite? + if !state || state.allow_nan? + to_s + else + raise GeneratorError, "#{self} not allowed in JSON" + end + when nan? + if !state || state.allow_nan? + to_s + else + raise GeneratorError, "#{self} not allowed in JSON" + end + else + to_s + end + end + end + + module String + # This string should be encoded with UTF-8 A call to this method + # returns a JSON string encoded with UTF16 big endian characters as + # \u????. + def to_json(*) + '"' << JSON.utf8_to_json(self) << '"' + end + + # Module that holds the extinding methods if, the String module is + # included. + module Extend + # Raw Strings are JSON Objects (the raw bytes are stored in an array for the + # key "raw"). The Ruby String can be created by this module method. + def json_create(o) + o['raw'].pack('C*') + end + end + + # Extends _modul_ with the String::Extend module. + def self.included(modul) + modul.extend Extend + end + + # This method creates a raw object hash, that can be nested into + # other data structures and will be unparsed as a raw string. This + # method should be used, if you want to convert raw strings to JSON + # instead of UTF-8 strings, e. g. binary data. + def to_json_raw_object + { + JSON.create_id => self.class.name, + 'raw' => self.unpack('C*'), + } + end + + # This method creates a JSON text from the result of + # a call to to_json_raw_object of this String. + def to_json_raw(*args) + to_json_raw_object.to_json(*args) + end + end + + module TrueClass + # Returns a JSON string for true: 'true'. + def to_json(*) 'true' end + end + + module FalseClass + # Returns a JSON string for false: 'false'. + def to_json(*) 'false' end + end + + module NilClass + # Returns a JSON string for nil: 'null'. + def to_json(*) 'null' end + end + end + end + end +end diff --git a/ext/json/lib/json/pure/parser.rb b/ext/json/lib/json/pure/parser.rb new file mode 100644 index 0000000000..e886ba8d2c --- /dev/null +++ b/ext/json/lib/json/pure/parser.rb @@ -0,0 +1,259 @@ +require 'strscan' + +module JSON + module Pure + # This class implements the JSON parser that is used to parse a JSON string + # into a Ruby data structure. + class Parser < StringScanner + STRING = /" ((?:[^\x0-\x1f"\\] | + \\["\\\/bfnrt] | + \\u[0-9a-fA-F]{4} | + \\[\x20-\xff])*) + "/nx + INTEGER = /(-?0|-?[1-9]\d*)/ + FLOAT = /(-? + (?:0|[1-9]\d*) + (?: + \.\d+(?i:e[+-]?\d+) | + \.\d+ | + (?i:e[+-]?\d+) + ) + )/x + NAN = /NaN/ + INFINITY = /Infinity/ + MINUS_INFINITY = /-Infinity/ + OBJECT_OPEN = /\{/ + OBJECT_CLOSE = /\}/ + ARRAY_OPEN = /\[/ + ARRAY_CLOSE = /\]/ + PAIR_DELIMITER = /:/ + COLLECTION_DELIMITER = /,/ + TRUE = /true/ + FALSE = /false/ + NULL = /null/ + IGNORE = %r( + (?: + //[^\n\r]*[\n\r]| # line comments + /\* # c-style comments + (?: + [^*/]| # normal chars + /[^*]| # slashes that do not start a nested comment + \*[^/]| # asterisks that do not end this comment + /(?=\*/) # single slash before this comment's end + )* + \*/ # the End of this comment + |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr + )+ + )mx + + UNPARSED = Object.new + + # Creates a new JSON::Pure::Parser instance for the string _source_. + # + # It will be configured by the _opts_ hash. _opts_ can have the following + # keys: + # * *max_nesting*: The maximum depth of nesting allowed in the parsed data + # structures. Disable depth checking with :max_nesting => false|nil|0, + # it defaults to 19. + # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in + # defiance of RFC 4627 to be parsed by the Parser. This option defaults + # to false. + # * *create_additions*: If set to false, the Parser doesn't create + # additions even if a matchin class and create_id was found. This option + # defaults to true. + def initialize(source, opts = {}) + super + if !opts.key?(:max_nesting) # defaults to 19 + @max_nesting = 19 + elsif opts[:max_nesting] + @max_nesting = opts[:max_nesting] + else + @max_nesting = 0 + end + @allow_nan = !!opts[:allow_nan] + ca = true + ca = opts[:create_additions] if opts.key?(:create_additions) + @create_id = ca ? JSON.create_id : nil + end + + alias source string + + # Parses the current JSON string _source_ and returns the complete data + # structure as a result. + def parse + reset + obj = nil + until eos? + case + when scan(OBJECT_OPEN) + obj and raise ParserError, "source '#{peek(20)}' not in JSON!" + @current_nesting = 1 + obj = parse_object + when scan(ARRAY_OPEN) + obj and raise ParserError, "source '#{peek(20)}' not in JSON!" + @current_nesting = 1 + obj = parse_array + when skip(IGNORE) + ; + else + raise ParserError, "source '#{peek(20)}' not in JSON!" + end + end + obj or raise ParserError, "source did not contain any JSON!" + obj + end + + private + + # Unescape characters in strings. + UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr } + UNESCAPE_MAP.update({ + ?" => '"', + ?\\ => '\\', + ?/ => '/', + ?b => "\b", + ?f => "\f", + ?n => "\n", + ?r => "\r", + ?t => "\t", + ?u => nil, + }) + + def parse_string + if scan(STRING) + return '' if self[1].empty? + self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c| + if u = UNESCAPE_MAP[c[1]] + u + else # \uXXXX + bytes = '' + i = 0 + while c[6 * i] == ?\\ && c[6 * i + 1] == ?u + bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16) + i += 1 + end + JSON::UTF16toUTF8.iconv(bytes) + end + end + else + UNPARSED + end + rescue Iconv::Failure => e + raise GeneratorError, "Caught #{e.class}: #{e}" + end + + def parse_value + case + when scan(FLOAT) + Float(self[1]) + when scan(INTEGER) + Integer(self[1]) + when scan(TRUE) + true + when scan(FALSE) + false + when scan(NULL) + nil + when (string = parse_string) != UNPARSED + string + when scan(ARRAY_OPEN) + @current_nesting += 1 + ary = parse_array + @current_nesting -= 1 + ary + when scan(OBJECT_OPEN) + @current_nesting += 1 + obj = parse_object + @current_nesting -= 1 + obj + when @allow_nan && scan(NAN) + NaN + when @allow_nan && scan(INFINITY) + Infinity + when @allow_nan && scan(MINUS_INFINITY) + MinusInfinity + else + UNPARSED + end + end + + def parse_array + raise NestingError, "nesting of #@current_nesting is to deep" if + @max_nesting.nonzero? && @current_nesting > @max_nesting + result = [] + delim = false + until eos? + case + when (value = parse_value) != UNPARSED + delim = false + result << value + skip(IGNORE) + if scan(COLLECTION_DELIMITER) + delim = true + elsif match?(ARRAY_CLOSE) + ; + else + raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!" + end + when scan(ARRAY_CLOSE) + if delim + raise ParserError, "expected next element in array at '#{peek(20)}'!" + end + break + when skip(IGNORE) + ; + else + raise ParserError, "unexpected token in array at '#{peek(20)}'!" + end + end + result + end + + def parse_object + raise NestingError, "nesting of #@current_nesting is to deep" if + @max_nesting.nonzero? && @current_nesting > @max_nesting + result = {} + delim = false + until eos? + case + when (string = parse_string) != UNPARSED + skip(IGNORE) + unless scan(PAIR_DELIMITER) + raise ParserError, "expected ':' in object at '#{peek(20)}'!" + end + skip(IGNORE) + unless (value = parse_value).equal? UNPARSED + result[string] = value + delim = false + skip(IGNORE) + if scan(COLLECTION_DELIMITER) + delim = true + elsif match?(OBJECT_CLOSE) + ; + else + raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!" + end + else + raise ParserError, "expected value in object at '#{peek(20)}'!" + end + when scan(OBJECT_CLOSE) + if delim + raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!" + end + if @create_id and klassname = result[@create_id] + klass = JSON.deep_const_get klassname + break unless klass and klass.json_creatable? + result = klass.json_create(result) + end + break + when skip(IGNORE) + ; + else + raise ParserError, "unexpected token in object at '#{peek(20)}'!" + end + end + result + end + end + end +end diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb new file mode 100644 index 0000000000..3d674ac44f --- /dev/null +++ b/ext/json/lib/json/version.rb @@ -0,0 +1,9 @@ +module JSON + # JSON version + VERSION = '1.1.2' + VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc: + VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc: + VERSION_MINOR = VERSION_ARRAY[1] # :nodoc: + VERSION_BUILD = VERSION_ARRAY[2] # :nodoc: + VARIANT_BINARY = false +end diff --git a/lib/json.rb b/lib/json.rb deleted file mode 100644 index 3b0b711550..0000000000 --- a/lib/json.rb +++ /dev/null @@ -1,235 +0,0 @@ -require 'json/common' -# = json - JSON for Ruby -# -# == Description -# -# This is a implementation of the JSON specification according to RFC 4627 -# (http://www.ietf.org/rfc/rfc4627.txt). Starting from version 1.0.0 on there -# will be two variants available: -# -# * A pure ruby variant, that relies on the iconv and the stringscan -# extensions, which are both part of the ruby standard library. -# * The quite a bit faster C extension variant, which is in parts implemented -# in C and comes with its own unicode conversion functions and a parser -# generated by the ragel state machine compiler -# (http://www.cs.queensu.ca/~thurston/ragel). -# -# Both variants of the JSON generator escape all non-ASCII an control -# characters with \uXXXX escape sequences, and support UTF-16 surrogate pairs -# in order to be able to generate the whole range of unicode code points. This -# means that generated JSON text is encoded as UTF-8 (because ASCII is a subset -# of UTF-8) and at the same time avoids decoding problems for receiving -# endpoints, that don't expect UTF-8 encoded texts. On the negative side this -# may lead to a bit longer strings than necessarry. -# -# All strings, that are to be encoded as JSON strings, should be UTF-8 byte -# sequences on the Ruby side. To encode raw binary strings, that aren't UTF-8 -# encoded, please use the to_json_raw_object method of String (which produces -# an object, that contains a byte array) and decode the result on the receiving -# endpoint. -# -# == Author -# -# Florian Frank -# -# == License -# -# This software is distributed under the same license as Ruby itself, see -# http://www.ruby-lang.org/en/LICENSE.txt. -# -# == Download -# -# The latest version of this library can be downloaded at -# -# * http://rubyforge.org/frs?group_id=953 -# -# Online Documentation should be located at -# -# * http://json.rubyforge.org -# -# == Usage -# -# To use JSON you can -# require 'json' -# to load the installed variant (either the extension 'json' or the pure -# variant 'json_pure'). If you have installed the extension variant, you can -# pick either the extension variant or the pure variant by typing -# require 'json/ext' -# or -# require 'json/pure' -# -# You can choose to load a set of common additions to ruby core's objects if -# you -# require 'json/add/core' -# -# After requiring this you can, e. g., serialise/deserialise Ruby ranges: -# -# JSON JSON(1..10) # => 1..10 -# -# To find out how to add JSON support to other or your own classes, read the -# Examples section below. -# -# To get the best compatibility to rails' JSON implementation, you can -# require 'json/add/rails' -# -# Both of the additions attempt to require 'json' (like above) first, if it has -# not been required yet. -# -# == Speed Comparisons -# -# I have created some benchmark results (see the benchmarks subdir of the -# package) for the JSON-Parser to estimate the speed up in the C extension: -# -# JSON::Pure::Parser:: 28.90 calls/second -# JSON::Ext::Parser:: 505.50 calls/second -# -# This is ca. 17.5 times the speed of the pure Ruby implementation. -# -# I have benchmarked the JSON-Generator as well. This generates a few more -# values, because there are different modes, that also influence the achieved -# speed: -# -# * JSON::Pure::Generator: -# generate:: 35.06 calls/second -# pretty_generate:: 34.00 calls/second -# fast_generate:: 41.06 calls/second -# -# * JSON::Ext::Generator: -# generate:: 492.11 calls/second -# pretty_generate:: 348.85 calls/second -# fast_generate:: 541.60 calls/second -# -# * Speedup Ext/Pure: -# generate safe:: 14.0 times -# generate pretty:: 10.3 times -# generate fast:: 13.2 times -# -# The rails framework includes a generator as well, also it seems to be rather -# slow: I measured only 23.87 calls/second which is slower than any of my pure -# generator results. Here a comparison of the different speedups with the Rails -# measurement as the divisor: -# -# * Speedup Pure/Rails: -# generate safe:: 1.5 times -# generate pretty:: 1.4 times -# generate fast:: 1.7 times -# -# * Speedup Ext/Rails: -# generate safe:: 20.6 times -# generate pretty:: 14.6 times -# generate fast:: 22.7 times -# -# To achieve the fastest JSON text output, you can use the -# fast_generate/fast_unparse methods. Beware, that this will disable the -# checking for circular Ruby data structures, which may cause JSON to go into -# an infinite loop. -# -# == Examples -# -# To create a JSON text from a ruby data structure, you -# can call JSON.generate (or JSON.unparse) like that: -# -# json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -# # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]" -# -# To create a valid JSON text you have to make sure, that the output is -# embedded in either a JSON array [] or a JSON object {}. The easiest way to do -# this, is by putting your values in a Ruby Array or Hash instance. -# -# To get back a ruby data structure from a JSON text, you have to call -# JSON.parse on it: -# -# JSON.parse json -# # => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"] -# -# Note, that the range from the original data structure is a simple -# string now. The reason for this is, that JSON doesn't support ranges -# or arbitrary classes. In this case the json library falls back to call -# Object#to_json, which is the same as #to_s.to_json. -# -# It's possible to add JSON support serialization to arbitrary classes by -# simply implementing a more specialized version of the #to_json method, that -# should return a JSON object (a hash converted to JSON with #to_json) like -# this (don't forget the *a for all the arguments): -# -# class Range -# def to_json(*a) -# { -# 'json_class' => self.class.name, # = 'Range' -# 'data' => [ first, last, exclude_end? ] -# }.to_json(*a) -# end -# end -# -# The hash key 'json_class' is the class, that will be asked to deserialise the -# JSON representation later. In this case it's 'Range', but any namespace of -# the form 'A::B' or '::A::B' will do. All other keys are arbitrary and can be -# used to store the necessary data to configure the object to be deserialised. -# -# If a the key 'json_class' is found in a JSON object, the JSON parser checks -# if the given class responds to the json_create class method. If so, it is -# called with the JSON object converted to a Ruby hash. So a range can -# be deserialised by implementing Range.json_create like this: -# -# class Range -# def self.json_create(o) -# new(*o['data']) -# end -# end -# -# Now it possible to serialise/deserialise ranges as well: -# -# json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -# # => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]" -# JSON.parse json -# # => [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -# -# JSON.generate always creates the shortest possible string representation of a -# ruby data structure in one line. This good for data storage or network -# protocols, but not so good for humans to read. Fortunately there's also -# JSON.pretty_generate (or JSON.pretty_generate) that creates a more -# readable output: -# -# puts JSON.pretty_generate([1, 2, {"a"=>3.141}, false, true, nil, 4..10]) -# [ -# 1, -# 2, -# { -# "a": 3.141 -# }, -# false, -# true, -# null, -# { -# "json_class": "Range", -# "data": [ -# 4, -# 10, -# false -# ] -# } -# ] -# -# There are also the methods Kernel#j for unparse, and Kernel#jj for -# pretty_unparse output to the console, that work analogous to Core Ruby's p -# and the pp library's pp methods. -# -# The script tools/server.rb contains a small example if you want to test, how -# receiving a JSON object from a webrick server in your browser with the -# javasript prototype library (http://www.prototypejs.org) works. -# -module JSON - require 'json/version' - - if VARIANT_BINARY - require 'json/ext' - else - begin - require 'json/ext' - rescue LoadError - require 'json/pure' - end - end - - JSON_LOADED = true -end diff --git a/lib/json/Array.xpm b/lib/json/Array.xpm deleted file mode 100644 index 27c48011f9..0000000000 --- a/lib/json/Array.xpm +++ /dev/null @@ -1,21 +0,0 @@ -/* XPM */ -static char * Array_xpm[] = { -"16 16 2 1", -" c None", -". c #000000", -" ", -" ", -" ", -" .......... ", -" . . ", -" . . ", -" . . ", -" . . ", -" . . ", -" . . ", -" . . ", -" . . ", -" .......... ", -" ", -" ", -" "}; diff --git a/lib/json/FalseClass.xpm b/lib/json/FalseClass.xpm deleted file mode 100644 index 25ce60832d..0000000000 --- a/lib/json/FalseClass.xpm +++ /dev/null @@ -1,21 +0,0 @@ -/* XPM */ -static char * False_xpm[] = { -"16 16 2 1", -" c None", -". c #FF0000", -" ", -" ", -" ", -" ...... ", -" . ", -" . ", -" . ", -" ...... ", -" . ", -" . ", -" . ", -" . ", -" . ", -" ", -" ", -" "}; diff --git a/lib/json/Hash.xpm b/lib/json/Hash.xpm deleted file mode 100644 index cd8f6f7b53..0000000000 --- a/lib/json/Hash.xpm +++ /dev/null @@ -1,21 +0,0 @@ -/* XPM */ -static char * Hash_xpm[] = { -"16 16 2 1", -" c None", -". c #000000", -" ", -" ", -" ", -" . . ", -" . . ", -" . . ", -" ......... ", -" . . ", -" . . ", -" ......... ", -" . . ", -" . . ", -" . . ", -" ", -" ", -" "}; diff --git a/lib/json/Key.xpm b/lib/json/Key.xpm deleted file mode 100644 index 9fd7281388..0000000000 --- a/lib/json/Key.xpm +++ /dev/null @@ -1,73 +0,0 @@ -/* XPM */ -static char * Key_xpm[] = { -"16 16 54 1", -" c None", -". c #110007", -"+ c #0E0900", -"@ c #000013", -"# c #070600", -"$ c #F6F006", -"% c #ECE711", -"& c #E5EE00", -"* c #16021E", -"= c #120900", -"- c #EDF12B", -"; c #000033", -"> c #0F0000", -", c #FFFE03", -"' c #E6E500", -") c #16021B", -"! c #F7F502", -"~ c #000E00", -"{ c #130000", -"] c #FFF000", -"^ c #FFE711", -"/ c #140005", -"( c #190025", -"_ c #E9DD27", -": c #E7DC04", -"< c #FFEC09", -"[ c #FFE707", -"} c #FFDE10", -"| c #150021", -"1 c #160700", -"2 c #FAF60E", -"3 c #EFE301", -"4 c #FEF300", -"5 c #E7E000", -"6 c #FFFF08", -"7 c #0E0206", -"8 c #040000", -"9 c #03052E", -"0 c #041212", -"a c #070300", -"b c #F2E713", -"c c #F9DE13", -"d c #36091E", -"e c #00001C", -"f c #1F0010", -"g c #FFF500", -"h c #DEDE00", -"i c #050A00", -"j c #FAF14A", -"k c #F5F200", -"l c #040404", -"m c #1A0D00", -"n c #EDE43D", -"o c #ECE007", -" ", -" ", -" .+@ ", -" #$%&* ", -" =-;>,') ", -" >!~{]^/ ", -" (_:<[}| ", -" 1234567 ", -" 890abcd ", -" efghi ", -" >jkl ", -" mnol ", -" >kl ", -" ll ", -" ", -" "}; diff --git a/lib/json/NilClass.xpm b/lib/json/NilClass.xpm deleted file mode 100644 index 3509f06c99..0000000000 --- a/lib/json/NilClass.xpm +++ /dev/null @@ -1,21 +0,0 @@ -/* XPM */ -static char * False_xpm[] = { -"16 16 2 1", -" c None", -". c #000000", -" ", -" ", -" ", -" ... ", -" . . ", -" . . ", -" . . ", -" . . ", -" . . ", -" . . ", -" . . ", -" . . ", -" ... ", -" ", -" ", -" "}; diff --git a/lib/json/Numeric.xpm b/lib/json/Numeric.xpm deleted file mode 100644 index e071e2ee9c..0000000000 --- a/lib/json/Numeric.xpm +++ /dev/null @@ -1,28 +0,0 @@ -/* XPM */ -static char * Numeric_xpm[] = { -"16 16 9 1", -" c None", -". c #FF0000", -"+ c #0000FF", -"@ c #0023DB", -"# c #00EA14", -"$ c #00FF00", -"% c #004FAF", -"& c #0028D6", -"* c #00F20C", -" ", -" ", -" ", -" ... +++@#$$$$ ", -" .+ %& $$ ", -" . + $ ", -" . + $$ ", -" . ++$$$$ ", -" . + $$ ", -" . + $ ", -" . + $ ", -" . + $ $$ ", -" .....++++*$$ ", -" ", -" ", -" "}; diff --git a/lib/json/String.xpm b/lib/json/String.xpm deleted file mode 100644 index f79a89cdc1..0000000000 --- a/lib/json/String.xpm +++ /dev/null @@ -1,96 +0,0 @@ -/* XPM */ -static char * String_xpm[] = { -"16 16 77 1", -" c None", -". c #000000", -"+ c #040404", -"@ c #080806", -"# c #090606", -"$ c #EEEAE1", -"% c #E7E3DA", -"& c #E0DBD1", -"* c #D4B46F", -"= c #0C0906", -"- c #E3C072", -"; c #E4C072", -"> c #060505", -", c #0B0A08", -"' c #D5B264", -") c #D3AF5A", -"! c #080602", -"~ c #E1B863", -"{ c #DDB151", -"] c #DBAE4A", -"^ c #DDB152", -"/ c #DDB252", -"( c #070705", -"_ c #0C0A07", -": c #D3A33B", -"< c #020201", -"[ c #DAAA41", -"} c #040302", -"| c #E4D9BF", -"1 c #0B0907", -"2 c #030201", -"3 c #020200", -"4 c #C99115", -"5 c #080704", -"6 c #DBC8A2", -"7 c #E7D7B4", -"8 c #E0CD9E", -"9 c #080601", -"0 c #040400", -"a c #010100", -"b c #0B0B08", -"c c #DCBF83", -"d c #DCBC75", -"e c #DEB559", -"f c #040301", -"g c #BC8815", -"h c #120E07", -"i c #060402", -"j c #0A0804", -"k c #D4A747", -"l c #D6A12F", -"m c #0E0C05", -"n c #C8C1B0", -"o c #1D1B15", -"p c #D7AD51", -"q c #070502", -"r c #080804", -"s c #BC953B", -"t c #C4BDAD", -"u c #0B0807", -"v c #DBAC47", -"w c #1B150A", -"x c #B78A2C", -"y c #D8A83C", -"z c #D4A338", -"A c #0F0B03", -"B c #181105", -"C c #C59325", -"D c #C18E1F", -"E c #060600", -"F c #CC992D", -"G c #B98B25", -"H c #B3831F", -"I c #C08C1C", -"J c #060500", -"K c #0E0C03", -"L c #0D0A00", -" ", -" .+@# ", -" .$%&*= ", -" .-;>,')! ", -" .~. .{]. ", -" .^/. (_:< ", -" .[.}|$12 ", -" 345678}90 ", -" a2bcdefgh ", -" ijkl.mno ", -" self.class.name.to_s, - 's' => tv_sec, - 'u' => tv_usec, - }.to_json(*args) - end -end - -class Date - def self.json_create(object) - civil(*object.values_at('y', 'm', 'd', 'sg')) - end - - def to_json(*args) - { - 'json_class' => self.class.name.to_s, - 'y' => year, - 'm' => month, - 'd' => day, - 'sg' => @sg, - }.to_json(*args) - end -end - -class DateTime - def self.json_create(object) - args = object.values_at('y', 'm', 'd', 'H', 'M', 'S') - of_a, of_b = object['of'].split('/') - args << Rational(of_a.to_i, of_b.to_i) - args << object['sg'] - civil(*args) - end - - def to_json(*args) - { - 'json_class' => self.class.name.to_s, - 'y' => year, - 'm' => month, - 'd' => day, - 'H' => hour, - 'M' => min, - 'S' => sec, - 'of' => offset.to_s, - 'sg' => @sg, - }.to_json(*args) - end -end - -class Range - def self.json_create(object) - new(*object['a']) - end - - def to_json(*args) - { - 'json_class' => self.class.name.to_s, - 'a' => [ first, last, exclude_end? ] - }.to_json(*args) - end -end - -class Struct - def self.json_create(object) - new(*object['v']) - end - - def to_json(*args) - klass = self.class.name.to_s - klass.empty? and raise JSON::JSONError, "Only named structs are supported!" - { - 'json_class' => klass, - 'v' => values, - }.to_json(*args) - end -end - -class Exception - def self.json_create(object) - result = new(object['m']) - result.set_backtrace object['b'] - result - end - - def to_json(*args) - { - 'json_class' => self.class.name.to_s, - 'm' => message, - 'b' => backtrace, - }.to_json(*args) - end -end - -class Regexp - def self.json_create(object) - new(object['s'], object['o']) - end - - def to_json(*) - { - 'json_class' => self.class.name.to_s, - 'o' => options, - 's' => source, - }.to_json - end -end diff --git a/lib/json/add/rails.rb b/lib/json/add/rails.rb deleted file mode 100644 index e86ed1aab9..0000000000 --- a/lib/json/add/rails.rb +++ /dev/null @@ -1,58 +0,0 @@ -# This file contains implementations of rails custom objects for -# serialisation/deserialisation. - -unless Object.const_defined?(:JSON) and ::JSON.const_defined?(:JSON_LOADED) and - ::JSON::JSON_LOADED - require 'json' -end - -class Object - def self.json_create(object) - obj = new - for key, value in object - next if key == 'json_class' - instance_variable_set "@#{key}", value - end - obj - end - - def to_json(*a) - result = { - 'json_class' => self.class.name - } - instance_variables.inject(result) do |r, name| - r[name[1..-1]] = instance_variable_get name - r - end - result.to_json(*a) - end -end - -class Symbol - def to_json(*a) - to_s.to_json(*a) - end -end - -module Enumerable - def to_json(*a) - to_a.to_json(*a) - end -end - -# class Regexp -# def to_json(*) -# inspect -# end -# end -# -# The above rails definition has some problems: -# -# 1. { 'foo' => /bar/ }.to_json # => "{foo: /bar/}" -# This isn't valid JSON, because the regular expression syntax is not -# defined in RFC 4627. (And unquoted strings are disallowed there, too.) -# Though it is valid Javascript. -# -# 2. { 'foo' => /bar/mix }.to_json # => "{foo: /bar/mix}" -# This isn't even valid Javascript. - diff --git a/lib/json/common.rb b/lib/json/common.rb deleted file mode 100644 index 7bc5ae0656..0000000000 --- a/lib/json/common.rb +++ /dev/null @@ -1,354 +0,0 @@ -require 'json/version' - -module JSON - class << self - # If _object_ is string like parse the string and return the parsed result - # as a Ruby data structure. Otherwise generate a JSON text from the Ruby - # data structure object and return it. - # - # The _opts_ argument is passed through to generate/parse respectively, see - # generate and parse for their documentation. - def [](object, opts = {}) - if object.respond_to? :to_str - JSON.parse(object.to_str, opts => {}) - else - JSON.generate(object, opts => {}) - end - end - - # Returns the JSON parser class, that is used by JSON. This might be either - # JSON::Ext::Parser or JSON::Pure::Parser. - attr_reader :parser - - # Set the JSON parser class _parser_ to be used by JSON. - def parser=(parser) # :nodoc: - @parser = parser - remove_const :Parser if const_defined? :Parser - const_set :Parser, parser - end - - # Return the constant located at _path_. The format of _path_ has to be - # either ::A::B::C or A::B::C. In any case A has to be located at the top - # level (absolute namespace path?). If there doesn't exist a constant at - # the given path, an ArgumentError is raised. - def deep_const_get(path) # :nodoc: - path = path.to_s - path.split(/::/).inject(Object) do |p, c| - case - when c.empty? then p - when p.const_defined?(c) then p.const_get(c) - else raise ArgumentError, "can't find const #{path}" - end - end - end - - # Set the module _generator_ to be used by JSON. - def generator=(generator) # :nodoc: - @generator = generator - generator_methods = generator::GeneratorMethods - for const in generator_methods.constants - klass = deep_const_get(const) - modul = generator_methods.const_get(const) - klass.class_eval do - instance_methods(false).each do |m| - m.to_s == 'to_json' and remove_method m - end - include modul - end - end - self.state = generator::State - const_set :State, self.state - end - - # Returns the JSON generator modul, that is used by JSON. This might be - # either JSON::Ext::Generator or JSON::Pure::Generator. - attr_reader :generator - - # Returns the JSON generator state class, that is used by JSON. This might - # be either JSON::Ext::Generator::State or JSON::Pure::Generator::State. - attr_accessor :state - - # This is create identifier, that is used to decide, if the _json_create_ - # hook of a class should be called. It defaults to 'json_class'. - attr_accessor :create_id - end - self.create_id = 'json_class' - - NaN = (-1.0) ** 0.5 - - Infinity = 1.0/0 - - MinusInfinity = -Infinity - - # The base exception for JSON errors. - class JSONError < StandardError; end - - # This exception is raised, if a parser error occurs. - class ParserError < JSONError; end - - # This exception is raised, if the nesting of parsed datastructures is too - # deep. - class NestingError < ParserError; end - - # This exception is raised, if a generator or unparser error occurs. - class GeneratorError < JSONError; end - # For backwards compatibility - UnparserError = GeneratorError - - # If a circular data structure is encountered while unparsing - # this exception is raised. - class CircularDatastructure < GeneratorError; end - - # This exception is raised, if the required unicode support is missing on the - # system. Usually this means, that the iconv library is not installed. - class MissingUnicodeSupport < JSONError; end - - module_function - - # Parse the JSON string _source_ into a Ruby data structure and return it. - # - # _opts_ can have the following - # keys: - # * *max_nesting*: The maximum depth of nesting allowed in the parsed data - # structures. Disable depth checking with :max_nesting => false, it defaults - # to 19. - # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in - # defiance of RFC 4627 to be parsed by the Parser. This option defaults - # to false. - # * *create_additions*: If set to false, the Parser doesn't create - # additions even if a matchin class and create_id was found. This option - # defaults to true. - def parse(source, opts = {}) - JSON.parser.new(source, opts).parse - end - - # Parse the JSON string _source_ into a Ruby data structure and return it. - # The bang version of the parse method, defaults to the more dangerous values - # for the _opts_ hash, so be sure only to parse trusted _source_ strings. - # - # _opts_ can have the following keys: - # * *max_nesting*: The maximum depth of nesting allowed in the parsed data - # structures. Enable depth checking with :max_nesting => anInteger. The parse! - # methods defaults to not doing max depth checking: This can be dangerous, - # if someone wants to fill up your stack. - # * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in - # defiance of RFC 4627 to be parsed by the Parser. This option defaults - # to true. - # * *create_additions*: If set to false, the Parser doesn't create - # additions even if a matchin class and create_id was found. This option - # defaults to true. - def parse!(source, opts = {}) - opts = { - :max_nesting => false, - :allow_nan => true - }.update(opts) - JSON.parser.new(source, opts).parse - end - - # Unparse the Ruby data structure _obj_ into a single line JSON string and - # return it. _state_ is - # * a JSON::State object, - # * or a Hash like object (responding to to_hash), - # * an object convertible into a hash by a to_h method, - # that is used as or to configure a State object. - # - # It defaults to a state object, that creates the shortest possible JSON text - # in one line, checks for circular data structures and doesn't allow NaN, - # Infinity, and -Infinity. - # - # A _state_ hash can have the following keys: - # * *indent*: a string used to indent levels (default: ''), - # * *space*: a string that is put after, a : or , delimiter (default: ''), - # * *space_before*: a string that is put before a : pair delimiter (default: ''), - # * *object_nl*: a string that is put at the end of a JSON object (default: ''), - # * *array_nl*: a string that is put at the end of a JSON array (default: ''), - # * *check_circular*: true if checking for circular data structures - # should be done (the default), false otherwise. - # * *allow_nan*: true if NaN, Infinity, and -Infinity should be - # generated, otherwise an exception is thrown, if these values are - # encountered. This options defaults to false. - # * *max_nesting*: The maximum depth of nesting allowed in the data - # structures from which JSON is to be generated. Disable depth checking - # with :max_nesting => false, it defaults to 19. - # - # See also the fast_generate for the fastest creation method with the least - # amount of sanity checks, and the pretty_generate method for some - # defaults for a pretty output. - def generate(obj, state = nil) - if state - state = State.from_state(state) - else - state = State.new - end - obj.to_json(state) - end - - # :stopdoc: - # I want to deprecate these later, so I'll first be silent about them, and later delete them. - alias unparse generate - module_function :unparse - # :startdoc: - - # Unparse the Ruby data structure _obj_ into a single line JSON string and - # return it. This method disables the checks for circles in Ruby objects, and - # also generates NaN, Infinity, and, -Infinity float values. - # - # *WARNING*: Be careful not to pass any Ruby data structures with circles as - # _obj_ argument, because this will cause JSON to go into an infinite loop. - def fast_generate(obj) - obj.to_json(nil) - end - - # :stopdoc: - # I want to deprecate these later, so I'll first be silent about them, and later delete them. - alias fast_unparse fast_generate - module_function :fast_unparse - # :startdoc: - - # Unparse the Ruby data structure _obj_ into a JSON string and return it. The - # returned string is a prettier form of the string returned by #unparse. - # - # The _opts_ argument can be used to configure the generator, see the - # generate method for a more detailed explanation. - def pretty_generate(obj, opts = nil) - state = JSON.state.new( - :indent => ' ', - :space => ' ', - :object_nl => "\n", - :array_nl => "\n", - :check_circular => true - ) - if opts - if opts.respond_to? :to_hash - opts = opts.to_hash - elsif opts.respond_to? :to_h - opts = opts.to_h - else - raise TypeError, "can't convert #{opts.class} into Hash" - end - state.configure(opts) - end - obj.to_json(state) - end - - # :stopdoc: - # I want to deprecate these later, so I'll first be silent about them, and later delete them. - alias pretty_unparse pretty_generate - module_function :pretty_unparse - # :startdoc: - - # Load a ruby data structure from a JSON _source_ and return it. A source can - # either be a string like object, an IO like object, or an object responding - # to the read method. If _proc_ was given, it will be called with any nested - # Ruby object as an argument recursively in depth first order. - # - # This method is part of the implementation of the load/dump interface of - # Marshal and YAML. - def load(source, proc = nil) - if source.respond_to? :to_str - source = source.to_str - elsif source.respond_to? :to_io - source = source.to_io.read - else - source = source.read - end - result = parse(source, :max_nesting => false, :allow_nan => true) - recurse_proc(result, &proc) if proc - result - end - - def recurse_proc(result, &proc) - case result - when Array - result.each { |x| recurse_proc x, &proc } - proc.call result - when Hash - result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc } - proc.call result - else - proc.call result - end - end - private :recurse_proc - module_function :recurse_proc - - alias restore load - module_function :restore - - # Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns - # the result. - # - # If anIO (an IO like object or an object that responds to the write method) - # was given, the resulting JSON is written to it. - # - # If the number of nested arrays or objects exceeds _limit_ an ArgumentError - # exception is raised. This argument is similar (but not exactly the - # same!) to the _limit_ argument in Marshal.dump. - # - # This method is part of the implementation of the load/dump interface of - # Marshal and YAML. - def dump(obj, anIO = nil, limit = nil) - if anIO and limit.nil? - anIO = anIO.to_io if anIO.respond_to?(:to_io) - unless anIO.respond_to?(:write) - limit = anIO - anIO = nil - end - end - limit ||= 0 - result = generate(obj, :allow_nan => true, :max_nesting => limit) - if anIO - anIO.write result - anIO - else - result - end - rescue JSON::NestingError - raise ArgumentError, "exceed depth limit" - end -end - -module ::Kernel - # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in - # one line. - def j(*objs) - objs.each do |obj| - puts JSON::generate(obj, :allow_nan => true, :max_nesting => false) - end - nil - end - - # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with - # indentation and over many lines. - def jj(*objs) - objs.each do |obj| - puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false) - end - nil - end - - # If _object_ is string like parse the string and return the parsed result as - # a Ruby data structure. Otherwise generate a JSON text from the Ruby data - # structure object and return it. - # - # The _opts_ argument is passed through to generate/parse respectively, see - # generate and parse for their documentation. - def JSON(object, opts = {}) - if object.respond_to? :to_str - JSON.parse(object.to_str, opts) - else - JSON.generate(object, opts) - end - end -end - -class ::Class - # Returns true, if this class can be used to create an instance - # from a serialised JSON string. The class has to implement a class - # method _json_create_ that expects a hash as first parameter, which includes - # the required data. - def json_creatable? - respond_to?(:json_create) - end -end - # vim: set et sw=2 ts=2: diff --git a/lib/json/editor.rb b/lib/json/editor.rb deleted file mode 100644 index 9a65400622..0000000000 --- a/lib/json/editor.rb +++ /dev/null @@ -1,1363 +0,0 @@ -# To use the GUI JSON editor, start the edit_json.rb executable script. It -# requires ruby-gtk to be installed. - -require 'gtk2' -require 'iconv' -require 'json' -require 'rbconfig' -require 'open-uri' - -module JSON - module Editor - include Gtk - - # Beginning of the editor window title - TITLE = 'JSON Editor'.freeze - - # Columns constants - ICON_COL, TYPE_COL, CONTENT_COL = 0, 1, 2 - - # JSON primitive types (Containers) - CONTAINER_TYPES = %w[Array Hash].sort - # All JSON primitive types - ALL_TYPES = (%w[TrueClass FalseClass Numeric String NilClass] + - CONTAINER_TYPES).sort - - # The Nodes necessary for the tree representation of a JSON document - ALL_NODES = (ALL_TYPES + %w[Key]).sort - - DEFAULT_DIALOG_KEY_PRESS_HANDLER = lambda do |dialog, event| - case event.keyval - when Gdk::Keyval::GDK_Return - dialog.response Dialog::RESPONSE_ACCEPT - when Gdk::Keyval::GDK_Escape - dialog.response Dialog::RESPONSE_REJECT - end - end - - # Returns the Gdk::Pixbuf of the icon named _name_ from the icon cache. - def Editor.fetch_icon(name) - @icon_cache ||= {} - unless @icon_cache.key?(name) - path = File.dirname(__FILE__) - @icon_cache[name] = Gdk::Pixbuf.new(File.join(path, name + '.xpm')) - end - @icon_cache[name] - end - - # Opens an error dialog on top of _window_ showing the error message - # _text_. - def Editor.error_dialog(window, text) - dialog = MessageDialog.new(window, Dialog::MODAL, - MessageDialog::ERROR, - MessageDialog::BUTTONS_CLOSE, text) - dialog.show_all - dialog.run - rescue TypeError - dialog = MessageDialog.new(Editor.window, Dialog::MODAL, - MessageDialog::ERROR, - MessageDialog::BUTTONS_CLOSE, text) - dialog.show_all - dialog.run - ensure - dialog.destroy if dialog - end - - # Opens a yes/no question dialog on top of _window_ showing the error - # message _text_. If yes was answered _true_ is returned, otherwise - # _false_. - def Editor.question_dialog(window, text) - dialog = MessageDialog.new(window, Dialog::MODAL, - MessageDialog::QUESTION, - MessageDialog::BUTTONS_YES_NO, text) - dialog.show_all - dialog.run do |response| - return Gtk::Dialog::RESPONSE_YES === response - end - ensure - dialog.destroy if dialog - end - - # Convert the tree model starting from Gtk::TreeIter _iter_ into a Ruby - # data structure and return it. - def Editor.model2data(iter) - return nil if iter.nil? - case iter.type - when 'Hash' - hash = {} - iter.each { |c| hash[c.content] = Editor.model2data(c.first_child) } - hash - when 'Array' - array = Array.new(iter.n_children) - iter.each_with_index { |c, i| array[i] = Editor.model2data(c) } - array - when 'Key' - iter.content - when 'String' - iter.content - when 'Numeric' - content = iter.content - if /\./.match(content) - content.to_f - else - content.to_i - end - when 'TrueClass' - true - when 'FalseClass' - false - when 'NilClass' - nil - else - fail "Unknown type found in model: #{iter.type}" - end - end - - # Convert the Ruby data structure _data_ into tree model data for Gtk and - # returns the whole model. If the parameter _model_ wasn't given a new - # Gtk::TreeStore is created as the model. The _parent_ parameter specifies - # the parent node (iter, Gtk:TreeIter instance) to which the data is - # appended, alternativeley the result of the yielded block is used as iter. - def Editor.data2model(data, model = nil, parent = nil) - model ||= TreeStore.new(Gdk::Pixbuf, String, String) - iter = if block_given? - yield model - else - model.append(parent) - end - case data - when Hash - iter.type = 'Hash' - data.sort.each do |key, value| - pair_iter = model.append(iter) - pair_iter.type = 'Key' - pair_iter.content = key.to_s - Editor.data2model(value, model, pair_iter) - end - when Array - iter.type = 'Array' - data.each do |value| - Editor.data2model(value, model, iter) - end - when Numeric - iter.type = 'Numeric' - iter.content = data.to_s - when String, true, false, nil - iter.type = data.class.name - iter.content = data.nil? ? 'null' : data.to_s - else - iter.type = 'String' - iter.content = data.to_s - end - model - end - - # The Gtk::TreeIter class is reopened and some auxiliary methods are added. - class Gtk::TreeIter - include Enumerable - - # Traverse each of this Gtk::TreeIter instance's children - # and yield to them. - def each - n_children.times { |i| yield nth_child(i) } - end - - # Recursively traverse all nodes of this Gtk::TreeIter's subtree - # (including self) and yield to them. - def recursive_each(&block) - yield self - each do |i| - i.recursive_each(&block) - end - end - - # Remove the subtree of this Gtk::TreeIter instance from the - # model _model_. - def remove_subtree(model) - while current = first_child - model.remove(current) - end - end - - # Returns the type of this node. - def type - self[TYPE_COL] - end - - # Sets the type of this node to _value_. This implies setting - # the respective icon accordingly. - def type=(value) - self[TYPE_COL] = value - self[ICON_COL] = Editor.fetch_icon(value) - end - - # Returns the content of this node. - def content - self[CONTENT_COL] - end - - # Sets the content of this node to _value_. - def content=(value) - self[CONTENT_COL] = value - end - end - - # This module bundles some method, that can be used to create a menu. It - # should be included into the class in question. - module MenuExtension - include Gtk - - # Creates a Menu, that includes MenuExtension. _treeview_ is the - # Gtk::TreeView, on which it operates. - def initialize(treeview) - @treeview = treeview - @menu = Menu.new - end - - # Returns the Gtk::TreeView of this menu. - attr_reader :treeview - - # Returns the menu. - attr_reader :menu - - # Adds a Gtk::SeparatorMenuItem to this instance's #menu. - def add_separator - menu.append SeparatorMenuItem.new - end - - # Adds a Gtk::MenuItem to this instance's #menu. _label_ is the label - # string, _klass_ is the item type, and _callback_ is the procedure, that - # is called if the _item_ is activated. - def add_item(label, keyval = nil, klass = MenuItem, &callback) - label = "#{label} (C-#{keyval.chr})" if keyval - item = klass.new(label) - item.signal_connect(:activate, &callback) - if keyval - self.signal_connect(:'key-press-event') do |item, event| - if event.state & Gdk::Window::ModifierType::CONTROL_MASK != 0 and - event.keyval == keyval - callback.call item - end - end - end - menu.append item - item - end - - # This method should be implemented in subclasses to create the #menu of - # this instance. It has to be called after an instance of this class is - # created, to build the menu. - def create - raise NotImplementedError - end - - def method_missing(*a, &b) - treeview.__send__(*a, &b) - end - end - - # This class creates the popup menu, that opens when clicking onto the - # treeview. - class PopUpMenu - include MenuExtension - - # Change the type or content of the selected node. - def change_node(item) - if current = selection.selected - parent = current.parent - old_type, old_content = current.type, current.content - if ALL_TYPES.include?(old_type) - @clipboard_data = Editor.model2data(current) - type, content = ask_for_element(parent, current.type, - current.content) - if type - current.type, current.content = type, content - current.remove_subtree(model) - toplevel.display_status("Changed a node in tree.") - window.change - end - else - toplevel.display_status( - "Cannot change node of type #{old_type} in tree!") - end - end - end - - # Cut the selected node and its subtree, and save it into the - # clipboard. - def cut_node(item) - if current = selection.selected - if current and current.type == 'Key' - @clipboard_data = { - current.content => Editor.model2data(current.first_child) - } - else - @clipboard_data = Editor.model2data(current) - end - model.remove(current) - window.change - toplevel.display_status("Cut a node from tree.") - end - end - - # Copy the selected node and its subtree, and save it into the - # clipboard. - def copy_node(item) - if current = selection.selected - if current and current.type == 'Key' - @clipboard_data = { - current.content => Editor.model2data(current.first_child) - } - else - @clipboard_data = Editor.model2data(current) - end - window.change - toplevel.display_status("Copied a node from tree.") - end - end - - # Paste the data in the clipboard into the selected Array or Hash by - # appending it. - def paste_node_appending(item) - if current = selection.selected - if @clipboard_data - case current.type - when 'Array' - Editor.data2model(@clipboard_data, model, current) - expand_collapse(current) - when 'Hash' - if @clipboard_data.is_a? Hash - parent = current.parent - hash = Editor.model2data(current) - model.remove(current) - hash.update(@clipboard_data) - Editor.data2model(hash, model, parent) - if parent - expand_collapse(parent) - elsif @expanded - expand_all - end - window.change - else - toplevel.display_status( - "Cannot paste non-#{current.type} data into '#{current.type}'!") - end - else - toplevel.display_status( - "Cannot paste node below '#{current.type}'!") - end - else - toplevel.display_status("Nothing to paste in clipboard!") - end - else - toplevel.display_status("Append a node into the root first!") - end - end - - # Paste the data in the clipboard into the selected Array inserting it - # before the selected element. - def paste_node_inserting_before(item) - if current = selection.selected - if @clipboard_data - parent = current.parent or return - parent_type = parent.type - if parent_type == 'Array' - selected_index = parent.each_with_index do |c, i| - break i if c == current - end - Editor.data2model(@clipboard_data, model, parent) do |m| - m.insert_before(parent, current) - end - expand_collapse(current) - toplevel.display_status("Inserted an element to " + - "'#{parent_type}' before index #{selected_index}.") - window.change - else - toplevel.display_status( - "Cannot insert node below '#{parent_type}'!") - end - else - toplevel.display_status("Nothing to paste in clipboard!") - end - else - toplevel.display_status("Append a node into the root first!") - end - end - - # Append a new node to the selected Hash or Array. - def append_new_node(item) - if parent = selection.selected - parent_type = parent.type - case parent_type - when 'Hash' - key, type, content = ask_for_hash_pair(parent) - key or return - iter = create_node(parent, 'Key', key) - iter = create_node(iter, type, content) - toplevel.display_status( - "Added a (key, value)-pair to '#{parent_type}'.") - window.change - when 'Array' - type, content = ask_for_element(parent) - type or return - iter = create_node(parent, type, content) - window.change - toplevel.display_status("Appendend an element to '#{parent_type}'.") - else - toplevel.display_status("Cannot append to '#{parent_type}'!") - end - else - type, content = ask_for_element - type or return - iter = create_node(nil, type, content) - window.change - end - end - - # Insert a new node into an Array before the selected element. - def insert_new_node(item) - if current = selection.selected - parent = current.parent or return - parent_parent = parent.parent - parent_type = parent.type - if parent_type == 'Array' - selected_index = parent.each_with_index do |c, i| - break i if c == current - end - type, content = ask_for_element(parent) - type or return - iter = model.insert_before(parent, current) - iter.type, iter.content = type, content - toplevel.display_status("Inserted an element to " + - "'#{parent_type}' before index #{selected_index}.") - window.change - else - toplevel.display_status( - "Cannot insert node below '#{parent_type}'!") - end - else - toplevel.display_status("Append a node into the root first!") - end - end - - # Recursively collapse/expand a subtree starting from the selected node. - def collapse_expand(item) - if current = selection.selected - if row_expanded?(current.path) - collapse_row(current.path) - else - expand_row(current.path, true) - end - else - toplevel.display_status("Append a node into the root first!") - end - end - - # Create the menu. - def create - add_item("Change node", ?n, &method(:change_node)) - add_separator - add_item("Cut node", ?X, &method(:cut_node)) - add_item("Copy node", ?C, &method(:copy_node)) - add_item("Paste node (appending)", ?A, &method(:paste_node_appending)) - add_item("Paste node (inserting before)", ?I, - &method(:paste_node_inserting_before)) - add_separator - add_item("Append new node", ?a, &method(:append_new_node)) - add_item("Insert new node before", ?i, &method(:insert_new_node)) - add_separator - add_item("Collapse/Expand node (recursively)", ?e, - &method(:collapse_expand)) - - menu.show_all - signal_connect(:button_press_event) do |widget, event| - if event.kind_of? Gdk::EventButton and event.button == 3 - menu.popup(nil, nil, event.button, event.time) - end - end - signal_connect(:popup_menu) do - menu.popup(nil, nil, 0, Gdk::Event::CURRENT_TIME) - end - end - end - - # This class creates the File pulldown menu. - class FileMenu - include MenuExtension - - # Clear the model and filename, but ask to save the JSON document, if - # unsaved changes have occured. - def new(item) - window.clear - end - - # Open a file and load it into the editor. Ask to save the JSON document - # first, if unsaved changes have occured. - def open(item) - window.file_open - end - - def open_location(item) - window.location_open - end - - # Revert the current JSON document in the editor to the saved version. - def revert(item) - window.instance_eval do - @filename and file_open(@filename) - end - end - - # Save the current JSON document. - def save(item) - window.file_save - end - - # Save the current JSON document under the given filename. - def save_as(item) - window.file_save_as - end - - # Quit the editor, after asking to save any unsaved changes first. - def quit(item) - window.quit - end - - # Create the menu. - def create - title = MenuItem.new('File') - title.submenu = menu - add_item('New', &method(:new)) - add_item('Open', ?o, &method(:open)) - add_item('Open location', ?l, &method(:open_location)) - add_item('Revert', &method(:revert)) - add_separator - add_item('Save', ?s, &method(:save)) - add_item('Save As', ?S, &method(:save_as)) - add_separator - add_item('Quit', ?q, &method(:quit)) - title - end - end - - # This class creates the Edit pulldown menu. - class EditMenu - include MenuExtension - - # Copy data from model into primary clipboard. - def copy(item) - data = Editor.model2data(model.iter_first) - json = JSON.pretty_generate(data, :max_nesting => false) - c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY) - c.text = json - end - - # Copy json text from primary clipboard into model. - def paste(item) - c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY) - if json = c.wait_for_text - window.ask_save if @changed - begin - window.edit json - rescue JSON::ParserError - window.clear - end - end - end - - # Find a string in all nodes' contents and select the found node in the - # treeview. - def find(item) - @search = ask_for_find_term(@search) or return - iter = model.get_iter('0') or return - iter.recursive_each do |i| - if @iter - if @iter != i - next - else - @iter = nil - next - end - elsif @search.match(i[CONTENT_COL]) - set_cursor(i.path, nil, false) - @iter = i - break - end - end - end - - # Repeat the last search given by #find. - def find_again(item) - @search or return - iter = model.get_iter('0') - iter.recursive_each do |i| - if @iter - if @iter != i - next - else - @iter = nil - next - end - elsif @search.match(i[CONTENT_COL]) - set_cursor(i.path, nil, false) - @iter = i - break - end - end - end - - # Sort (Reverse sort) all elements of the selected array by the given - # expression. _x_ is the element in question. - def sort(item) - if current = selection.selected - if current.type == 'Array' - parent = current.parent - ary = Editor.model2data(current) - order, reverse = ask_for_order - order or return - begin - block = eval "lambda { |x| #{order} }" - if reverse - ary.sort! { |a,b| block[b] <=> block[a] } - else - ary.sort! { |a,b| block[a] <=> block[b] } - end - rescue => e - Editor.error_dialog(self, "Failed to sort Array with #{order}: #{e}!") - else - Editor.data2model(ary, model, parent) do |m| - m.insert_before(parent, current) - end - model.remove(current) - expand_collapse(parent) - window.change - toplevel.display_status("Array has been sorted.") - end - else - toplevel.display_status("Only Array nodes can be sorted!") - end - else - toplevel.display_status("Select an Array to sort first!") - end - end - - # Create the menu. - def create - title = MenuItem.new('Edit') - title.submenu = menu - add_item('Copy', ?c, &method(:copy)) - add_item('Paste', ?v, &method(:paste)) - add_separator - add_item('Find', ?f, &method(:find)) - add_item('Find Again', ?g, &method(:find_again)) - add_separator - add_item('Sort', ?S, &method(:sort)) - title - end - end - - class OptionsMenu - include MenuExtension - - # Collapse/Expand all nodes by default. - def collapsed_nodes(item) - if expanded - self.expanded = false - collapse_all - else - self.expanded = true - expand_all - end - end - - # Toggle pretty saving mode on/off. - def pretty_saving(item) - @pretty_item.toggled - window.change - end - - attr_reader :pretty_item - - # Create the menu. - def create - title = MenuItem.new('Options') - title.submenu = menu - add_item('Collapsed nodes', nil, CheckMenuItem, &method(:collapsed_nodes)) - @pretty_item = add_item('Pretty saving', nil, CheckMenuItem, - &method(:pretty_saving)) - @pretty_item.active = true - window.unchange - title - end - end - - # This class inherits from Gtk::TreeView, to configure it and to add a lot - # of behaviour to it. - class JSONTreeView < Gtk::TreeView - include Gtk - - # Creates a JSONTreeView instance, the parameter _window_ is - # a MainWindow instance and used for self delegation. - def initialize(window) - @window = window - super(TreeStore.new(Gdk::Pixbuf, String, String)) - self.selection.mode = SELECTION_BROWSE - - @expanded = false - self.headers_visible = false - add_columns - add_popup_menu - end - - # Returns the MainWindow instance of this JSONTreeView. - attr_reader :window - - # Returns true, if nodes are autoexpanding, false otherwise. - attr_accessor :expanded - - private - - def add_columns - cell = CellRendererPixbuf.new - column = TreeViewColumn.new('Icon', cell, - 'pixbuf' => ICON_COL - ) - append_column(column) - - cell = CellRendererText.new - column = TreeViewColumn.new('Type', cell, - 'text' => TYPE_COL - ) - append_column(column) - - cell = CellRendererText.new - cell.editable = true - column = TreeViewColumn.new('Content', cell, - 'text' => CONTENT_COL - ) - cell.signal_connect(:edited, &method(:cell_edited)) - append_column(column) - end - - def unify_key(iter, key) - return unless iter.type == 'Key' - parent = iter.parent - if parent.any? { |c| c != iter and c.content == key } - old_key = key - i = 0 - begin - key = sprintf("%s.%d", old_key, i += 1) - end while parent.any? { |c| c != iter and c.content == key } - end - iter.content = key - end - - def cell_edited(cell, path, value) - iter = model.get_iter(path) - case iter.type - when 'Key' - unify_key(iter, value) - toplevel.display_status('Key has been changed.') - when 'FalseClass' - value.downcase! - if value == 'true' - iter.type, iter.content = 'TrueClass', 'true' - end - when 'TrueClass' - value.downcase! - if value == 'false' - iter.type, iter.content = 'FalseClass', 'false' - end - when 'Numeric' - iter.content = (Integer(value) rescue Float(value) rescue 0).to_s - when 'String' - iter.content = value - when 'Hash', 'Array' - return - else - fail "Unknown type found in model: #{iter.type}" - end - window.change - end - - def configure_value(value, type) - value.editable = false - case type - when 'Array', 'Hash' - value.text = '' - when 'TrueClass' - value.text = 'true' - when 'FalseClass' - value.text = 'false' - when 'NilClass' - value.text = 'null' - when 'Numeric', 'String' - value.text ||= '' - value.editable = true - else - raise ArgumentError, "unknown type '#{type}' encountered" - end - end - - def add_popup_menu - menu = PopUpMenu.new(self) - menu.create - end - - public - - # Create a _type_ node with content _content_, and add it to _parent_ - # in the model. If _parent_ is nil, create a new model and put it into - # the editor treeview. - def create_node(parent, type, content) - iter = if parent - model.append(parent) - else - new_model = Editor.data2model(nil) - toplevel.view_new_model(new_model) - new_model.iter_first - end - iter.type, iter.content = type, content - expand_collapse(parent) if parent - iter - end - - # Ask for a hash key, value pair to be added to the Hash node _parent_. - def ask_for_hash_pair(parent) - key_input = type_input = value_input = nil - - dialog = Dialog.new("New (key, value) pair for Hash", nil, nil, - [ Stock::OK, Dialog::RESPONSE_ACCEPT ], - [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] - ) - dialog.width_request = 640 - - hbox = HBox.new(false, 5) - hbox.pack_start(Label.new("Key:"), false) - hbox.pack_start(key_input = Entry.new) - key_input.text = @key || '' - dialog.vbox.pack_start(hbox, false) - key_input.signal_connect(:activate) do - if parent.any? { |c| c.content == key_input.text } - toplevel.display_status('Key already exists in Hash!') - key_input.text = '' - else - toplevel.display_status('Key has been changed.') - end - end - - hbox = HBox.new(false, 5) - hbox.pack_start(Label.new("Type:"), false) - hbox.pack_start(type_input = ComboBox.new(true)) - ALL_TYPES.each { |t| type_input.append_text(t) } - type_input.active = @type || 0 - dialog.vbox.pack_start(hbox, false) - - type_input.signal_connect(:changed) do - value_input.editable = false - case ALL_TYPES[type_input.active] - when 'Array', 'Hash' - value_input.text = '' - when 'TrueClass' - value_input.text = 'true' - when 'FalseClass' - value_input.text = 'false' - when 'NilClass' - value_input.text = 'null' - else - value_input.text = '' - value_input.editable = true - end - end - - hbox = HBox.new(false, 5) - hbox.pack_start(Label.new("Value:"), false) - hbox.pack_start(value_input = Entry.new) - value_input.width_chars = 60 - value_input.text = @value || '' - dialog.vbox.pack_start(hbox, false) - - dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) - dialog.show_all - self.focus = dialog - dialog.run do |response| - if response == Dialog::RESPONSE_ACCEPT - @key = key_input.text - type = ALL_TYPES[@type = type_input.active] - content = value_input.text - return @key, type, content - end - end - return - ensure - dialog.destroy - end - - # Ask for an element to be appended _parent_. - def ask_for_element(parent = nil, default_type = nil, value_text = @content) - type_input = value_input = nil - - dialog = Dialog.new( - "New element into #{parent ? parent.type : 'root'}", - nil, nil, - [ Stock::OK, Dialog::RESPONSE_ACCEPT ], - [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] - ) - hbox = HBox.new(false, 5) - hbox.pack_start(Label.new("Type:"), false) - hbox.pack_start(type_input = ComboBox.new(true)) - default_active = 0 - types = parent ? ALL_TYPES : CONTAINER_TYPES - types.each_with_index do |t, i| - type_input.append_text(t) - if t == default_type - default_active = i - end - end - type_input.active = default_active - dialog.vbox.pack_start(hbox, false) - type_input.signal_connect(:changed) do - configure_value(value_input, types[type_input.active]) - end - - hbox = HBox.new(false, 5) - hbox.pack_start(Label.new("Value:"), false) - hbox.pack_start(value_input = Entry.new) - value_input.width_chars = 60 - value_input.text = value_text if value_text - configure_value(value_input, types[type_input.active]) - - dialog.vbox.pack_start(hbox, false) - - dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) - dialog.show_all - self.focus = dialog - dialog.run do |response| - if response == Dialog::RESPONSE_ACCEPT - type = types[type_input.active] - @content = case type - when 'Numeric' - Integer(value_input.text) rescue Float(value_input.text) rescue 0 - else - value_input.text - end.to_s - return type, @content - end - end - return - ensure - dialog.destroy if dialog - end - - # Ask for an order criteria for sorting, using _x_ for the element in - # question. Returns the order criterium, and true/false for reverse - # sorting. - def ask_for_order - dialog = Dialog.new( - "Give an order criterium for 'x'.", - nil, nil, - [ Stock::OK, Dialog::RESPONSE_ACCEPT ], - [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] - ) - hbox = HBox.new(false, 5) - - hbox.pack_start(Label.new("Order:"), false) - hbox.pack_start(order_input = Entry.new) - order_input.text = @order || 'x' - order_input.width_chars = 60 - - hbox.pack_start(reverse_checkbox = CheckButton.new('Reverse'), false) - - dialog.vbox.pack_start(hbox, false) - - dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) - dialog.show_all - self.focus = dialog - dialog.run do |response| - if response == Dialog::RESPONSE_ACCEPT - return @order = order_input.text, reverse_checkbox.active? - end - end - return - ensure - dialog.destroy if dialog - end - - # Ask for a find term to search for in the tree. Returns the term as a - # string. - def ask_for_find_term(search = nil) - dialog = Dialog.new( - "Find a node matching regex in tree.", - nil, nil, - [ Stock::OK, Dialog::RESPONSE_ACCEPT ], - [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] - ) - hbox = HBox.new(false, 5) - - hbox.pack_start(Label.new("Regex:"), false) - hbox.pack_start(regex_input = Entry.new) - hbox.pack_start(icase_checkbox = CheckButton.new('Icase'), false) - regex_input.width_chars = 60 - if search - regex_input.text = search.source - icase_checkbox.active = search.casefold? - end - - dialog.vbox.pack_start(hbox, false) - - dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) - dialog.show_all - self.focus = dialog - dialog.run do |response| - if response == Dialog::RESPONSE_ACCEPT - begin - return Regexp.new(regex_input.text, icase_checkbox.active? ? Regexp::IGNORECASE : 0) - rescue => e - Editor.error_dialog(self, "Evaluation of regex /#{regex_input.text}/ failed: #{e}!") - return - end - end - end - return - ensure - dialog.destroy if dialog - end - - # Expand or collapse row pointed to by _iter_ according - # to the #expanded attribute. - def expand_collapse(iter) - if expanded - expand_row(iter.path, true) - else - collapse_row(iter.path) - end - end - end - - # The editor main window - class MainWindow < Gtk::Window - include Gtk - - def initialize(encoding) - @changed = false - @encoding = encoding - super(TOPLEVEL) - display_title - set_default_size(800, 600) - signal_connect(:delete_event) { quit } - - vbox = VBox.new(false, 0) - add(vbox) - #vbox.border_width = 0 - - @treeview = JSONTreeView.new(self) - @treeview.signal_connect(:'cursor-changed') do - display_status('') - end - - menu_bar = create_menu_bar - vbox.pack_start(menu_bar, false, false, 0) - - sw = ScrolledWindow.new(nil, nil) - sw.shadow_type = SHADOW_ETCHED_IN - sw.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) - vbox.pack_start(sw, true, true, 0) - sw.add(@treeview) - - @status_bar = Statusbar.new - vbox.pack_start(@status_bar, false, false, 0) - - @filename ||= nil - if @filename - data = read_data(@filename) - view_new_model Editor.data2model(data) - end - - signal_connect(:button_release_event) do |_,event| - if event.button == 2 - c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY) - if url = c.wait_for_text - location_open url - end - false - else - true - end - end - end - - # Creates the menu bar with the pulldown menus and returns it. - def create_menu_bar - menu_bar = MenuBar.new - @file_menu = FileMenu.new(@treeview) - menu_bar.append @file_menu.create - @edit_menu = EditMenu.new(@treeview) - menu_bar.append @edit_menu.create - @options_menu = OptionsMenu.new(@treeview) - menu_bar.append @options_menu.create - menu_bar - end - - # Sets editor status to changed, to indicate that the edited data - # containts unsaved changes. - def change - @changed = true - display_title - end - - # Sets editor status to unchanged, to indicate that the edited data - # doesn't containt unsaved changes. - def unchange - @changed = false - display_title - end - - # Puts a new model _model_ into the Gtk::TreeView to be edited. - def view_new_model(model) - @treeview.model = model - @treeview.expanded = true - @treeview.expand_all - unchange - end - - # Displays _text_ in the status bar. - def display_status(text) - @cid ||= nil - @status_bar.pop(@cid) if @cid - @cid = @status_bar.get_context_id('dummy') - @status_bar.push(@cid, text) - end - - # Opens a dialog, asking, if changes should be saved to a file. - def ask_save - if Editor.question_dialog(self, - "Unsaved changes to JSON model. Save?") - if @filename - file_save - else - file_save_as - end - end - end - - # Quit this editor, that is, leave this editor's main loop. - def quit - ask_save if @changed - if Gtk.main_level > 0 - destroy - Gtk.main_quit - end - nil - end - - # Display the new title according to the editor's current state. - def display_title - title = TITLE.dup - title << ": #@filename" if @filename - title << " *" if @changed - self.title = title - end - - # Clear the current model, after asking to save all unsaved changes. - def clear - ask_save if @changed - @filename = nil - self.view_new_model nil - end - - def check_pretty_printed(json) - pretty = !!((nl_index = json.index("\n")) && nl_index != json.size - 1) - @options_menu.pretty_item.active = pretty - end - private :check_pretty_printed - - # Open the data at the location _uri_, if given. Otherwise open a dialog - # to ask for the _uri_. - def location_open(uri = nil) - uri = ask_for_location unless uri - uri or return - ask_save if @changed - data = load_location(uri) or return - view_new_model Editor.data2model(data) - end - - # Open the file _filename_ or call the #select_file method to ask for a - # filename. - def file_open(filename = nil) - filename = select_file('Open as a JSON file') unless filename - data = load_file(filename) or return - view_new_model Editor.data2model(data) - end - - # Edit the string _json_ in the editor. - def edit(json) - if json.respond_to? :read - json = json.read - end - data = parse_json json - view_new_model Editor.data2model(data) - end - - # Save the current file. - def file_save - if @filename - store_file(@filename) - else - file_save_as - end - end - - # Save the current file as the filename - def file_save_as - filename = select_file('Save as a JSON file') - store_file(filename) - end - - # Store the current JSON document to _path_. - def store_file(path) - if path - data = Editor.model2data(@treeview.model.iter_first) - File.open(path + '.tmp', 'wb') do |output| - data or break - if @options_menu.pretty_item.active? - output.puts JSON.pretty_generate(data, :max_nesting => false) - else - output.write JSON.generate(data, :max_nesting => false) - end - end - File.rename path + '.tmp', path - @filename = path - toplevel.display_status("Saved data to '#@filename'.") - unchange - end - rescue SystemCallError => e - Editor.error_dialog(self, "Failed to store JSON file: #{e}!") - end - - # Load the file named _filename_ into the editor as a JSON document. - def load_file(filename) - if filename - if File.directory?(filename) - Editor.error_dialog(self, "Try to select a JSON file!") - nil - else - @filename = filename - if data = read_data(filename) - toplevel.display_status("Loaded data from '#@filename'.") - end - display_title - data - end - end - end - - # Load the data at location _uri_ into the editor as a JSON document. - def load_location(uri) - data = read_data(uri) or return - @filename = nil - toplevel.display_status("Loaded data from '#{uri}'.") - display_title - data - end - - def parse_json(json) - check_pretty_printed(json) - if @encoding && !/^utf8$/i.match(@encoding) - iconverter = Iconv.new('utf8', @encoding) - json = iconverter.iconv(json) - end - JSON::parse(json, :max_nesting => false, :create_additions => false) - end - private :parse_json - - # Read a JSON document from the file named _filename_, parse it into a - # ruby data structure, and return the data. - def read_data(filename) - open(filename) do |f| - json = f.read - return parse_json(json) - end - rescue => e - Editor.error_dialog(self, "Failed to parse JSON file: #{e}!") - return - end - - # Open a file selecton dialog, displaying _message_, and return the - # selected filename or nil, if no file was selected. - def select_file(message) - filename = nil - fs = FileSelection.new(message) - fs.set_modal(true) - @default_dir = File.join(Dir.pwd, '') unless @default_dir - fs.set_filename(@default_dir) - fs.set_transient_for(self) - fs.signal_connect(:destroy) { Gtk.main_quit } - fs.ok_button.signal_connect(:clicked) do - filename = fs.filename - @default_dir = File.join(File.dirname(filename), '') - fs.destroy - Gtk.main_quit - end - fs.cancel_button.signal_connect(:clicked) do - fs.destroy - Gtk.main_quit - end - fs.show_all - Gtk.main - filename - end - - # Ask for location URI a to load data from. Returns the URI as a string. - def ask_for_location - dialog = Dialog.new( - "Load data from location...", - nil, nil, - [ Stock::OK, Dialog::RESPONSE_ACCEPT ], - [ Stock::CANCEL, Dialog::RESPONSE_REJECT ] - ) - hbox = HBox.new(false, 5) - - hbox.pack_start(Label.new("Location:"), false) - hbox.pack_start(location_input = Entry.new) - location_input.width_chars = 60 - location_input.text = @location || '' - - dialog.vbox.pack_start(hbox, false) - - dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER) - dialog.show_all - dialog.run do |response| - if response == Dialog::RESPONSE_ACCEPT - return @location = location_input.text - end - end - return - ensure - dialog.destroy if dialog - end - end - - class << self - # Starts a JSON Editor. If a block was given, it yields - # to the JSON::Editor::MainWindow instance. - def start(encoding = 'utf8') # :yield: window - Gtk.init - @window = Editor::MainWindow.new(encoding) - @window.icon_list = [ Editor.fetch_icon('json') ] - yield @window if block_given? - @window.show_all - Gtk.main - end - - # Edit the string _json_ with encoding _encoding_ in the editor. - def edit(json, encoding = 'utf8') - start(encoding) do |window| - window.edit json - end - end - - attr_reader :window - end - end -end - # vim: set et sw=2 ts=2: diff --git a/lib/json/ext.rb b/lib/json/ext.rb deleted file mode 100644 index ff4fa42329..0000000000 --- a/lib/json/ext.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'json/common' - -module JSON - # This module holds all the modules/classes that implement JSON's - # functionality as C extensions. - module Ext - require 'json/ext/parser' - require 'json/ext/generator' - $DEBUG and warn "Using c extension for JSON." - JSON.parser = Parser - JSON.generator = Generator - end -end diff --git a/lib/json/json.xpm b/lib/json/json.xpm deleted file mode 100644 index 2cb626bb05..0000000000 --- a/lib/json/json.xpm +++ /dev/null @@ -1,1499 +0,0 @@ -/* XPM */ -static char * json_xpm[] = { -"64 64 1432 2", -" c None", -". c #641839", -"+ c #CF163C", -"@ c #D31C3B", -"# c #E11A38", -"$ c #5F242D", -"% c #320C22", -"& c #9B532D", -"* c #F32E34", -"= c #820F33", -"- c #4B0F34", -"; c #8E1237", -"> c #944029", -", c #961325", -"' c #A00C24", -") c #872C23", -"! c #694021", -"~ c #590D1F", -"{ c #420528", -"] c #D85A2D", -"^ c #7E092B", -"/ c #0E0925", -"( c #0D081F", -"_ c #0F081E", -": c #12071F", -"< c #360620", -"[ c #682A21", -"} c #673F21", -"| c #780E21", -"1 c #A82320", -"2 c #8D1D1F", -"3 c #970127", -"4 c #0D0123", -"5 c #0D0324", -"6 c #3B1E28", -"7 c #C28429", -"8 c #0C0523", -"9 c #0C041E", -"0 c #0E031A", -"a c #11031A", -"b c #13031B", -"c c #13031C", -"d c #11031D", -"e c #19051E", -"f c #390E20", -"g c #9C0C20", -"h c #C00721", -"i c #980320", -"j c #14031E", -"k c #CD9F32", -"l c #C29F2E", -"m c #0F0325", -"n c #0D0321", -"o c #0E0324", -"p c #D08329", -"q c #9D1B27", -"r c #1C0320", -"s c #0D011A", -"t c #120117", -"u c #130017", -"v c #150018", -"w c #160119", -"x c #17021A", -"y c #15021B", -"z c #11021E", -"A c #0F021F", -"B c #8C1821", -"C c #CF4522", -"D c #831821", -"E c #BA7033", -"F c #EDB339", -"G c #C89733", -"H c #280727", -"I c #0F051F", -"J c #0E0420", -"K c #591F27", -"L c #E47129", -"M c #612224", -"N c #0C021D", -"O c #120018", -"P c #140017", -"Q c #170017", -"R c #190018", -"S c #1B0019", -"T c #1B011A", -"U c #18011B", -"V c #15011C", -"W c #12031E", -"X c #460A21", -"Y c #A13823", -"Z c #784323", -"` c #5A0C21", -" . c #BC4530", -".. c #EB5B38", -"+. c #CE4E3B", -"@. c #DD9334", -"#. c #751A27", -"$. c #11071E", -"%. c #0F041C", -"&. c #1E0824", -"*. c #955A28", -"=. c #9A5027", -"-. c #1E0321", -";. c #11011A", -">. c #140018", -",. c #180018", -"'. c #1F001A", -"). c #20001B", -"!. c #1E001A", -"~. c #1B001A", -"{. c #16021B", -"]. c #16041E", -"^. c #220622", -"/. c #5F3525", -"(. c #DE5724", -"_. c #611021", -":. c #0F0925", -"<. c #D1892E", -"[. c #F27036", -"}. c #EC633B", -"|. c #DA293C", -"1. c #E64833", -"2. c #912226", -"3. c #11081C", -"4. c #110419", -"5. c #0F041E", -"6. c #451425", -"7. c #BF6F28", -"8. c #332225", -"9. c #0E021E", -"0. c #13001B", -"a. c #17001A", -"b. c #1C001B", -"c. c #21001C", -"d. c #23001C", -"e. c #21001B", -"f. c #19021A", -"g. c #17041E", -"h. c #150721", -"i. c #602424", -"j. c #D51223", -"k. c #540820", -"l. c #D04D2D", -"m. c #EA8933", -"n. c #875637", -"o. c #88543A", -"p. c #E5923A", -"q. c #891931", -"r. c #130B25", -"s. c #10051B", -"t. c #110217", -"u. c #12021A", -"v. c #761826", -"w. c #E2A728", -"x. c #300224", -"y. c #10011E", -"z. c #16001B", -"A. c #1B001B", -"B. c #21001A", -"C. c #1E0019", -"D. c #1D0019", -"E. c #1A011A", -"F. c #17031C", -"G. c #120720", -"H. c #4E0822", -"I. c #670721", -"J. c #C07630", -"K. c #F59734", -"L. c #BE1B35", -"M. c #0E1435", -"N. c #522037", -"O. c #DB8039", -"P. c #D45933", -"Q. c #420927", -"R. c #0F041D", -"S. c #140118", -"T. c #13021D", -"U. c #100423", -"V. c #7B6227", -"W. c #C04326", -"X. c #0E0020", -"Y. c #13001D", -"Z. c #18001B", -"`. c #1E001B", -" + c #22001C", -".+ c #22001B", -"++ c #1B011B", -"@+ c #16041D", -"#+ c #130520", -"$+ c #860521", -"%+ c #710520", -"&+ c #670A2A", -"*+ c #A66431", -"=+ c #E97536", -"-+ c #F8833A", -";+ c #F77A3A", -">+ c #C45337", -",+ c #0A1C35", -"'+ c #993638", -")+ c #F7863B", -"!+ c #F49736", -"~+ c #94462B", -"{+ c #0E031F", -"]+ c #130119", -"^+ c #160018", -"/+ c #16011B", -"(+ c #15021F", -"_+ c #120123", -":+ c #A65C28", -"<+ c #5C4D23", -"[+ c #0F001F", -"}+ c #14001D", -"|+ c #1A001B", -"1+ c #1F001B", -"2+ c #24001D", -"3+ c #25001D", -"4+ c #24001C", -"5+ c #1F001C", -"6+ c #1A011C", -"7+ c #16021E", -"8+ c #3F0421", -"9+ c #BC0522", -"0+ c #1C041E", -"a+ c #7F5531", -"b+ c #E68A38", -"c+ c #F8933E", -"d+ c #FA7942", -"e+ c #FB7543", -"f+ c #FA6F41", -"g+ c #F1793D", -"h+ c #7D3B3A", -"i+ c #28263B", -"j+ c #D45441", -"k+ c #F8A238", -"l+ c #996B2D", -"m+ c #0E0421", -"n+ c #12011A", -"o+ c #180019", -"p+ c #17001C", -"q+ c #12001F", -"r+ c #4C2B2A", -"s+ c #DB8130", -"t+ c #540023", -"u+ c #0F0120", -"v+ c #16011C", -"w+ c #22001D", -"x+ c #25001F", -"y+ c #26001F", -"z+ c #25001E", -"A+ c #24001E", -"B+ c #1D001C", -"C+ c #18011D", -"D+ c #16031F", -"E+ c #3C0522", -"F+ c #9B0821", -"G+ c #13041E", -"H+ c #F6462E", -"I+ c #E6AB37", -"J+ c #E7A03E", -"K+ c #FA9F44", -"L+ c #FB8A48", -"M+ c #FD7A4A", -"N+ c #FD794A", -"O+ c #FD7748", -"P+ c #FD7E45", -"Q+ c #FD8343", -"R+ c #FB5D42", -"S+ c #6E3A40", -"T+ c #EE8A37", -"U+ c #7E252B", -"V+ c #100520", -"W+ c #13011A", -"X+ c #170019", -"Y+ c #15001C", -"Z+ c #0F0020", -"`+ c #564427", -" @ c #E0BA29", -".@ c #5E2B25", -"+@ c #10011F", -"@@ c #17011C", -"#@ c #1E001D", -"$@ c #23001F", -"%@ c #250020", -"&@ c #24001F", -"*@ c #23001E", -"=@ c #21001E", -"-@ c #1B001C", -";@ c #17021D", -">@ c #14041E", -",@ c #AC0B25", -"'@ c #5E1420", -")@ c #F28635", -"!@ c #C2733E", -"~@ c #984C44", -"{@ c #EA9148", -"]@ c #FB844B", -"^@ c #FD7E4C", -"/@ c #FE7E4C", -"(@ c #FE7E4B", -"_@ c #FE7749", -":@ c #FD7148", -"<@ c #FB7D46", -"[@ c #F89641", -"}@ c #B95634", -"|@ c #0D0927", -"1@ c #11041D", -"2@ c #150119", -"3@ c #180017", -"4@ c #16001A", -"5@ c #13001E", -"6@ c #110023", -"7@ c #944C29", -"8@ c #EE6229", -"9@ c #3D0324", -"0@ c #12021F", -"a@ c #19011D", -"b@ c #21001F", -"c@ c #22001F", -"d@ c #20001E", -"e@ c #1F001D", -"f@ c #1C001C", -"g@ c #19011C", -"h@ c #3D1621", -"i@ c #B53622", -"j@ c #31061F", -"k@ c #841D34", -"l@ c #F2703F", -"m@ c #C14445", -"n@ c #E67349", -"o@ c #FB8E4B", -"p@ c #FD834C", -"q@ c #FE834D", -"r@ c #FE834C", -"s@ c #FE804C", -"t@ c #FD814B", -"u@ c #FB7D49", -"v@ c #F79B43", -"w@ c #AF1234", -"x@ c #0D0625", -"y@ c #13021C", -"z@ c #1A0019", -"A@ c #190019", -"B@ c #410225", -"C@ c #D39729", -"D@ c #AA5927", -"E@ c #0E0422", -"F@ c #15021E", -"G@ c #1A011D", -"H@ c #1D001D", -"I@ c #15031D", -"J@ c #240820", -"K@ c #A01023", -"L@ c #670B21", -"M@ c #3D0D33", -"N@ c #E63C3E", -"O@ c #EF7C45", -"P@ c #F59048", -"Q@ c #FB944A", -"R@ c #FD904A", -"S@ c #FE8E4B", -"T@ c #FE854A", -"U@ c #FE854B", -"V@ c #FE884C", -"W@ c #FC954B", -"X@ c #F8AB45", -"Y@ c #C37A35", -"Z@ c #0D0425", -"`@ c #13011B", -" # c #170018", -".# c #1A0018", -"+# c #1C0019", -"@# c #15001B", -"## c #100120", -"$# c #311F25", -"%# c #E68E28", -"&# c #7A1425", -"*# c #130321", -"=# c #17011E", -"-# c #1A001D", -";# c #19001B", -"># c #16021C", -",# c #130521", -"'# c #6F3123", -")# c #6D3022", -"!# c #C89433", -"~# c #EA7E3E", -"{# c #DB2943", -"]# c #EF7745", -"^# c #FB8544", -"/# c #FD9A43", -"(# c #FE9941", -"_# c #FE9D43", -":# c #FEA548", -"<# c #FEAE49", -"[# c #FCB944", -"}# c #CA9F35", -"|# c #0E0225", -"1# c #11001B", -"2# c #160019", -"3# c #12011B", -"4# c #0F0220", -"5# c #351D26", -"6# c #D85B28", -"7# c #6C0F26", -"8# c #190121", -"9# c #1B001E", -"0# c #1A001C", -"a# c #1D001B", -"b# c #130220", -"c# c #703A23", -"d# c #713A23", -"e# c #140327", -"f# c #411B36", -"g# c #C8713E", -"h# c #7A3A3F", -"i# c #CE2C3C", -"j# c #E77338", -"k# c #9C6535", -"l# c #9C6233", -"m# c #9C6332", -"n# c #9C6A35", -"o# c #C37D3C", -"p# c #FEAC41", -"q# c #FEC23E", -"r# c #826330", -"s# c #100122", -"t# c #120019", -"u# c #150017", -"v# c #190017", -"w# c #1B0018", -"x# c #12001A", -"y# c #10021F", -"z# c #1A0326", -"A# c #5F292A", -"B# c #7B4E29", -"C# c #3C0E25", -"D# c #1A0020", -"E# c #14021F", -"F# c #723B23", -"G# c #14001A", -"H# c #58042A", -"I# c #A28337", -"J# c #C8813B", -"K# c #B14B38", -"L# c #761231", -"M# c #5A132A", -"N# c #0D0726", -"O# c #0C0623", -"P# c #0B0723", -"Q# c #0B0A26", -"R# c #321C2D", -"S# c #C45B33", -"T# c #FEBB33", -"U# c #13052A", -"V# c #13011F", -"W# c #160017", -"X# c #15001A", -"Y# c #12001D", -"Z# c #94062A", -"`# c #630D2C", -" $ c #85292B", -".$ c #AA5E29", -"+$ c #1F0123", -"@$ c #19011F", -"#$ c #1E001C", -"$$ c #15031F", -"%$ c #712122", -"&$ c #712223", -"*$ c #14011B", -"=$ c #110321", -"-$ c #AF0C2B", -";$ c #E7D534", -">$ c #EAC934", -",$ c #84582D", -"'$ c #1B0824", -")$ c #11041E", -"!$ c #10021B", -"~$ c #100119", -"{$ c #100218", -"]$ c #0F041A", -"^$ c #0E0720", -"/$ c #2C1026", -"($ c #D8A328", -"_$ c #140322", -":$ c #160016", -"<$ c #14001F", -"[$ c #120024", -"}$ c #100128", -"|$ c #3C032F", -"1$ c #2C062E", -"2$ c #29022B", -"3$ c #A31D29", -"4$ c #976A25", -"5$ c #1A0321", -"6$ c #17031E", -"7$ c #1B021D", -"8$ c #20001C", -"9$ c #14041F", -"0$ c #703422", -"a$ c #6F3522", -"b$ c #8D0328", -"c$ c #920329", -"d$ c #0F0326", -"e$ c #100321", -"f$ c #11021B", -"g$ c #130117", -"h$ c #140016", -"i$ c #150015", -"j$ c #140015", -"k$ c #130116", -"l$ c #120219", -"m$ c #11031C", -"n$ c #12031D", -"o$ c #170016", -"p$ c #160020", -"q$ c #250029", -"r$ c #670033", -"s$ c #DCA238", -"t$ c #F5C736", -"u$ c #9A732E", -"v$ c #110227", -"w$ c #110324", -"x$ c #811924", -"y$ c #A04323", -"z$ c #250721", -"A$ c #1A041F", -"B$ c #1E011D", -"C$ c #1C011C", -"D$ c #18031D", -"E$ c #130721", -"F$ c #6F3623", -"G$ c #6B3622", -"H$ c #1A001A", -"I$ c #14011F", -"J$ c #12011E", -"K$ c #11011C", -"L$ c #140117", -"M$ c #170015", -"N$ c #150016", -"O$ c #120119", -"P$ c #11011B", -"Q$ c #11001A", -"R$ c #130018", -"S$ c #170118", -"T$ c #170119", -"U$ c #18021E", -"V$ c #1A0126", -"W$ c #6F2332", -"X$ c #E5563B", -"Y$ c #F1B83F", -"Z$ c #F6CC38", -"`$ c #9D7A2D", -" % c #130123", -".% c #130320", -"+% c #2A0721", -"@% c #B00E24", -"#% c #7D0B23", -"$% c #1F0522", -"%% c #1E0220", -"&% c #1D011E", -"*% c #1A031E", -"=% c #15051F", -"-% c #241322", -";% c #A32F23", -">% c #670E21", -",% c #1C001A", -"'% c #19001A", -")% c #180016", -"!% c #160118", -"~% c #140219", -"{% c #11021C", -"]% c #10021E", -"^% c #0F011D", -"/% c #170117", -"(% c #160219", -"_% c #17041D", -":% c #190523", -"<% c #8C042E", -"[% c #B65838", -"}% c #E9D73F", -"|% c #EED43E", -"1% c #D85538", -"2% c #493129", -"3% c #130120", -"4% c #15021D", -"5% c #330822", -"6% c #8A0825", -"7% c #3C0424", -"8% c #1E0322", -"9% c #1C0321", -"0% c #180421", -"a% c #130822", -"b% c #AF2D24", -"c% c #BC5623", -"d% c #2F071F", -"e% c #1A041C", -"f% c #1C031C", -"g% c #1D011C", -"h% c #160117", -"i% c #150419", -"j% c #12081D", -"k% c #0F0923", -"l% c #A77027", -"m% c #A60525", -"n% c #11021A", -"o% c #130218", -"p% c #150319", -"q% c #16061D", -"r% c #180923", -"s% c #9C1D2B", -"t% c #A32636", -"u% c #A66E3B", -"v% c #4B2E3C", -"w% c #412C36", -"x% c #36012D", -"y% c #140123", -"z% c #17001E", -"A% c #19011B", -"B% c #1A0421", -"C% c #340425", -"D% c #9E0326", -"E% c #1F0424", -"F% c #1C0524", -"G% c #180724", -"H% c #A91024", -"I% c #D55D24", -"J% c #90071E", -"K% c #3C051D", -"L% c #1C021C", -"M% c #1C011A", -"N% c #1D001A", -"O% c #160116", -"P% c #150216", -"Q% c #140217", -"R% c #140618", -"S% c #120D1D", -"T% c #231925", -"U% c #B16A2E", -"V% c #FDAC34", -"W% c #D58631", -"X% c #280E2A", -"Y% c #0D0A23", -"Z% c #0F0920", -"`% c #120C21", -" & c #1F1026", -".& c #A3352E", -"+& c #EE9F36", -"@& c #5D2A3C", -"#& c #960D3C", -"$& c #970638", -"%& c #A00330", -"&& c #4D0126", -"*& c #1C001F", -"=& c #280120", -"-& c #290223", -";& c #1F0425", -">& c #260726", -",& c #340A26", -"'& c #850925", -")& c #3A0823", -"!& c #82071D", -"~& c #5E071D", -"{& c #18051C", -"]& c #18021A", -"^& c #190118", -"/& c #160217", -"(& c #150418", -"_& c #130618", -":& c #110718", -"<& c #10081A", -"[& c #110D1D", -"}& c #291C24", -"|& c #A73B2D", -"1& c #FD6B36", -"2& c #FD853C", -"3& c #FD863B", -"4& c #C24A35", -"5& c #6B442F", -"6& c #6D302D", -"7& c #6E252E", -"8& c #8E3B32", -"9& c #DE7739", -"0& c #F48E3F", -"a& c #DD8D41", -"b& c #854F3D", -"c& c #7E2D35", -"d& c #33082B", -"e& c #1C0222", -"f& c #20001F", -"g& c #1F0222", -"h& c #1A0524", -"i& c #440C27", -"j& c #BC1427", -"k& c #20041B", -"l& c #53061C", -"m& c #25071B", -"n& c #11061A", -"o& c #130418", -"p& c #140317", -"q& c #150217", -"r& c #160318", -"s& c #12051B", -"t& c #100C1D", -"u& c #0E101E", -"v& c #0C121F", -"w& c #0C1321", -"x& c #781725", -"y& c #B25D2C", -"z& c #FA6335", -"A& c #FD633C", -"B& c #FE6D42", -"C& c #FE7C42", -"D& c #FE813F", -"E& c #FE873C", -"F& c #FD743B", -"G& c #FB683B", -"H& c #FA7A3E", -"I& c #F98242", -"J& c #F97844", -"K& c #F98943", -"L& c #F79C3D", -"M& c #A25133", -"N& c #280B28", -"O& c #1D021F", -"P& c #1F011C", -"Q& c #280321", -"R& c #1C0724", -"S& c #3F1C27", -"T& c #D33C27", -"U& c #0E061B", -"V& c #0C091C", -"W& c #0C0A1B", -"X& c #0E091A", -"Y& c #11081B", -"Z& c #100A20", -"`& c #0E0D23", -" * c #551227", -".* c #B21829", -"+* c #C42329", -"@* c #C62C29", -"#* c #C55429", -"$* c #E76F2B", -"%* c #F14232", -"&* c #F95E3A", -"** c #FC6740", -"=* c #FE6E45", -"-* c #FE7246", -";* c #FE7545", -">* c #FE7744", -",* c #FD7745", -"'* c #FD7845", -")* c #FD7847", -"!* c #FD7948", -"~* c #FD7B44", -"{* c #FC7C3B", -"]* c #6F3130", -"^* c #140B24", -"/* c #19031D", -"(* c #1C011B", -"_* c #5A011F", -":* c #B70421", -"<* c #380824", -"[* c #3E2626", -"}* c #9F5626", -"|* c #13051E", -"1* c #360A21", -"2* c #361223", -"3* c #371724", -"4* c #381824", -"5* c #3B1524", -"6* c #3E1E26", -"7* c #471A29", -"8* c #DB252E", -"9* c #ED2733", -"0* c #EE5436", -"a* c #F04237", -"b* c #F33934", -"c* c #F53D2F", -"d* c #D7312B", -"e* c #AF212B", -"f* c #3A2C31", -"g* c #F65F39", -"h* c #FB6F41", -"i* c #FD6D45", -"j* c #FE7047", -"k* c #FE7647", -"l* c #FE7847", -"m* c #FE7848", -"n* c #FE7748", -"o* c #FE7948", -"p* c #FE7C48", -"q* c #FE7C47", -"r* c #FE7642", -"s* c #FE7439", -"t* c #6D332C", -"u* c #100B21", -"v* c #16031B", -"w* c #2B001B", -"x* c #22011F", -"y* c #220521", -"z* c #1B0A23", -"A* c #421425", -"B* c #951924", -"C* c #381023", -"D* c #E94028", -"E* c #E7302B", -"F* c #EF432D", -"G* c #F4302E", -"H* c #F32C30", -"I* c #CB4432", -"J* c #DD3235", -"K* c #EF4B3A", -"L* c #F0333E", -"M* c #CC3D3F", -"N* c #E4313C", -"O* c #F34834", -"P* c #D13E2C", -"Q* c #431825", -"R* c #0E1424", -"S* c #3C202C", -"T* c #F15537", -"U* c #F97140", -"V* c #FC6E45", -"W* c #FE7547", -"X* c #FE7947", -"Y* c #FE7B48", -"Z* c #FE7D48", -"`* c #FE8047", -" = c #FE7A42", -".= c #FE7A38", -"+= c #6D442B", -"@= c #0F0B21", -"#= c #15031A", -"$= c #49001B", -"%= c #2F001C", -"&= c #21021E", -"*= c #220620", -"== c #1B0D23", -"-= c #641625", -";= c #951823", -">= c #390F25", -",= c #AC3A2A", -"'= c #B6492E", -")= c #ED7531", -"!= c #F45A34", -"~= c #F54C36", -"{= c #C72D39", -"]= c #DE283C", -"^= c #F33B40", -"/= c #F34142", -"(= c #D0393F", -"_= c #E72E39", -":= c #DB3C2E", -"<= c #461724", -"[= c #0F0D1E", -"}= c #140B1E", -"|= c #341427", -"1= c #CB4834", -"2= c #F7743F", -"3= c #FB7145", -"4= c #FE7747", -"5= c #FE7A47", -"6= c #FF7B48", -"7= c #FF7C48", -"8= c #FE7F47", -"9= c #FE8247", -"0= c #FE8642", -"a= c #FE8439", -"b= c #6D442D", -"c= c #0F0A21", -"d= c #14031A", -"e= c #20031D", -"f= c #210821", -"g= c #191024", -"h= c #CC1C25", -"i= c #961423", -"j= c #2C162C", -"k= c #BD242E", -"l= c #EF2C31", -"m= c #F54C34", -"n= c #F34037", -"o= c #F5353A", -"p= c #F7413D", -"q= c #F8423D", -"r= c #F93A39", -"s= c #F95731", -"t= c #341425", -"u= c #110A1D", -"v= c #140619", -"w= c #18051B", -"x= c #200F26", -"y= c #864833", -"z= c #F8773F", -"A= c #FC7445", -"B= c #FF7E48", -"C= c #FF7E49", -"D= c #FF7D49", -"E= c #FF7D48", -"F= c #FE8347", -"G= c #FE8743", -"H= c #FE893B", -"I= c #6E452F", -"J= c #100E23", -"K= c #14041A", -"L= c #55041D", -"M= c #540921", -"N= c #161124", -"O= c #CE6A25", -"P= c #3F1129", -"Q= c #170A29", -"R= c #0F0F29", -"S= c #15132B", -"T= c #1E182D", -"U= c #A82B3D", -"V= c #CB6633", -"W= c #CC6932", -"X= c #CC3D2D", -"Y= c #331225", -"Z= c #0F091C", -"`= c #120417", -" - c #160216", -".- c #190419", -"+- c #210F26", -"@- c #8C4934", -"#- c #F97A40", -"$- c #FC7545", -"%- c #FF7B49", -"&- c #FE7D46", -"*- c #FE7E43", -"=- c #FD7B3E", -"-- c #FA6934", -";- c #532328", -">- c #130B1D", -",- c #150519", -"'- c #14041C", -")- c #120920", -"!- c #C43624", -"~- c #A21E23", -"{- c #F87C30", -"]- c #C9302D", -"^- c #300F2A", -"/- c #591129", -"(- c #171328", -"_- c #171628", -":- c #141829", -"<- c #101A2B", -"[- c #0F172B", -"}- c #0F1226", -"|- c #0E0C20", -"1- c #100619", -"2- c #140316", -"3- c #19051B", -"4- c #3C1428", -"5- c #E04B36", -"6- c #FA7B41", -"7- c #FD7346", -"8- c #FE7548", -"9- c #FF7849", -"0- c #FF7749", -"a- c #FE7B47", -"b- c #FE7945", -"c- c #FC7740", -"d- c #FA7E39", -"e- c #C1432F", -"f- c #131523", -"g- c #130A1C", -"h- c #420621", -"i- c #D08423", -"j- c #F87739", -"k- c #C03D37", -"l- c #962B34", -"m- c #A14332", -"n- c #E54B30", -"o- c #9E3E2F", -"p- c #7F262E", -"q- c #922D2E", -"r- c #9C4B2E", -"s- c #65212C", -"t- c #101628", -"u- c #101022", -"v- c #11091C", -"w- c #130619", -"x- c #160A1E", -"y- c #43252C", -"z- c #F66439", -"A- c #FA6942", -"B- c #FD6C47", -"C- c #FE6E48", -"D- c #FE6F48", -"E- c #FE7049", -"F- c #FE714A", -"G- c #FE744A", -"H- c #FE7846", -"I- c #FD7243", -"J- c #FC703E", -"K- c #FA6C37", -"L- c #81312B", -"M- c #121123", -"N- c #15071D", -"O- c #16031A", -"P- c #17021B", -"Q- c #8F3D22", -"R- c #F8393E", -"S- c #E42A3D", -"T- c #E7473B", -"U- c #FB503B", -"V- c #FB4F3A", -"W- c #F95439", -"X- c #ED4C38", -"Y- c #F45938", -"Z- c #FB6537", -"`- c #EA5236", -" ; c #CE6232", -".; c #CD392C", -"+; c #181425", -"@; c #120F21", -"#; c #130D20", -"$; c #151225", -"%; c #903431", -"&; c #F8703D", -"*; c #FB6344", -"=; c #FD6748", -"-; c #FE6849", -";; c #FE6949", -">; c #FE6A49", -",; c #FE6C4A", -"'; c #FE704A", -"); c #FE734A", -"!; c #FE7449", -"~; c #FE7347", -"{; c #FE7145", -"]; c #FD6C42", -"^; c #FD753D", -"/; c #F36E35", -"(; c #CB452C", -"_; c #600D24", -":; c #1C061F", -"<; c #1E031F", -"[; c #5B3821", -"}; c #CE9822", -"|; c #FA4341", -"1; c #FB4341", -"2; c #FC4541", -"3; c #FC4542", -"4; c #FC4143", -"5; c #FC4D42", -"6; c #FB5042", -"7; c #FB5342", -"8; c #FC5242", -"9; c #FD4F40", -"0; c #FD503E", -"a; c #FB6339", -"b; c #F45E33", -"c; c #A12A2E", -"d; c #401E2C", -"e; c #452D2F", -"f; c #F74F38", -"g; c #FA5940", -"h; c #FC6245", -"i; c #FE6447", -"j; c #FE6449", -"k; c #FE6549", -"l; c #FE6749", -"m; c #FE6B49", -"n; c #FE6D49", -"o; c #FE6D48", -"p; c #FE6D47", -"q; c #FE6D45", -"r; c #FE6C44", -"s; c #FE6A42", -"t; c #FE663C", -"u; c #FC6233", -"v; c #752129", -"w; c #1F0922", -"x; c #750520", -"y; c #81061F", -"z; c #FA3D42", -"A; c #FB4142", -"B; c #FD4543", -"C; c #FD4844", -"D; c #FD4A45", -"E; c #FD4D45", -"F; c #FD5045", -"G; c #FD5345", -"H; c #FE5346", -"I; c #FE5445", -"J; c #FD5444", -"K; c #FC4F41", -"L; c #FA513D", -"M; c #F95339", -"N; c #F63736", -"O; c #F75737", -"P; c #F95F3B", -"Q; c #FB5840", -"R; c #FD5F43", -"S; c #FE6345", -"T; c #FE6547", -"U; c #FE6548", -"V; c #FE6448", -"W; c #FE6248", -"X; c #FE6348", -"Y; c #FE6748", -"Z; c #FE6848", -"`; c #FE6846", -" > c #FE6A45", -".> c #FE6D43", -"+> c #FE703F", -"@> c #FC6F36", -"#> c #6F302B", -"$> c #140A22", -"%> c #FA3B42", -"&> c #FC4243", -"*> c #FD4744", -"=> c #FE4A45", -"-> c #FE4C47", -";> c #FE4D47", -">> c #FE5047", -",> c #FE5347", -"'> c #FE5447", -")> c #FD5246", -"!> c #FB503F", -"~> c #FA543D", -"{> c #9B3D3B", -"]> c #A3433B", -"^> c #F9683D", -"/> c #FC6940", -"(> c #FE6342", -"_> c #FE6645", -":> c #FE6646", -"<> c #FE6147", -"[> c #FE6048", -"}> c #FE6148", -"|> c #FE6746", -"1> c #FE6A46", -"2> c #FE6F45", -"3> c #FE7441", -"4> c #FC7D39", -"5> c #6C422E", -"6> c #0F0F23", -"7> c #FA4142", -"8> c #FC4643", -"9> c #FE4D46", -"0> c #FE4E47", -"a> c #FE4F48", -"b> c #FE5148", -"c> c #FE5348", -"d> c #FE5548", -"e> c #FE5247", -"f> c #FD5445", -"g> c #FC5544", -"h> c #F96041", -"i> c #D33F3D", -"j> c #392D39", -"k> c #973C38", -"l> c #F94E3A", -"m> c #FD693E", -"n> c #FE6C43", -"o> c #FE6047", -"p> c #FE5D47", -"q> c #FE5E48", -"r> c #FE6948", -"s> c #FE6947", -"t> c #FE6B47", -"u> c #FE6E46", -"v> c #FD6D43", -"w> c #FB723D", -"x> c #D54A33", -"y> c #301C29", -"z> c #FB4A42", -"A> c #FD4B44", -"B> c #FE4F47", -"C> c #FE5048", -"D> c #FE5648", -"E> c #FE5848", -"F> c #FE5747", -"G> c #FE5547", -"H> c #FC5945", -"I> c #F95742", -"J> c #F3543D", -"K> c #A33336", -"L> c #302032", -"M> c #152433", -"N> c #CD3E38", -"O> c #FD5A3F", -"P> c #FE6343", -"Q> c #FE6446", -"R> c #FE6247", -"S> c #FE6A47", -"T> c #FC6542", -"U> c #FB6A3B", -"V> c #FA6D34", -"W> c #D73C2D", -"X> c #442428", -"Y> c #281323", -"Z> c #FD4E42", -"`> c #FD4D43", -" , c #FE4D45", -"., c #FE5248", -"+, c #FE5947", -"@, c #FE5C47", -"#, c #FE5B47", -"$, c #FE5A47", -"%, c #FE5847", -"&, c #FC5C45", -"*, c #F95B43", -"=, c #F3613F", -"-, c #E74F37", -";, c #8C2431", -">, c #161E2F", -",, c #CD4E33", -"', c #FD503A", -"), c #FE5D40", -"!, c #FE6445", -"~, c #FE6946", -"{, c #FE6847", -"], c #FE6747", -"^, c #FD6644", -"/, c #FD6241", -"(, c #FD5B3D", -"_, c #FE6739", -":, c #FE6135", -"<, c #AB4830", -"[, c #733E2A", -"}, c #161224", -"|, c #FC4E42", -"1, c #FE4D44", -"2, c #FE4E46", -"3, c #FE5147", -"4, c #FE5E47", -"5, c #FD5C46", -"6, c #FA5B44", -"7, c #F45441", -"8, c #EB393A", -"9, c #CC3433", -"0, c #47212F", -"a, c #59242F", -"b, c #FC6734", -"c, c #FC6F3A", -"d, c #FC723E", -"e, c #FD6540", -"f, c #FE6442", -"g, c #FE6643", -"h, c #FE6944", -"i, c #FE6546", -"j, c #FE6444", -"k, c #FE6143", -"l, c #FE5E41", -"m, c #FE613F", -"n, c #FE683C", -"o, c #FE7937", -"p, c #A25030", -"q, c #692629", -"r, c #151122", -"s, c #FA573F", -"t, c #FB4D40", -"u, c #FC4F43", -"v, c #FE5246", -"w, c #FF6347", -"x, c #FE5F48", -"y, c #F65942", -"z, c #F0493D", -"A, c #ED3736", -"B, c #73262F", -"C, c #10152C", -"D, c #3B292F", -"E, c #363034", -"F, c #AC3938", -"G, c #FC6B3B", -"H, c #FD763C", -"I, c #FE6D3F", -"J, c #FE6341", -"K, c #FE6642", -"L, c #FE6745", -"M, c #FE6245", -"N, c #FE6244", -"O, c #FE6841", -"P, c #FF683B", -"Q, c #EC7035", -"R, c #D0412D", -"S, c #3A1627", -"T, c #CF3938", -"U, c #F6543C", -"V, c #FB5040", -"W, c #FD5544", -"X, c #FE5A48", -"Y, c #FE5D48", -"Z, c #FE5F47", -"`, c #FF6147", -" ' c #FD5C45", -".' c #FB5B43", -"+' c #FA5A42", -"@' c #F76040", -"#' c #F4623D", -"$' c #F26D38", -"%' c #EC4130", -"&' c #380E2B", -"*' c #13122C", -"=' c #362D31", -"-' c #353435", -";' c #352E37", -">' c #2D3337", -",' c #CC5838", -"'' c #CD6F3A", -")' c #CE6E3D", -"!' c #FE793F", -"~' c #FD7541", -"{' c #FD6243", -"]' c #FE6545", -"^' c #FF6543", -"/' c #FF6240", -"(' c #FE723B", -"_' c #FE8034", -":' c #442D2C", -"<' c #311725", -"[' c #222830", -"}' c #B73B36", -"|' c #F94C3D", -"1' c #FD5543", -"2' c #FE5B48", -"3' c #FF5E47", -"4' c #FE5C48", -"5' c #FC5B44", -"6' c #F95640", -"7' c #C34E3D", -"8' c #A45A3A", -"9' c #F37438", -"0' c #F28935", -"a' c #AF422F", -"b' c #240D2B", -"c' c #88292F", -"d' c #FA8E34", -"e' c #FC7E38", -"f' c #FC5939", -"g' c #694A37", -"h' c #693437", -"i' c #382638", -"j' c #142439", -"k' c #9F483A", -"l' c #C45E3C", -"m' c #FD7240", -"n' c #FF6645", -"o' c #FF6245", -"p' c #FF6045", -"q' c #FF6146", -"r' c #FF6246", -"s' c #FF6446", -"t' c #FF6545", -"u' c #FE763F", -"v' c #FE7237", -"w' c #C65331", -"x' c #3D272A", -"y' c #0D1E2B", -"z' c #683032", -"A' c #F9453A", -"B' c #FD5341", -"C' c #FE5A46", -"D' c #FF5A48", -"E' c #FE5948", -"F' c #FD5A47", -"G' c #FC5D43", -"H' c #F95B3D", -"I' c #713F37", -"J' c #1E2D32", -"K' c #C44531", -"L' c #EF7A2F", -"M' c #6B2E2C", -"N' c #0F0E2C", -"O' c #F56633", -"P' c #FA803A", -"Q' c #FC673E", -"R' c #FD673E", -"S' c #FC6F3C", -"T' c #FA6E3B", -"U' c #C6633A", -"V' c #A06739", -"W' c #835638", -"X' c #381F38", -"Y' c #713B38", -"Z' c #7B503C", -"`' c #FE7741", -" ) c #FE7344", -".) c #FE6D46", -"+) c #FF6946", -"@) c #FF5E46", -"#) c #FF5D46", -"$) c #FF5D47", -"%) c #FF5F48", -"&) c #FF6248", -"*) c #FE6941", -"=) c #FC783C", -"-) c #C46B35", -";) c #892730", -">) c #111629", -",) c #1F2630", -"') c #AD3939", -")) c #FC5D41", -"!) c #FE5946", -"~) c #FF5848", -"{) c #FE5549", -"]) c #FC5E42", -"^) c #FA673B", -"/) c #DB7033", -"() c #392E2B", -"_) c #311A28", -":) c #3C2127", -"<) c #1D1027", -"[) c #92102C", -"}) c #F58336", -"|) c #FA673E", -"1) c #FD6642", -"2) c #FD5A41", -"3) c #FC6D41", -"4) c #FC6D3F", -"5) c #FD683E", -"6) c #F38C39", -"7) c #CE6535", -"8) c #612E34", -"9) c #1D2637", -"0) c #71513E", -"a) c #FF6847", -"b) c #FF5F47", -"c) c #FF5A46", -"d) c #FF5847", -"e) c #FF5748", -"f) c #FF594A", -"g) c #FF5E4B", -"h) c #FE654C", -"i) c #FE694B", -"j) c #FE6B48", -"k) c #FC6A43", -"l) c #F7683E", -"m) c #EC6E39", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" . + @ # $ % ", -" & * = - ; > , ' ) ! ~ ", -" { ] ^ / ( _ : < [ } | 1 2 ", -" 3 4 5 6 7 8 9 0 a b c d e f g h i j ", -" k l m n o p q r s t u v w x y z A B C D ", -" E F G H I J K L M N O P Q R S T U V W X Y Z ` ", -" ...+.@.#.$.%.&.*.=.-.;.>.,.S '.).!.~.{.].^./.(._. ", -" :.<.[.}.|.1.2.3.4.5.6.7.8.9.0.a.b.c.d.e.!.S f.g.h.i.j.k. ", -" l.m.n.o.p.q.r.s.t.u.J v.w.x.y.z.A.c.d.d.B.C.D.E.F.G.H.I. ", -" J.K.L.M.N.O.P.Q.R.t S.T.U.V.W.X.Y.Z.`. +d.d..+B.'.++@+#+$+%+ ", -" &+*+=+-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+[+}+|+1+d.2+3+4+d.5+6+7+8+9+0+ ", -" a+b+c+d+e+f+g+h+i+j+k+l+m+n+^+o+p+q+r+s+t+u+v+b.w+x+y+z+A+w+B+C+D+E+F+G+ ", -" H+I+J+K+L+M+N+O+P+Q+R+S+T+U+V+W+Q ,.X+Y+Z+`+ @.@+@@@#@$@%@&@*@=@#@-@;@>@,@'@ ", -" )@!@~@{@]@^@/@(@_@:@<@[@}@|@1@2@3@R ,.4@5@6@7@8@9@0@a@#@b@c@=@d@e@f@g@>@h@i@j@ ", -" k@l@m@n@o@p@q@r@s@t@u@v@w@x@y@^+R S z@A@z.q+B@C@D@E@F@G@H@#@e@#@#@f@g@I@J@K@L@ ", -" M@N@O@P@Q@R@S@T@U@V@W@X@Y@Z@`@ #.#+#+#S A@@###$#%#&#*#=#-#f@B+B+B+f@;#>#,#'#)# ", -" !#~#{#]#^#/#(#(#_#:#<#[#}#|#1#^+.#S +#+#z@2#3#4#5#6#7#8#9#0#A.B+B+a#A.@@b#c#d# ", -" e#f#g#h#i#j#k#l#m#n#o#p#q#r#s#t#u#v#.#w#S R ^+x#y#z#A#B#C#D#-#A.a#`.`.b.g@E#d#F# ", -" G#0@H#I#J#K#L#M#N#O#P#Q#R#S#T#U#V#>.W#3@v#R R X+X#Y#s#Z#`# $.$+$@$g@f@5+5+#$6+$$%$&$ ", -" *$=$-$;$>$,$'$)$!$~${$]$^$/$($_$*$u#:$Q 3@,.X+z.<$[$}$|$1$2$3$4$5$6$7$e@8$#$G@9$0$a$ ", -" ,.4@E#b$c$d$e$f$g$h$i$j$k$l$m$n$`@>.:$o$3@,. #a.p$q$r$s$t$u$v$w$x$y$z$A$B$#@C$D$E$F$G$ ", -" R S H$v+I$J$K$n+L$:$o$o$M$N$L$O$P$Q$R$N$o$3@S$T$U$V$W$X$Y$Z$`$ %.%+%@%#%$%%%&%*%=%-%;%>% ", -" E.,%~.'%Z.4@v W#o$)%)%)%Q !%~%{%]%^%Q$u u#/%(%_%:%<%[%}%|%1%2%3%4%=%5%6%7%8%9%0%a%b%c%d% ", -" e%f%g%a#,%,%z@R 3@3@3@)%Q h%i%j%k%l%m%{+n%o%p%q%r%s%t%u%v%w%x%y%z%A%*%B%C%D%E%F%G%H%I% ", -" J%K%L%M%N%D.S v#)%)%O%P%Q%R%S%T%U%V%W%X%Y%Z%`% &.&+&@&#&$&%&&&*&f@a##@=&-&;&>&,&'&)& ", -" !&~&{&]&^&.#w#^&/%/&(&_&:&<&[&}&|&1&2&3&4&5&6&7&8&9&0&a&b&c&d&e&e@1+5+e@f&g&h&i&j& ", -" k&l&m&n&o&p&q&r&i%s&3.t&u&v&w&x&y&z&A&B&C&D&E&F&G&H&I&J&K&L&M&N&O&P&1+`.e@f&Q&R&S&T& ", -" 0 U&V&W&X&<&Y&j%Z&`& *.*+*@*#*$*%*&***=*-*;*>*>*,*'*)*!*~*{*]*^*/*(*a#B+#@_*:*<*[*}* ", -" |*1*2*3*4*5*6*7*8*9*0*a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*E.w*d.e@x*y*z*A*B* ", -" C*D*E*F*G*H*I*J*K*L*M*N*O*P*Q*R*S*T*U*V*W*l*X*o*o*Y*Z*`* =.=+=@=#='%$=%=e@&=*===-=;= ", -" >=,='=)=!=~={=]=^=/=(=_=:=<=[=}=|=1=2=3=4=5=p*6=6=7=8=9=0=a=b=c=d=A@~.b.B+e=f=g=h=i= ", -" j=k=l=m=n=o=p=q=r=s=t=u=v=w=x=y=z=A=5=Z*B=C=D=E=8=F=G=H=I=J=K=S$R z@'%L=M=N=O= ", -" P=Q=R=S=T=U=V=W=X=Y=Z=`= -.-+-@-#-$-5=p*E=D=%-%-q*&-*-=---;->-,-/%3@^+'-)-!-~- ", -" {-]-^-/-(-_-:-<-[-}-|-1-2- -3-4-5-6-7-8-n*m*9-0-9-o*a-b-c-d-e-f-g-(&h%w c h-i- ", -" j-k-l-m-n-o-p-q-r-s-t-u-v-w-,-x-y-z-A-B-C-D-E-E-F-G-_@m*H-I-J-K-L-M-N-O-P-(+Q- ", -" R-S-T-U-V-W-X-Y-Z-`- ;.;+;@;#;$;%;&;*;=;-;-;;;>;,;';);!;~;{;];^;/;(;_;:;<;[;}; ", -" |;1;2;3;4;5;6;7;8;9;0;a;b;c;d;e;f;g;h;i;j;j;k;k;l;m;n;o;p;q;r;s;t;u;v;w;x;y; ", -" z;A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;k;Y;Z;`; >r;.>+>@>#>$> ", -" %>&>*>=>->;>>>,>'>,>)>F;8;!>~>{>]>^>/>(>_>:>i;<>[>X;}>i;|>1>q;2>3>4>5>6> ", -" 7>8>=>9>0>a>b>c>d>,>e>e>f>g>h>i>j>k>l>m>n>:>i;o>p>q>W;r>s>t>p;u>v>w>x>y> ", -" z>A>9>0>B>C>c>D>E>F>G>G>F>H>I>J>K>L>M>N>O>P>Q>R>o>R>T;s>S>S>S>t>1>T>U>V>W>X>Y> ", -" Z>`> ,9>B>.,D>+,@,#,$,%,$,&,*,=,-,;,>,,,',),P>!,!,_>~,t>s>{,],{,],^,/,(,_,:,<,[,}, ", -" |,`>1,2,3,G>+,4,o>o>4,@,@,5,6,7,8,9,0,a,b,c,d,e,f,g,h, >~,|>T;T;T;i,j,k,l,m,n,o,p,q,r, ", -" s,t,u,v,G>%,@,o>w,R>x,p>@,5,6,y,z,A,B,C,D,E,F,G,H,I,J,K,L,L,i,i;i;i;Q>S;M,N,P>O,P,Q,R,S, ", -" T,U,V,W,%,X,Y,Z,`,[>q>@, '.'+'@'#'$'%'&'*'='-';'>',''')'!'~'{'N,i,:>_>]'M,M,Q>_>^'/'('_':'<' ", -" ['}'|'1'$,X,2'p>3'4'2'@,5'6'7'8'9'0'a'b'c'd'e'f'g'h'i'j'k'l'd,m'g, > >n'o'p'q'r's't'.>u'v'w'x' ", -" y'z'A'B'C'X,X,2'D'E'E'F'G'H'I'J'K'L'M'N'O'P'Q'R'S'T'U'V'W'X'Y'Z'`' ).)+)r'@)#)$)%)&)l;1>*)=)-);) ", -" >),)')))!)X,E'X,~){)d>!)])^)/)()_):)<)[)})|)1)f,2)3)4)5)6)7)8)9)0)*--*a)b)c)d)e)f)g)h)i)j)k)l)m) ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" "}; diff --git a/lib/json/pure.rb b/lib/json/pure.rb deleted file mode 100644 index b86d905523..0000000000 --- a/lib/json/pure.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'json/common' -require 'json/pure/parser' -require 'json/pure/generator' - -module JSON - begin - require 'iconv' - # An iconv instance to convert from UTF8 to UTF16 Big Endian. - UTF16toUTF8 = Iconv.new('utf-8', 'utf-16be') # :nodoc: - # An iconv instance to convert from UTF16 Big Endian to UTF8. - UTF8toUTF16 = Iconv.new('utf-16be', 'utf-8') # :nodoc: - UTF8toUTF16.iconv('no bom') - rescue Errno::EINVAL, Iconv::InvalidEncoding - # Iconv doesn't support big endian utf-16. Let's try to hack this manually - # into the converters. - begin - old_verbose, $VERBSOSE = $VERBOSE, nil - # An iconv instance to convert from UTF8 to UTF16 Big Endian. - UTF16toUTF8 = Iconv.new('utf-8', 'utf-16') # :nodoc: - # An iconv instance to convert from UTF16 Big Endian to UTF8. - UTF8toUTF16 = Iconv.new('utf-16', 'utf-8') # :nodoc: - UTF8toUTF16.iconv('no bom') - if UTF8toUTF16.iconv("\xe2\x82\xac") == "\xac\x20" - swapper = Class.new do - def initialize(iconv) # :nodoc: - @iconv = iconv - end - - def iconv(string) # :nodoc: - result = @iconv.iconv(string) - JSON.swap!(result) - end - end - UTF8toUTF16 = swapper.new(UTF8toUTF16) # :nodoc: - end - if UTF16toUTF8.iconv("\xac\x20") == "\xe2\x82\xac" - swapper = Class.new do - def initialize(iconv) # :nodoc: - @iconv = iconv - end - - def iconv(string) # :nodoc: - string = JSON.swap!(string.dup) - @iconv.iconv(string) - end - end - UTF16toUTF8 = swapper.new(UTF16toUTF8) # :nodoc: - end - rescue Errno::EINVAL, Iconv::InvalidEncoding - raise MissingUnicodeSupport, "iconv doesn't seem to support UTF-8/UTF-16 conversions" - ensure - $VERBOSE = old_verbose - end - rescue LoadError - raise MissingUnicodeSupport, - "iconv couldn't be loaded, which is required for UTF-8/UTF-16 conversions" - end - - # Swap consecutive bytes of _string_ in place. - def self.swap!(string) # :nodoc: - 0.upto(string.size / 2) do |i| - break unless string[2 * i + 1] - string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i] - end - string - end - - # This module holds all the modules/classes that implement JSON's - # functionality in pure ruby. - module Pure - $DEBUG and warn "Using pure library for JSON." - JSON.parser = Parser - JSON.generator = Generator - end -end diff --git a/lib/json/pure/generator.rb b/lib/json/pure/generator.rb deleted file mode 100644 index 0fe73be41a..0000000000 --- a/lib/json/pure/generator.rb +++ /dev/null @@ -1,394 +0,0 @@ -module JSON - MAP = { - "\x0" => '\u0000', - "\x1" => '\u0001', - "\x2" => '\u0002', - "\x3" => '\u0003', - "\x4" => '\u0004', - "\x5" => '\u0005', - "\x6" => '\u0006', - "\x7" => '\u0007', - "\b" => '\b', - "\t" => '\t', - "\n" => '\n', - "\xb" => '\u000b', - "\f" => '\f', - "\r" => '\r', - "\xe" => '\u000e', - "\xf" => '\u000f', - "\x10" => '\u0010', - "\x11" => '\u0011', - "\x12" => '\u0012', - "\x13" => '\u0013', - "\x14" => '\u0014', - "\x15" => '\u0015', - "\x16" => '\u0016', - "\x17" => '\u0017', - "\x18" => '\u0018', - "\x19" => '\u0019', - "\x1a" => '\u001a', - "\x1b" => '\u001b', - "\x1c" => '\u001c', - "\x1d" => '\u001d', - "\x1e" => '\u001e', - "\x1f" => '\u001f', - '"' => '\"', - '\\' => '\\\\', - '/' => '\/', - } # :nodoc: - - # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with - # UTF16 big endian characters as \u????, and return it. - def utf8_to_json(string) # :nodoc: - string = string.gsub(/["\\\/\x0-\x1f]/) { |c| MAP[c] } - string.gsub!(/( - (?: - [\xc2-\xdf][\x80-\xbf] | - [\xe0-\xef][\x80-\xbf]{2} | - [\xf0-\xf4][\x80-\xbf]{3} - )+ | - [\x80-\xc1\xf5-\xff] # invalid - )/nx) { |c| - c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'" - s = JSON::UTF8toUTF16.iconv(c).unpack('H*')[0] - s.gsub!(/.{4}/n, '\\\\u\&') - } - string - rescue Iconv::Failure => e - raise GeneratorError, "Caught #{e.class}: #{e}" - end - module_function :utf8_to_json - - module Pure - module Generator - # This class is used to create State instances, that are use to hold data - # while generating a JSON text from a a Ruby data structure. - class State - # Creates a State object from _opts_, which ought to be Hash to create - # a new State instance configured by _opts_, something else to create - # an unconfigured instance. If _opts_ is a State object, it is just - # returned. - def self.from_state(opts) - case opts - when self - opts - when Hash - new(opts) - else - new - end - end - - # Instantiates a new State object, configured by _opts_. - # - # _opts_ can have the following keys: - # - # * *indent*: a string used to indent levels (default: ''), - # * *space*: a string that is put after, a : or , delimiter (default: ''), - # * *space_before*: a string that is put before a : pair delimiter (default: ''), - # * *object_nl*: a string that is put at the end of a JSON object (default: ''), - # * *array_nl*: a string that is put at the end of a JSON array (default: ''), - # * *check_circular*: true if checking for circular data structures - # should be done (the default), false otherwise. - # * *check_circular*: true if checking for circular data structures - # should be done, false (the default) otherwise. - # * *allow_nan*: true if NaN, Infinity, and -Infinity should be - # generated, otherwise an exception is thrown, if these values are - # encountered. This options defaults to false. - def initialize(opts = {}) - @seen = {} - @indent = '' - @space = '' - @space_before = '' - @object_nl = '' - @array_nl = '' - @check_circular = true - @allow_nan = false - configure opts - end - - # This string is used to indent levels in the JSON text. - attr_accessor :indent - - # This string is used to insert a space between the tokens in a JSON - # string. - attr_accessor :space - - # This string is used to insert a space before the ':' in JSON objects. - attr_accessor :space_before - - # This string is put at the end of a line that holds a JSON object (or - # Hash). - attr_accessor :object_nl - - # This string is put at the end of a line that holds a JSON array. - attr_accessor :array_nl - - # This integer returns the maximum level of data structure nesting in - # the generated JSON, max_nesting = 0 if no maximum is checked. - attr_accessor :max_nesting - - def check_max_nesting(depth) # :nodoc: - return if @max_nesting.zero? - current_nesting = depth + 1 - current_nesting > @max_nesting and - raise NestingError, "nesting of #{current_nesting} is too deep" - end - - # Returns true, if circular data structures should be checked, - # otherwise returns false. - def check_circular? - @check_circular - end - - # Returns true if NaN, Infinity, and -Infinity should be considered as - # valid JSON and output. - def allow_nan? - @allow_nan - end - - # Returns _true_, if _object_ was already seen during this generating - # run. - def seen?(object) - @seen.key?(object.__id__) - end - - # Remember _object_, to find out if it was already encountered (if a - # cyclic data structure is if a cyclic data structure is rendered). - def remember(object) - @seen[object.__id__] = true - end - - # Forget _object_ for this generating run. - def forget(object) - @seen.delete object.__id__ - end - - # Configure this State instance with the Hash _opts_, and return - # itself. - def configure(opts) - @indent = opts[:indent] if opts.key?(:indent) - @space = opts[:space] if opts.key?(:space) - @space_before = opts[:space_before] if opts.key?(:space_before) - @object_nl = opts[:object_nl] if opts.key?(:object_nl) - @array_nl = opts[:array_nl] if opts.key?(:array_nl) - @check_circular = !!opts[:check_circular] if opts.key?(:check_circular) - @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan) - if !opts.key?(:max_nesting) # defaults to 19 - @max_nesting = 19 - elsif opts[:max_nesting] - @max_nesting = opts[:max_nesting] - else - @max_nesting = 0 - end - self - end - - # Returns the configuration instance variables as a hash, that can be - # passed to the configure method. - def to_h - result = {} - for iv in %w[indent space space_before object_nl array_nl check_circular allow_nan max_nesting] - result[iv.intern] = instance_variable_get("@#{iv}") - end - result - end - end - - module GeneratorMethods - module Object - # Converts this object to a string (calling #to_s), converts - # it to a JSON string, and returns the result. This is a fallback, if no - # special method #to_json was defined for some object. - def to_json(*) to_s.to_json end - end - - module Hash - # Returns a JSON string containing a JSON object, that is unparsed from - # this Hash instance. - # _state_ is a JSON::State object, that can also be used to configure the - # produced JSON string output further. - # _depth_ is used to find out nesting depth, to indent accordingly. - def to_json(state = nil, depth = 0, *) - if state - state = JSON.state.from_state(state) - state.check_max_nesting(depth) - json_check_circular(state) { json_transform(state, depth) } - else - json_transform(state, depth) - end - end - - private - - def json_check_circular(state) - if state and state.check_circular? - state.seen?(self) and raise JSON::CircularDatastructure, - "circular data structures not supported!" - state.remember self - end - yield - ensure - state and state.forget self - end - - def json_shift(state, depth) - state and not state.object_nl.empty? or return '' - state.indent * depth - end - - def json_transform(state, depth) - delim = ',' - delim << state.object_nl if state - result = '{' - result << state.object_nl if state - result << map { |key,value| - s = json_shift(state, depth + 1) - s << key.to_s.to_json(state, depth + 1) - s << state.space_before if state - s << ':' - s << state.space if state - s << value.to_json(state, depth + 1) - }.join(delim) - result << state.object_nl if state - result << json_shift(state, depth) - result << '}' - result - end - end - - module Array - # Returns a JSON string containing a JSON array, that is unparsed from - # this Array instance. - # _state_ is a JSON::State object, that can also be used to configure the - # produced JSON string output further. - # _depth_ is used to find out nesting depth, to indent accordingly. - def to_json(state = nil, depth = 0, *) - if state - state = JSON.state.from_state(state) - state.check_max_nesting(depth) - json_check_circular(state) { json_transform(state, depth) } - else - json_transform(state, depth) - end - end - - private - - def json_check_circular(state) - if state and state.check_circular? - state.seen?(self) and raise JSON::CircularDatastructure, - "circular data structures not supported!" - state.remember self - end - yield - ensure - state and state.forget self - end - - def json_shift(state, depth) - state and not state.array_nl.empty? or return '' - state.indent * depth - end - - def json_transform(state, depth) - delim = ',' - delim << state.array_nl if state - result = '[' - result << state.array_nl if state - result << map { |value| - json_shift(state, depth + 1) << value.to_json(state, depth + 1) - }.join(delim) - result << state.array_nl if state - result << json_shift(state, depth) - result << ']' - result - end - end - - module Integer - # Returns a JSON string representation for this Integer number. - def to_json(*) to_s end - end - - module Float - # Returns a JSON string representation for this Float number. - def to_json(state = nil, *) - case - when infinite? - if !state || state.allow_nan? - to_s - else - raise GeneratorError, "#{self} not allowed in JSON" - end - when nan? - if !state || state.allow_nan? - to_s - else - raise GeneratorError, "#{self} not allowed in JSON" - end - else - to_s - end - end - end - - module String - # This string should be encoded with UTF-8 A call to this method - # returns a JSON string encoded with UTF16 big endian characters as - # \u????. - def to_json(*) - '"' << JSON.utf8_to_json(self) << '"' - end - - # Module that holds the extinding methods if, the String module is - # included. - module Extend - # Raw Strings are JSON Objects (the raw bytes are stored in an array for the - # key "raw"). The Ruby String can be created by this module method. - def json_create(o) - o['raw'].pack('C*') - end - end - - # Extends _modul_ with the String::Extend module. - def self.included(modul) - modul.extend Extend - end - - # This method creates a raw object hash, that can be nested into - # other data structures and will be unparsed as a raw string. This - # method should be used, if you want to convert raw strings to JSON - # instead of UTF-8 strings, e. g. binary data. - def to_json_raw_object - { - JSON.create_id => self.class.name, - 'raw' => self.unpack('C*'), - } - end - - # This method creates a JSON text from the result of - # a call to to_json_raw_object of this String. - def to_json_raw(*args) - to_json_raw_object.to_json(*args) - end - end - - module TrueClass - # Returns a JSON string for true: 'true'. - def to_json(*) 'true' end - end - - module FalseClass - # Returns a JSON string for false: 'false'. - def to_json(*) 'false' end - end - - module NilClass - # Returns a JSON string for nil: 'null'. - def to_json(*) 'null' end - end - end - end - end -end diff --git a/lib/json/pure/parser.rb b/lib/json/pure/parser.rb deleted file mode 100644 index e886ba8d2c..0000000000 --- a/lib/json/pure/parser.rb +++ /dev/null @@ -1,259 +0,0 @@ -require 'strscan' - -module JSON - module Pure - # This class implements the JSON parser that is used to parse a JSON string - # into a Ruby data structure. - class Parser < StringScanner - STRING = /" ((?:[^\x0-\x1f"\\] | - \\["\\\/bfnrt] | - \\u[0-9a-fA-F]{4} | - \\[\x20-\xff])*) - "/nx - INTEGER = /(-?0|-?[1-9]\d*)/ - FLOAT = /(-? - (?:0|[1-9]\d*) - (?: - \.\d+(?i:e[+-]?\d+) | - \.\d+ | - (?i:e[+-]?\d+) - ) - )/x - NAN = /NaN/ - INFINITY = /Infinity/ - MINUS_INFINITY = /-Infinity/ - OBJECT_OPEN = /\{/ - OBJECT_CLOSE = /\}/ - ARRAY_OPEN = /\[/ - ARRAY_CLOSE = /\]/ - PAIR_DELIMITER = /:/ - COLLECTION_DELIMITER = /,/ - TRUE = /true/ - FALSE = /false/ - NULL = /null/ - IGNORE = %r( - (?: - //[^\n\r]*[\n\r]| # line comments - /\* # c-style comments - (?: - [^*/]| # normal chars - /[^*]| # slashes that do not start a nested comment - \*[^/]| # asterisks that do not end this comment - /(?=\*/) # single slash before this comment's end - )* - \*/ # the End of this comment - |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr - )+ - )mx - - UNPARSED = Object.new - - # Creates a new JSON::Pure::Parser instance for the string _source_. - # - # It will be configured by the _opts_ hash. _opts_ can have the following - # keys: - # * *max_nesting*: The maximum depth of nesting allowed in the parsed data - # structures. Disable depth checking with :max_nesting => false|nil|0, - # it defaults to 19. - # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in - # defiance of RFC 4627 to be parsed by the Parser. This option defaults - # to false. - # * *create_additions*: If set to false, the Parser doesn't create - # additions even if a matchin class and create_id was found. This option - # defaults to true. - def initialize(source, opts = {}) - super - if !opts.key?(:max_nesting) # defaults to 19 - @max_nesting = 19 - elsif opts[:max_nesting] - @max_nesting = opts[:max_nesting] - else - @max_nesting = 0 - end - @allow_nan = !!opts[:allow_nan] - ca = true - ca = opts[:create_additions] if opts.key?(:create_additions) - @create_id = ca ? JSON.create_id : nil - end - - alias source string - - # Parses the current JSON string _source_ and returns the complete data - # structure as a result. - def parse - reset - obj = nil - until eos? - case - when scan(OBJECT_OPEN) - obj and raise ParserError, "source '#{peek(20)}' not in JSON!" - @current_nesting = 1 - obj = parse_object - when scan(ARRAY_OPEN) - obj and raise ParserError, "source '#{peek(20)}' not in JSON!" - @current_nesting = 1 - obj = parse_array - when skip(IGNORE) - ; - else - raise ParserError, "source '#{peek(20)}' not in JSON!" - end - end - obj or raise ParserError, "source did not contain any JSON!" - obj - end - - private - - # Unescape characters in strings. - UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr } - UNESCAPE_MAP.update({ - ?" => '"', - ?\\ => '\\', - ?/ => '/', - ?b => "\b", - ?f => "\f", - ?n => "\n", - ?r => "\r", - ?t => "\t", - ?u => nil, - }) - - def parse_string - if scan(STRING) - return '' if self[1].empty? - self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c| - if u = UNESCAPE_MAP[c[1]] - u - else # \uXXXX - bytes = '' - i = 0 - while c[6 * i] == ?\\ && c[6 * i + 1] == ?u - bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16) - i += 1 - end - JSON::UTF16toUTF8.iconv(bytes) - end - end - else - UNPARSED - end - rescue Iconv::Failure => e - raise GeneratorError, "Caught #{e.class}: #{e}" - end - - def parse_value - case - when scan(FLOAT) - Float(self[1]) - when scan(INTEGER) - Integer(self[1]) - when scan(TRUE) - true - when scan(FALSE) - false - when scan(NULL) - nil - when (string = parse_string) != UNPARSED - string - when scan(ARRAY_OPEN) - @current_nesting += 1 - ary = parse_array - @current_nesting -= 1 - ary - when scan(OBJECT_OPEN) - @current_nesting += 1 - obj = parse_object - @current_nesting -= 1 - obj - when @allow_nan && scan(NAN) - NaN - when @allow_nan && scan(INFINITY) - Infinity - when @allow_nan && scan(MINUS_INFINITY) - MinusInfinity - else - UNPARSED - end - end - - def parse_array - raise NestingError, "nesting of #@current_nesting is to deep" if - @max_nesting.nonzero? && @current_nesting > @max_nesting - result = [] - delim = false - until eos? - case - when (value = parse_value) != UNPARSED - delim = false - result << value - skip(IGNORE) - if scan(COLLECTION_DELIMITER) - delim = true - elsif match?(ARRAY_CLOSE) - ; - else - raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!" - end - when scan(ARRAY_CLOSE) - if delim - raise ParserError, "expected next element in array at '#{peek(20)}'!" - end - break - when skip(IGNORE) - ; - else - raise ParserError, "unexpected token in array at '#{peek(20)}'!" - end - end - result - end - - def parse_object - raise NestingError, "nesting of #@current_nesting is to deep" if - @max_nesting.nonzero? && @current_nesting > @max_nesting - result = {} - delim = false - until eos? - case - when (string = parse_string) != UNPARSED - skip(IGNORE) - unless scan(PAIR_DELIMITER) - raise ParserError, "expected ':' in object at '#{peek(20)}'!" - end - skip(IGNORE) - unless (value = parse_value).equal? UNPARSED - result[string] = value - delim = false - skip(IGNORE) - if scan(COLLECTION_DELIMITER) - delim = true - elsif match?(OBJECT_CLOSE) - ; - else - raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!" - end - else - raise ParserError, "expected value in object at '#{peek(20)}'!" - end - when scan(OBJECT_CLOSE) - if delim - raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!" - end - if @create_id and klassname = result[@create_id] - klass = JSON.deep_const_get klassname - break unless klass and klass.json_creatable? - result = klass.json_create(result) - end - break - when skip(IGNORE) - ; - else - raise ParserError, "unexpected token in object at '#{peek(20)}'!" - end - end - result - end - end - end -end diff --git a/lib/json/version.rb b/lib/json/version.rb deleted file mode 100644 index 3d674ac44f..0000000000 --- a/lib/json/version.rb +++ /dev/null @@ -1,9 +0,0 @@ -module JSON - # JSON version - VERSION = '1.1.2' - VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc: - VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc: - VERSION_MINOR = VERSION_ARRAY[1] # :nodoc: - VERSION_BUILD = VERSION_ARRAY[2] # :nodoc: - VARIANT_BINARY = false -end -- cgit v1.2.3