serialbench 0.9.2 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99cdf688cd6a4bd4662e8f0182911ec87f6b2ecf184045c6514cee6959c09e2d
4
- data.tar.gz: 3e1ba2b330008ef51fe28c7fad6b0571bad06c491ca9c6a7e46634c558b20a23
3
+ metadata.gz: fb37afdfb65ed1d7226788c226fad30d1779b39578ad762bf097783fea638b08
4
+ data.tar.gz: 8d7a5f9ae73d6b2cd74b84d9861e4fbc9934c371960cc578d3bd0cef2798bc44
5
5
  SHA512:
6
- metadata.gz: 30fc0f72e78b350c78b918a6366793be940a35ab6b826e3a74c08547d11b0251f90d8be9ec4822a7b1c197656805e6c7e2142c483b123981c7e7e5ba64c1e030
7
- data.tar.gz: a44b14c1638c2c450b89d8f8a032041cdfb14eb1a1ccb57eb9a6b88d248f2f18d1be127095babef559f938918048b89b1c7d950cf9fb9f64eabcbfd29787de61
6
+ metadata.gz: 4a426f90212feb0b50c4a5b488f82bac91bbdfe30208cefc66390baa864ff217b92d551c38504d666dee1aa6f96b389cbdda65a35b91fac0cb34909ae5a280b9
7
+ data.tar.gz: 1202d30b4bfc472ea3a30f7b66f5eb0ac2874933411031b0add26ceb3f69dd1fd0d469df71a39665cfc8a53fb84dabf29c7f51811a6e121247d486ea2e235be8
@@ -29,12 +29,34 @@ CONTENT=$(base64 < "$RESULTS_FILE" | tr -d '\r\n')
29
29
 
30
30
  echo "Pushing ${TARGET_PATH} to serialbench/data..."
31
31
 
32
- # Try create; if file exists (422), get sha and update
33
- HTTP_CODE=$(curl -s -o /tmp/data-push-resp.json -w "%{http_code}" -X PUT \
34
- -H "Authorization: Bearer ${DATA_TOKEN}" \
35
- -H "Accept: application/vnd.github+json" \
36
- "https://api.github.com/repos/serialbench/data/contents/${TARGET_PATH}" \
37
- -d "{\"message\": \"${PLATFORM} ruby-${RUBY_VERSION} ${FMT}\", \"content\": \"${CONTENT}\", \"branch\": \"main\"}")
32
+ # Try create; 409 (branch race) and 422 (exists) both retry with fresh sha
33
+ put_file() {
34
+ local sha_arg=()
35
+ [ -n "${1:-}" ] && sha_arg=(-d "{\"message\": \"${PLATFORM} ruby-${RUBY_VERSION} ${FMT} (update)\", \"content\": \"${CONTENT}\", \"sha\": \"$1\", \"branch\": \"main\"}")
36
+ [ -n "${1:-}" ] || sha_arg=(-d "{\"message\": \"${PLATFORM} ruby-${RUBY_VERSION} ${FMT}\", \"content\": \"${CONTENT}\", \"branch\": \"main\"}")
37
+ curl -s -o /tmp/data-push-resp.json -w "%{http_code}" -X PUT \
38
+ -H "Authorization: Bearer ${DATA_TOKEN}" \
39
+ -H "Accept: application/vnd.github+json" \
40
+ "https://api.github.com/repos/serialbench/data/contents/${TARGET_PATH}" \
41
+ "${sha_arg[@]}"
42
+ }
43
+
44
+ HTTP_CODE=$(put_file "")
45
+ for TRY in 1 2 3 4 5; do
46
+ if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then break; fi
47
+ if [ "$HTTP_CODE" = "409" ] || [ "$HTTP_CODE" = "422" ]; then
48
+ SHA=$(curl -sf \
49
+ -H "Authorization: Bearer ${DATA_TOKEN}" \
50
+ "https://api.github.com/repos/serialbench/data/contents/${TARGET_PATH}" \
51
+ | grep -oE '"sha":\s*"[a-f0-9]+"' | head -1 | grep -oE '[a-f0-9]{40}')
52
+ [ -z "$SHA" ] && break
53
+ echo "attempt $TRY got $HTTP_CODE — refetching sha and retrying"
54
+ sleep $((TRY * 3))
55
+ HTTP_CODE=$(put_file "$SHA")
56
+ else
57
+ break
58
+ fi
59
+ done
38
60
 
