moxml 0.5.129 → 0.5.131
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/Gemfile +1 -1
- data/lib/moxml/adapter/leptris/materialize.rb +13 -0
- data/lib/moxml/adapter/leptris.rb +48 -7
- data/lib/moxml/version.rb +1 -1
- data/spec/moxml/adapter/leptris_spec.rb +18 -5
- data/spec/moxml/materialize_namespace_uri_spec.rb +35 -0
- data/spec/moxml/recover_mode_spec.rb +61 -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: 2de6081e549de3acc9fe0fe2697d0f44e137673eaa311dc9beec4de6346dfb54
|
|
4
|
+
data.tar.gz: 026a76efc77132825449057e48559626e3f3ec4166d54fb5ea817d51accb830e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 369b3b0ca0527711f87e4b18f9b268c503612c9aed6aa8ea4fcfcb6dd9ad835be5cbb80b9ff00b0606b3df908bb150a0ef22bba6041157bfdb41a0bd8264a153
|
|
7
|
+
data.tar.gz: a86491ce96b541420ddf36fc2cbe1324c2f283d212d0271cb2eb61129bca15258dd7d92dbe7a022c6e3e4055140fb395111c96e8518ebd5b5d882a036186ae33
|
data/Gemfile
CHANGED
|
@@ -23,6 +23,18 @@ module Moxml
|
|
|
23
23
|
CHILDREN_CAPACITY = 64
|
|
24
24
|
private_constant :CHILDREN_CAPACITY
|
|
25
25
|
|
|
26
|
+
# #367: HTML-parsed trees carry no namespace declarations,
|
|
27
|
+
# so the engine's attribute-URI face answers nil for the
|
|
28
|
+
# RESERVED prefixes too (xml:lang loses its identity). The
|
|
29
|
+
# wrapper path resolves them from the builtin table
|
|
30
|
+
# (AttributeResolver) — the bulk stride mirrors that, so
|
|
31
|
+
# records and wrapper API agree on every tree flavor.
|
|
32
|
+
RESERVED_ATTR_PREFIX_URIS = {
|
|
33
|
+
"xml" => "http://www.w3.org/XML/1998/namespace",
|
|
34
|
+
"xmlns" => "http://www.w3.org/2000/xmlns/",
|
|
35
|
+
}.freeze
|
|
36
|
+
private_constant :RESERVED_ATTR_PREFIX_URIS
|
|
37
|
+
|
|
26
38
|
# Bulk materialization (issues #132/#143): a Ruby-side
|
|
27
39
|
# post-order recursion over RAW C pointers — no binding
|
|
28
40
|
# wrapper per node, no per-node FFI callback, no depth memo
|
|
@@ -132,6 +144,7 @@ module Moxml
|
|
|
132
144
|
# path's resolver semantics (moxml's canonical shape).
|
|
133
145
|
attr_name = attr_name[(colon + 1)..]
|
|
134
146
|
attr_uri = f.leptris_attribute_namespace_uri(attr)
|
|
147
|
+
attr_uri ||= RESERVED_ATTR_PREFIX_URIS[attr_prefix]
|
|
135
148
|
else
|
|
136
149
|
attr_prefix = nil
|
|
137
150
|
attr_uri = nil
|
|
@@ -182,6 +182,18 @@ module Moxml
|
|
|
182
182
|
defined?(::Leptris::HTML) &&
|
|
183
183
|
::Leptris::HTML.respond_to?(:parse_fragment)
|
|
184
184
|
|
|
185
|
+
# The engine's recovery mode (moxml#369, engine leptris#1660
|
|
186
|
+
# via binding 1.9.344.0): non-strict parses pass recover:
|
|
187
|
+
# true and keep the best-effort tree (libxml2
|
|
188
|
+
# XML_PARSE_RECOVER semantics — mismatched closers close
|
|
189
|
+
# intermediates, unclosed elements close at EOF) instead of
|
|
190
|
+
# the rescue-to-empty fallback. The kwarg probe pairs the
|
|
191
|
+
# floor (a binding without the kwarg cannot arm).
|
|
192
|
+
RECOVER_MODE =
|
|
193
|
+
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.344.0") &&
|
|
194
|
+
::Leptris::XML::ParseOptions.instance_method(:initialize)
|
|
195
|
+
.parameters.any? { |_, key| key == :recover }
|
|
196
|
+
|
|
185
197
|
# Markup-accumulating builder face (leptris-ruby#374 lever 2,
|
|
186
198
|
# binding 1.9.313): the DSL accumulates markup in a plain
|
|
187
199
|
# String and ONE native crossing (parse + attach) flushes the
|
|
@@ -1031,23 +1043,45 @@ module Moxml
|
|
|
1031
1043
|
# multi-pass consumers (comparison, diff, signature).
|
|
1032
1044
|
# dtdattr: true opts into DTD ATTLIST default materialization
|
|
1033
1045
|
# (off by default since libleptris 1.9.8, matching libxml2).
|
|
1046
|
+
# #369: non-strict parses RECOVER on engines that carry
|
|
1047
|
+
# the mode (1.9.344+) — the best-effort tree survives
|
|
1048
|
+
# recoverable structural errors (libxml2
|
|
1049
|
+
# XML_PARSE_RECOVER semantics). Strict callers never
|
|
1050
|
+
# recover: malformed input raises. Direct adapter calls
|
|
1051
|
+
# are caller-owned-options by contract (Context applies
|
|
1052
|
+
# config.strict_parsing; bypassing it means the caller
|
|
1053
|
+
# chose the mode).
|
|
1054
|
+
recover = !options[:strict] && RECOVER_MODE
|
|
1034
1055
|
native_doc = begin
|
|
1035
1056
|
::Leptris::XML::Document.parse(
|
|
1036
1057
|
processed,
|
|
1037
1058
|
readonly: options[:readonly] == true,
|
|
1038
|
-
options: parse_flags(options,
|
|
1059
|
+
options: parse_flags(options, recover: recover,
|
|
1060
|
+
context: _context),
|
|
1039
1061
|
)
|
|
1040
1062
|
rescue ::Leptris::XML::ParseError => e
|
|
1041
|
-
# libleptris has no recovery mode that survives unclosed
|
|
1042
|
-
# tags; non-strict callers get an empty document, matching
|
|
1043
|
-
# the Libxml adapter's non-strict behavior. The fatal
|
|
1044
|
-
# error rides the document as parse diagnostics (issue
|
|
1045
|
-
# #147) — otherwise nothing says why it came back empty.
|
|
1046
1063
|
raise Moxml::ParseError.new(e.message) if options[:strict]
|
|
1047
1064
|
|
|
1065
|
+
# Engines below the 1.9.344 recover floor (or the
|
|
1066
|
+
# residual unrecoverable edges, e.g. trailing content
|
|
1067
|
+
# after the root close) have no best-effort tree: the
|
|
1068
|
+
# empty-document fallback stands, with the fatal error
|
|
1069
|
+
# as parse diagnostics (issue #147) — otherwise nothing
|
|
1070
|
+
# says why it came back empty.
|
|
1048
1071
|
recover_errors = [e.message]
|
|
1049
1072
|
create_document
|
|
1050
1073
|
end
|
|
1074
|
+
if recover && native_doc.root.nil? && !recover_errors
|
|
1075
|
+
# The recover path answers a rootless document for
|
|
1076
|
+
# input with nothing to recover into (whitespace-only,
|
|
1077
|
+
# declaration-only) — libxml2's recover behaves the
|
|
1078
|
+
# same. Surface WHY on parse_errors so consumers report
|
|
1079
|
+
# it instead of silently holding an empty tree.
|
|
1080
|
+
recover_errors =
|
|
1081
|
+
["leptris parse recovered no document root " \
|
|
1082
|
+
"(empty or whitespace-only input)"]
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1051
1085
|
register_mint_klasses!(native_doc)
|
|
1052
1086
|
ctx = _context || Context.new(:leptris)
|
|
1053
1087
|
doc = Wrappers::Document.new(native_doc, ctx)
|
|
@@ -1191,7 +1225,7 @@ module Moxml
|
|
|
1191
1225
|
::Leptris::XML::ParseOptions.const_defined?(:SKIP_DUP_DETECTION) &&
|
|
1192
1226
|
::Leptris::XML::ParseOptions.const_defined?(:SKIP_SOURCE_POSITIONS)
|
|
1193
1227
|
|
|
1194
|
-
def parse_flags(options, context: nil)
|
|
1228
|
+
def parse_flags(options, recover: false, context: nil)
|
|
1195
1229
|
flags = 0
|
|
1196
1230
|
flags |= ::Leptris::XML::ParseOptions::DTDATTR if options[:dtdattr] == true
|
|
1197
1231
|
flags |= ::Leptris::XML::ParseOptions::KEEP_ENTITY_REFS if NATIVE_ENTITY_REFS &&
|
|
@@ -1203,6 +1237,13 @@ module Moxml
|
|
|
1203
1237
|
flags |= po::SKIP_DUP_DETECTION if options[:skip_dup_detection] == true
|
|
1204
1238
|
flags |= po::SKIP_SOURCE_POSITIONS if options[:skip_source_positions] == true
|
|
1205
1239
|
end
|
|
1240
|
+
if recover
|
|
1241
|
+
# #369: flags and the recover kwarg ride the same
|
|
1242
|
+
# options struct (leptris-ruby's constructor composes
|
|
1243
|
+
# both).
|
|
1244
|
+
return ::Leptris::XML::ParseOptions.new(flags, recover: true)
|
|
1245
|
+
end
|
|
1246
|
+
|
|
1206
1247
|
flags.zero? ? nil : ::Leptris::XML::ParseOptions.new(flags)
|
|
1207
1248
|
end
|
|
1208
1249
|
|
data/lib/moxml/version.rb
CHANGED
|
@@ -595,11 +595,24 @@ RSpec.describe Moxml::Adapter::Leptris do
|
|
|
595
595
|
describe "recover-path parse diagnostics" do
|
|
596
596
|
let(:ctx) { Moxml.new(:leptris) }
|
|
597
597
|
|
|
598
|
-
it "records the fatal error on non-strict parses" do
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
598
|
+
it "records the fatal error on non-strict parses below the recover floor" do
|
|
599
|
+
unless Moxml::Adapter::Leptris::RECOVER_MODE
|
|
600
|
+
doc = ctx.parse("<root><unclosed>", strict: false)
|
|
601
|
+
expect(doc.root).to be_nil
|
|
602
|
+
expect(doc.parse_errors).not_to be_empty
|
|
603
|
+
expect(doc.parse_errors).to all(be_a(String))
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
it "recovers an unclosed root at the floor (#369)" do
|
|
608
|
+
if Moxml::Adapter::Leptris::RECOVER_MODE
|
|
609
|
+
doc = ctx.parse("<root><unclosed>", strict: false)
|
|
610
|
+
expect(doc.root).not_to be_nil
|
|
611
|
+
# recovery diagnostics ride parse_errors once leptris-ruby
|
|
612
|
+
# #1200's DOM face lands; today the recovered tree is the
|
|
613
|
+
# contract.
|
|
614
|
+
expect(doc.parse_errors).to eq([])
|
|
615
|
+
end
|
|
603
616
|
end
|
|
604
617
|
|
|
605
618
|
it "answers [] on clean parses" do
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# #367: the bulk records stride carries the bound namespace URI
|
|
6
|
+
# for RESERVED-prefixed attributes on HTML-fragment-parsed trees —
|
|
7
|
+
# HTML trees carry no declarations, so the engine's attribute-URI
|
|
8
|
+
# face answers nil for xml:lang there; the bulk path mirrors the
|
|
9
|
+
# wrapper resolver's builtin table so records and wrapper agree.
|
|
10
|
+
RSpec.describe "materialize_fields reserved-prefix URIs (#367)" do
|
|
11
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
12
|
+
let(:frag) { ctx.parse_html_fragment('<span lang="en" xml:lang="en">text</span>') }
|
|
13
|
+
let(:span) { frag.children.first }
|
|
14
|
+
|
|
15
|
+
def records_attrs
|
|
16
|
+
out = nil
|
|
17
|
+
span.materialize_fields do |kind, _q, _p, _u, _ns, attrs, _t, _d| # rubocop:disable Metrics/ParameterLists,Lint/UnusedBlockArgument
|
|
18
|
+
out = attrs.dup if kind == :element
|
|
19
|
+
end
|
|
20
|
+
out
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "matches the wrapper API for prefixed attributes on fragment trees" do
|
|
24
|
+
records = records_attrs
|
|
25
|
+
expect(records).to eq(
|
|
26
|
+
["lang", "en", nil, nil,
|
|
27
|
+
"lang", "en", "http://www.w3.org/XML/1998/namespace", "xml"],
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "agrees with the wrapper attribute API stride-for-stride" do
|
|
32
|
+
wrapper = span.attributes.map { |a| [a.name, a.value, a.namespace&.uri, a.namespace&.prefix] }
|
|
33
|
+
expect(records_attrs.each_slice(4).to_a).to eq(wrapper)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# #369: non-strict parses RECOVER on engines carrying the mode
|
|
6
|
+
# (leptris 1.9.344+, leptris#1660) — the best-effort tree survives
|
|
7
|
+
# recoverable structural errors (libxml2 XML_PARSE_RECOVER
|
|
8
|
+
# semantics) instead of the rescue-to-empty fallback. Strict
|
|
9
|
+
# callers never recover: malformed input raises.
|
|
10
|
+
RSpec.describe "recover mode (#369)" do
|
|
11
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
12
|
+
|
|
13
|
+
describe "engines with the recover mode" do
|
|
14
|
+
before do
|
|
15
|
+
unless Moxml::Adapter::Leptris::RECOVER_MODE
|
|
16
|
+
skip "binding #{Leptris::VERSION} predates the recover mode"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "recovers a mismatched closer into a tree" do
|
|
21
|
+
doc = ctx.parse("<root><a>x</b></root>", strict: false)
|
|
22
|
+
expect(doc.root).not_to be_nil
|
|
23
|
+
expect(doc.to_xml).to include("<a>x</a>")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "closes unclosed elements at EOF" do
|
|
27
|
+
doc = ctx.parse("<root><a>x</root>", strict: false)
|
|
28
|
+
expect(doc.root).not_to be_nil
|
|
29
|
+
expect(doc.to_xml).to include("<a>x</a>")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "keeps strict parsing raising" do
|
|
33
|
+
expect { ctx.parse("<root><a>x</b></root>", strict: true) }
|
|
34
|
+
.to raise_error(Moxml::ParseError)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "raises by default (Context strict_parsing defaults true)" do
|
|
38
|
+
expect { ctx.parse("<root><a>x</b></root>") }
|
|
39
|
+
.to raise_error(Moxml::ParseError)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "the recovered tree carries no parse errors (diagnostics ride #1200)" do
|
|
43
|
+
doc = ctx.parse("<root><a>x</b></root>", strict: false)
|
|
44
|
+
expect(doc.parse_errors).to eq([])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe "engines below the recover floor" do
|
|
49
|
+
before do
|
|
50
|
+
if Moxml::Adapter::Leptris::RECOVER_MODE
|
|
51
|
+
skip "binding #{Leptris::VERSION} carries the recover mode"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "keeps the empty-document fallback with diagnostics" do
|
|
56
|
+
doc = ctx.parse("<root><a>x</b></root>", strict: false)
|
|
57
|
+
expect(doc.root).to be_nil
|
|
58
|
+
expect(doc.parse_errors).not_to be_empty
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
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.131
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -508,6 +508,7 @@ files:
|
|
|
508
508
|
- spec/moxml/error_spec.rb
|
|
509
509
|
- spec/moxml/html_fragment_spec.rb
|
|
510
510
|
- spec/moxml/lazy_parse_spec.rb
|
|
511
|
+
- spec/moxml/materialize_namespace_uri_spec.rb
|
|
511
512
|
- spec/moxml/materializer_spec.rb
|
|
512
513
|
- spec/moxml/moxml_spec.rb
|
|
513
514
|
- spec/moxml/namespace_spec.rb
|
|
@@ -530,6 +531,7 @@ files:
|
|
|
530
531
|
- spec/moxml/plan_spec.rb
|
|
531
532
|
- spec/moxml/processing_instruction_spec.rb
|
|
532
533
|
- spec/moxml/readonly_parse_spec.rb
|
|
534
|
+
- spec/moxml/recover_mode_spec.rb
|
|
533
535
|
- spec/moxml/sax/namespace_splitter_spec.rb
|
|
534
536
|
- spec/moxml/sax_entity_parity_spec.rb
|
|
535
537
|
- spec/moxml/sax_spec.rb
|