moxml 0.5.62 → 0.5.63
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 +4 -4
- data/benchmark/plan_bench.rb +22 -0
- data/lib/moxml/adapter/base.rb +10 -0
- data/lib/moxml/adapter/leptris.rb +26 -0
- data/lib/moxml/struct_plan.rb +81 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml.rb +1 -0
- data/spec/moxml/struct_plan_spec.rb +69 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60493a9fbb6b03a4db31f83e99f269c5d5cf1797f5e0e94f4b6139616905e45e
|
|
4
|
+
data.tar.gz: 4df319d1a67b69f2a08e99cf338131c55584b1e68cb4b46687a23c2f6329263c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6690afe362a4af5781c54271c29daf1af651a6bc34975fd078a9c1d56ec56ac6c5af13a63746a801c206162aa25c7d09314f5bca55db48d356d83586466eda7f
|
|
7
|
+
data.tar.gz: a53a4a73958a40338ed54fbeb6c9098264e5f480b8125888b7471c9d6d11ec9dde455fc35e7de8847c3246fa834e607ab16f8eed5f2f47089547a45ff5b17f8b
|
data/benchmark/plan_bench.rb
CHANGED
|
@@ -23,6 +23,11 @@ XML = parts.join
|
|
|
23
23
|
|
|
24
24
|
CTX = Moxml.new(:leptris)
|
|
25
25
|
|
|
26
|
+
STRUCT_PLAN = Moxml::StructPlan.new do
|
|
27
|
+
element "record", Record, attrs: { "id" => :id, "kind" => :kind }, children: :fields
|
|
28
|
+
element "field", Field, attrs: { "name" => :name, "unit" => :unit }, text: :value
|
|
29
|
+
end
|
|
30
|
+
|
|
26
31
|
PLAN = Moxml::Plan.new do
|
|
27
32
|
on("record") { |attrs, _t, fields| Record.new(attrs["id"], attrs["kind"], fields) }
|
|
28
33
|
on("field") { |attrs, text, _k| Field.new(attrs["name"], attrs["unit"], text) }
|
|
@@ -35,6 +40,13 @@ def plan_run
|
|
|
35
40
|
records
|
|
36
41
|
end
|
|
37
42
|
|
|
43
|
+
def struct_run
|
|
44
|
+
records = STRUCT_PLAN.parse(XML, CTX)
|
|
45
|
+
raise "shape" unless records.size == 150 && records[7].fields[3].value == "value 7.3"
|
|
46
|
+
|
|
47
|
+
records
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
def wrapper_run
|
|
39
51
|
doc = CTX.parse(XML)
|
|
40
52
|
catalog = Catalog.new([])
|
|
@@ -99,13 +111,23 @@ end
|
|
|
99
111
|
|
|
100
112
|
# warm
|
|
101
113
|
plan_run
|
|
114
|
+
struct_run
|
|
102
115
|
wrapper_run
|
|
103
116
|
nokogiri_run
|
|
104
117
|
|
|
118
|
+
struct_us = nil
|
|
119
|
+
struct_allocs = nil
|
|
120
|
+
if Gem::Version.new(Leptris::VERSION) >= Gem::Version.new("1.9.197.1")
|
|
121
|
+
struct_us, struct_allocs = bench { struct_run }
|
|
122
|
+
end
|
|
105
123
|
plan_us, plan_allocs = bench { plan_run }
|
|
106
124
|
wrap_us, wrap_allocs = bench { wrapper_run }
|
|
107
125
|
nk_us, nk_allocs = bench { nokogiri_run }
|
|
108
126
|
|
|
127
|
+
if struct_us
|
|
128
|
+
puts format("struct (parse+materialize) %<t>8.0f us %<a>7d allocs",
|
|
129
|
+
t: struct_us, a: struct_allocs)
|
|
130
|
+
end
|
|
109
131
|
puts format("plan (parse+materialize) %<t>8.0f us %<a>7d allocs",
|
|
110
132
|
t: plan_us, a: plan_allocs)
|
|
111
133
|
puts format("wrapper (parse+walk) %<t>8.0f us %<a>7d allocs",
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -371,6 +371,16 @@ namespace_validation_mode: :strict)
|
|
|
371
371
|
nil
|
|
372
372
|
end
|
|
373
373
|
|
|
374
|
+
# Struct-plan executor (Moxml::StructPlan): +spec+ compiles
|
|
375
|
+
# the consumer's shape as {name => [Struct class, attrs
|
|
376
|
+
# Hash (name => slot Symbol), text slot, children slot]}.
|
|
377
|
+
# Returns the array of top-level minted structs, or nil when
|
|
378
|
+
# the adapter has no C executor (the StructPlan then runs
|
|
379
|
+
# its Moxml::Plan fallback over plan_rows).
|
|
380
|
+
def plan_structs(_native, _spec)
|
|
381
|
+
nil
|
|
382
|
+
end
|
|
383
|
+
|
|
374
384
|
# Deterministic native-memory release for adapters backed by
|
|
375
385
|
# C trees (issue #134). GC-managed engines no-op; released
|
|
376
386
|
# documents raise the engine's use-after-free error on
|
|
@@ -537,6 +537,32 @@ module Moxml
|
|
|
537
537
|
NATIVE_READ_LAYER &&
|
|
538
538
|
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.193.4")
|
|
539
539
|
|
|
540
|
+
# leptris-ruby#266 (binding 1.9.197.1): Native.plan_structs —
|
|
541
|
+
# the C struct executor behind Moxml::StructPlan.
|
|
542
|
+
NATIVE_PLAN_STRUCTS =
|
|
543
|
+
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.197.1")
|
|
544
|
+
|
|
545
|
+
def self.plan_structs(native, spec)
|
|
546
|
+
return nil unless NATIVE_PLAN_STRUCTS
|
|
547
|
+
|
|
548
|
+
doc = if native.is_a?(::Leptris::XML::Document)
|
|
549
|
+
native
|
|
550
|
+
else
|
|
551
|
+
doc_for(native)
|
|
552
|
+
end
|
|
553
|
+
return nil if doc.nil? || attachments.get(doc, :entity_markers)
|
|
554
|
+
|
|
555
|
+
root_binding = if native.is_a?(::Leptris::XML::Document)
|
|
556
|
+
doc.root
|
|
557
|
+
else
|
|
558
|
+
to_binding(native)
|
|
559
|
+
end
|
|
560
|
+
return nil unless root_binding
|
|
561
|
+
|
|
562
|
+
::Leptris::XML::Native.plan_structs(doc, root_binding.c_address,
|
|
563
|
+
spec)
|
|
564
|
+
end
|
|
565
|
+
|
|
540
566
|
def self.plan_rows(native)
|
|
541
567
|
return nil unless NATIVE_READ_LAYER
|
|
542
568
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
# Struct-compiled materialization — the fully compiled grammar:
|
|
5
|
+
# declare the document shape once as STRUCTS; adapters with a C
|
|
6
|
+
# executor (leptris 1.9.197.1+) mint the typed objects directly
|
|
7
|
+
# with zero Ruby frames per element; everything else falls back to
|
|
8
|
+
# an equivalent Moxml::Plan over the row stream (spec-pinned
|
|
9
|
+
# equal).
|
|
10
|
+
#
|
|
11
|
+
# plan = Moxml::StructPlan.new do
|
|
12
|
+
# element "record", Record, attrs: { "id" => :id, "kind" => :kind },
|
|
13
|
+
# children: :fields
|
|
14
|
+
# element "field", Field, attrs: { "name" => :name, "unit" => :unit },
|
|
15
|
+
# text: :value
|
|
16
|
+
# end
|
|
17
|
+
# records = plan.parse(xml, ctx) # Array of Record
|
|
18
|
+
#
|
|
19
|
+
# Slot symbols must be the Struct's member names. Semantics equal
|
|
20
|
+
# Moxml::Plan: unmatched elements are barriers — their matched
|
|
21
|
+
# descendants surface at top level.
|
|
22
|
+
class StructPlan
|
|
23
|
+
Element = Struct.new(:klass, :attrs, :text, :children)
|
|
24
|
+
|
|
25
|
+
def initialize(&block)
|
|
26
|
+
@elements = {}
|
|
27
|
+
instance_eval(&block) if block
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Registers a struct element. +attrs+ maps attribute names to
|
|
31
|
+
# member slots; +text+ and +children+ name the member slots
|
|
32
|
+
# receiving the first text child and the matched children array.
|
|
33
|
+
# Returns self so registrations chain.
|
|
34
|
+
def element(name, klass, attrs: {}, text: nil, children: nil)
|
|
35
|
+
@elements[name] = Element.new(klass, attrs, text, children)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The compiled spec the C executor consumes:
|
|
40
|
+
# {name => [klass, attrs, text slot, children slot]}.
|
|
41
|
+
def compile
|
|
42
|
+
@elements.each_with_object({}) do |(name, el), spec|
|
|
43
|
+
spec[name] = [el.klass, el.attrs, el.text, el.children]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parse(xml, context)
|
|
48
|
+
materialize(context.parse(xml))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def materialize(node)
|
|
52
|
+
adapter = node.context.config.adapter
|
|
53
|
+
roots = adapter.plan_structs(node.native, compile)
|
|
54
|
+
return roots if roots
|
|
55
|
+
|
|
56
|
+
plan.materialize(node)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# The fallback: the same element table as block handlers over
|
|
62
|
+
# the plan row stream. instance_eval'd DSL — capture the table
|
|
63
|
+
# in a local (the Plan's self has no @elements).
|
|
64
|
+
def plan
|
|
65
|
+
@plan ||= begin
|
|
66
|
+
elements = @elements
|
|
67
|
+
Moxml::Plan.new do
|
|
68
|
+
elements.each do |name, el|
|
|
69
|
+
on(name) do |attrs, text, children|
|
|
70
|
+
obj = el.klass.new
|
|
71
|
+
el.attrs.each { |aname, slot| obj[slot] = attrs[aname] }
|
|
72
|
+
obj[el.text] = text if el.text
|
|
73
|
+
obj[el.children] = children if el.children
|
|
74
|
+
obj
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/moxml/version.rb
CHANGED
data/lib/moxml.rb
CHANGED
|
@@ -94,6 +94,7 @@ module Moxml
|
|
|
94
94
|
autoload :XmlEmitter, "moxml/xml_emitter"
|
|
95
95
|
autoload :Materializer, "moxml/materializer"
|
|
96
96
|
autoload :Plan, "moxml/plan"
|
|
97
|
+
autoload :StructPlan, "moxml/struct_plan"
|
|
97
98
|
autoload :Adapter, "moxml/adapter"
|
|
98
99
|
autoload :XPath, "moxml/xpath"
|
|
99
100
|
autoload :SAX, "moxml/sax"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# Moxml::StructPlan: the fully compiled grammar — struct slots
|
|
6
|
+
# declared once, minted in C on leptris (1.9.197.1+) and via the
|
|
7
|
+
# plan fallback everywhere else. Results must be identical to
|
|
8
|
+
# Moxml::Plan.
|
|
9
|
+
SRecord = Struct.new(:id, :kind, :fields)
|
|
10
|
+
SField = Struct.new(:name, :unit, :value)
|
|
11
|
+
|
|
12
|
+
RSpec.describe Moxml::StructPlan do
|
|
13
|
+
let(:xml) do
|
|
14
|
+
'<catalog><record id="r0" kind="k0"><field name="f0" unit="u0">v 0.0</field><field name="f1" unit="u1">v 0.1</field></record><record id="r1" kind="k1"><field name="f0" unit="u0">v 1.0</field></record></catalog>'
|
|
15
|
+
end
|
|
16
|
+
let(:plan) do
|
|
17
|
+
described_class.new do
|
|
18
|
+
element "record", SRecord,
|
|
19
|
+
attrs: { "id" => :id, "kind" => :kind }, children: :fields
|
|
20
|
+
element "field", SField,
|
|
21
|
+
attrs: { "name" => :name, "unit" => :unit }, text: :value
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
let(:block_plan) do
|
|
25
|
+
Moxml::Plan.new do
|
|
26
|
+
on("record") { |attrs, _t, fields| SRecord.new(attrs["id"], attrs["kind"], fields) }
|
|
27
|
+
on("field") { |attrs, text, _k| SField.new(attrs["name"], attrs["unit"], text) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
|
|
32
|
+
|
|
33
|
+
def with_adapter(name)
|
|
34
|
+
yield Moxml.new(name)
|
|
35
|
+
rescue StandardError
|
|
36
|
+
skip "#{name} unavailable"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
ADAPTERS.each do |adapter_name|
|
|
40
|
+
describe "on #{adapter_name}" do
|
|
41
|
+
it "materializes the same structs as Moxml::Plan" do
|
|
42
|
+
with_adapter(adapter_name) do |ctx|
|
|
43
|
+
expect(plan.parse(xml, ctx))
|
|
44
|
+
.to eq(block_plan.parse(xml, ctx))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "treats unmatched elements as barriers like Moxml::Plan" do
|
|
51
|
+
barrier_xml = "<catalog><wrap><record id=\"r9\"/></wrap><record id=\"r8\"/></catalog>"
|
|
52
|
+
with_adapter(:leptris) do |ctx|
|
|
53
|
+
rows = plan.parse(barrier_xml, ctx)
|
|
54
|
+
expect(rows.map(&:id)).to eq(%w[r9 r8])
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "runs the C executor on leptris (not the fallback)" do
|
|
59
|
+
with_adapter(:leptris) do |ctx|
|
|
60
|
+
doc = ctx.parse(xml)
|
|
61
|
+
roots = Moxml::Adapter::Leptris.plan_structs(
|
|
62
|
+
doc.native,
|
|
63
|
+
"record" => [SRecord, { "id" => :id, "kind" => :kind }, nil, :fields],
|
|
64
|
+
"field" => [SField, { "name" => :name, "unit" => :unit }, :value, nil],
|
|
65
|
+
)
|
|
66
|
+
expect(roots).to eq(plan.parse(xml, ctx))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: moxml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.63
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -271,6 +271,7 @@ files:
|
|
|
271
271
|
- lib/moxml/signature/transform_pipeline.rb
|
|
272
272
|
- lib/moxml/signature/verification_result.rb
|
|
273
273
|
- lib/moxml/signature/verifier.rb
|
|
274
|
+
- lib/moxml/struct_plan.rb
|
|
274
275
|
- lib/moxml/text.rb
|
|
275
276
|
- lib/moxml/version.rb
|
|
276
277
|
- lib/moxml/wrappers.rb
|
|
@@ -510,6 +511,7 @@ files:
|
|
|
510
511
|
- spec/moxml/signature/key_extractor_spec.rb
|
|
511
512
|
- spec/moxml/signature/model_spec.rb
|
|
512
513
|
- spec/moxml/signature/round_trip_spec.rb
|
|
514
|
+
- spec/moxml/struct_plan_spec.rb
|
|
513
515
|
- spec/moxml/text_spec.rb
|
|
514
516
|
- spec/moxml/version_spec.rb
|
|
515
517
|
- spec/moxml/xml_emitter_spec.rb
|