39
61
  if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
40
62
  echo "Pushed ${TARGET_PATH}"
@@ -116,6 +116,12 @@ jobs:
116
116
  - name: Install gems
117
117
  run: bundle install
118
118
 
119
+ - name: Load canonical fixtures
120
+ run: |
121
+ git clone --depth 1 https://github.com/serialbench/fixtures.git test_data
122
+ (cd test_data && sha256sum -c SHA256SUMS) || (cd test_data && shasum -a 256 -c SHA256SUMS)
123
+ shell: bash
124
+
119
125
  - name: Point teptris at its vendored DLL (Windows)
120
126
  if: startsWith(matrix.platform, 'windows-')
121
127
  shell: bash
@@ -249,6 +255,41 @@ jobs:
249
255
  run: .github/scripts/push-to-data.sh toml ${{ matrix.ruby-version }} ${{ matrix.platform }}
250
256
  shell: bash
251
257
 
258
+ - name: Benchmark html
259
+ continue-on-error: true
260
+ shell: bash
261
+ env:
262
+ GITHUB_RUNNER_PLATFORM: ${{ matrix.platform }}
263
+ DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
264
+ DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
265
+ run: |
266
+ if [ "${{ github.run_attempt }}" -gt 1 ] && .github/scripts/format-status.sh html ${{ matrix.ruby-version }} ${{ matrix.platform }}; then
267
+ echo "html already in the data repo — skipping (gap-filling rerun)"
268
+ exit 0
269
+ fi
270
+ bundle exec serialbench environment execute \
271
+ config/environments/ci-ruby-${{ matrix.ruby-version }}.yml \
272
+ config/benchmarks/full-html.yml \
273
+ "results/runs/ci-ruby-${{ matrix.ruby-version }}-${{ matrix.platform }}/html"
274
+
275
+ - name: Upload html
276
+ if: ${{ !cancelled() }}
277
+ uses: actions/upload-artifact@v4
278
+ with:
279
+ name: benchmark-results-${{ matrix.platform }}-ruby-${{ matrix.ruby-version }}-html
280
+ path: results/runs/ci-ruby-${{ matrix.ruby-version }}-${{ matrix.platform }}/html
281
+ retention-days: 30
282
+ if-no-files-found: warn
283
+
284
+ - name: Push html to data repo
285
+ if: ${{ !cancelled() }}
286
+ continue-on-error: true
287
+ env:
288
+ DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
289
+ DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
290
+ run: .github/scripts/push-to-data.sh html ${{ matrix.ruby-version }} ${{ matrix.platform }}
291
+ shell: bash
292
+
252
293
  # Each format is an independent checkpoint with its own upload.
253
294
  # A failure in one format doesn't prevent the others from uploading.
254
295
  - name: Benchmark xml
@@ -296,7 +337,7 @@ jobs:
296
337
  DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
297
338
  run: |
298
339
  MISSING=""
299
- for FMT in json yaml toml xml; do
340
+ for FMT in json yaml toml html xml; do
300
341
  if .github/scripts/format-status.sh "$FMT" "${{ matrix.ruby-version }}" "${{ matrix.platform }}"; then
301
342
  echo "$FMT: in data repo"
302
343
  else
data/.gitignore CHANGED
@@ -30,3 +30,4 @@ results/
30
30
 
31
31
  # Ruby version manager files
32
32
  .ruby-version
33
+ test_data/
data/README.adoc CHANGED
@@ -1,9 +1,9 @@
1
1
  = Serialbench: Ruby serialization library performance benchmarker
2
2
 
