psych 1.3.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +138 -0
  3. data/Manifest.txt +27 -8
  4. data/README.rdoc +23 -2
  5. data/Rakefile +1 -1
  6. data/ext/psych/depend +3 -0
  7. data/ext/psych/extconf.rb +27 -11
  8. data/ext/psych/psych.h +4 -4
  9. data/ext/psych/{emitter.c → psych_emitter.c} +0 -0
  10. data/ext/psych/{emitter.h → psych_emitter.h} +0 -0
  11. data/ext/psych/{parser.c → psych_parser.c} +1 -1
  12. data/ext/psych/{parser.h → psych_parser.h} +0 -0
  13. data/ext/psych/{to_ruby.c → psych_to_ruby.c} +3 -1
  14. data/ext/psych/{to_ruby.h → psych_to_ruby.h} +0 -0
  15. data/ext/psych/{yaml_tree.c → psych_yaml_tree.c} +0 -0
  16. data/ext/psych/{yaml_tree.h → psych_yaml_tree.h} +0 -0
  17. data/ext/psych/yaml/LICENSE +19 -0
  18. data/ext/psych/yaml/api.c +1392 -0
  19. data/ext/psych/yaml/config.h +11 -0
  20. data/ext/psych/yaml/dumper.c +394 -0
  21. data/ext/psych/yaml/emitter.c +2329 -0
  22. data/ext/psych/yaml/loader.c +432 -0
  23. data/ext/psych/yaml/parser.c +1374 -0
  24. data/ext/psych/yaml/reader.c +465 -0
  25. data/ext/psych/yaml/scanner.c +3570 -0
  26. data/ext/psych/yaml/writer.c +141 -0
  27. data/ext/psych/yaml/yaml.h +1971 -0
  28. data/ext/psych/yaml/yaml_private.h +643 -0
  29. data/lib/psych.rb +217 -51
  30. data/lib/psych/class_loader.rb +101 -0
  31. data/lib/psych/core_ext.rb +1 -8
  32. data/lib/psych/deprecated.rb +3 -1
  33. data/lib/psych/exception.rb +13 -0
  34. data/lib/psych/handler.rb +13 -0
  35. data/lib/psych/handlers/recorder.rb +39 -0
  36. data/lib/psych/json/stream.rb +1 -0
  37. data/lib/psych/nodes/node.rb +3 -1
  38. data/lib/psych/scalar_scanner.rb +46 -25
  39. data/lib/psych/stream.rb +1 -0
  40. data/lib/psych/streaming.rb +10 -5
  41. data/lib/psych/syntax_error.rb +3 -1
  42. data/lib/psych/visitors/json_tree.rb +5 -2
  43. data/lib/psych/visitors/to_ruby.rb +123 -75
  44. data/lib/psych/visitors/yaml_tree.rb +59 -17
  45. data/lib/psych/y.rb +9 -0
  46. data/test/psych/handlers/test_recorder.rb +25 -0
  47. data/test/psych/helper.rb +30 -1
  48. data/test/psych/test_alias_and_anchor.rb +1 -1
  49. data/test/psych/test_array.rb +1 -1
  50. data/test/psych/test_boolean.rb +1 -1
  51. data/test/psych/test_class.rb +1 -1
  52. data/test/psych/test_coder.rb +3 -3
  53. data/test/psych/test_date_time.rb +1 -1
  54. data/test/psych/test_deprecated.rb +6 -2
  55. data/test/psych/test_document.rb +1 -1
  56. data/test/psych/test_emitter.rb +1 -1
  57. data/test/psych/test_encoding.rb +51 -65
  58. data/test/psych/test_engine_manager.rb +1 -11
  59. data/test/psych/test_exception.rb +40 -19
  60. data/test/psych/test_hash.rb +1 -1
  61. data/test/psych/test_json_tree.rb +1 -1
  62. data/test/psych/test_merge_keys.rb +52 -1
  63. data/test/psych/test_nil.rb +1 -1
  64. data/test/psych/test_null.rb +1 -1
  65. data/test/psych/test_numeric.rb +21 -1
  66. data/test/psych/test_object.rb +1 -1
  67. data/test/psych/test_object_references.rb +3 -3
  68. data/test/psych/test_omap.rb +1 -1
  69. data/test/psych/test_parser.rb +1 -1
  70. data/test/psych/test_psych.rb +15 -15
  71. data/test/psych/test_safe_load.rb +97 -0
  72. data/test/psych/test_scalar.rb +1 -1
  73. data/test/psych/test_scalar_scanner.rb +17 -2
  74. data/test/psych/test_serialize_subclasses.rb +1 -1
  75. data/test/psych/test_set.rb +1 -1
  76. data/test/psych/test_stream.rb +1 -1
  77. data/test/psych/test_string.rb +51 -3
  78. data/test/psych/test_struct.rb +1 -1
  79. data/test/psych/test_symbol.rb +1 -1
  80. data/test/psych/test_tainted.rb +8 -8
  81. data/test/psych/test_to_yaml_properties.rb +1 -1
  82. data/test/psych/test_tree_builder.rb +1 -1
  83. data/test/psych/test_yaml.rb +22 -2
  84. data/test/psych/test_yamldbm.rb +1 -1
  85. data/test/psych/test_yamlstore.rb +1 -1
  86. data/test/psych/visitors/test_to_ruby.rb +5 -4
  87. data/test/psych/visitors/test_yaml_tree.rb +19 -1
  88. metadata +45 -34
