moxml 0.5.15 → 0.5.17

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: '098d93bb8d9951a645e7f49e4031993942b8965368b8dcdbcc6e590514633571'
4
- data.tar.gz: b1f5f35e34a6a458547d7740860d0bdb7d786fb857532050191ac2c188906794
3
+ metadata.gz: 6de47d8ce88e56853b7523dfbcb2049e61d586878dca573d13bacd85138b4a4e
4
+ data.tar.gz: 520a050794c1275e5bc519454319a34fd059b9bbcf3f0efd0f5d8e0876bb7e97
5
5
  SHA512:
6
- metadata.gz: 4bdb52155bbfeb4f4e8567d2feb3e0b705562340db67955c346a0c1980823743ef882fb44c0d4abd6a42eefa5b1a8fd8bf77f2c20fd72c8074f70e187e0fe199
7
- data.tar.gz: cb5f85126e157a7271c052632bdcd6a42c4f2e99c9096bac69ea50aa5cfd75b98f9bc94f5d502f63298cc2e9c687706cb45a49aeeae17d6abd56d1301b858d8d
6
+ metadata.gz: 6b197e6da24e0b40ead4fa25ee944b242ee3484475b2927a16e126faa208f8ff4bb7709795b1d2830b1394fbb0fad8d28f99ed32ec9dbf902710752286965e7c
7
+ data.tar.gz: 89b1ae523cf06687e67e4d07a088cdef2f942a1b4dcff8ba56f468fb887b1ac3bba936b10145c057eb0f6cf767c9e7ba35e86fb753854ee391f29951f2e17485
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gemspec
9
9
  gem "benchmark"
10
10
  gem "benchmark-ips"
11
11
  gem "get_process_mem"
12
- gem "leptris", "~> 1.1"
12
+ gem "leptris", "~> 1.9.32"
13
13
  gem "libxml-ruby", "~> 5.0"
14
14
  gem "nokogiri", "~> 1.18"
15
15
  gem "openssl", "~> 3.0"
@@ -55,7 +55,9 @@ the tracked native; wrappers re-key through `refresh_native!`.
55
55
 
56
56
  Serialization::
57
57
  `serialize(node, options)`. Options: `indent`, `encoding`,