3
3
  image:https://img.shields.io/gem/v/serialbench.svg["Gem Version", link="https://rubygems.org/gems/serialbench"]
4
- image:https://github.com/metanorma/serialbench/actions/workflows/ci.yml/badge.svg["Build Status", link="https://github.com/metanorma/serialbench/actions/workflows/ci.yml"]
5
- image:https://github.com/metanorma/serialbench/actions/workflows/benchmark.yml/badge.svg["Benchmark Status", link="https://github.com/metanorma/serialbench/actions/workflows/benchmark.yml"]
6
- image:https://img.shields.io/github/issues-pr-raw/metanorma/serialbench.svg["Pull Requests", link="https://github.com/metanorma/serialbench/pulls"]
4
+ image:https://github.com/serialbench/serialbench/actions/workflows/ci.yml/badge.svg["Build Status", link="https://github.com/serialbench/serialbench/actions/workflows/ci.yml"]
5
+ image:https://github.com/serialbench/serialbench/actions/workflows/benchmark.yml/badge.svg["Benchmark Status", link="https://github.com/serialbench/serialbench/actions/workflows/benchmark.yml"]
6
+ image:https://img.shields.io/github/issues-pr-raw/serialbench/serialbench.svg["Pull Requests", link="https://github.com/serialbench/serialbench/pulls"]
7
7
 
8
8
  == Overview
9
9
 
@@ -0,0 +1,26 @@
1
+ # Per-format split of full.yml: each format runs in its own process so
2
+ # memory profiling of large documents cannot compound across formats
3
+ # (windows runners OOMed with all four formats in one process).
4
+ name: full-html
5
+
6
+ data_sizes:
7
+ - small
8
+ - medium
9
+ - large
10
+
11
+ formats:
12
+ - html
13
+
14
+ iterations:
15
+ small: 10
16
+ medium: 3
17
+ large: 1
18
+
19
+ operations:
20
+ - parsing
21
+ - generation
22
+ - streaming
23
+ - xpath
24
+ - memory
25
+
26
+ warmup: 2
@@ -21,6 +21,9 @@ operations:
21
21
  - generation
22
22
  - streaming
23
23
  - xpath
24
+ - xquery
25
+ - xslt
26
+ - validation
24
27
  - memory
25
28
 
26
29
  warmup: 2
@@ -26,15 +26,140 @@ module Serialbench
26
26
  end
27
27
 
28
28
  # Each operation maps to a lambda. Adding an operation = one entry.
