serialbench 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/benchmark.yml +125 -0
- data/.github/workflows/ci.yml +74 -0
- data/.rspec +4 -0
- data/Gemfile +34 -0
- data/README.adoc +592 -0
- data/Rakefile +63 -0
- data/exe/serialbench +6 -0
- data/lib/serialbench/benchmark_runner.rb +540 -0
- data/lib/serialbench/chart_generator.rb +821 -0
- data/lib/serialbench/cli.rb +438 -0
- data/lib/serialbench/memory_profiler.rb +31 -0
- data/lib/serialbench/result_formatter.rb +182 -0
- data/lib/serialbench/result_merger.rb +1201 -0
- data/lib/serialbench/serializers/base_serializer.rb +63 -0
- data/lib/serialbench/serializers/json/base_json_serializer.rb +67 -0
- data/lib/serialbench/serializers/json/json_serializer.rb +58 -0
- data/lib/serialbench/serializers/json/oj_serializer.rb +102 -0
- data/lib/serialbench/serializers/json/yajl_serializer.rb +67 -0
- data/lib/serialbench/serializers/toml/base_toml_serializer.rb +76 -0
- data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +55 -0
- data/lib/serialbench/serializers/toml/tomlib_serializer.rb +50 -0
- data/lib/serialbench/serializers/xml/base_parser.rb +69 -0
- data/lib/serialbench/serializers/xml/base_xml_serializer.rb +71 -0
- data/lib/serialbench/serializers/xml/libxml_parser.rb +98 -0
- data/lib/serialbench/serializers/xml/libxml_serializer.rb +127 -0
- data/lib/serialbench/serializers/xml/nokogiri_parser.rb +111 -0
- data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +118 -0
- data/lib/serialbench/serializers/xml/oga_parser.rb +85 -0
- data/lib/serialbench/serializers/xml/oga_serializer.rb +125 -0
- data/lib/serialbench/serializers/xml/ox_parser.rb +64 -0
- data/lib/serialbench/serializers/xml/ox_serializer.rb +88 -0
- data/lib/serialbench/serializers/xml/rexml_parser.rb +129 -0
- data/lib/serialbench/serializers/xml/rexml_serializer.rb +121 -0
- data/lib/serialbench/serializers.rb +62 -0
- data/lib/serialbench/version.rb +5 -0
- data/lib/serialbench.rb +42 -0
- data/serialbench.gemspec +51 -0
- metadata +239 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Serialbench
|
4
|
+
module Serializers
|
5
|
+
class BaseSerializer
|
6
|
+
def initialize
|
7
|
+
# Override in subclasses
|
8
|
+
end
|
9
|
+
|
10
|
+
def available?
|
11
|
+
raise NotImplementedError, 'Subclasses must implement #available?'
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
raise NotImplementedError, 'Subclasses must implement #name'
|
16
|
+
end
|
17
|
+
|
18
|
+
def version
|
19
|
+
raise NotImplementedError, 'Subclasses must implement #version'
|
20
|
+
end
|
21
|
+
|
22
|
+
def format
|
23
|
+
raise NotImplementedError, 'Subclasses must implement #format'
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse(data)
|
27
|
+
raise NotImplementedError, 'Subclasses must implement #parse'
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate(object)
|
31
|
+
raise NotImplementedError, 'Subclasses must implement #generate'
|
32
|
+
end
|
33
|
+
|
34
|
+
def stream_parse(data, &block)
|
35
|
+
# Default implementation falls back to regular parsing
|
36
|
+
# Override in subclasses that support streaming
|
37
|
+
result = parse(data)
|
38
|
+
yield(:document, result) if block_given?
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def supports_streaming?
|
43
|
+
# Override in subclasses that support streaming
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def require_library(library_name)
|
50
|
+
require library_name
|
51
|
+
true
|
52
|
+
rescue LoadError
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_version(constant_path)
|
57
|
+
constant_path.split('::').reduce(Object) { |obj, const| obj.const_get(const) }
|
58
|
+
rescue NameError
|
59
|
+
'unknown'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Json
|
8
|
+
class BaseJsonSerializer < BaseSerializer
|
9
|
+
def format
|
10
|
+
:json
|
11
|
+
end
|
12
|
+
|
13
|
+
# JSON-specific methods
|
14
|
+
def parse_object(json_string)
|
15
|
+
parse(json_string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_json(object, options = {})
|
19
|
+
generate(object, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# JSON-specific features
|
23
|
+
def features
|
24
|
+
{
|
25
|
+
pretty_print: supports_pretty_print?,
|
26
|
+
streaming: supports_streaming?,
|
27
|
+
symbol_keys: supports_symbol_keys?,
|
28
|
+
custom_types: supports_custom_types?
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def supports_pretty_print?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def supports_symbol_keys?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def supports_custom_types?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Subclasses should override this to specify their library name
|
47
|
+
def library_require_name
|
48
|
+
raise NotImplementedError, 'Subclasses must implement #library_require_name'
|
49
|
+
end
|
50
|
+
|
51
|
+
public
|
52
|
+
|
53
|
+
# Check if the JSON library is available
|
54
|
+
def available?
|
55
|
+
return @available if defined?(@available)
|
56
|
+
|
57
|
+
@available = begin
|
58
|
+
require library_require_name
|
59
|
+
true
|
60
|
+
rescue LoadError
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_json_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Json
|
8
|
+
class JsonSerializer < BaseJsonSerializer
|
9
|
+
def available?
|
10
|
+
require_library('json')
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
'json'
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
require 'json'
|
19
|
+
JSON::VERSION
|
20
|
+
rescue LoadError, NameError
|
21
|
+
'built-in'
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse(json_string)
|
25
|
+
require 'json'
|
26
|
+
JSON.parse(json_string)
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate(object, options = {})
|
30
|
+
require 'json'
|
31
|
+
if options[:pretty]
|
32
|
+
JSON.pretty_generate(object)
|
33
|
+
else
|
34
|
+
JSON.generate(object)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def supports_streaming?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def supports_pretty_print?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def supports_symbol_keys?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
def supports_custom_types?
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_json_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Json
|
8
|
+
class OjSerializer < BaseJsonSerializer
|
9
|
+
def available?
|
10
|
+
require_library('oj')
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
'oj'
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
require 'oj'
|
19
|
+
Oj::VERSION
|
20
|
+
rescue LoadError, NameError
|
21
|
+
'unknown'
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse(json_string)
|
25
|
+
require 'oj'
|
26
|
+
Oj.load(json_string)
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate(object, options = {})
|
30
|
+
require 'oj'
|
31
|
+
if options[:pretty]
|
32
|
+
Oj.dump(object, indent: 2)
|
33
|
+
else
|
34
|
+
Oj.dump(object)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def stream_parse(json_string, &block)
|
39
|
+
require 'oj'
|
40
|
+
# Oj supports streaming through saj (Simple API for JSON)
|
41
|
+
handler = StreamHandler.new(&block)
|
42
|
+
Oj.saj_parse(handler, json_string)
|
43
|
+
handler.result
|
44
|
+
rescue LoadError, NoMethodError
|
45
|
+
# Fallback to regular parsing if streaming not available
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
def supports_streaming?
|
50
|
+
require 'oj'
|
51
|
+
Oj.respond_to?(:saj_parse)
|
52
|
+
rescue LoadError
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def supports_pretty_print?
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def supports_symbol_keys?
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def supports_custom_types?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Stream handler for Oj SAJ parsing
|
72
|
+
class StreamHandler
|
73
|
+
attr_reader :result
|
74
|
+
|
75
|
+
def initialize(&block)
|
76
|
+
@block = block
|
77
|
+
@result = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def hash_start(key)
|
81
|
+
@block&.call(:hash_start, key)
|
82
|
+
end
|
83
|
+
|
84
|
+
def hash_end(key)
|
85
|
+
@block&.call(:hash_end, key)
|
86
|
+
end
|
87
|
+
|
88
|
+
def array_start(key)
|
89
|
+
@block&.call(:array_start, key)
|
90
|
+
end
|
91
|
+
|
92
|
+
def array_end(key)
|
93
|
+
@block&.call(:array_end, key)
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_value(value, key)
|
97
|
+
@block&.call(:value, { key: key, value: value })
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_json_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Json
|
8
|
+
class YajlSerializer < BaseJsonSerializer
|
9
|
+
def name
|
10
|
+
'yajl'
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(json_string)
|
14
|
+
require 'yajl'
|
15
|
+
Yajl::Parser.parse(json_string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(data)
|
19
|
+
require 'yajl'
|
20
|
+
Yajl::Encoder.encode(data)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_streaming(json_string, &block)
|
24
|
+
require 'yajl'
|
25
|
+
|
26
|
+
parser = Yajl::Parser.new
|
27
|
+
parser.on_parse_complete = block if block
|
28
|
+
|
29
|
+
# Parse the JSON string
|
30
|
+
result = parser.parse(json_string)
|
31
|
+
|
32
|
+
# Return number of top-level objects processed
|
33
|
+
case result
|
34
|
+
when Array
|
35
|
+
result.length
|
36
|
+
when Hash
|
37
|
+
1
|
38
|
+
else
|
39
|
+
1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def supports_streaming?
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def version
|
48
|
+
return 'unknown' unless available?
|
49
|
+
|
50
|
+
require 'yajl'
|
51
|
+
# YAJL doesn't have a VERSION constant, try to get gem version
|
52
|
+
begin
|
53
|
+
Gem.loaded_specs['yajl-ruby']&.version&.to_s || 'unknown'
|
54
|
+
rescue StandardError
|
55
|
+
'unknown'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def library_require_name
|
62
|
+
'yajl'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Toml
|
8
|
+
class BaseTomlSerializer < BaseSerializer
|
9
|
+
def format
|
10
|
+
:toml
|
11
|
+
end
|
12
|
+
|
13
|
+
# TOML-specific methods
|
14
|
+
def parse_config(toml_string)
|
15
|
+
parse(toml_string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_toml(object, options = {})
|
19
|
+
generate(object, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# TOML-specific features
|
23
|
+
def features
|
24
|
+
{
|
25
|
+
comments: supports_comments?,
|
26
|
+
arrays_of_tables: supports_arrays_of_tables?,
|
27
|
+
inline_tables: supports_inline_tables?,
|
28
|
+
multiline_strings: supports_multiline_strings?
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def supports_streaming?
|
33
|
+
# TOML is typically not streamed due to its structure
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def supports_comments?
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def supports_arrays_of_tables?
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def supports_inline_tables?
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def supports_multiline_strings?
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
# Subclasses should override this to specify their library name
|
56
|
+
def library_require_name
|
57
|
+
raise NotImplementedError, 'Subclasses must implement #library_require_name'
|
58
|
+
end
|
59
|
+
|
60
|
+
public
|
61
|
+
|
62
|
+
# Check if the TOML library is available
|
63
|
+
def available?
|
64
|
+
return @available if defined?(@available)
|
65
|
+
|
66
|
+
@available = begin
|
67
|
+
require library_require_name
|
68
|
+
true
|
69
|
+
rescue LoadError
|
70
|
+
false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_toml_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Toml
|
8
|
+
class TomlRbSerializer < BaseTomlSerializer
|
9
|
+
def available?
|
10
|
+
require_library('toml-rb')
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
'toml-rb'
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
require 'toml-rb'
|
19
|
+
# toml-rb doesn't expose a VERSION constant, so we'll use gem version
|
20
|
+
Gem.loaded_specs['toml-rb']&.version&.to_s || 'unknown'
|
21
|
+
rescue LoadError, NameError
|
22
|
+
'unknown'
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse(toml_string)
|
26
|
+
require 'toml-rb'
|
27
|
+
TomlRB.parse(toml_string)
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate(object, options = {})
|
31
|
+
require 'toml-rb'
|
32
|
+
TomlRB.dump(object)
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def supports_comments?
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def supports_arrays_of_tables?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def supports_inline_tables?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def supports_multiline_strings?
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_toml_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Toml
|
8
|
+
class TomlibSerializer < BaseTomlSerializer
|
9
|
+
def name
|
10
|
+
'tomlib'
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(toml_string)
|
14
|
+
require 'tomlib'
|
15
|
+
Tomlib.load(toml_string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(data)
|
19
|
+
require 'tomlib'
|
20
|
+
Tomlib.dump(data)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_streaming(toml_string, &block)
|
24
|
+
# TOML doesn't typically support streaming parsing
|
25
|
+
# Parse the entire document and yield it
|
26
|
+
result = parse(toml_string)
|
27
|
+
block&.call(result) if block
|
28
|
+
1 # Return 1 document processed
|
29
|
+
end
|
30
|
+
|
31
|
+
def supports_streaming?
|
32
|
+
false # TOML is typically parsed as a whole document
|
33
|
+
end
|
34
|
+
|
35
|
+
def version
|
36
|
+
return 'unknown' unless available?
|
37
|
+
|
38
|
+
require 'tomlib'
|
39
|
+
Tomlib::VERSION
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def library_require_name
|
45
|
+
'tomlib'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Serialbench
|
4
|
+
module Parsers
|
5
|
+
class BaseParser
|
6
|
+
attr_reader :name, :version
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@name = self.class.name.split('::').last.gsub('Parser', '').downcase
|
10
|
+
@version = detect_version
|
11
|
+
end
|
12
|
+
|
13
|
+
# Parse XML string into document object
|
14
|
+
def parse_dom(xml_string)
|
15
|
+
raise NotImplementedError, 'Subclasses must implement parse_dom'
|
16
|
+
end
|
17
|
+
|
18
|
+
# Parse XML with SAX-style streaming
|
19
|
+
def parse_sax(xml_string, handler = nil)
|
20
|
+
raise NotImplementedError, 'Subclasses must implement parse_sax'
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generate XML string from document object
|
24
|
+
def generate_xml(document, options = {})
|
25
|
+
raise NotImplementedError, 'Subclasses must implement generate_xml'
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check if library is available
|
29
|
+
def available?
|
30
|
+
require library_require_name
|
31
|
+
true
|
32
|
+
rescue LoadError
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get library features
|
37
|
+
def features
|
38
|
+
{
|
39
|
+
xpath: supports_xpath?,
|
40
|
+
namespaces: supports_namespaces?,
|
41
|
+
validation: supports_validation?,
|
42
|
+
streaming: supports_streaming?
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def supports_streaming?
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def detect_version
|
53
|
+
'unknown'
|
54
|
+
end
|
55
|
+
|
56
|
+
def supports_xpath?
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
def supports_namespaces?
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def supports_validation?
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Xml
|
8
|
+
class BaseXmlSerializer < BaseSerializer
|
9
|
+
def format
|
10
|
+
:xml
|
11
|
+
end
|
12
|
+
|
13
|
+
# XML-specific methods
|
14
|
+
def parse_dom(xml_string)
|
15
|
+
parse(xml_string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_sax(xml_string, &block)
|
19
|
+
stream_parse(xml_string, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate_xml(document, options = {})
|
23
|
+
generate(document, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
# XML-specific features
|
27
|
+
def features
|
28
|
+
{
|
29
|
+
xpath: supports_xpath?,
|
30
|
+
namespaces: supports_namespaces?,
|
31
|
+
validation: supports_validation?,
|
32
|
+
streaming: supports_streaming?
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def supports_xpath?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def supports_namespaces?
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
def supports_validation?
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Subclasses should override this to specify their library name
|
51
|
+
def library_require_name
|
52
|
+
raise NotImplementedError, 'Subclasses must implement #library_require_name'
|
53
|
+
end
|
54
|
+
|
55
|
+
public
|
56
|
+
|
57
|
+
# Check if the XML library is available
|
58
|
+
def available?
|
59
|
+
return @available if defined?(@available)
|
60
|
+
|
61
|
+
@available = begin
|
62
|
+
require library_require_name
|
63
|
+
true
|
64
|
+
rescue LoadError
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|