@@ -18,10 +18,12 @@ require 'psych/handlers/document_stream'
18
18
  ###
19
19
  # = Overview
20
20
  #
21
- # Psych is a YAML parser and emitter. Psych leverages
22
- # libyaml[http://libyaml.org] for it's YAML parsing and emitting capabilities.
23
- # In addition to wrapping libyaml, Psych also knows how to serialize and
24
- # de-serialize most Ruby objects to and from the YAML format.
21
+ # Psych is a YAML parser and emitter.
22
+ # Psych leverages libyaml [Home page: http://pyyaml.org/wiki/LibYAML]
23
+ # or [Git repo: https://github.com/zerotao/libyaml] for its YAML parsing
24
+ # and emitting capabilities. In addition to wrapping libyaml, Psych also
25
+ # knows how to serialize and de-serialize most Ruby objects to and from
26
+ # the YAML format.
25
27
  #
26
28
  # = I NEED TO PARSE OR EMIT YAML RIGHT NOW!
27
29
  #
@@ -41,14 +43,72 @@ require 'psych/handlers/document_stream'
41
43
  # level, is an event based parser. Mid level is access to the raw YAML AST,
42
44
  # and at the highest level is the ability to unmarshal YAML to ruby objects.
43
45
  #
44
- # === Low level parsing
46
+ # == YAML Emitting
45
47
  #
46
- # The lowest level parser should be used when the YAML input is already known,
47
- # and the developer does not want to pay the price of building an AST or
48
- # automatic detection and conversion to ruby objects. See Psych::Parser for
49
- # more information on using the event based parser.
48
+ # Psych provides a range of interfaces ranging from low to high level for
49
+ # producing YAML documents. Very similar to the YAML parsing interfaces, Psych
50
+ # provides at the lowest level, an event based system, mid-level is building
51
+ # a YAML AST, and the highest level is converting a Ruby object straight to
52
+ # a YAML document.
53
+ #
54
+ # == High-level API
55
+ #
56
+ # === Parsing
57
+ #
58
+ # The high level YAML parser provided by Psych simply takes YAML as input and
59
+ # returns a Ruby data structure. For information on using the high level parser
60
+ # see Psych.load
61
+ #
62
+ # ==== Reading from a string
63
+ #
64
+ # Psych.load("--- a") # => 'a'
65
+ # Psych.load("---\n - a\n - b") # => ['a', 'b']
66
+ #
67
+ # ==== Reading from a file
68
+ #
69
+ # Psych.load_file("database.yml")
50
70
  #