29
+ _DEFAULT_RNG_SCHEMA = <<'RNG'.freeze
30
+ <?xml version="1.0" encoding="UTF-8"?>
31
+ <grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
32
+ <start>
33
+ <choice>
34
+ <ref name="config"/>
35
+ <ref name="users"/>
36
+ <ref name="dataset"/>
37
+ </choice>
38
+ </start>
39
+
40
+ <define name="config">
41
+ <element name="config">
42
+ <element name="database">
43
+ <element name="host"><text/></element>
44
+ <element name="port"><data type="integer"/></element>
45
+ <element name="name"><text/></element>
46
+ <element name="user"><text/></element>
47
+ <element name="password"><text/></element>
48
+ </element>
49
+ <element name="cache">
50
+ <element name="enabled"><data type="boolean"/></element>
51
+ <element name="ttl"><data type="integer"/></element>
52
+ </element>
53
+ </element>
54
+ </define>
55
+
56
+ <define name="users">
57
+ <element name="users">
58
+ <oneOrMore>
59
+ <ref name="user"/>
60
+ </oneOrMore>
61
+ </element>
62
+ </define>
63
+
64
+ <define name="user">
65
+ <element name="user">
66
+ <attribute name="id"><data type="integer"/></attribute>
67
+ <element name="name"><text/></element>
68
+ <element name="email"><text/></element>
69
+ <element name="created_at"><text/></element>
70
+ <element name="profile">
71
+ <element name="age"><data type="integer"/></element>
72
+ <element name="city"><text/></element>
73
+ <element name="preferences">
74
+ <element name="theme"><text/></element>
75
+ <element name="notifications"><data type="boolean"/></element>
76
+ </element>
77
+ </element>
78
+ </element>
79
+ </define>
80
+
81
+ <define name="dataset">
82
+ <element name="dataset">
83
+ <element name="header">
84
+ <element name="created"><text/></element>
85
+ <element name="count"><data type="integer"/></element>
86
+ <element name="format"><text/></element>
87
+ </element>
88
+ <element name="records">
89
+ <oneOrMore>
90
+ <ref name="record"/>
91
+ </oneOrMore>
92
+ </element>
93
+ </element>
94
+ </define>
95
+
96
+ <define name="record">
97
+ <element name="record">
98
+ <attribute name="id"><data type="integer"/></attribute>
99
+ <element name="timestamp"><text/></element>
100
+ <element name="data">
101
+ <element name="field1"><text/></element>
102
+ <element name="field2"><data type="integer"/></element>
103
+ <element name="field3"><text/></element>
104
+ <element name="nested">
105
+ <oneOrMore><element name="item"><text/></element></oneOrMore>
106
+ </element>
107
+ </element>
108
+ <element name="metadata">
109
+ <element name="source"><text/></element>
110
+ <element name="version"><text/></element>
111
+ <element name="checksum"><text/></element>
112
+ </element>
113
+ </element>
114
+ </define>
115
+ </grammar>
116
+ RNG
117
+
118
+ _DEFAULT_XSLT_STYLESHEET = <<'XSL'.freeze
119
+ <?xml version="1.0" encoding="UTF-8"?>
120
+ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
121
+ <xsl:output method="xml" indent="yes"/>
122
+ <xsl:template match="/">
123
+ <report>
124
+ <xsl:apply-templates select="//user | //record | //database | //cache"/>
125
+ <total>
126
+ <xsl:value-of select="count(//user) + count(//record)"/>
127
+ </total>
128
+ </report>
129
+ </xsl:template>
130
+ <xsl:template match="user">
131
+ <row kind="user" id="{@id}" label="{name}"/>
132
+ </xsl:template>
133
+ <xsl:template match="record">
134
+ <row kind="record" id="{@id}" label="{data/field1}"/>
135
+ </xsl:template>
136
+ <xsl:template match="database">
137
+ <row kind="database" label="{name}"/>
138
+ </xsl:template>
139
+ <xsl:template match="cache">
140
+ <row kind="cache" label="{ttl}"/>
141
+ </xsl:template>
142
+ </xsl:stylesheet>
143
+ XSL
144
+
145
+ XPATH_QUERIES = ['//book', "//book[@id='101']", '//book[price > 30]/title'].freeze
146
+ XQUERY_EXPRESSIONS = ['count(//user | //record)', "//record[@id='101']/data/field1", '//user[profile/age > 40]/name'].freeze
147
+ RNG_SCHEMA = File.expand_path('test_data/schema.rng', Dir.pwd).then { |p| File.exist?(p) ? File.read(p) : _DEFAULT_RNG_SCHEMA }
148
+ XSLT_STYLESHEET = File.expand_path('test_data/transform.xsl', Dir.pwd).then { |p| File.exist?(p) ? File.read(p) : _DEFAULT_XSLT_STYLESHEET }
149
+
29
150
  OPERATIONS = {
30
151
  'parsing' => ->(s, data) { s.parse(data) },
31
152
  'generation' => ->(s, data) { s.generate(s.parse(data)) },
32
153
  'xpath' => lambda { |s, data|
33
154
  doc = s.parse(data)
34
- s.xpath_query(doc, '//book')
35
- s.xpath_query(doc, "//book[@id='101']")
36
- s.xpath_query(doc, '//book[price > 30]/title')
155
+ XPATH_QUERIES.each { |q| s.xpath_query(doc, q) }
156
+ },
157
+ 'xquery' => lambda { |s, data|
158
+ doc = s.parse(data)
159
+ XQUERY_EXPRESSIONS.each { |x| s.xquery_eval(doc, x) }
37
160
  },
161
+ 'xslt' => ->(s, data) { s.xslt_transform(s.parse(data), XSLT_STYLESHEET) },
162
+ 'validation' => ->(s, data) { s.validate(s.parse(data), RNG_SCHEMA) },
38
163
  'streaming' => ->(s, data) { s.stream_parse(data) { |_event, _data| } },
39
164
  }.freeze