58
- `declaration`, `no_declaration`, `expand_empty`, `line_ending`.
58
+ `declaration`, `no_declaration`, `expand_empty`, `line_ending`,
59
+ `indent_text` (the indent-unit string — honored where the engine
60
+ has it, e.g. Nokogiri; leptris waits on leptris-ruby#109).
59
61
  Entity-marker restoration is owned by the wrapper layer
60
62
  (`Node#to_xml`), guarded by `entity_bearing?`.
61
63
 
@@ -86,9 +88,9 @@ eight record fields per node, returning nil falls back to the
86
88
  generic wrapper walk. Leptris walks raw C pointers with the batch
87
89
  `leptris_node_children` call — no wrappers, no per-node callback.
88
90
 
89
- Constants like `DOC_NODE_SUPPORTED` / `DTDATTR_SUPPORTED` /
90
- `BULK_FIELD_READS` (Leptris) follow the same idea: one probe
91
- per engine capability, computed at load.
91
+ Version gates like `PREFIXED_ATTR_PREDICATES_NATIVE` and the
92
+ `MINIMUM_BINDING_VERSION` floor (Leptris) follow the same idea: one
93
+ probe per engine capability, computed at load.
92
94
 
93
95
  == Return shapes
94
96
 
@@ -13,11 +13,14 @@ with a native XPath 1.0 engine, SAX, and canonicalization.
13
13
 
14
14
  == Default resolution
15
15
 
16
- Leptris becomes the default when the installed binding supports
17
- programmatic document construction (`Leptris::XML::Document.create`,
18
- leptris >= 1.3). Otherwise the default falls back to Nokogiri. Under
19
- Opal the default is rexml (stock oga requires its C extension and
20
- libleptris does not build for Opal).
16
+ Leptris becomes the default when the installed binding meets the
17
+ adapter's floor `Moxml::Adapter::Leptris::MINIMUM_BINDING_VERSION`
18
+ (leptris >= 1.9.32; issue #149): bounded traverse, built documents
19
+ that reflect their parts immediately, and every capability surface
20
+ the adapter assumes. Below the floor the default falls back to
21
+ Nokogiri rather than driving a binding the adapter no longer
22
+ accommodates. Under Opal the default is rexml (stock oga requires
23
+ its C extension and libleptris does not build for Opal).
21
24
 
22
25
  [source,ruby]
23
26
  ----
@@ -59,16 +62,18 @@ implementation over one `leptris_node_traverse` call.
59
62
 
60
63
  == Known limitations (tracked upstream)
61
64
 
62
- * Tab indentation (`indent_text`) needs a serialize-time parameter in
63
- libleptris; post-hoc rewriting would corrupt mixed content.
64
- * Child PIs serialize inline (engine emits `<r>\n<?pi x?> <e/>`
65
- rather than libxml2's one-PI-per-indented-line layout); safe
66
- post-hoc rewriting is impossible in mixed content.
67
- * DOCTYPE internal subsets serialize inline; libxml2 separates the
68
- declarations with newlines.
69
- * Document-level PIs are read-only natives (`target=`/`data=`/
70
- `unlink` rejected); write-through and removal are pending C
71
- surface (leptris/leptris#612).
72
65
  * A PI preceding the DOCTYPE reorders on serialization — libleptris
73
66
  normalizes DOCTYPE-first and the document node does not record its
74
67
  relative position.
68
+ * Tab indentation: the binding's `indent_text` is the display-form
69
+ boolean, not Nokogiri's indent-unit string — the unit ask is
70
+ leptris-ruby#109. moxml forwards `indent_text:` to Nokogiri
71
+ meanwhile.
72
+
73
+ Resolved with leptris-ruby 1.9.42 (leptris/leptris#636/#612): child
74
+ PIs serialize on their own indented lines, DOCTYPE internal subsets
75
+ newline-separate their declarations (moxml re-formats the subset to
76
+ the libxml2 layout), the stray trailing newline after non-ASCII
77
+ ending elements is gone engine-side, and document-level PIs accept
78
+ `target=`/`content=`/removal with full round-trip. The moxml-side
79
+ compat guards remain as no-ops for older floor bindings.
@@ -100,4 +100,9 @@ context.parse(src).to_xml(
100
100
  ) == Nokogiri::XML(src).to_xml(indent: 2, encoding: "UTF-8") # => true
101
101
  ----
102
102
 
103
- Known residual divergences (engine-level, tracked upstream): tab indentation needs a serialize-time parameter in libleptris, and a PI preceding the DOCTYPE reorders (the engine normalizes DOCTYPE-first).
103
+ Known residual divergences (engine-level, tracked upstream): a PI
104
+ preceding the DOCTYPE reorders (the engine normalizes DOCTYPE-first),
105
+ and tab indentation waits on the binding exposing an indent-unit
106
+ parameter (leptris-ruby#109) — child-PI lines, DOCTYPE subset
107
+ layout, and the non-ASCII trailing-newline quirk were resolved with
108
+ leptris-ruby 1.9.42.
@@ -10,7 +10,7 @@ module Moxml
10
10
  # swept too (the context wrapper identity map self-cleans via
11
11
  # its size valve).
12
12
  DOCUMENT_ATTACHMENT_KEYS = %i[
13
- entity_markers doc_pi_nodes declaration doctype
13
+ entity_markers declaration doctype
14
14
  had_source_declaration document_text
15
15
  ].freeze
16
16
 
@@ -29,64 +29,18 @@ module Moxml
29
29
  doctype_wrapper = attachments.get(doc, :doctype)
30
30
  children << doctype_wrapper if doctype_wrapper
31
31
 
32
- if DOC_NODE_SUPPORTED
33
- # The libxml2-model document node lists prolog PIs/comments,
34
- # the root, and epilog PIs/comments in document order —
35
- # the Nokogiri-shaped contract, epilog anchoring included
36
- # (issue #130). Built (programmatic) documents are not yet
37
- # fully reflected by the node (binding gap: an attached
38
- # root does not appear); document_node_children answers
39
- # nil there for the legacy parts path.
40
- doc_children = document_node_children(doc)
41
- if doc_children
42
- children.concat(doc_children)
43
- else
44
- children.concat(document_pi_nodes(doc))
45
- children << doc.root if doc.root
46
- end
47
- else
48
- # Legacy path: document-level PIs live outside the element
49
- # tree in a flat pre-root list (libleptris < 1.9.7 C
50
- # model); epilog anchoring is not representable there.
51
- children.concat(document_pi_nodes(doc))
52
-
53
- children << doc.root if doc.root
54
- end
32
+ # The libxml2-model document node lists prolog PIs/comments,
33
+ # the root, and epilog PIs/comments in document order — the
34
+ # Nokogiri-shaped contract, epilog anchoring included (issue
35
+ # #130). Built documents reflect their parts immediately
36
+ # since leptris-ruby 1.9.32 (leptris-ruby#91).
37
+ children.concat(doc.children.to_a)
55
38
 
56
39
  texts = attachments.get(doc, :document_text)
57
40
  children.concat(texts) if texts
58
41
  children
59
42
  end
60
43
 
61
- # Document-level PI pseudo-nodes, materialized once from the C
62
- # list and cached per document: children and serialization read
63
- # the same objects, so wrapper mutations round-trip. Build the
64
- # cache BEFORE appending a PI with add_pi, or the C-side
65
- # addition would be double-counted.
66
- # The document node's children, or nil when the node does not
67
- # reflect reality: parsed documents always list the root
68
- # element among their children, but programmatically built
69
- # ones do not (binding gap) — those keep the legacy parts
70
- # path.
71
- def document_node_children(doc)
72
- doc_children = doc.children.to_a
73
- has_root = doc_children.any?(::Leptris::XML::Element)
74
- return doc_children if has_root
75
- return doc_children if doc.root.nil?
76
-
77
- nil
78
- end
79
-
80
- def document_pi_nodes(doc)
81
- attachments.get(doc, :doc_pi_nodes) || begin
82
- nodes = doc.processing_instructions.map do |(target, data)|
83
- CustomizedLeptris::DocumentPI.new(target, data, doc)
84
- end
85
- attachments.set(doc, :doc_pi_nodes, nodes)
86
- nodes
87
- end
88
- end
89
-
90
44
  def add_document_child(doc, child)
91
45
  case child
92
46
  when CustomizedLeptris::Declaration
@@ -102,15 +56,9 @@ module Moxml
102
56
  when ::Leptris::XML::Element
103
57
  doc.root = child
104
58
  when ::Leptris::XML::ProcessingInstruction
105
- document_pi_nodes(doc)
106
59
  doc.add_pi(child.target, child.content.to_s)
107
- document_pi_nodes(doc) << CustomizedLeptris::DocumentPI.new(
108
- child.target, child.content.to_s, doc
109
- )
110
60
  when CustomizedLeptris::DocumentPI
111
- document_pi_nodes(doc)
112
61
  doc.add_pi(child.target, child.data)
113
- document_pi_nodes(doc) << child
114
62
  when ::Leptris::XML::Text
115
63
  texts = attachments.get(doc, :document_text) || []
116
64
  texts << child
@@ -149,22 +97,10 @@ module Moxml
149
97
  native = native_doctype_xml(doc)
150
98
  parts << native << "\n" if native
151
99
 
152
- if DOC_NODE_SUPPORTED
153
- # The libxml2-model document node: prolog PIs/comments,
154
- # the root, epilog PIs/comments in document order, so
155
- # epilog parts serialize after the root (issue #130).
156
- doc_children = document_node_children(doc)
157
- if doc_children
158
- doc_children.each { |child| parts << raw_serialize(child, options) << "\n" }
159
- else
160
- document_pi_nodes(doc).each { |pi| parts << pi.to_xml << "\n" }
161
- parts << raw_serialize(doc.root, options) << "\n" if doc.root
162
- end
163
- else
164
- document_pi_nodes(doc).each { |pi| parts << pi.to_xml << "\n" }
165
-
166
- parts << raw_serialize(doc.root, options) << "\n" if doc.root
167
- end
100
+ # The libxml2-model document node: prolog PIs/comments, the
101
+ # root, epilog PIs/comments — in document order, so epilog
102
+ # parts serialize after the root (issue #130).
103
+ doc.children.each { |child| parts << raw_serialize(child, options) << "\n" }
168
104
 
169
105
  texts = attachments.get(doc, :document_text)
170
106
  texts&.each { |text| parts << XmlEmitter.escape_text(text.content.to_s) }
@@ -177,9 +113,75 @@ module Moxml
177
113
  return nil unless dt
178
114
 
179
115
  subset = dt.internal_subset if dt.class.method_defined?(:internal_subset)
116
+ subset = format_internal_subset(subset) if LIBXML2_LAYOUT_PARITY
180
117
  XmlEmitter.doctype_xml(dt.root_name, dt.public_id, dt.system_id, subset)
181
118
  end
182
119
 
120
+ # libxml2's DTD dump layout (leptris/leptris#636): newline
121
+ # after "[", one after every markup declaration, none after
122
+ # comments (they glue to both neighbors); an empty subset
123
+ # drops the brackets. Returns the INNER text for
124
+ # XmlEmitter.doctype_xml, nil when there is nothing to emit.
125
+ # The engine reports internal_subset as raw source text, so
126
+ # the declarations are re-tokenized — quote-aware, since an
127
+ # attribute default can contain ">".
128
+ def format_internal_subset(subset)
129
+ return nil if subset.nil? || subset.empty?
130
+
131
+ out = +"\n"
132
+ pos = 0
133
+ length = subset.length
134
+ while pos < length
135
+ start = subset.index("<", pos)
136
+ break if start.nil?
137
+
138
+ terminator, skip = if subset[start, 4] == "<!--"
139
+ ["-->", 4]
140
+ elsif subset[start, 2] == "<?"
141
+ ["?>", 2]
142
+ else
143
+ [nil, 0]
144
+ end
145
+ if terminator
146
+ stop = subset.index(terminator, start + skip)
147
+ break if stop.nil?
148
+
149
+ item_end = stop + terminator.length
150
+ else
151
+ item_end = markup_decl_end(subset, start)
152
+ break if item_end.nil?
153
+ end
154
+ out << subset[start...item_end]
155
+ # Comments carry no trailing newline; declarations do.
156
+ out << "\n" unless subset[start, 4] == "<!--"
157
+ pos = item_end
158
+ end
159
+ out == "\n" ? nil : out
160
+ end
161
+
162
+ # End index of a markup declaration starting at `start`:
163
+ # the first ">" outside quotes.
164
+ QUOTE_CHARS = ['"', "'"].freeze
165
+ private_constant :QUOTE_CHARS
166
+
167
+ def markup_decl_end(subset, start)
168
+ quote = nil
169
+ i = start
170
+ length = subset.length
171
+ while i < length
172
+ ch = subset[i]
173
+ if quote
174
+ quote = nil if ch == quote
175
+ elsif QUOTE_CHARS.include?(ch)
176
+ quote = ch
177
+ elsif ch == ">"
178
+ return i + 1
179
+ end
180
+ i += 1
181
+ end
182
+ nil
183
+ end
184
+
183
185
  def default_declaration_xml(doc, options)
184
186
  encoding = options[:encoding] || doc.encoding
185
187
  encoding = "UTF-8" if encoding.to_s.empty?
@@ -39,8 +39,6 @@ module Moxml
39
39
  end
40
40
 
41
41
  def materialize_fields(native, buffers, &block)
42
- return nil unless Leptris::BULK_FIELD_READS
43
-
44
42
  doc = native.is_a?(::Leptris::XML::Document) ? native : native.document
45
43
  # Marker-bearing text needs the split pipeline (children-level
46
44
  # ER expansion); the bulk path has no marker handling.
@@ -70,7 +70,10 @@ module Moxml
70
70
  )
71
71
  # Element output always ends with the close tag — but the
72
72
  # engine's serializer appends a stray trailing newline when
73
- # the element's last text child is non-ASCII.
73
+ # the element's last text child is non-ASCII (fixed engine
74
+ # side in 1.9.42; kept for older floor bindings). Note: the
75
+ # binding's indent_text kwarg is the display-form boolean,
76
+ # not Nokogiri's indent-unit string — do not forward it.
74
77
  xml.sub(/\n+\z/, "")
75
78
  end
76
79
 
@@ -4,11 +4,6 @@ return if RUBY_ENGINE == "opal"
4
4
 
5
5
  require "stringio"
6
6
  require "leptris"
7
- # leptris-ruby 1.9.0 regression (leptris-ruby#53): the eager ffi load
8
- # shadows the Leptris::XML autoload manifest, leaving Document/Element
9
- # unreachable. Loading the manifest explicitly is a no-op when the gem
10
- # is fixed.
11
- require "leptris/xml"
12
7
 
13
8
  module Moxml
14
9
  module Adapter
@@ -19,29 +14,14 @@ module Moxml
19
14
  # programmatic DOCTYPE, so those live in CustomizedLeptris value
20
15
  # objects stored through NativeAttachment.
21
16
  class Leptris < Base
22
- # libleptris 1.9.7 (leptris-ruby 1.9.26): Document#node exposes
23
- # the libxml2-model document node prolog/epilog parts in
24
- # document order. Older bindings keep the flat pre-root PI path.
25
- DOC_NODE_SUPPORTED = ::Leptris::XML::Document.method_defined?(:node)
26
- # libleptris 1.9.8 (leptris-ruby 1.9.30): plain parse excludes
27
- # DTD ATTLIST defaults, matching libxml2/Nokogiri semantics;
28
- # ParseOptions::DTDATTR opts in (leptris/leptris#606).
29
- DTDATTR_SUPPORTED = ::Leptris::XML::ParseOptions.const_defined?(:DTDATTR)
30
-
31
- # The raw field-read surface for the bulk materializer (issue
32
- # #143). Bindings without the full set fall back to the generic
33
- # wrapper walk.
34
- BULK_FIELD_READS = %i[
35
- leptris_node_children leptris_node_get_type
36
- leptris_element_name leptris_element_prefix leptris_element_namespace
37
- leptris_element_namespace_count leptris_element_namespace_decl_prefix
38
- leptris_element_namespace_decl_uri leptris_element_first_attribute
39
- leptris_attribute_get_name leptris_attribute_get_value
40
- leptris_attribute_next leptris_attribute_namespace_uri
41
- leptris_text_node_get_content leptris_cdata_node_get_content
42
- leptris_comment_node_get_content leptris_pi_node_get_target
43
- leptris_pi_node_get_data
44
- ].all? { |fn| ::Leptris::XML::FFI.respond_to?(fn) }
17
+ # The binding floor (issue #149): 1.9.32 carried the traverse
18
+ # fix (leptris-ruby#89) and made built documents reflect their
19
+ # parts immediately (leptris-ruby#91); the document node
20
+ # (1.9.26), DTDATTR (1.9.8), and the batch child-pointer read
21
+ # (1.7.0) surfaces predate it. Older bindings are not eligible
22
+ # for the default (see Config.leptris_preferred_available?) and
23
+ # the adapter no longer carries accommodation paths for them.
24
+ MINIMUM_BINDING_VERSION = "1.9.32"
45
25
 
46
26
  # leptris-ruby#103: prefixed attribute tests inside predicates
47
27
  # stopped resolving through the document's in-scope declarations
@@ -51,6 +31,15 @@ module Moxml
51
31
  PREFIXED_ATTR_PREDICATES_NATIVE =
52
32
  Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.40")
53
33
 
34
+ # leptris/leptris#636 (leptris-ruby 1.9.42): libxml2-compatible
35
+ # pretty-print — child-PI lines, DOCTYPE internal-subset layout,
36
+ # no stray trailing newline after non-ASCII text, and the indent
37
+ # unit (`to_xml`'s indent_text takes the unit string). Older
38
+ # floor bindings treat indent_text as a display-form boolean, so
39
+ # the passthrough and the parity pins gate on this.
40
+ LIBXML2_LAYOUT_PARITY =
41
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.42")
42
+
54
43
  NO_PARSE_ERRORS = [].freeze
55
44
  private_constant :NO_PARSE_ERRORS
56
45
 
@@ -115,12 +104,11 @@ module Moxml
115
104
  doc
116
105
  end
117
106
 
118
- # nil when DTDATTR is unsupported or not requested the
119
- # binding treats a nil options hash as plain defaults.
107
+ # nil unless DTDATTR is requested the binding treats a nil
108
+ # options hash as plain defaults (defaults exclude ATTLIST
109
+ # defaults, matching libxml2/Nokogiri semantics).
120
110
  def dtdattr_parse_options(options)
121
- return nil unless DTDATTR_SUPPORTED && options[:dtdattr] == true
122
-
123
- ::Leptris::XML::ParseOptions.dtdattr
111
+ options[:dtdattr] == true ? ::Leptris::XML::ParseOptions.dtdattr : nil
124
112
  end
125
113
 
126
114
  def parse_errors(native_doc)
@@ -429,11 +429,13 @@ module Moxml
429
429
  custom_decl = "<?xml #{attrs.join(' ')}?>"
430
430
  end
431
431
 
432
- result = node.to_xml(
432
+ serialize_kwargs = {
433
433
  indent: options[:indent],
434
434
  encoding: options[:encoding],
435
435
  save_with: save_options,
436
- )
436
+ }
437
+ serialize_kwargs[:indent_text] = options[:indent_text] if options[:indent_text]
438
+ result = node.to_xml(**serialize_kwargs)
437
439
 
438
440
  if custom_decl
439
441
  result = "#{custom_decl}\n#{result}"
data/lib/moxml/config.rb CHANGED
@@ -58,18 +58,20 @@ module Moxml
58
58
  OPAL_FALLBACK_ADAPTER
59
59
  end
60
60
 
61
- # True when the leptris gem is installed AND new enough for the
62
- # adapter (it needs programmatic document construction, which
63
- # released 1.1.x lacks). Memoized: the require probe runs once.
61
+ # True when the leptris gem is installed AND meets the adapter's
62
+ # binding floor (issue #149; 1.9.32 traverse bounding #89,
63
+ # built-docs parts #91, and every capability surface the adapter
64
+ # now assumes). Memoized: the probe runs once. Below the floor
65
+ # the default falls back to Nokogiri rather than driving a
66
+ # binding the adapter no longer accommodates.
64
67
  def leptris_preferred_available?
65
68
  return @leptris_preferred_available if defined?(@leptris_preferred_available)
66
69
 
67
70
  @leptris_preferred_available = begin
68
71
  require "leptris"
69
- # leptris-ruby 1.9.0 regression (leptris-ruby#53): the XML
70
- # autoload manifest is shadowed; load it explicitly.
71
- require "leptris/xml"
72
- ::Leptris::XML::Document.respond_to?(:create)
72
+ Moxml::Adapter.load(:leptris)
73
+ Gem::Version.new(::Leptris::VERSION) >=
74
+ Gem::Version.new(Moxml::Adapter::Leptris::MINIMUM_BINDING_VERSION)
73
75
  rescue LoadError, NameError
74
76
  false
75
77
  end
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.15"
4
+ VERSION = "0.5.17"
5
5
  end
@@ -157,6 +157,15 @@ RSpec.describe Moxml::Adapter::Leptris do
157
157
  selfclosing: %(<r><a/><b/><c>t</c><d/></r>),
158
158
  }
159
159
 
160
+ # leptris/leptris#636 (binding 1.9.42): child-PI lines and
161
+ # DOCTYPE internal-subset layout match libxml2.
162
+ if defined?(described_class::LIBXML2_LAYOUT_PARITY) && described_class::LIBXML2_LAYOUT_PARITY
163
+ cases[:pi_child] = %(<r><?pi data?><e/></r>)
164
+ cases[:pi_child_mixed] = %(<r>t<?pi d?><e>x</e>u</r>)
165
+ cases[:doctype_subset] = %(<?xml version="1.0"?><!DOCTYPE r [<!ELEMENT r (#PCDATA)>]><r>t</r>)
166
+ cases[:doctype_subset_multi] = %(<?xml version="1.0"?><!DOCTYPE r [<!ELEMENT r (#PCDATA)><!ATTLIST e a CDATA "d">]><r><e/></r>)
167
+ end
168
+
160
169
  cases.each do |name, source|
161
170
  target = Nokogiri::XML(source).to_xml(indent: 2, encoding: "UTF-8")
162
171
  output = ctx.parse(source).to_xml(
@@ -166,6 +175,16 @@ RSpec.describe Moxml::Adapter::Leptris do
166
175
  end
167
176
  end
168
177
 
178
+ it "round-trips document-level PI mutations through serialization" do
179
+ # leptris/leptris#612: parse-created document PIs carry doc
180
+ # linkage — the setters work and the tree round-trips.
181
+ doc = ctx.parse("<?pi-prolog before?><root/>")
182
+ pi = doc.children.to_a.first
183
+ pi.target = "renamed"
184
+ pi.content = "changed"
185
+ expect(doc.to_xml).to include("<?renamed changed?>")
186
+ end
187
+
169
188
  it "reports the tracked native for a re-added document PI" do
170
189
  doc = ctx.parse("<?pi-src orig?><root/>")
171
190
  moved = doc.children.to_a[0]
@@ -186,16 +205,12 @@ RSpec.describe Moxml::Adapter::Leptris do
186
205
  end
187
206
 
188
207
  it "excludes ATTLIST defaults on plain parse" do
189
- skip "requires leptris with no-DTDATTR semantics" unless Moxml::Adapter::Leptris::DTDATTR_SUPPORTED
190
-
191
208
  doc = ctx.parse(dtd_xml)
192
209
  expect(doc.at_xpath("//e9")["attr"]).to be_nil
193
210
  expect(doc.to_xml).not_to include("attr=")
194
211
  end
195
212
 
196
213
  it "materializes ATTLIST defaults with dtdattr: true" do
197
- skip "requires leptris with ParseOptions::DTDATTR" unless Moxml::Adapter::Leptris::DTDATTR_SUPPORTED
198
-
199
214
  doc = ctx.parse(dtd_xml, dtdattr: true)
200
215
  expect(doc.at_xpath("//e9")["attr"]).to eq("default")
201
216
  expect(doc.to_xml).to include(%(attr="default"))
@@ -85,7 +85,7 @@ RSpec.describe Moxml::Adapter do
85
85
  .to include(Moxml::Config.default_adapter)
86
86
  end
87
87
 
88
- it "prefers leptris only when the installed gem supports document construction" do
88
+ it "prefers leptris only when the installed binding meets the floor" do
89
89
  if Moxml::Config.leptris_preferred_available?
90
90
  expect(Moxml::Config.runtime_default_adapter).to eq(:leptris)
91
91
  else
@@ -93,6 +93,15 @@ RSpec.describe Moxml::Adapter do
93
93
  end
94
94
  end
95
95
 
96
+ it "pins the leptris binding floor at 1.9.32 (issue #149)" do
97
+ skip "leptris not installed" unless described_class.available?(:leptris)
98
+
99
+ adapter = described_class.load(:leptris)
100
+ expect(adapter::MINIMUM_BINDING_VERSION).to eq("1.9.32")
101
+ expect(Gem::Version.new(Leptris::VERSION))
102
+ .to be >= Gem::Version.new(adapter::MINIMUM_BINDING_VERSION)
103
+ end
104
+
96
105
  it "OPAL_DEFAULT_ADAPTER is a member of OPAL_AVAILABLE_ADAPTERS" do
97
106
  expect(Moxml::Adapter::OPAL_AVAILABLE_ADAPTERS)
98
107
  .to include(Moxml::Config::OPAL_DEFAULT_ADAPTER)
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.15
4
+ version: 0.5.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.