51
- # === Mid level parsing
71
+ # ==== Exception handling
72
+ #
73
+ # begin
74
+ # # The second argument chnages only the exception contents
75
+ # Psych.parse("--- `", "file.txt")
76
+ # rescue Psych::SyntaxError => ex
77
+ # ex.file # => 'file.txt'
78
+ # ex.message # => "(file.txt): found character that cannot start any token"
79
+ # end
80
+ #
81
+ # === Emitting
82
+ #
83
+ # The high level emitter has the easiest interface. Psych simply takes a Ruby
84
+ # data structure and converts it to a YAML document. See Psych.dump for more
85
+ # information on dumping a Ruby data structure.
86
+ #
87
+ # ==== Writing to a string
88
+ #
89
+ # # Dump an array, get back a YAML string
90
+ # Psych.dump(['a', 'b']) # => "---\n- a\n- b\n"
91
+ #
92
+ # # Dump an array to an IO object
93
+ # Psych.dump(['a', 'b'], StringIO.new) # => #<StringIO:0x000001009d0890>
94
+ #
95
+ # # Dump an array with indentation set
96
+ # Psych.dump(['a', ['b']], :indentation => 3) # => "---\n- a\n- - b\n"
97
+ #
98
+ # # Dump an array to an IO with indentation set
99
+ # Psych.dump(['a', ['b']], StringIO.new, :indentation => 3)
100
+ #
101
+ # ==== Writing to a file
102
+ #
103
+ # Currently there is no direct API for dumping Ruby structure to file:
104
+ #
105
+ # File.open('database.yml', 'w') do |file|
106
+ # file.write(Psych.dump(['a', 'b']))
107
+ # end
108
+ #
109
+ # == Mid-level API
110
+ #
111
+ # === Parsing
52
112
  #
53
113
  # Psych provides access to an AST produced from parsing a YAML document. This
54
114
  # tree is built using the Psych::Parser and Psych::TreeBuilder. The AST can
@@ -56,28 +116,33 @@ require 'psych/handlers/document_stream'
56
116
  # Psych::Nodes, and Psych::Nodes::Node for more information on dealing with
57
117
  # YAML syntax trees.
58
118
  #
59
- # === High level parsing
119
+ # ==== Reading from a string
60
120
  #
61
- # The high level YAML parser provided by Psych simply takes YAML as input and
62
- # returns a Ruby data structure. For information on using the high level parser
63
- # see Psych.load
121
+ # # Returns Psych::Nodes::Stream
122
+ # Psych.parse_stream("---\n - a\n - b")
64
123
  #
65
- # == YAML Emitting
124
+ # # Returns Psych::Nodes::Document
125
+ # Psych.parse("---\n - a\n - b")
66
126
  #
67
- # Psych provides a range of interfaces ranging from low to high level for
68
- # producing YAML documents. Very similar to the YAML parsing interfaces, Psych
69
- # provides at the lowest level, an event based system, mid-level is building
70
- # a YAML AST, and the highest level is converting a Ruby object straight to
71
- # a YAML document.
127
+ # ==== Reading from a file
72
128
  #
73
- # === Low level emitting
129
+ # # Returns Psych::Nodes::Stream
130
+ # Psych.parse_stream(File.read('database.yml'))
74
131
  #
75
- # The lowest level emitter is an event based system. Events are sent to a
76
- # Psych::Emitter object. That object knows how to convert the events to a YAML
77
- # document. This interface should be used when document format is known in
78
- # advance or speed is a concern. See Psych::Emitter for more information.
132
+ # # Returns Psych::Nodes::Document
133
+ # Psych.parse_file('database.yml')
134
+ #
135
+ # ==== Exception handling
136
+ #
137
+ # begin
138
+ # # The second argument chnages only the exception contents
139
+ # Psych.parse("--- `", "file.txt")
140
+ # rescue Psych::SyntaxError => ex
141
+ # ex.file # => 'file.txt'
142
+ # ex.message # => "(file.txt): found character that cannot start any token"
143
+ # end
79
144
  #
80
- # === Mid level emitting
145
+ # === Emitting
81
146
  #
82
147
  # At the mid level is building an AST. This AST is exactly the same as the AST
83
148
  # used when parsing a YAML document. Users can build an AST by hand and the
@@ -85,25 +150,77 @@ require 'psych/handlers/document_stream'
85
150
  # Psych::Nodes::Node, and Psych::TreeBuilder for more information on building
86
151
  # a YAML AST.
87
152
  #
88
- # === High level emitting
153
+ # ==== Writing to a string
89
154
  #