40
165
 
@@ -161,6 +286,12 @@ module Serialbench
161
286
  serializers.select { |s| s.supports?(:sax) || s.supports?(:streaming) }
162
287
  when 'xpath'
163
288
  serializers.select { |s| s.supports?(:xpath) }
289
+ when 'xquery'
290
+ serializers.select { |s| s.supports?(:xquery) }
291
+ when 'xslt'
292
+ serializers.select { |s| s.supports?(:xslt) }
293
+ when 'validation'
294
+ serializers.select { |s| s.supports?(:validation) }
164
295
  else
165
296
  serializers
166
297
  end
@@ -48,10 +48,10 @@ module Serialbench
48
48
  attribute :name, :string
49
49
  attribute :description, :string
50
50
  attribute :data_sizes, :string, collection: true, values: %w[small medium large]
51
- attribute :formats, :string, collection: true, values: %w[xml json yaml toml]
51
+ attribute :formats, :string, collection: true, values: %w[xml html json yaml toml]
52
52
  attribute :iterations, BenchmarkIteration
53
53
  attribute :warmup, :integer, default: -> { 1 }
54
- attribute :operations, :string, collection: true, values: %w[parsing generation xpath streaming memory]
54
+ attribute :operations, :string, collection: true, values: %w[parsing generation xpath xquery xslt validation streaming memory]
55
55
 
56
56
  key_value do
57
57
  map 'name', to: :name
@@ -5,7 +5,7 @@ require 'lutaml/model'
5
5
  module Serialbench
6
6
  module Models
7
7
  class SerializerInformation < Lutaml::Model::Serializable
8
- attribute :format, :string, values: %w[xml json yaml toml]
8
+ attribute :format, :string, values: %w[xml html json yaml toml]
9
9
  attribute :name, :string
10
10
  attribute :version, :string
11
11
  attribute :features, :hash
@@ -20,7 +20,7 @@ module Serialbench
20
20
 
21
21
  class AdapterPerformance < Lutaml::Model::Serializable
22
22
  attribute :adapter, :string
23
- attribute :format, :string, values: %w[xml json yaml toml]
23
+ attribute :format, :string, values: %w[xml html json yaml toml]
24
24
  attribute :data_size, :string, values: %w[small medium large]
25
25
 
26
26
  key_value do
@@ -71,6 +71,9 @@ module Serialbench
71
71
  attribute :memory, MemoryPerformance, collection: true
72
72
  attribute :streaming, IterationPerformance, collection: true
73
73
  attribute :xpath, IterationPerformance, collection: true
74
+ attribute :xquery, IterationPerformance, collection: true
75
+ attribute :xslt, IterationPerformance, collection: true
76
+ attribute :validation, IterationPerformance, collection: true
74
77
 
75
78
  key_value do
76
79
  map 'serializers', to: :serializers
@@ -79,6 +82,9 @@ module Serialbench
79
82
  map 'memory', to: :memory
80
83
  map 'streaming', to: :streaming
81
84
  map 'xpath', to: :xpath
85
+ map 'xquery', to: :xquery
86
+ map 'xslt', to: :xslt
87
+ map 'validation', to: :validation
82
88
  end
83
89
  end
