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,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_parser'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Parsers
|
7
|
+
class LibxmlParser < BaseParser
|
8
|
+
def parse_dom(xml_string)
|
9
|
+
require 'libxml'
|
10
|
+
LibXML::XML::Document.string(xml_string)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_sax(xml_string, handler = nil)
|
14
|
+
require 'libxml'
|
15
|
+
handler ||= LibxmlSaxHandler.new
|
16
|
+
parser = LibXML::XML::SaxParser.string(xml_string)
|
17
|
+
parser.callbacks = handler
|
18
|
+
parser.parse
|
19
|
+
handler
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate_xml(document, options = {})
|
23
|
+
require 'libxml'
|
24
|
+
document.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def supports_streaming?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
# SAX handler for LibXML
|
32
|
+
class LibxmlSaxHandler
|
33
|
+
attr_reader :elements_processed, :text_nodes_processed
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@elements_processed = 0
|
37
|
+
@text_nodes_processed = 0
|
38
|
+
# Include LibXML callbacks if available
|
39
|
+
return unless defined?(::LibXML)
|
40
|
+
|
41
|
+
return if self.class.ancestors.include?(::LibXML::XML::SaxParser::Callbacks)
|
42
|
+
|
43
|
+
self.class.send(:include,
|
44
|
+
::LibXML::XML::SaxParser::Callbacks)
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_start_document
|
48
|
+
# Document processing started
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_end_document
|
52
|
+
# Document processing complete
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_start_element(element, attributes)
|
56
|
+
@elements_processed += 1
|
57
|
+
end
|
58
|
+
|
59
|
+
def on_characters(chars)
|
60
|
+
@text_nodes_processed += 1 unless chars.strip.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_end_element(element)
|
64
|
+
# Element processing complete
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_error(msg)
|
68
|
+
# Handle parsing errors
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
def library_require_name
|
75
|
+
'libxml'
|
76
|
+
end
|
77
|
+
|
78
|
+
def detect_version
|
79
|
+
require 'libxml'
|
80
|
+
LibXML::XML::VERSION
|
81
|
+
rescue LoadError
|
82
|
+
'not available'
|
83
|
+
end
|
84
|
+
|
85
|
+
def supports_xpath?
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
def supports_namespaces?
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
def supports_validation?
|
94
|
+
true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_xml_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Xml
|
8
|
+
class LibxmlSerializer < BaseXmlSerializer
|
9
|
+
def name
|
10
|
+
'libxml'
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(xml_string)
|
14
|
+
require 'libxml'
|
15
|
+
LibXML::XML::Parser.string(xml_string).parse
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(data)
|
19
|
+
require 'libxml'
|
20
|
+
# If data is already a LibXML document, convert to string
|
21
|
+
if data.respond_to?(:to_s) && data.class.name.include?('LibXML')
|
22
|
+
data.to_s(indent: true)
|
23
|
+
else
|
24
|
+
# Create a simple XML structure from hash-like data
|
25
|
+
doc = LibXML::XML::Document.new
|
26
|
+
doc.root = build_xml_from_data(data)
|
27
|
+
doc.to_s(indent: true)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_streaming(xml_string, &block)
|
32
|
+
require 'libxml'
|
33
|
+
|
34
|
+
handler = StreamingHandler.new(&block)
|
35
|
+
parser = LibXML::XML::SaxParser.string(xml_string)
|
36
|
+
parser.callbacks = handler
|
37
|
+
parser.parse
|
38
|
+
handler.elements_processed
|
39
|
+
end
|
40
|
+
|
41
|
+
def supports_streaming?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def version
|
46
|
+
return 'unknown' unless available?
|
47
|
+
|
48
|
+
require 'libxml'
|
49
|
+
LibXML::XML::VERSION
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def library_require_name
|
55
|
+
'libxml'
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def build_xml_from_data(data, name = 'root')
|
61
|
+
require 'libxml'
|
62
|
+
|
63
|
+
case data
|
64
|
+
when Hash
|
65
|
+
element = LibXML::XML::Node.new(name.to_s)
|
66
|
+
data.each do |key, value|
|
67
|
+
child = build_xml_from_data(value, key.to_s)
|
68
|
+
element << child
|
69
|
+
end
|
70
|
+
element
|
71
|
+
when Array
|
72
|
+
element = LibXML::XML::Node.new(name.to_s)
|
73
|
+
data.each_with_index do |item, index|
|
74
|
+
child = build_xml_from_data(item, "item_#{index}")
|
75
|
+
element << child
|
76
|
+
end
|
77
|
+
element
|
78
|
+
else
|
79
|
+
element = LibXML::XML::Node.new(name.to_s)
|
80
|
+
element.content = data.to_s
|
81
|
+
element
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# SAX handler for streaming
|
86
|
+
class StreamingHandler
|
87
|
+
include LibXML::XML::SaxParser::Callbacks if defined?(LibXML::XML::SaxParser::Callbacks)
|
88
|
+
|
89
|
+
attr_reader :elements_processed
|
90
|
+
|
91
|
+
def initialize(&block)
|
92
|
+
@block = block
|
93
|
+
@elements_processed = 0
|
94
|
+
@current_element = nil
|
95
|
+
@element_stack = []
|
96
|
+
end
|
97
|
+
|
98
|
+
def on_start_element(element, attributes)
|
99
|
+
@elements_processed += 1
|
100
|
+
attrs = Hash[attributes || []]
|
101
|
+
@current_element = { name: element, attributes: attrs, children: [], text: '' }
|
102
|
+
@element_stack.push(@current_element)
|
103
|
+
end
|
104
|
+
|
105
|
+
def on_end_element(element)
|
106
|
+
element_data = @element_stack.pop
|
107
|
+
if @element_stack.empty?
|
108
|
+
@block&.call(element_data) if @block
|
109
|
+
else
|
110
|
+
@element_stack.last[:children] << element_data
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def on_characters(chars)
|
115
|
+
return if chars.strip.empty?
|
116
|
+
|
117
|
+
@element_stack.last[:text] += chars if @element_stack.any?
|
118
|
+
end
|
119
|
+
|
120
|
+
def on_cdata_block(cdata)
|
121
|
+
@element_stack.last[:text] += cdata if @element_stack.any?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_parser'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Parsers
|
7
|
+
class NokogiriParser < BaseParser
|
8
|
+
def parse_dom(xml_string)
|
9
|
+
require 'nokogiri'
|
10
|
+
Nokogiri::XML(xml_string)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_sax(xml_string, handler = nil)
|
14
|
+
require 'nokogiri'
|
15
|
+
handler ||= NokogiriSaxHandler.new
|
16
|
+
parser = Nokogiri::XML::SAX::Parser.new(handler)
|
17
|
+
parser.parse(xml_string)
|
18
|
+
handler
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate_xml(document, options = {})
|
22
|
+
require 'nokogiri'
|
23
|
+
indent = options.fetch(:indent, 0)
|
24
|
+
if indent > 0
|
25
|
+
document.to_xml(indent: indent)
|
26
|
+
else
|
27
|
+
document.to_xml
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def supports_streaming?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
# SAX handler for Nokogiri
|
36
|
+
class NokogiriSaxHandler < (defined?(::Nokogiri) ? ::Nokogiri::XML::SAX::Document : Object)
|
37
|
+
attr_reader :elements_processed, :text_nodes_processed
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@elements_processed = 0
|
41
|
+
@text_nodes_processed = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def start_document
|
45
|
+
# Document processing started
|
46
|
+
end
|
47
|
+
|
48
|
+
def end_document
|
49
|
+
# Document processing complete
|
50
|
+
end
|
51
|
+
|
52
|
+
def start_element(name, attrs = [])
|
53
|
+
@elements_processed += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def start_element_namespace(name, attrs = [], prefix = nil, uri = nil, ns = [])
|
57
|
+
@elements_processed += 1
|
58
|
+
end
|
59
|
+
|
60
|
+
def characters(string)
|
61
|
+
@text_nodes_processed += 1 unless string.strip.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def end_element(name)
|
65
|
+
# Element processing complete
|
66
|
+
end
|
67
|
+
|
68
|
+
def end_element_namespace(name, prefix = nil, uri = nil)
|
69
|
+
# Element processing complete
|
70
|
+
end
|
71
|
+
|
72
|
+
def error(message)
|
73
|
+
# Handle parsing errors
|
74
|
+
end
|
75
|
+
|
76
|
+
def warning(message)
|
77
|
+
# Handle parsing warnings
|
78
|
+
end
|
79
|
+
|
80
|
+
def xmldecl(version, encoding, standalone)
|
81
|
+
# Handle XML declaration
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def library_require_name
|
88
|
+
'nokogiri'
|
89
|
+
end
|
90
|
+
|
91
|
+
def detect_version
|
92
|
+
require 'nokogiri'
|
93
|
+
Nokogiri::VERSION
|
94
|
+
rescue LoadError
|
95
|
+
'not available'
|
96
|
+
end
|
97
|
+
|
98
|
+
def supports_xpath?
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def supports_namespaces?
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def supports_validation?
|
107
|
+
true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_xml_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Xml
|
8
|
+
class NokogiriSerializer < BaseXmlSerializer
|
9
|
+
def name
|
10
|
+
'nokogiri'
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(xml_string)
|
14
|
+
require 'nokogiri'
|
15
|
+
Nokogiri::XML(xml_string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(data)
|
19
|
+
require 'nokogiri'
|
20
|
+
# If data is already a Nokogiri document, convert to string
|
21
|
+
if data.respond_to?(:to_xml)
|
22
|
+
data.to_xml(indent: 2)
|
23
|
+
else
|
24
|
+
# Create a simple XML structure from hash-like data
|
25
|
+
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
26
|
+
build_xml_from_data(xml, data)
|
27
|
+
end
|
28
|
+
builder.to_xml
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_streaming(xml_string, &block)
|
33
|
+
require 'nokogiri'
|
34
|
+
|
35
|
+
handler = StreamingHandler.new(&block)
|
36
|
+
parser = Nokogiri::XML::SAX::Parser.new(handler)
|
37
|
+
parser.parse(xml_string)
|
38
|
+
handler.elements_processed
|
39
|
+
end
|
40
|
+
|
41
|
+
def supports_streaming?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def version
|
46
|
+
return 'unknown' unless available?
|
47
|
+
|
48
|
+
require 'nokogiri'
|
49
|
+
Nokogiri::VERSION
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def library_require_name
|
55
|
+
'nokogiri'
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def build_xml_from_data(xml, data, root_name = 'root')
|
61
|
+
case data
|
62
|
+
when Hash
|
63
|
+
xml.send(root_name) do
|
64
|
+
data.each do |key, value|
|
65
|
+
build_xml_from_data(xml, value, key)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
when Array
|
69
|
+
data.each_with_index do |item, index|
|
70
|
+
build_xml_from_data(xml, item, "item_#{index}")
|
71
|
+
end
|
72
|
+
else
|
73
|
+
xml.send(root_name, data.to_s)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# SAX handler for streaming
|
78
|
+
class StreamingHandler
|
79
|
+
attr_reader :elements_processed
|
80
|
+
|
81
|
+
def initialize(&block)
|
82
|
+
require 'nokogiri'
|
83
|
+
@block = block
|
84
|
+
@elements_processed = 0
|
85
|
+
@current_element = nil
|
86
|
+
@element_stack = []
|
87
|
+
end
|
88
|
+
|
89
|
+
def start_element(name, attributes = [])
|
90
|
+
@elements_processed += 1
|
91
|
+
attrs = Hash[attributes]
|
92
|
+
@current_element = { name: name, attributes: attrs, children: [], text: '' }
|
93
|
+
@element_stack.push(@current_element)
|
94
|
+
end
|
95
|
+
|
96
|
+
def end_element(name)
|
97
|
+
element = @element_stack.pop
|
98
|
+
if @element_stack.empty?
|
99
|
+
@block&.call(element) if @block
|
100
|
+
else
|
101
|
+
@element_stack.last[:children] << element
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def characters(string)
|
106
|
+
return if string.strip.empty?
|
107
|
+
|
108
|
+
@element_stack.last[:text] += string if @element_stack.any?
|
109
|
+
end
|
110
|
+
|
111
|
+
def cdata_block(string)
|
112
|
+
@element_stack.last[:text] += string if @element_stack.any?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_parser'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Parsers
|
7
|
+
class OgaParser < BaseParser
|
8
|
+
def parse_dom(xml_string)
|
9
|
+
require 'oga'
|
10
|
+
Oga.parse_xml(xml_string)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_sax(xml_string, handler = nil)
|
14
|
+
require 'oga'
|
15
|
+
handler ||= DefaultSaxHandler.new
|
16
|
+
Oga.sax_parse_xml(xml_string, handler)
|
17
|
+
handler
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_xml(document, options = {})
|
21
|
+
require 'oga'
|
22
|
+
document.to_xml
|
23
|
+
end
|
24
|
+
|
25
|
+
def supports_streaming?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def library_require_name
|
32
|
+
'oga'
|
33
|
+
end
|
34
|
+
|
35
|
+
def detect_version
|
36
|
+
require 'oga'
|
37
|
+
Oga::VERSION
|
38
|
+
rescue LoadError
|
39
|
+
'not available'
|
40
|
+
end
|
41
|
+
|
42
|
+
def supports_xpath?
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
def supports_namespaces?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def supports_validation?
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Default SAX handler for Oga
|
55
|
+
class DefaultSaxHandler
|
56
|
+
attr_reader :elements_processed, :text_nodes_processed
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
@elements_processed = 0
|
60
|
+
@text_nodes_processed = 0
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_element(namespace, name, attributes = {})
|
64
|
+
@elements_processed += 1
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_text(text)
|
68
|
+
@text_nodes_processed += 1 unless text.strip.empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
def on_cdata(text)
|
72
|
+
@text_nodes_processed += 1 unless text.strip.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_comment(text)
|
76
|
+
# Handle comments
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_processing_instruction(name, text)
|
80
|
+
# Handle processing instructions
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_xml_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Xml
|
8
|
+
class OgaSerializer < BaseXmlSerializer
|
9
|
+
def name
|
10
|
+
'oga'
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(xml_string)
|
14
|
+
require 'oga'
|
15
|
+
Oga.parse_xml(xml_string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(data)
|
19
|
+
require 'oga'
|
20
|
+
# If data is already an Oga document, convert to string
|
21
|
+
if data.respond_to?(:to_xml)
|
22
|
+
data.to_xml
|
23
|
+
else
|
24
|
+
# Create a simple XML structure from hash-like data
|
25
|
+
document = Oga::XML::Document.new
|
26
|
+
root = build_xml_from_data(data)
|
27
|
+
document.children << root
|
28
|
+
document.to_xml
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_streaming(xml_string, &block)
|
33
|
+
require 'oga'
|
34
|
+
|
35
|
+
handler = StreamingHandler.new(&block)
|
36
|
+
parser = Oga::XML::SaxParser.new(handler, xml_string)
|
37
|
+
parser.parse
|
38
|
+
handler.elements_processed
|
39
|
+
end
|
40
|
+
|
41
|
+
def supports_streaming?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def version
|
46
|
+
return 'unknown' unless available?
|
47
|
+
|
48
|
+
require 'oga'
|
49
|
+
Oga::VERSION
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def library_require_name
|
55
|
+
'oga'
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def build_xml_from_data(data, name = 'root')
|
61
|
+
require 'oga'
|
62
|
+
|
63
|
+
case data
|
64
|
+
when Hash
|
65
|
+
element = Oga::XML::Element.new(name: name)
|
66
|
+
data.each do |key, value|
|
67
|
+
child = build_xml_from_data(value, key.to_s)
|
68
|
+
element.children << child
|
69
|
+
end
|
70
|
+
element
|
71
|
+
when Array
|
72
|
+
element = Oga::XML::Element.new(name: name)
|
73
|
+
data.each_with_index do |item, index|
|
74
|
+
child = build_xml_from_data(item, "item_#{index}")
|
75
|
+
element.children << child
|
76
|
+
end
|
77
|
+
element
|
78
|
+
else
|
79
|
+
element = Oga::XML::Element.new(name: name)
|
80
|
+
text_node = Oga::XML::Text.new(text: data.to_s)
|
81
|
+
element.children << text_node
|
82
|
+
element
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# SAX handler for streaming
|
87
|
+
class StreamingHandler
|
88
|
+
attr_reader :elements_processed
|
89
|
+
|
90
|
+
def initialize(&block)
|
91
|
+
@block = block
|
92
|
+
@elements_processed = 0
|
93
|
+
@current_element = nil
|
94
|
+
@element_stack = []
|
95
|
+
end
|
96
|
+
|
97
|
+
def on_element(namespace, name, attributes = {})
|
98
|
+
@elements_processed += 1
|
99
|
+
@current_element = { name: name, namespace: namespace, attributes: attributes, children: [], text: '' }
|
100
|
+
@element_stack.push(@current_element)
|
101
|
+
end
|
102
|
+
|
103
|
+
def on_text(text)
|
104
|
+
return if text.strip.empty?
|
105
|
+
|
106
|
+
@element_stack.last[:text] += text if @element_stack.any?
|
107
|
+
end
|
108
|
+
|
109
|
+
def on_cdata(text)
|
110
|
+
@element_stack.last[:text] += text if @element_stack.any?
|
111
|
+
end
|
112
|
+
|
113
|
+
def after_element(namespace, name)
|
114
|
+
element = @element_stack.pop
|
115
|
+
if @element_stack.empty?
|
116
|
+
@block&.call(element) if @block
|
117
|
+
else
|
118
|
+
@element_stack.last[:children] << element
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|