90
- # The high level emitter has the easiest interface. Psych simply takes a Ruby
91
- # data structure and converts it to a YAML document. See Psych.dump for more
92
- # information on dumping a Ruby data structure.
155
+ # # We need Psych::Nodes::Stream (not Psych::Nodes::Document)
156
+ # stream = Psych.parse_stream("---\n - a\n - b")
157
+ #
158
+ # stream.to_yaml # => "---\n- a\n- b\n"
159
+ #
160
+ # ==== Writing to a file
161
+ #
162
+ # # We need Psych::Nodes::Stream (not Psych::Nodes::Document)
163
+ # stream = Psych.parse_stream(File.read('database.yml'))
164
+ #
165
+ # File.open('database.yml', 'w') do |file|
166
+ # file.write(stream.to_yaml)
167
+ # end
168
+ #
169
+ # == Low-level API
170
+ #
171
+ # === Parsing
172
+ #
173
+ # The lowest level parser should be used when the YAML input is already known,
174
+ # and the developer does not want to pay the price of building an AST or
175
+ # automatic detection and conversion to ruby objects. See Psych::Parser for
176
+ # more information on using the event based parser.
177
+ #
178
+ # ==== Reading to Psych::Nodes::Stream structure
179
+ #
180
+ # parser = Psych::Parser.new(TreeBuilder.new) # => #<Psych::Parser>
181
+ # parser = Psych.parser # it's an alias for the above
182
+ #
183
+ # parser.parse("---\n - a\n - b") # => #<Psych::Parser>
184
+ # parser.handler # => #<Psych::TreeBuilder>
185
+ # parser.handler.root # => #<Psych::Nodes::Stream>
186
+ #
187
+ # ==== Receiving an events stream
188
+ #
189
+ # parser = Psych::Parser.new(Psych::Handlers::Recorder.new)
190
+ #
191
+ # parser.parse("---\n - a\n - b")
192
+ # parser.events # => [list of [event, args] lists]
193
+ # # event is one of: Psych::Handler::EVENTS
194
+ # # args are the arguments passed to the event
195
+ #
196
+ # === Emitting
197
+ #
198
+ # The lowest level emitter is an event based system. Events are sent to a
199
+ # Psych::Emitter object. That object knows how to convert the events to a YAML
200
+ # document. This interface should be used when document format is known in
201
+ # advance or speed is a concern. See Psych::Emitter for more information.
202
+ #
203
+ # ==== Writing to a ruby structure
204
+ #
205
+ # Psych.parser.parse("--- a") # => #<Psych::Parser>
206
+ #
207
+ # parser.handler.first # => #<Psych::Nodes::Stream>
208
+ # parser.handler.first.to_ruby # => ["a"]
209
+ #
210
+ # parser.handler.root.first # => #<Psych::Nodes::Document>
211
+ # parser.handler.root.first.to_ruby # => "a"
212
+ #
213
+ # # You can instantiate an Emitter manually
214
+ # Psych::Visitors::ToRuby.new.accept(parser.handler.root.first)
215
+ # # => "a"
93
216
 
94
217
  module Psych
95
218
  # The version is Psych you're using
96
- VERSION = '1.3.4'
219
+ VERSION = '2.0.0'
97
220
 
98
221
  # The version of libyaml Psych is using
99
222
  LIBYAML_VERSION = Psych.libyaml_version.join '.'
100
223
 
101
- class Exception < RuntimeError
102
- end
103
-
104
- class BadAlias < Exception
105
- end
106
-
107
224
  ###
108
225
  # Load +yaml+ in to a Ruby data structure. If multiple documents are
109
226
  # provided, the object contained in the first document will be returned.
@@ -121,7 +238,7 @@ module Psych
121
238
  # Psych.load("--- `", "file.txt")
122
239
  # rescue Psych::SyntaxError => ex
123
240
  # ex.file # => 'file.txt'
124
- # ex.message # => "(foo.txt): found character that cannot start any token"
241
+ # ex.message # => "(file.txt): found character that cannot start any token"
125
242
  # end
126
243
  def self.load yaml, filename = nil
127
244
  result = parse(yaml, filename)
@@ -129,7 +246,56 @@ module Psych
129
246
  end
130
247
 
131
248
  ###