84
90
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Html
8
+ class BaseHtmlSerializer < BaseSerializer
9
+ def self.format
10
+ :html
11
+ end
12
+
13
+ def capabilities
14
+ super | Set.new(%i[dom parse generate])
15
+ end
16
+
17
+ def features
18
+ {
19
+ html5: supports?(:html5),
20
+ xpath: supports?(:xpath)
21
+ }
22
+ end
23
+
24
+ def generate(data)
25
+ data.is_a?(Hash) ? from_hash(data) : serialize_document(data)
26
+ end
27
+
28
+ def from_hash(hash)
29
+ rows = hash.flat_map do |key, value|
30
+ case value
31
+ when Hash then value.map { |k, v| "<tr><td>#{k}</td><td>#{v}</td></tr>" }
32
+ else ["<tr><td>#{key}</td><td>#{value}</td></tr>"]
33
+ end
34
+ end
35
+ serialize_document(parse("<html><body><table>#{rows.join}</table></body></html>"))
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_html_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Html
8
+ class LeptrisSerializer < BaseHtmlSerializer
9
+ def name
10
+ 'leptris'
11
+ end
12
+
13
+ def capabilities
14
+ super | Set.new(%i[xpath html5])
15
+ end
16
+
17
+ def parse(html_string)
18
+ require 'leptris'
19
+ require 'leptris/xml'
20
+ Leptris::XML.parse_html(html_string)
21
+ end
22
+
23
+ def xpath_query(document, expression)
24
+ document.xpath(expression).size
25
+ end
26
+
27
+ def serialize_document(document)
28
+ document.to_xml
29
+ end
30
+
31
+ def available?
32
+ return @available if defined?(@available)
33
+
34
+ @available = begin
35
+ require 'leptris'
36
+ require 'leptris/xml'
37
+ Leptris::XML.parse_html('<p>probe</p>')
38
+ true
39
+ rescue StandardError, LoadError => e
40
+ warn "#{name} unavailable: #{e.class}: #{e.message}"
41
+ false
42
+ end
43
+ end
44
+
45
+ def version
46
+ return 'unknown' unless available?
47
+
48
+ require 'leptris'
49
+ Leptris::VERSION
50
+ end
51
+
52
+ def library_require_name
53
+ 'leptris'
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_html_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Html
8
+ class NokogiriSerializer < BaseHtmlSerializer
9
+ def name
10
+ 'nokogiri'
11
+ end
12
+
13
+ def capabilities
14
+ super | Set.new(%i[xpath html5])
15
+ end
16
+
17
+ def parse(html_string)
18
+ require 'nokogiri'
19
+ Nokogiri::HTML5(html_string)
20
+ end
21
+
22
+ def xpath_query(document, expression)
23
+ document.xpath(expression).size
24
+ end
25
+
26
+ def serialize_document(document)
27
+ document.to_html
28
+ end
29
+
30
+ def available?
31
+ return @available if defined?(@available)
32
+
33
+ @available = begin
34
+ require 'nokogiri'
35
+ Nokogiri::HTML5('<p>probe</p>')
36
+ true
37
+ rescue StandardError, LoadError => e
38
+ warn "#{name} unavailable: #{e.class}: #{e.message}"
39
+ false
40
+ end
41
+ end
42
+
43
+ def version
44
+ return 'unknown' unless available?
45
+
46
+ require 'nokogiri'
47
+ Nokogiri::VERSION
48
+ end
49
+
50
+ def library_require_name
51
+ 'nokogiri'
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_html_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Html
8
+ class OgaSerializer < BaseHtmlSerializer
9
+ def name
10
+ 'oga'
11
+ end
12
+
13
+ def parse(html_string)
14
+ require 'oga'
15
+ Oga.parse_html(html_string)
16
+ end
17
+
18
+ def serialize_document(document)
19
+ document.to_xml
20
+ end
21
+
22
+ def available?
23
+ return @available if defined?(@available)
24
+
25
+ @available = begin
26
+ require 'oga'
27
+ Oga.parse_html('<p>probe</p>')
28
+ true
29
+ rescue StandardError, LoadError => e
30
+ warn "#{name} unavailable: #{e.class}: #{e.message}"
31
+ false
32
+ end
33
+ end
34
+
35
+ def version
36
+ return 'unknown' unless available?
37
+
38
+ require 'oga'
39
+ Oga::VERSION
40
+ end
41
+
42
+ def library_require_name
43
+ 'oga'
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -37,7 +37,26 @@ module Serialbench
37
37
  end
