moxml 0.5.24 → 0.5.25

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: b1181803fec34397cfd858d8671e2dcd6a02a88418dc5848a5ea9c7a44d7bc64
4
- data.tar.gz: b9809e6ed48ae4a5a946ed9487fe61d0a89d333b51921de70b50dd271956ba0a
3
+ metadata.gz: 7962623114db23fce306ee38745c51ccf85fe8333c60a274322eb3d5a6dd6270
4
+ data.tar.gz: 00b614eed4a674d4ad43a1cbf1f4dd0483de2e929b7e78728c4aab7106662b56
5
5
  SHA512:
6
- metadata.gz: 26df2786fe6a81b696fe2273c2d5f36151bb6af9daf2ce10f057662f7df3ce8ca75970f883a832dbc0c4741e7262be87d79a6eac143298dc9c398ba198f6ba43
7
- data.tar.gz: d7d155e11b4d1718e072ad5d2eabcb3aad8c2875753eea571bde7e710deee1c88a8acb3244cb01b4055b4c7e648384f91410c42ad576d600ab4c49ffcb39b895
6
+ metadata.gz: 9e49d1c70a4332def347256eccf22f7defa256eaec8290987a73079daa0bcfed0f141f4862a9b457fbbc6f606be5386c3d5717e35e767b1088d2e7f88e0fde5d
7
+ data.tar.gz: d3ad61616f0b7bf163fef6333c59595bbce7b40dc96fd01c7a64f8bf2e8db2419102f3633444d1a54453bff7f6ad170051f75c9756e1c406233e25716426f3c7
@@ -54,7 +54,8 @@ jobs:
54
54
  - name: Run rubocop
55
55
  run: bundle exec rubocop
56
56
 
57
- - name: Run performance gates
57
+ - name: Run performance gates (YJIT)
58
58
  env:
59
59
  RUN_PERFORMANCE: "1"
60
+ RUBY_YJIT_ENABLE: "1"
60
61
  run: bundle exec rake spec:performance
@@ -33,3 +33,34 @@ doc.xpath('//title')
33
33
  root.xpath('./*/title')
34
34
  ----
35
35
 
36
+
37
+ === Run with YJIT
38
+
39
+ The wrapper layer is Ruby-dispatch-heavy — exactly what YJIT compiles
40
+ well. Enable it for any throughput-sensitive workload (Ruby >= 3.3;
41
+ production-ready since then):
42
+
43
+ [source,bash]
44
+ ----
45
+ RUBY_YJIT_ENABLE=1 ruby your_app.rb # or: ruby --yjit
46
+ ----
47
+
48
+ Measured against raw engine calls on a 52KB fixture (leptris,
49
+ min-of-N, 100 warm reads): without YJIT the facade adds +23% on tree
50
+ walks and +43% on warm attribute reads; **with YJIT those become
51
+ -12% (faster than the binding's own walk) and +10%**, and `.name`
52
+ reads reach parity. Parse, serialize and duplicate are C-bound
53
+ either way. No semantic surface changes — it is a runtime flag.
54
+
55
+ Consumers on CRuby 3.3+ (canon's pretty-print pipeline, batch
56
+ conversion) should treat it as the default; moxml's own CI
57
+ performance gates run with YJIT enabled.
58
+
59
+ === Prefer bulk paths for per-node conversion
60
+
61
+ Per-node Ruby iteration pays the wrapper tax per element. When the
62
+ goal is conversion rather than manipulation, the bulk paths move the
63
+ loop into C: `materialize_fields` streams the flattened record
64
+ fields from one C call and measures faster than an equivalent
65
+ raw-Nokogiri DOM walk. See
66
+ link:conversion-apis[Conversion and memory lifecycle APIs].
data/lib/moxml/entity.rb CHANGED
@@ -49,6 +49,34 @@ module Moxml
49
49
  # marker gsub's full-buffer copy entirely.
50
50
  NON_STANDARD_ENTITY_RE = /&(?!amp;|lt;|gt;|quot;|apos;)(#{NAME_PATTERN});/
51
51
 
52
+ # True when a NON-standard named entity exists somewhere (the
53
+ # gsub would then be needed). The leptris binding's C probe
54
+ # (leptris-ruby#124) answers in one pass at memcmp speed; it is
55
+ # resolved lazily because adapters load on demand. Otherwise:
56
+ # the cheap `&` pre-filter, then the Ruby regex.
57
+ def nonstandard_entity?(str)
58
+ probe = c_entity_probe
59
+ if probe
60
+ probe.call(str, str.bytesize) == 1
61
+ else
62
+ str.include?("&") && str.match?(NON_STANDARD_ENTITY_RE)
63
+ end
64
+ end
65
+
66
+ def c_entity_probe
67
+ if @c_entity_probe.nil?
68
+ @c_entity_probe =
69
+ if defined?(::Leptris::XML::FFI) &&
70
+ ::Leptris::XML::FFI.respond_to?(:leptris_str_has_nonstandard_entity)
71
+ ::Leptris::XML::FFI.method(:leptris_str_has_nonstandard_entity)
72
+ else
73
+ false
74
+ end
75
+ end
76
+ @c_entity_probe || nil
77
+ end
78
+ module_function :nonstandard_entity?, :c_entity_probe
79
+
52
80
  NAMED_DECODE_MAP = {
53
81
  "amp" => "&", "lt" => "<", "gt" => ">",
54
82
  "quot" => '"', "apos" => "'"
@@ -87,11 +115,12 @@ module Moxml
87
115
  # Fast path: no `&` means no entity references to mark — skip
88
116
  # the regex scan and string allocation entirely. The vast
89
117
  # majority of XML payloads contain no entity references.
90
- return [str, false] unless str.include?("&")
91
118
  # Second fast path: only predefined entities — the gsub would
92
119
  # pass every match through unchanged, so skip its full-buffer
93
- # copy too.
94
- return [str, false] unless str.match?(NON_STANDARD_ENTITY_RE)
120
+ # copy too. When the leptris binding is loaded, its C probe
121
+ # (leptris-ruby#124) answers in one memcmp-class pass; the
122
+ # binding loads lazily, so the probe is resolved on first use.
123
+ return [str, false] unless nonstandard_entity?(str)
95
124
 
96
125
  marked = false
97
126
  processed = str.gsub(NAME_RE) do |match|
data/lib/moxml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.5.24"
4
+ VERSION = "0.5.25"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moxml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.24
4
+ version: 0.5.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-02 00:00:00.000000000 Z
11
+ date: 2026-09-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Moxml is a unified XML manipulation library that provides a common API