132
- # Parse a YAML string in +yaml+. Returns the first object of a YAML AST.
249
+ # Safely load the yaml string in +yaml+. By default, only the following
250
+ # classes are allowed to be deserialized:
251
+ #
252
+ # * TrueClass
253
+ # * FalseClass
254
+ # * NilClass
255
+ # * Numeric
256
+ # * String
257
+ # * Array
258
+ # * Hash
259
+ #
260
+ # Recursive data structures are not allowed by default. Arbitrary classes
261
+ # can be allowed by adding those classes to the +whitelist+. They are
262
+ # additive. For example, to allow Date deserialization:
263
+ #
264
+ # Psych.safe_load(yaml, [Date])
265
+ #
266
+ # Now the Date class can be loaded in addition to the classes listed above.
267
+ #
268
+ # Aliases can be explicitly allowed by changing the +aliases+ parameter.
269
+ # For example:
270
+ #
271
+ # x = []
272
+ # x << x
273
+ # yaml = Psych.dump x
274
+ # Psych.safe_load yaml # => raises an exception
275
+ # Psych.safe_load yaml, [], [], true # => loads the aliases
276
+ #
277
+ # A Psych::DisallowedClass exception will be raised if the yaml contains a
278
+ # class that isn't in the whitelist.
279
+ #
280
+ # A Psych::BadAlias exception will be raised if the yaml contains aliases
281
+ # but the +aliases+ parameter is set to false.
282
+ def self.safe_load yaml, whitelist_classes = [], whitelist_symbols = [], aliases = false, filename = nil
283
+ result = parse(yaml, filename)
284
+ return unless result
285
+
286
+ class_loader = ClassLoader::Restricted.new(whitelist_classes.map(&:to_s),
287
+ whitelist_symbols.map(&:to_s))
288
+ scanner = ScalarScanner.new class_loader
289
+ if aliases
290
+ visitor = Visitors::ToRuby.new scanner, class_loader
291
+ else
292
+ visitor = Visitors::NoAliasRuby.new scanner, class_loader
293
+ end
294
+ visitor.accept result
295
+ end
296
+
297
+ ###
298
+ # Parse a YAML string in +yaml+. Returns the Psych::Nodes::Document.
133
299
  # +filename+ is used in the exception message if a Psych::SyntaxError is
134
300
  # raised.
135
301
  #
@@ -137,13 +303,13 @@ module Psych
137
303
  #
138
304
  # Example:
139
305
  #
140
- # Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Sequence:0x00>
306
+ # Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Document:0x00>
141
307
  #
142
308
  # begin
143
309
  # Psych.parse("--- `", "file.txt")
144
310
  # rescue Psych::SyntaxError => ex
145
311
  # ex.file # => 'file.txt'
146
- # ex.message # => "(foo.txt): found character that cannot start any token"
312
+ # ex.message # => "(file.txt): found character that cannot start any token"
147
313
  # end
148
314
  #
149
315
  # See Psych::Nodes for more information about YAML AST.
@@ -155,7 +321,7 @@ module Psych
155
321
  end
156
322
 
157
323
  ###
158
- # Parse a file at +filename+. Returns the YAML AST.
324
+ # Parse a file at +filename+. Returns the Psych::Nodes::Document.
159
325
  #
160
326
  # Raises a Psych::SyntaxError when a YAML syntax error is detected.
161
327
  def self.parse_file filename
@@ -171,7 +337,7 @@ module Psych
171
337
  end
172
338
 
173
339
  ###
174
- # Parse a YAML string in +yaml+. Returns the full AST for the YAML document.
340
+ # Parse a YAML string in +yaml+. Returns the Psych::Nodes::Stream.
175
341
  # This method can handle multiple YAML documents contained in +yaml+.
176
342
  # +filename+ is used in the exception message if a Psych::SyntaxError is
177
343
  # raised.
@@ -193,7 +359,7 @@ module Psych
193
359
  # Psych.parse_stream("--- `", "file.txt")
194
360
  # rescue Psych::SyntaxError => ex
195
361
  # ex.file # => 'file.txt'
196
- # ex.message # => "(foo.txt): found character that cannot start any token"
362
+ # ex.message # => "(file.txt): found character that cannot start any token"
197
363
  # end
198
364
  #
199
365
  # See Psych::Nodes for more information about YAML AST.
