moxml 0.1.26 → 0.3.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 +4 -4
- data/.github/workflows/windows.yml +62 -0
- data/Gemfile +3 -1
- data/README.adoc +69 -6
- data/lib/compat/opal/moxml_boot.rb +6 -1
- data/lib/moxml/adapter/base.rb +53 -56
- data/lib/moxml/adapter/customized_leptris/declaration.rb +32 -0
- data/lib/moxml/adapter/customized_leptris/doctype.rb +31 -0
- data/lib/moxml/adapter/customized_leptris/entity_reference.rb +12 -0
- data/lib/moxml/adapter/customized_leptris/text_segment.rb +26 -0
- data/lib/moxml/adapter/customized_leptris.rb +16 -0
- data/lib/moxml/adapter/customized_libxml/cdata.rb +3 -12
- data/lib/moxml/adapter/customized_libxml/comment.rb +2 -9
- data/lib/moxml/adapter/customized_libxml/declaration.rb +1 -10
- data/lib/moxml/adapter/customized_libxml/node.rb +8 -0
- data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +2 -10
- data/lib/moxml/adapter/customized_libxml/text.rb +2 -9
- data/lib/moxml/adapter/customized_oga/entity_decoder.rb +37 -0
- data/lib/moxml/adapter/customized_oga/raw_value_override.rb +52 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +4 -34
- data/lib/moxml/adapter/customized_oga.rb +2 -0
- data/lib/moxml/adapter/customized_ox/entity_reference.rb +1 -17
- data/lib/moxml/adapter/customized_rexml/entity_reference.rb +1 -11
- data/lib/moxml/adapter/headed_ox.rb +9 -128
- data/lib/moxml/adapter/leptris.rb +965 -0
- data/lib/moxml/adapter/libxml.rb +137 -117
- data/lib/moxml/adapter/nokogiri.rb +29 -8
- data/lib/moxml/adapter/oga.rb +139 -55
- data/lib/moxml/adapter/ox.rb +93 -137
- data/lib/moxml/adapter/rexml.rb +64 -51
- data/lib/moxml/adapter.rb +2 -1
- data/lib/moxml/attribute.rb +8 -4
- data/lib/moxml/attribute_resolver.rb +189 -0
- data/lib/moxml/builder.rb +0 -18
- data/lib/moxml/config.rb +29 -5
- data/lib/moxml/context.rb +33 -0
- data/lib/moxml/document.rb +1 -1
- data/lib/moxml/element.rb +29 -12
- data/lib/moxml/entity/reference.rb +28 -0
- data/lib/moxml/entity/restorer.rb +95 -0
- data/lib/moxml/entity.rb +126 -0
- data/lib/moxml/entity_registry.rb +1 -1
- data/lib/moxml/error.rb +0 -42
- data/lib/moxml/node.rb +135 -10
- data/lib/moxml/node_set.rb +1 -1
- data/lib/moxml/sax/namespace_splitter.rb +5 -2
- data/lib/moxml/signature/model/key/dsa_key_value.rb +1 -2
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_emitter.rb +71 -0
- data/lib/moxml/xpath/compiler.rb +143 -8
- data/lib/moxml/xpath/parser.rb +22 -15
- data/lib/moxml.rb +3 -3
- data/spec/examples/readme_examples_spec.rb +0 -4
- data/spec/examples/xpath_examples_spec.rb +0 -11
- data/spec/integration/all_adapters_spec.rb +18 -1
- data/spec/integration/sax_parity_spec.rb +58 -0
- data/spec/integration/shared_examples/edge_cases.rb +0 -10
- data/spec/integration/shared_examples/high_level/{document_builder_behavior.rb → parse_behavior.rb} +1 -2
- data/spec/integration/shared_examples/integration_workflows.rb +0 -10
- data/spec/integration/shared_examples/node_wrappers/attribute_behavior.rb +98 -0
- data/spec/integration/shared_examples/node_wrappers/namespace_behavior.rb +26 -0
- data/spec/integration/shared_examples/node_wrappers/node_behavior.rb +15 -8
- data/spec/moxml/adapter/leptris_spec.rb +75 -0
- data/spec/moxml/adapter/libxml_internals_spec.rb +1 -1
- data/spec/moxml/adapter/ox_spec.rb +20 -6
- data/spec/moxml/adapter/platform_spec.rb +15 -2
- data/spec/moxml/adapter/shared_examples/adapter_contract.rb +180 -0
- data/spec/moxml/attribute_resolver_spec.rb +108 -0
- data/spec/moxml/config_spec.rb +0 -1
- data/spec/moxml/context_spec.rb +22 -0
- data/spec/moxml/doctype_spec.rb +1 -1
- data/spec/moxml/entity_spec.rb +76 -0
- data/spec/moxml/error_spec.rb +0 -50
- data/spec/moxml/node_spec.rb +104 -0
- data/spec/moxml/sax_entity_parity_spec.rb +358 -0
- data/spec/moxml/xml_emitter_spec.rb +64 -0
- data/spec/moxml/xpath/axes_spec.rb +112 -0
- data/spec/moxml/xpath/parser_spec.rb +6 -0
- data/spec/moxml/xpath_capabilities_spec.rb +5 -3
- data/spec/performance/benchmark_spec.rb +13 -6
- data/spec/performance/thread_safety_spec.rb +0 -4
- data/spec/spec_helper.rb +5 -1
- metadata +23 -6
- data/lib/moxml/adapter/libxml/entity_restorer.rb +0 -94
- data/lib/moxml/document_builder.rb +0 -167
- data/spec/moxml/document_builder_spec.rb +0 -30
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 51298c0a0ce4ed7c7868e294dc8174ada558b4a1c36c168bc18cdcfe2f728aec
|
|
4
|
+
data.tar.gz: a5e5ffce0b817e397bf98852aecfd9bb341d5154a94540f486432ea32dee8788
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf7d542cb81a21f1cfa517fafa2c07bdd996b9b7665ebf98e91c4e3f87ce79a1e1fa07f0d8f19837f6f45c73c36663ee54d602b70d72228a8de6e1ccb201a764
|
|
7
|
+
data.tar.gz: b971104eebb5bb3383bdacbc895d6cd54f0b4df400126f83f9e8002d80ec3e75c0afc1d3369d2c1277732b64326a2c0c044bb156078fdb6c3bb81a1c9c9fdeab
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: windows
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
adapters:
|
|
14
|
+
name: Adapter specs on Windows (libxml)
|
|
15
|
+
runs-on: windows-latest
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v6
|
|
19
|
+
with:
|
|
20
|
+
submodules: "recursive"
|
|
21
|
+
|
|
22
|
+
- name: Set up Ruby
|
|
23
|
+
uses: ruby/setup-ruby@v1
|
|
24
|
+
with:
|
|
25
|
+
ruby-version: "3.3"
|
|
26
|
+
bundler-cache: false
|
|
27
|
+
|
|
28
|
+
- name: Install libxml2 and build tools via MSYS2
|
|
29
|
+
shell: cmd
|
|
30
|
+
run: |
|
|
31
|
+
C:\msys64\usr\bin\pacman.exe -S --needed --noconfirm ^
|
|
32
|
+
mingw-w64-ucrt-x86_64-libxml2 ^
|
|
33
|
+
mingw-w64-ucrt-x86_64-libiconv ^
|
|
34
|
+
mingw-w64-ucrt-x86_64-zlib ^
|
|
35
|
+
mingw-w64-ucrt-x86_64-ragel ^
|
|
36
|
+
mingw-w64-ucrt-x86_64-make ^
|
|
37
|
+
mingw-w64-ucrt-x86_64-gcc
|
|
38
|
+
|
|
39
|
+
- name: Put MSYS2 UCRT64 toolchain on PATH
|
|
40
|
+
shell: pwsh
|
|
41
|
+
run: |
|
|
42
|
+
"PATH=C:\msys64\ucrt64\bin;C:\msys64\usr\bin;$env:PATH" >> $env:GITHUB_ENV
|
|
43
|
+
|
|
44
|
+
- name: Configure libxml-ruby build
|
|
45
|
+
shell: pwsh
|
|
46
|
+
run: |
|
|
47
|
+
bundle config build.libxml-ruby --with-xml2-include=C:/msys64/ucrt64/include/libxml2 --with-xml2-lib=C:/msys64/ucrt64/lib --with-iconv-include=C:/msys64/ucrt64/include --with-iconv-lib=C:/msys64/ucrt64/lib
|
|
48
|
+
|
|
49
|
+
- name: Generate ragel / ruby-ll outputs in vendored forks
|
|
50
|
+
shell: pwsh
|
|
51
|
+
run: |
|
|
52
|
+
gem install ruby-ll --no-document
|
|
53
|
+
rake vendor:prepare
|
|
54
|
+
|
|
55
|
+
- name: Install dependencies
|
|
56
|
+
run: bundle install --jobs 4
|
|
57
|
+
|
|
58
|
+
- name: Compile vendored native extensions
|
|
59
|
+
run: bundle exec rake vendor:compile
|
|
60
|
+
|
|
61
|
+
- name: Run adapter specs
|
|
62
|
+
run: bundle exec rake spec:adapter
|
data/Gemfile
CHANGED
|
@@ -9,6 +9,7 @@ gemspec
|
|
|
9
9
|
gem "benchmark"
|
|
10
10
|
gem "benchmark-ips"
|
|
11
11
|
gem "get_process_mem"
|
|
12
|
+
gem "leptris", "~> 1.1"
|
|
12
13
|
gem "libxml-ruby", "~> 5.0"
|
|
13
14
|
gem "nokogiri", "~> 1.18"
|
|
14
15
|
gem "openssl", "~> 3.0"
|
|
@@ -30,7 +31,8 @@ gem "rubocop-performance"
|
|
|
30
31
|
gem "rubocop-rake"
|
|
31
32
|
gem "rubocop-rspec"
|
|
32
33
|
gem "simplecov", require: false
|
|
33
|
-
|
|
34
|
+
# stackprof relies on POSIX signals (SIGPROF/sigaction) unavailable on Windows
|
|
35
|
+
gem "stackprof" unless Gem.win_platform?
|
|
34
36
|
gem "tempfile"
|
|
35
37
|
|
|
36
38
|
# Needed by get_process_mem on Windows
|
data/README.adoc
CHANGED
|
@@ -47,12 +47,21 @@ LibXML:: https://github.com/xml4r/libxml-ruby[libxml-ruby], Ruby bindings
|
|
|
47
47
|
for the performant https://github.com/GNOME/libxml2[libxml2] C library.
|
|
48
48
|
Alternative to Nokogiri with similar performance characteristics.
|
|
49
49
|
|
|
50
|
+
Leptris:: https://github.com/leptris/leptris[Leptris], a pure-C99 XML 1.0
|
|
51
|
+
parser with a W3C-conformant XPath 1.0 engine, consumed through the
|
|
52
|
+
https://github.com/leptris/leptris-ruby[leptris-ruby] FFI binding
|
|
53
|
+
(precompiled platform gems — no compilation at install). Fastest on
|
|
54
|
+
every measured operation; requires leptris >= 1.2 for programmatic
|
|
55
|
+
document construction.
|
|
56
|
+
|
|
50
57
|
==== Default adapter selection
|
|
51
58
|
|
|
52
59
|
When no adapter is explicitly specified, Moxml selects one automatically based on
|
|
53
60
|
the runtime environment:
|
|
54
61
|
|
|
55
62
|
. If running on Opal (`RUBY_ENGINE == "opal"`), Oga is used (pure Ruby, no C extensions)
|
|
63
|
+
. Otherwise, if the installed leptris gem supports programmatic document
|
|
64
|
+
construction (`Leptris::XML::Document.create`, leptris >= 1.2), Leptris is used
|
|
56
65
|
. Otherwise, Moxml detects already-loaded XML libraries in this order:
|
|
57
66
|
.. Nokogiri
|
|
58
67
|
.. Ox
|
|
@@ -63,7 +72,7 @@ the runtime environment:
|
|
|
63
72
|
----
|
|
64
73
|
# Automatic detection — picks the best available adapter
|
|
65
74
|
ctx = Moxml.new
|
|
66
|
-
ctx.config.adapter_name # => :
|
|
75
|
+
ctx.config.adapter_name # => :leptris (leptris >= 1.2), :nokogiri otherwise, :oga on Opal
|
|
67
76
|
|
|
68
77
|
# Explicit override — always takes precedence
|
|
69
78
|
ctx = Moxml.new(:ox)
|
|
@@ -80,9 +89,9 @@ The following table summarizes the features supported by each library.
|
|
|
80
89
|
NOTE: The checkmarks indicate support for the feature, while the footnotes
|
|
81
90
|
provide additional context for specific features.
|
|
82
91
|
|
|
83
|
-
[cols="1,1,1,1,1,3"]
|
|
92
|
+
[cols="1,1,1,1,1,1,1,3"]
|
|
84
93
|
|===
|
|
85
|
-
|Feature |Nokogiri |Oga |REXML |LibXML |Ox |HeadedOx
|
|
94
|
+
|Feature |Nokogiri |Oga |REXML |LibXML |Ox |HeadedOx |Leptris
|
|
86
95
|
|
|
87
96
|
|Parsing, serializing
|
|
88
97
|
| ✅
|
|
@@ -91,6 +100,7 @@ provide additional context for specific features.
|
|
|
91
100
|
| ✅
|
|
92
101
|
| ✅
|
|
93
102
|
| ✅
|
|
103
|
+
| ✅
|
|
94
104
|
|
|
95
105
|
|SAX parsing
|
|
96
106
|
| ✅ Full (10/10 events)
|
|
@@ -99,6 +109,7 @@ provide additional context for specific features.
|
|
|
99
109
|
| ✅ Full (10/10 events)
|
|
100
110
|
| ⚠️ Core (4/10 events) See NOTE 7.
|
|
101
111
|
| ⚠️ Core (4/10 events) See NOTE 7.
|
|
112
|
+
| ✅ Full See NOTE 8.
|
|
102
113
|
|
|
103
114
|
|Node manipulation
|
|
104
115
|
| ✅
|
|
@@ -107,6 +118,7 @@ provide additional context for specific features.
|
|
|
107
118
|
| ✅
|
|
108
119
|
| ✅ See NOTE 1.
|
|
109
120
|
| ✅ See NOTE 1.
|
|
121
|
+
| ✅
|
|
110
122
|
|
|
111
123
|
|Basic XPath
|
|
112
124
|
| ✅
|
|
@@ -115,6 +127,8 @@ provide additional context for specific features.
|
|
|
115
127
|
| ✅
|
|
116
128
|
| Uses Ox-specific API `locate`. See NOTE 2.
|
|
117
129
|
| ✅ Full XPath 1.0. See NOTE 3.
|
|
130
|
+
| ✅ Full XPath 1.0 via Moxml's Ruby engine. See NOTE 8.
|
|
131
|
+
|
|
118
132
|
|
|
119
133
|
|XPath with namespaces
|
|
120
134
|
| ✅
|
|
@@ -123,6 +137,7 @@ provide additional context for specific features.
|
|
|
123
137
|
| ✅
|
|
124
138
|
| Uses Ox-specific API `locate`. See NOTE 2.
|
|
125
139
|
| ⚠️ Basic. See NOTE 3.
|
|
140
|
+
| ✅ Full See NOTE 8.
|
|
126
141
|
|
|
127
142
|
|===
|
|
128
143
|
|
|
@@ -139,15 +154,23 @@ NOTE: Ox/HeadedOx SAX: Only core events supported (start_element, end_element,
|
|
|
139
154
|
characters, errors). No separate CDATA, comment, or processing instruction
|
|
140
155
|
events.
|
|
141
156
|
|
|
157
|
+
NOTE: Leptris: XPath is evaluated by Moxml's pure-Ruby XPath 1.0 engine
|
|
158
|
+
(same as HeadedOx) rather than libleptris's native engine, because the
|
|
159
|
+
native engine does not resolve expression prefixes against
|
|
160
|
+
document-declared namespaces. XML declarations, programmatic DOCTYPEs,
|
|
161
|
+
and entity references are value objects stored via NativeAttachment —
|
|
162
|
+
libleptris has no first-class nodes for them.
|
|
163
|
+
|
|
142
164
|
== Adapter comparison
|
|
143
165
|
|
|
144
166
|
=== Feature compatibility matrix
|
|
145
167
|
|
|
146
|
-
[cols="3,1,1,1,1,1,1", options="header"]
|
|
168
|
+
[cols="3,1,1,1,1,1,1,1", options="header"]
|
|
147
169
|
|===
|
|
148
|
-
| Feature/Operation | Nokogiri | Oga | REXML | LibXML | Ox | HeadedOx
|
|
170
|
+
| Feature/Operation | Nokogiri | Oga | REXML | LibXML | Ox | HeadedOx | Leptris
|
|
149
171
|
|
|
150
172
|
| *Core Operations*
|
|
173
|
+
|
|
151
174
|
|
|
|
152
175
|
|
|
|
153
176
|
|
|
|
@@ -162,6 +185,7 @@ events.
|
|
|
162
185
|
| ✅ Full
|
|
163
186
|
| ✅ Full
|
|
164
187
|
| ✅ Full
|
|
188
|
+
| ✅ Full
|
|
165
189
|
|
|
166
190
|
| Parse XML file/IO
|
|
167
191
|
| ✅ Full
|
|
@@ -170,6 +194,7 @@ events.
|
|
|
170
194
|
| ✅ Full
|
|
171
195
|
| ✅ Full
|
|
172
196
|
| ✅ Full
|
|
197
|
+
| ✅ Full
|
|
173
198
|
|
|
174
199
|
| Serialize to XML
|
|
175
200
|
| ✅ Full
|
|
@@ -178,9 +203,12 @@ events.
|
|
|
178
203
|
| ✅ Full
|
|
179
204
|
| ✅ Full
|
|
180
205
|
| ✅ Full
|
|
206
|
+
| ✅ Full
|
|
181
207
|
|
|
182
208
|
| *Element Operations*
|
|
209
|
+
|
|
183
210
|
|
|
|
211
|
+
|
|
184
212
|
|
|
|
185
213
|
|
|
|
186
214
|
|
|
|
@@ -194,6 +222,7 @@ events.
|
|
|
194
222
|
| ✅ Full
|
|
195
223
|
| ✅ Full
|
|
196
224
|
| ✅ Full
|
|
225
|
+
| ✅ Full
|
|
197
226
|
|
|
198
227
|
| Get/set attributes
|
|
199
228
|
| ✅ Full
|
|
@@ -202,6 +231,7 @@ events.
|
|
|
202
231
|
| ✅ Full
|
|
203
232
|
| ✅ Full
|
|
204
233
|
| ✅ Full
|
|
234
|
+
| ✅ Full
|
|
205
235
|
|
|
206
236
|
| Add/remove children
|
|
207
237
|
| ✅ Full
|
|
@@ -210,6 +240,7 @@ events.
|
|
|
210
240
|
| ✅ Full
|
|
211
241
|
| ✅ Full
|
|
212
242
|
| ✅ Full
|
|
243
|
+
| ✅ Full
|
|
213
244
|
|
|
214
245
|
| Replace nodes
|
|
215
246
|
| ✅ Full
|
|
@@ -218,9 +249,12 @@ events.
|
|
|
218
249
|
| ✅ Full
|
|
219
250
|
| ⚠️ Limited^1^
|
|
220
251
|
| ⚠️ Limited^1^
|
|
252
|
+
| ✅ Full
|
|
221
253
|
|
|
222
254
|
| *Namespace Operations*
|
|
255
|
+
|
|
223
256
|
|
|
|
257
|
+
|
|
224
258
|
|
|
|
225
259
|
|
|
|
226
260
|
|
|
|
@@ -234,6 +268,7 @@ events.
|
|
|
234
268
|
| ✅ Full
|
|
235
269
|
| ✅ Full
|
|
236
270
|
| ✅ Full
|
|
271
|
+
| ✅ Full
|
|
237
272
|
|
|
238
273
|
| Default namespaces
|
|
239
274
|
| ✅ Full
|
|
@@ -242,6 +277,7 @@ events.
|
|
|
242
277
|
| ✅ Full
|
|
243
278
|
| ⚠️ Basic
|
|
244
279
|
| ⚠️ Basic
|
|
280
|
+
| ✅ Full
|
|
245
281
|
|
|
246
282
|
| Namespace inheritance
|
|
247
283
|
| ✅ Full
|
|
@@ -250,6 +286,7 @@ events.
|
|
|
250
286
|
| ✅ Full
|
|
251
287
|
| ❌ None
|
|
252
288
|
| ❌ None^5^
|
|
289
|
+
| ✅ Full
|
|
253
290
|
|
|
254
291
|
| Namespaced attributes
|
|
255
292
|
| ✅ Full
|
|
@@ -258,9 +295,12 @@ events.
|
|
|
258
295
|
| ✅ Full
|
|
259
296
|
| ⚠️ Limited
|
|
260
297
|
| ⚠️ Limited^5^
|
|
298
|
+
| ✅ Full
|
|
261
299
|
|
|
262
300
|
| *XPath Queries*
|
|
301
|
+
|
|
263
302
|
|
|
|
303
|
+
|
|
264
304
|
|
|
|
265
305
|
|
|
|
266
306
|
|
|
|
@@ -274,6 +314,7 @@ events.
|
|
|
274
314
|
| ✅ Full
|
|
275
315
|
| ✅ Full
|
|
276
316
|
| ✅ Full
|
|
317
|
+
| ✅ Full
|
|
277
318
|
|
|
278
319
|
| Attribute predicates (`[@id]`)
|
|
279
320
|
| ✅ Full
|
|
@@ -282,6 +323,7 @@ events.
|
|
|
282
323
|
| ✅ Full
|
|
283
324
|
| ⚠️ Existence only^2^
|
|
284
325
|
| ✅ Full
|
|
326
|
+
| ✅ Full
|
|
285
327
|
|
|
286
328
|
| Attribute values (`[@id='123']`)
|
|
287
329
|
| ✅ Full
|
|
@@ -290,6 +332,7 @@ events.
|
|
|
290
332
|
| ✅ Full
|
|
291
333
|
| ❌ None^3^
|
|
292
334
|
| ✅ Full
|
|
335
|
+
| ✅ Full
|
|
293
336
|
|
|
294
337
|
| Logical operators (`[@a and @b]`)
|
|
295
338
|
| ✅ Full
|
|
@@ -298,6 +341,7 @@ events.
|
|
|
298
341
|
| ✅ Full
|
|
299
342
|
| ❌ None
|
|
300
343
|
| ✅ Full
|
|
344
|
+
| ✅ Full
|
|
301
345
|
|
|
302
346
|
| Position predicates (`[1]`, `[last()]`)
|
|
303
347
|
| ✅ Full
|
|
@@ -306,6 +350,7 @@ events.
|
|
|
306
350
|
| ✅ Full
|
|
307
351
|
| ❌ None
|
|
308
352
|
| ✅ Full
|
|
353
|
+
| ✅ Full
|
|
309
354
|
|
|
310
355
|
| Text predicates (`[text()='x']`)
|
|
311
356
|
| ✅ Full
|
|
@@ -314,6 +359,7 @@ events.
|
|
|
314
359
|
| ✅ Full
|
|
315
360
|
| ❌ None
|
|
316
361
|
| ✅ Full
|
|
362
|
+
| ✅ Full
|
|
317
363
|
|
|
318
364
|
| Namespace-aware queries
|
|
319
365
|
| ✅ Full
|
|
@@ -322,6 +368,7 @@ events.
|
|
|
322
368
|
| ✅ Full
|
|
323
369
|
| ❌ None
|
|
324
370
|
| ⚠️ Basic^5^
|
|
371
|
+
| ✅ Full
|
|
325
372
|
|
|
326
373
|
| Parent axis (`..`)
|
|
327
374
|
| ✅ Full
|
|
@@ -330,6 +377,7 @@ events.
|
|
|
330
377
|
| ✅ Full
|
|
331
378
|
| ❌ None
|
|
332
379
|
| ✅ Full
|
|
380
|
+
| ✅ Full
|
|
333
381
|
|
|
334
382
|
| Sibling axes
|
|
335
383
|
| ✅ Full
|
|
@@ -338,6 +386,7 @@ events.
|
|
|
338
386
|
| ✅ Full
|
|
339
387
|
| ❌ None
|
|
340
388
|
| ❌ None^5^
|
|
389
|
+
| ✅ Full
|
|
341
390
|
|
|
342
391
|
| XPath functions (`count()`, etc.)
|
|
343
392
|
| ✅ Full
|
|
@@ -346,9 +395,12 @@ events.
|
|
|
346
395
|
| ✅ Full
|
|
347
396
|
| ❌ None
|
|
348
397
|
| ✅ All 27
|
|
398
|
+
| ✅ All 27
|
|
349
399
|
|
|
350
400
|
| *Special Content*
|
|
401
|
+
|
|
351
402
|
|
|
|
403
|
+
|
|
352
404
|
|
|
|
353
405
|
|
|
|
354
406
|
|
|
|
@@ -362,6 +414,7 @@ events.
|
|
|
362
414
|
| ✅ Full
|
|
363
415
|
| ✅ Full
|
|
364
416
|
| ✅ Full
|
|
417
|
+
| ✅ Full
|
|
365
418
|
|
|
366
419
|
| Comments
|
|
367
420
|
| ✅ Full
|
|
@@ -370,6 +423,7 @@ events.
|
|
|
370
423
|
| ✅ Full
|
|
371
424
|
| ✅ Full
|
|
372
425
|
| ✅ Full
|
|
426
|
+
| ✅ Full
|
|
373
427
|
|
|
374
428
|
| Processing instructions
|
|
375
429
|
| ✅ Full
|
|
@@ -378,6 +432,7 @@ events.
|
|
|
378
432
|
| ✅ Full
|
|
379
433
|
| ✅ Full
|
|
380
434
|
| ✅ Full
|
|
435
|
+
| ✅ Full
|
|
381
436
|
|
|
382
437
|
| DOCTYPE declarations
|
|
383
438
|
| ✅ Full
|
|
@@ -386,9 +441,12 @@ events.
|
|
|
386
441
|
| ✅ Full
|
|
387
442
|
| ✅ Full
|
|
388
443
|
| ✅ Full
|
|
444
|
+
| ⚠️ Value-object sidecar^8^
|
|
389
445
|
|
|
390
446
|
| *Performance*
|
|
447
|
+
|
|
391
448
|
|
|
|
449
|
+
|
|
392
450
|
|
|
|
393
451
|
|
|
|
394
452
|
|
|
|
@@ -402,6 +460,7 @@ events.
|
|
|
402
460
|
| Fast
|
|
403
461
|
| Very Fast
|
|
404
462
|
| Very Fast
|
|
463
|
+
| Fastest
|
|
405
464
|
|
|
406
465
|
| Serialize speed
|
|
407
466
|
| Fast
|
|
@@ -410,6 +469,7 @@ events.
|
|
|
410
469
|
| Medium
|
|
411
470
|
| Very Fast
|
|
412
471
|
| Very Fast
|
|
472
|
+
| Very Fast
|
|
413
473
|
|
|
414
474
|
| Memory usage
|
|
415
475
|
| Good
|
|
@@ -418,6 +478,7 @@ events.
|
|
|
418
478
|
| Good
|
|
419
479
|
| Excellent
|
|
420
480
|
| Excellent
|
|
481
|
+
| Excellent
|
|
421
482
|
|
|
422
483
|
| Thread safety
|
|
423
484
|
| ✅ Yes
|
|
@@ -426,13 +487,15 @@ events.
|
|
|
426
487
|
| ✅ Yes
|
|
427
488
|
| ✅ Yes
|
|
428
489
|
| ✅ Yes
|
|
490
|
+
| ⚠️ One doc per thread^8^
|
|
429
491
|
|===
|
|
430
492
|
+
|
|
431
493
|
^1^ Ox/HeadedOx: Text node replacement may fail in some cases due to internal node structure +
|
|
432
494
|
^2^ Ox: `//book[@id]` works (returns all book elements), but doesn't filter by attribute existence +
|
|
433
495
|
^3^ HeadedOx: Full XPath 1.0 with all 27 functions and 6 axes. Pure Ruby XPath engine on Ox's C parser. 99.20% pass rate. See link:docs/headed-ox.adoc[] +
|
|
434
496
|
^4^ Ox: Use `.find { |el| el["id"] == "123" }` instead of XPath attribute value predicates +
|
|
435
|
-
^5^ HeadedOx limitations: Namespace introspection and 7 axes not implemented. See link:docs/HEADED_OX_LIMITATIONS.md[]
|
|
497
|
+
^5^ HeadedOx limitations: Namespace introspection and 7 axes not implemented. See link:docs/HEADED_OX_LIMITATIONS.md[] +
|
|
498
|
+
^8^ Leptris: XPath runs through Moxml's pure-Ruby XPath 1.0 engine (the native C engine does not resolve expression prefixes against document namespaces); DOCTYPEs and XML declarations are value objects stored via NativeAttachment; libleptris documents are not thread-safe — one document per thread
|
|
436
499
|
|
|
437
500
|
=== Adapter selection guide
|
|
438
501
|
|
|
@@ -24,6 +24,10 @@ require "moxml/namespace"
|
|
|
24
24
|
require "moxml/doctype"
|
|
25
25
|
require "moxml/entity_reference"
|
|
26
26
|
require "moxml/entity_registry"
|
|
27
|
+
require "moxml/entity"
|
|
28
|
+
require "moxml/entity/restorer"
|
|
29
|
+
require "moxml/entity/reference"
|
|
30
|
+
require "moxml/xml_emitter"
|
|
27
31
|
require "moxml/adapter"
|
|
28
32
|
require "moxml/adapter/base"
|
|
29
33
|
require "moxml/adapter/customized_rexml"
|
|
@@ -36,10 +40,11 @@ require "moxml/sax/block_handler"
|
|
|
36
40
|
require "moxml/sax/namespace_splitter"
|
|
37
41
|
require "moxml/adapter/rexml"
|
|
38
42
|
require "moxml/adapter/customized_oga"
|
|
43
|
+
require "moxml/adapter/customized_oga/entity_decoder"
|
|
44
|
+
require "moxml/adapter/customized_oga/raw_value_override"
|
|
39
45
|
require "moxml/adapter/customized_oga/xml_declaration"
|
|
40
46
|
require "moxml/adapter/customized_oga/xml_generator"
|
|
41
47
|
require "moxml/adapter/oga"
|
|
42
|
-
require "moxml/document_builder"
|
|
43
48
|
require "moxml/builder"
|
|
44
49
|
require "moxml/context"
|
|
45
50
|
require "moxml/config"
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -5,62 +5,29 @@ module Moxml
|
|
|
5
5
|
class Base
|
|
6
6
|
# include XmlUtils
|
|
7
7
|
|
|
8
|
-
# Entity
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
ENTITY_NAME_RE = /&(#{ENTITY_NAME_PATTERN});/
|
|
18
|
-
ENTITY_MARKER_RE = /\u{FFFC}\u{FEFF}(#{ENTITY_NAME_PATTERN});/
|
|
19
|
-
SERIALIZED_ENTITY_MARKER_RE = /(#{ENTITY_NAME_PATTERN});/
|
|
20
|
-
STANDARD_ENTITIES = %w[amp lt gt quot apos].freeze
|
|
8
|
+
# Entity round-trip pipeline lives in Moxml::Entity; these
|
|
9
|
+
# constants and methods stay on the adapter protocol so
|
|
10
|
+
# adapters and wrappers keep their calling convention.
|
|
11
|
+
ENTITY_MARKER = Entity::MARKER
|
|
12
|
+
ENTITY_NAME_PATTERN = Entity::NAME_PATTERN
|
|
13
|
+
ENTITY_NAME_RE = Entity::NAME_RE
|
|
14
|
+
ENTITY_MARKER_RE = Entity::MARKER_RE
|
|
15
|
+
SERIALIZED_ENTITY_MARKER_RE = Entity::SERIALIZED_MARKER_RE
|
|
16
|
+
STANDARD_ENTITIES = Entity::STANDARD_ENTITIES
|
|
21
17
|
|
|
22
18
|
class << self
|
|
23
19
|
include XmlUtils
|
|
24
20
|
|
|
25
|
-
# Replace non-standard entity references with markers before parsing.
|
|
26
|
-
# Always returns a UTF-8 encoded string.
|
|
27
21
|
def preprocess_entities(xml)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
str = if xml.encoding == Encoding::BINARY
|
|
31
|
-
# Binary strings are assumed to be UTF-8. If the bytes are
|
|
32
|
-
# not valid UTF-8, fall back to encoding as UTF-8 with
|
|
33
|
-
# replacement to avoid raising on gsub.
|
|
34
|
-
dup = xml.dup.force_encoding("UTF-8")
|
|
35
|
-
if dup.valid_encoding?
|
|
36
|
-
dup
|
|
37
|
-
else
|
|
38
|
-
xml.dup.encode("UTF-8",
|
|
39
|
-
"ASCII-8BIT", invalid: :replace, undef: :replace)
|
|
40
|
-
end
|
|
41
|
-
elsif xml.encoding == Encoding::UTF_8
|
|
42
|
-
xml
|
|
43
|
-
else
|
|
44
|
-
xml.encode("UTF-8")
|
|
45
|
-
end
|
|
46
|
-
# Fast path: no `&` means no entity references to mark — skip
|
|
47
|
-
# the regex scan and string allocation entirely. The vast
|
|
48
|
-
# majority of XML payloads contain no entity references.
|
|
49
|
-
return str unless str.include?("&")
|
|
50
|
-
|
|
51
|
-
str.gsub(ENTITY_NAME_RE) do |match|
|
|
52
|
-
STANDARD_ENTITIES.include?(::Regexp.last_match(1)) ? match : "#{ENTITY_MARKER}#{::Regexp.last_match(1)};"
|
|
53
|
-
end
|
|
22
|
+
Entity.preprocess_entities(xml)
|
|
54
23
|
end
|
|
55
24
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
25
|
+
def decode_entities(text)
|
|
26
|
+
Entity.decode_entities(text)
|
|
27
|
+
end
|
|
59
28
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
result = str.gsub(ENTITY_MARKER_RE, '&\1;')
|
|
63
|
-
result.gsub(SERIALIZED_ENTITY_MARKER_RE, '&\1;')
|
|
29
|
+
def restore_entities(text)
|
|
30
|
+
Entity.restore_entities(text)
|
|
64
31
|
end
|
|
65
32
|
|
|
66
33
|
def set_root(_doc, _element)
|
|
@@ -71,7 +38,7 @@ module Moxml
|
|
|
71
38
|
)
|
|
72
39
|
end
|
|
73
40
|
|
|
74
|
-
def parse(_xml, _options = {})
|
|
41
|
+
def parse(_xml, _options = {}, _context = nil)
|
|
75
42
|
raise Moxml::NotImplementedError.new(
|
|
76
43
|
"parse not implemented",
|
|
77
44
|
feature: "parse",
|
|
@@ -172,12 +139,35 @@ namespace_validation_mode: :strict)
|
|
|
172
139
|
create_native_entity_reference(name)
|
|
173
140
|
end
|
|
174
141
|
|
|
142
|
+
# Mutation return contract: protocol methods that may change
|
|
143
|
+
# which native a wrapper tracks (set_attribute_name,
|
|
144
|
+
# set_namespace, set_attribute_value) always return the native
|
|
145
|
+
# the wrapper must keep tracking — the same object when
|
|
146
|
+
# mutated in place, a fresh object when the adapter recreates
|
|
147
|
+
# the node.
|
|
175
148
|
def set_attribute_name(attribute, name)
|
|
176
149
|
attribute.name = name
|
|
150
|
+
attribute
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def set_namespace(_node, _namespace)
|
|
154
|
+
raise Moxml::NotImplementedError.new(
|
|
155
|
+
"set_namespace not implemented",
|
|
156
|
+
feature: "set_namespace",
|
|
157
|
+
adapter: name,
|
|
158
|
+
)
|
|
177
159
|
end
|
|
178
160
|
|
|
179
161
|
def set_attribute_value(attribute, value)
|
|
180
162
|
attribute.value = value
|
|
163
|
+
attribute
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Remove a specific native attribute node from its owning
|
|
167
|
+
# element. Semantics (which attribute a name addresses) live
|
|
168
|
+
# in Moxml::AttributeResolver; this is the raw primitive.
|
|
169
|
+
def remove_attribute_native(attr)
|
|
170
|
+
attr.remove
|
|
181
171
|
end
|
|
182
172
|
|
|
183
173
|
def entity_reference_name(node)
|
|
@@ -193,13 +183,6 @@ namespace_validation_mode: :strict)
|
|
|
193
183
|
node
|
|
194
184
|
end
|
|
195
185
|
|
|
196
|
-
def prepare_for_new_document(node, _target_doc)
|
|
197
|
-
# Hook for adapters that need special handling when moving nodes
|
|
198
|
-
# between documents (e.g., LibXML's document.import)
|
|
199
|
-
# Default: no-op for backward compatibility
|
|
200
|
-
node
|
|
201
|
-
end
|
|
202
|
-
|
|
203
186
|
# Check if the native document has an XML declaration
|
|
204
187
|
# @param native_doc the native document object
|
|
205
188
|
# @param wrapper [Moxml::Document] the wrapper with has_xml_declaration flag
|
|
@@ -212,6 +195,20 @@ namespace_validation_mode: :strict)
|
|
|
212
195
|
# Called when a Declaration node is removed from a document.
|
|
213
196
|
def remove_declaration(_native_doc); end
|
|
214
197
|
|
|
198
|
+
# Source line of a native node (1-based), or nil when the
|
|
199
|
+
# underlying backend does not track source positions.
|
|
200
|
+
# Adapters that track lines (Nokogiri, LibXML) override this.
|
|
201
|
+
def line_number(_node)
|
|
202
|
+
nil
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Local name of a native attribute node. Adapters whose natives
|
|
206
|
+
# carry qualified names override this to expose the local part;
|
|
207
|
+
# the wrapper composes the prefix.
|
|
208
|
+
def attribute_name(attr)
|
|
209
|
+
attr.name
|
|
210
|
+
end
|
|
211
|
+
|
|
215
212
|
# Return the actual native node after an add_child operation.
|
|
216
213
|
# Override for adapters where node identity may change (e.g., LibXML doc.root=).
|
|
217
214
|
def actual_native(child_native, _parent_native)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module Adapter
|
|
5
|
+
module CustomizedLeptris
|
|
6
|
+
# Wrapper for an XML declaration. libleptris stores declaration
|
|
7
|
+
# data on the document struct (read-only, serialized via options)
|
|
8
|
+
# and has no first-class declaration node, so Moxml exposes this
|
|
9
|
+
# mutable value object instead.
|
|
10
|
+
class Declaration
|
|
11
|
+
attr_accessor :version, :encoding, :standalone, :parent_doc
|
|
12
|
+
|
|
13
|
+
def initialize(version = "1.0", encoding = "UTF-8", standalone = nil)
|
|
14
|
+
@version = version
|
|
15
|
+
@encoding = encoding
|
|
16
|
+
@standalone = standalone
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_xml
|
|
20
|
+
XmlEmitter.declaration_xml(version, encoding, standalone)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ==(other)
|
|
24
|
+
other.is_a?(self.class) &&
|
|
25
|
+
version == other.version &&
|
|
26
|
+
encoding == other.encoding &&
|
|
27
|
+
standalone == other.standalone
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module Adapter
|
|
5
|
+
module CustomizedLeptris
|
|
6
|
+
# Wrapper for a programmatic DOCTYPE. libleptris only parses
|
|
7
|
+
# DOCTYPEs from source; it has no API to create one, so Moxml
|
|
8
|
+
# stores this value object in the document's attachments.
|
|
9
|
+
class Doctype
|
|
10
|
+
attr_accessor :name, :external_id, :system_id, :parent_doc
|
|
11
|
+
|
|
12
|
+
def initialize(name, external_id = nil, system_id = nil)
|
|
13
|
+
@name = name
|
|
14
|
+
@external_id = external_id
|
|
15
|
+
@system_id = system_id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_xml
|
|
19
|
+
XmlEmitter.doctype_xml(name, external_id, system_id)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ==(other)
|
|
23
|
+
other.is_a?(self.class) &&
|
|
24
|
+
name == other.name &&
|
|
25
|
+
external_id == other.external_id &&
|
|
26
|
+
system_id == other.system_id
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|