38
38
 
39
39
  def capabilities
40
- super | Set.new(%i[xpath sax stax])
40
+ super | Set.new(%i[xpath xquery xslt validation sax stax])
41
+ end
42
+
43
+ def xquery_eval(document, expression)
44
+ require 'leptris'
45
+ require 'leptris/xml'
46
+ result = Leptris::XML::XQuery.parse(expression).eval(document)
47
+ result.respond_to?(:length) ? result.length : 1
48
+ end
49
+
50
+ def xslt_transform(document, stylesheet)
51
+ require 'leptris'
52
+ require 'leptris/xml'
53
+ Leptris::XML::XSLT.parse(stylesheet).apply_to(document).serialize
54
+ end
55
+
56
+ def validate(document, schema)
57
+ require 'leptris'
58
+ require 'leptris/xml'
59
+ Leptris::XML::RelaxNG.parse(schema).valid?(document)
41
60
  end
42
61
 
43
62
  def xpath_query(document, expression)
@@ -39,7 +39,17 @@ module Serialbench
39
39
  end
40
40
 
41
41
  def capabilities
42
- super | Set.new(%i[xpath sax stax])
42
+ super | Set.new(%i[xpath xslt validation sax stax])
43
+ end
44
+
45
+ def xslt_transform(document, stylesheet)
46
+ require 'nokogiri'
47
+ Nokogiri::XSLT(stylesheet).transform(document).to_s
48
+ end
49
+
50
+ def validate(document, schema)
51
+ require 'nokogiri'
52
+ Nokogiri::XML::RelaxNG(schema).valid?(document)
43
53
  end
44
54
 
45
55
  def xpath_query(document, expression)
@@ -12,6 +12,12 @@ require_relative 'serializers/xml/oga_serializer'
12
12
  require_relative 'serializers/xml/libxml_serializer'
13
13
  require_relative 'serializers/xml/leptris_serializer'
14
14
 
15
+ # HTML Serializers
16
+ require_relative 'serializers/html/base_html_serializer'
17
+ require_relative 'serializers/html/nokogiri_serializer'
18
+ require_relative 'serializers/html/oga_serializer'
19
+ require_relative 'serializers/html/leptris_serializer'
20
+
15
21
  # JSON Serializers
16
22
  require_relative 'serializers/json/base_json_serializer'
17
23
  require_relative 'serializers/json/json_serializer'
@@ -52,6 +58,11 @@ module Serialbench
52
58
  Json::YajlSerializer,
53
59
  Json::YeptrisSerializer
54
60
  ],