@@ -238,7 +404,7 @@ module Psych
238
404
  io = nil
239
405
  end
240
406
 
241
- visitor = Psych::Visitors::YAMLTree.new options
407
+ visitor = Psych::Visitors::YAMLTree.create options
242
408
  visitor << o
243
409
  visitor.tree.yaml io, options
244
410
  end
@@ -250,7 +416,7 @@ module Psych
250
416
  #
251
417
  # Psych.dump_stream("foo\n ", {}) # => "--- ! \"foo\\n \"\n--- {}\n"
252
418
  def self.dump_stream *objects
253
- visitor = Psych::Visitors::YAMLTree.new {}
419
+ visitor = Psych::Visitors::YAMLTree.create({})
254
420
  objects.each do |o|
255
421
  visitor << o
256
422
  end
@@ -258,10 +424,10 @@ module Psych
258
424
  end
259
425
 
260
426
  ###
261
- # Dump Ruby object +o+ to a JSON string.
262
- def self.to_json o
263
- visitor = Psych::Visitors::JSONTree.new
264
- visitor << o
427
+ # Dump Ruby +object+ to a JSON string.
428
+ def self.to_json object
429
+ visitor = Psych::Visitors::JSONTree.create
430
+ visitor << object
265
431
  visitor.tree.yaml
266
432
  end
267
433
 
@@ -318,7 +484,7 @@ module Psych
318
484
  @load_tags = {}
319
485
  @dump_tags = {}
320
486
  def self.add_tag tag, klass
321
- @load_tags[tag] = klass
487
+ @load_tags[tag] = klass.name
322
488
  @dump_tags[klass] = tag
323
489
  end
324
490
 
@@ -0,0 +1,101 @@
1
+ require 'psych/omap'
2
+ require 'psych/set'
3
+
4
+ module Psych
5
+ class ClassLoader # :nodoc:
6
+ BIG_DECIMAL = 'BigDecimal'
7
+ COMPLEX = 'Complex'
8
+ DATE = 'Date'
9
+ DATE_TIME = 'DateTime'
10
+ EXCEPTION = 'Exception'
11
+ OBJECT = 'Object'
12
+ PSYCH_OMAP = 'Psych::Omap'
13
+ PSYCH_SET = 'Psych::Set'
14
+ RANGE = 'Range'
15
+ RATIONAL = 'Rational'
16
+ REGEXP = 'Regexp'
17
+ STRUCT = 'Struct'
18
+ SYMBOL = 'Symbol'
19
+
20
+ def initialize
21
+ @cache = CACHE.dup
22
+ end
23
+
24
+ def load klassname
25
+ return nil if !klassname || klassname.empty?
26
+
27
+ find klassname
28
+ end
29
+
30
+ def symbolize sym
31
+ symbol
32
+ sym.to_sym
33
+ end
34
+
35
+ constants.each do |const|
36
+ konst = const_get const
37
+ define_method(const.to_s.downcase) do
38
+ load konst
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def find klassname
45
+ @cache[klassname] ||= resolve(klassname)
46
+ end
47
+
48
+ def resolve klassname
49
+ name = klassname
50
+ retried = false
51
+
52
+ begin
53
+ path2class(name)
54
+ rescue ArgumentError, NameError => ex
55
+ unless retried
56
+ name = "Struct::#{name}"
57
+ retried = ex
58
+ retry
59
+ end
60
+ raise retried
61
+ end
62
+ end
63
+
64
+ CACHE = Hash[constants.map { |const|
65
+ val = const_get const
66
+ begin
67
+ [val, ::Object.const_get(val)]
68
+ rescue
69
+ nil
70
+ end
71
+ }.compact]
72
+
73
+ class Restricted < ClassLoader
74
+ def initialize classes, symbols
75
+ @classes = classes
76
+ @symbols = symbols
77
+ super()
78
+ end
79
+
80
+ def symbolize sym
81
+ return super if @symbols.empty?
82
+
83
+ if @symbols.include? sym
84
+ super
85
+ else
86
+ raise DisallowedClass, 'Symbol'
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def find klassname
93
+ if @classes.include? klassname
94
+ super
95
+ else
96
+ raise DisallowedClass, klassname
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end