summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/json.rb10
-rw-r--r--lib/json/Array.xpm21
-rw-r--r--lib/json/FalseClass.xpm21
-rw-r--r--lib/json/Hash.xpm21
-rw-r--r--lib/json/Key.xpm73
-rw-r--r--lib/json/NilClass.xpm21
-rw-r--r--lib/json/Numeric.xpm28
-rw-r--r--lib/json/String.xpm96
-rw-r--r--lib/json/TrueClass.xpm21
-rw-r--r--lib/json/add/core.rb135
-rw-r--r--lib/json/add/rails.rb58
-rw-r--r--lib/json/common.rb356
-rw-r--r--lib/json/editor.rb1371
-rw-r--r--lib/json/ext.rb15
-rw-r--r--lib/json/ext/.keep0
-rw-r--r--lib/json/json.xpm1499
-rw-r--r--lib/json/pure.rb77
-rw-r--r--lib/json/pure/generator.rb429
-rw-r--r--lib/json/pure/parser.rb269
-rw-r--r--lib/json/version.rb8
20 files changed, 4529 insertions, 0 deletions
diff --git a/lib/json.rb b/lib/json.rb
new file mode 100644
index 0000000..789b0de
--- /dev/null
+++ b/lib/json.rb
@@ -0,0 +1,10 @@
+require 'json/common'
+module JSON
+ require 'json/version'
+
+ begin
+ require 'json/ext'
+ rescue LoadError
+ require 'json/pure'
+ end
+end
diff --git a/lib/json/Array.xpm b/lib/json/Array.xpm
new file mode 100644
index 0000000..27c4801
--- /dev/null
+++ b/lib/json/Array.xpm
@@ -0,0 +1,21 @@
+/* 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
new file mode 100644
index 0000000..25ce608
--- /dev/null
+++ b/lib/json/FalseClass.xpm
@@ -0,0 +1,21 @@
+/* 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
new file mode 100644
index 0000000..cd8f6f7
--- /dev/null
+++ b/lib/json/Hash.xpm
@@ -0,0 +1,21 @@
+/* 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
new file mode 100644
index 0000000..9fd7281
--- /dev/null
+++ b/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/lib/json/NilClass.xpm b/lib/json/NilClass.xpm
new file mode 100644
index 0000000..3509f06
--- /dev/null
+++ b/lib/json/NilClass.xpm
@@ -0,0 +1,21 @@
+/* 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
new file mode 100644
index 0000000..e071e2e
--- /dev/null
+++ b/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/lib/json/String.xpm b/lib/json/String.xpm
new file mode 100644
index 0000000..f79a89c
--- /dev/null
+++ b/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 ",
+" <pq. rstu ",
+" .]v. wx= ",
+" .yzABCDE ",
+" .FGHIJ ",
+" 0KL0 ",
+" "};
diff --git a/lib/json/TrueClass.xpm b/lib/json/TrueClass.xpm
new file mode 100644
index 0000000..143eef4
--- /dev/null
+++ b/lib/json/TrueClass.xpm
@@ -0,0 +1,21 @@
+/* XPM */
+static char * TrueClass_xpm[] = {
+"16 16 2 1",
+" c None",
+". c #0BF311",
+" ",
+" ",
+" ",
+" ......... ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" ",
+" ",
+" "};
diff --git a/lib/json/add/core.rb b/lib/json/add/core.rb
new file mode 100644
index 0000000..4423e7a
--- /dev/null
+++ b/lib/json/add/core.rb
@@ -0,0 +1,135 @@
+# This file contains implementations of ruby core's custom objects for
+# serialisation/deserialisation.
+
+unless Object.const_defined?(:JSON) and ::JSON.const_defined?(:JSON_LOADED) and
+ ::JSON::JSON_LOADED
+ require 'json'
+end
+require 'date'
+
+class Time
+ def self.json_create(object)
+ if usec = object.delete('u') # used to be tv_usec -> tv_nsec
+ object['n'] = usec * 1000
+ end
+ if respond_to?(:tv_nsec)
+ at(*object.values_at('s', 'n'))
+ else
+ at(object['s'], object['n'] / 1000)
+ end
+ end
+
+ def to_json(*args)
+ {
+ 'json_class' => self.class.name,
+ 's' => tv_sec,
+ 'n' => respond_to?(:tv_nsec) ? tv_nsec : tv_usec * 1000
+ }.to_json(*args)
+ end
+end
+
+class Date
+ def self.json_create(object)
+ civil(*object.values_at('y', 'm', 'd', 'sg'))
+ end
+
+ alias start sg unless method_defined?(:start)
+
+ def to_json(*args)
+ {
+ 'json_class' => self.class.name,
+ 'y' => year,
+ 'm' => month,
+ 'd' => day,
+ 'sg' => start,
+ }.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('/')
+ if of_b and of_b != '0'
+ args << Rational(of_a.to_i, of_b.to_i)
+ else
+ args << of_a
+ end
+ args << object['sg']
+ civil(*args)
+ end
+
+ alias start sg unless method_defined?(:start)
+
+ def to_json(*args)
+ {
+ 'json_class' => self.class.name,
+ 'y' => year,
+ 'm' => month,
+ 'd' => day,
+ 'H' => hour,
+ 'M' => min,
+ 'S' => sec,
+ 'of' => offset.to_s,
+ 'sg' => start,
+ }.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,
+ '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
+ klass.to_s.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,
+ '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,
+ 'o' => options,
+ 's' => source,
+ }.to_json
+ end
+end
diff --git a/lib/json/add/rails.rb b/lib/json/add/rails.rb
new file mode 100644
index 0000000..e86ed1a
--- /dev/null
+++ b/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/lib/json/common.rb b/lib/json/common.rb
new file mode 100644
index 0000000..61548eb
--- /dev/null
+++ b/lib/json/common.rb
@@ -0,0 +1,356 @@
+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
+ private
+
+ # 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
diff --git a/lib/json/editor.rb b/lib/json/editor.rb
new file mode 100644
index 0000000..1e13f33
--- /dev/null
+++ b/lib/json/editor.rb
@@ -0,0 +1,1371 @@
+# 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 =
+ if value == 'Infinity'
+ value
+ else
+ (Integer(value) rescue Float(value) rescue 0).to_s
+ end
+ 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'
+ if (t = value_input.text) == 'Infinity'
+ 1 / 0.0
+ else
+ Integer(t) rescue Float(t) rescue 0
+ end
+ 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
diff --git a/lib/json/ext.rb b/lib/json/ext.rb
new file mode 100644
index 0000000..719e560
--- /dev/null
+++ b/lib/json/ext.rb
@@ -0,0 +1,15 @@
+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
+
+ JSON_LOADED = true
+end
diff --git a/lib/json/ext/.keep b/lib/json/ext/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/json/ext/.keep
diff --git a/lib/json/json.xpm b/lib/json/json.xpm
new file mode 100644
index 0000000..2cb626b
--- /dev/null
+++ b/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/lib/json/pure.rb b/lib/json/pure.rb
new file mode 100644
index 0000000..565ef0c
--- /dev/null
+++ b/lib/json/pure.rb
@@ -0,0 +1,77 @@
+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 LoadError
+ raise MissingUnicodeSupport,
+ "iconv couldn't be loaded, which is required for UTF-8/UTF-16 conversions"
+ 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
+ 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
+
+ JSON_LOADED = true
+end
diff --git a/lib/json/pure/generator.rb b/lib/json/pure/generator.rb
new file mode 100644
index 0000000..d51316e
--- /dev/null
+++ b/lib/json/pure/generator.rb
@@ -0,0 +1,429 @@
+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.
+ if String.method_defined?(:force_encoding)
+ def utf8_to_json(string) # :nodoc:
+ string = string.dup
+ string << '' # XXX workaround: avoid buffer sharing
+ string.force_encoding(Encoding::ASCII_8BIT)
+ string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
+ 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.force_encoding(Encoding::UTF_8)
+ string
+ rescue Iconv::Failure => e
+ raise GeneratorError, "Caught #{e.class}: #{e}"
+ end
+ else
+ def utf8_to_json(string) # :nodoc:
+ string = string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
+ 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
+ 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 = ','
+ if state
+ delim << state.object_nl
+ result = '{'
+ result << state.object_nl
+ result << map { |key,value|
+ s = json_shift(state, depth + 1)
+ s << key.to_s.to_json(state, depth + 1)
+ s << state.space_before
+ s << ':'
+ s << state.space
+ s << value.to_json(state, depth + 1)
+ }.join(delim)
+ result << state.object_nl
+ result << json_shift(state, depth)
+ result << '}'
+ else
+ result = '{'
+ result << map { |key,value|
+ key.to_s.to_json << ':' << value.to_json
+ }.join(delim)
+ result << '}'
+ end
+ 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 = ','
+ if state
+ delim << state.array_nl
+ result = '['
+ result << state.array_nl
+ result << map { |value|
+ json_shift(state, depth + 1) << value.to_json(state, depth + 1)
+ }.join(delim)
+ result << state.array_nl
+ result << json_shift(state, depth)
+ result << ']'
+ else
+ '[' << map { |value| value.to_json }.join(delim) << ']'
+ end
+ 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
new file mode 100644
index 0000000..7e8fe08
--- /dev/null
+++ b/lib/json/pure/parser.rb
@@ -0,0 +1,269 @@
+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"\\] |
+ # escaped special characters:
+ \\["\\\/bfnrt] |
+ \\u[0-9a-fA-F]{4} |
+ # match all but escaped special characters:
+ \\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\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.
+ # * *object_class*: Defaults to Hash
+ # * *array_class*: Defaults to Array
+ 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
+ @object_class = opts[:object_class] || Hash
+ @array_class = opts[:array_class] || Array
+ 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?
+ string = self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
+ if u = UNESCAPE_MAP[$&[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
+ if string.respond_to?(:force_encoding)
+ string.force_encoding(Encoding::UTF_8)
+ end
+ string
+ 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 too deep" if
+ @max_nesting.nonzero? && @current_nesting > @max_nesting
+ result = @array_class.new
+ 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 too deep" if
+ @max_nesting.nonzero? && @current_nesting > @max_nesting
+ result = @object_class.new
+ 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
new file mode 100644
index 0000000..28bea76
--- /dev/null
+++ b/lib/json/version.rb
@@ -0,0 +1,8 @@
+module JSON
+ # JSON version
+ VERSION = '1.1.8'
+ 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:
+end