61
+ html: [
62
+ Html::NokogiriSerializer,
63
+ Html::OgaSerializer,
64
+ Html::LeptrisSerializer
65
+ ],
55
66
  yaml: [
56
67
  Yaml::PsychSerializer,
57
68
  Yaml::SyckSerializer,
@@ -38,8 +38,11 @@ module Serialbench
38
38
  when 'medium.yaml' then generate_medium_yaml
39
39
  when 'large.yaml' then generate_large_yaml
40
40
  when 'small.toml' then generate_small_toml
41
+ when 'small.html' then generate_small_html
41
42
  when 'medium.toml' then generate_medium_toml
43
+ when 'medium.html' then generate_medium_html
42
44
  when 'large.toml' then generate_large_toml
45
+ when 'large.html' then generate_large_html
43
46
  else raise ArgumentError, "no test data generator for #{key}"
44
47
  end
45
48
  end
@@ -205,6 +208,50 @@ module Serialbench
205
208
  XML
206
209
  end
207
210
 
211
+ # HTML test data generators (same dataset as XML, as tables)
212
+ def generate_small_html
213
+ data = small_test_data_structure
214
+ rows = data[:config].flat_map do |_section, entries|
215
+ entries.flat_map do |_k, v|
216
+ case v
217
+ when Hash then v.map { |k2, v2| "<tr><td>#{k2}</td><td>#{v2}</td></tr>" }
218
+ else []
219
+ end
220
+ end
221
+ end
222
+ html_page('config', rows)
223
+ end
224
+
225
+ def generate_medium_html
226
+ data = medium_test_data_structure
227
+ rows = data[:users].map do |u|
228
+ "<tr><td>#{u[:id]}</td><td>#{u[:name][0, 30]}</td></tr>"
229
+ end
230
+ html_page('users', rows)
231
+ end
232
+
233
+ def generate_large_html
234
+ data = large_test_data_structure
235
+ rows = data[:dataset][:records].map do |r|
236
+ "<tr><td>#{r[:id]}</td><td>#{r[:data][:field1][0, 30]}</td></tr>"
237
+ end
238
+ html_page('dataset', rows)
239
+ end
240
+
241
+ def html_page(title, rows)
242
+ <<~HTML
243
+ <!DOCTYPE html>
244
+ <html>
245
+ <head><meta charset="utf-8"><title>serialbench #{title}</title></head>
246
+ <body>
247
+ <table>
248
+ #{rows.join("\n")}
249
+ </table>
250
+ </body>
251
+ </html>
252
+ HTML
253
+ end
254
+
208
255
  # JSON test data generators
209
256
  def generate_small_json
210
257
  JSON.generate(small_test_data_structure)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Serialbench
4
- VERSION = '0.9.2'
4
+ VERSION = '0.10.0'
5
5
  end
data/serialbench.gemspec CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
 
15
15
  spec.summary = 'Comprehensive serialization benchmarking tool for Ruby'
16
16
  spec.description = 'A benchmarking suite for comparing performance of various serialization libraries in Ruby, including XML, JSON, and TOML parsers/generators.'
17
- spec.homepage = 'https://github.com/serialbench/serialbench'
17
+ spec.homepage = 'https://github.com/serialbench/serialbench-ruby'
18
18
  spec.license = 'BSD-2-Clause'
19
19
  spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
20
20
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serialbench
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
@@ -270,6 +270,7 @@ files:
270
270
  - Gemfile
271
271
  - README.adoc
272
272
  - Rakefile
273
+ - config/benchmarks/full-html.yml
273
274
  - config/benchmarks/full-json.yml
274
275
  - config/benchmarks/full-toml.yml
275
276
  - config/benchmarks/full-xml.yml
@@ -323,6 +324,10 @@ files:
323
324
  - lib/serialbench/runners/local_runner.rb
324
325
  - lib/serialbench/serializers.rb
325
326
  - lib/serialbench/serializers/base_serializer.rb
327
+ - lib/serialbench/serializers/html/base_html_serializer.rb
328
+ - lib/serialbench/serializers/html/leptris_serializer.rb
329
+ - lib/serialbench/serializers/html/nokogiri_serializer.rb
330
+ - lib/serialbench/serializers/html/oga_serializer.rb
326
331
  - lib/serialbench/serializers/json/base_json_serializer.rb
327
332
  - lib/serialbench/serializers/json/json_serializer.rb
328
333
  - lib/serialbench/serializers/json/oj_serializer.rb
@@ -349,13 +354,13 @@ files:
349
354
  - lib/serialbench/version.rb
350
355
  - lib/serialbench/yaml_validator.rb
351
356
  - serialbench.gemspec
352
- homepage: https://github.com/serialbench/serialbench
357
+ homepage: https://github.com/serialbench/serialbench-ruby
353
358
  licenses:
354
359
  - BSD-2-Clause
355
360
  metadata:
356
- homepage_uri: https://github.com/serialbench/serialbench
357
- source_code_uri: https://github.com/serialbench/serialbench
358
- bug_tracker_uri: https://github.com/serialbench/serialbench/issues
361
+ homepage_uri: https://github.com/serialbench/serialbench-ruby
362
+ source_code_uri: https://github.com/serialbench/serialbench-ruby
363
+ bug_tracker_uri: https://github.com/serialbench/serialbench-ruby/issues
359
364
  rdoc_options: []
360
365
  require_paths:
361
366
  - lib