moxml 0.5.17 → 0.5.18

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: 6de47d8ce88e56853b7523dfbcb2049e61d586878dca573d13bacd85138b4a4e
4
- data.tar.gz: 520a050794c1275e5bc519454319a34fd059b9bbcf3f0efd0f5d8e0876bb7e97
3
+ metadata.gz: c793d84315c4005a5141698aeb716c001d23b7157b768c77cbdb3539a4b08f53
4
+ data.tar.gz: 35c1d60e6d3a8e17e9768c733d028962bcb6e5798e3c8528b18deaebd855ae5f
5
5
  SHA512:
6
- metadata.gz: 6b197e6da24e0b40ead4fa25ee944b242ee3484475b2927a16e126faa208f8ff4bb7709795b1d2830b1394fbb0fad8d28f99ed32ec9dbf902710752286965e7c
7
- data.tar.gz: 89b1ae523cf06687e67e4d07a088cdef2f942a1b4dcff8ba56f468fb887b1ac3bba936b10145c057eb0f6cf767c9e7ba35e86fb753854ee391f29951f2e17485
6
+ metadata.gz: '092b193420e9117adbd6dade8718474c9a72de401eb0b7a741d36639bbd7e9285c7e53d55f96b482b17bea3382d1da3adb1cae6bbc7b787c552859d80377db39'
7
+ data.tar.gz: 18a342a60a7dda872730dd2a9158aa852948596e21fc4613961d0336ece2f12977d02fe434e7fa64429f3ce73015a4434285f3f052425c4eb6d88c20a9571db7
@@ -18,7 +18,9 @@ code disagree.
18
18
  == Method groups
19
19
 
20
20
  Parse and document lifecycle::
21
- `parse(xml, options, context)`, `create_document`, `create` factories
21
+ `parse(xml, options, context)` `noblanks: true` drops
22
+ whitespace-only text nodes where the engine has the flag
23
+ (Nokogiri, leptris), `create_document`, `create` factories
22
24
  (`create_native_element`, `create_native_text`, `create_native_cdata`,
23
25
  `create_native_comment`, `create_native_processing_instruction`,
24
26
  `create_native_declaration`, `create_native_doctype`,
@@ -56,8 +58,9 @@ the tracked native; wrappers re-key through `refresh_native!`.
56
58
  Serialization::
57
59
  `serialize(node, options)`. Options: `indent`, `encoding`,
58
60
  `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).
61
+ `indent_text` (the indent-unit string — Nokogiri and
62
+ leptris >= 1.9.45; leptris text-bearing leaves wait on
63
+ leptris/leptris#658).
61
64
  Entity-marker restoration is owned by the wrapper layer
62
65
  (`Node#to_xml`), guarded by `entity_bearing?`.
63
66
 
@@ -30,6 +30,11 @@ ctx.config.adapter_name # => :leptris / :nokogiri / :rexml (Opal)
30
30
 
31
31
  == Parse options
32
32
 
33
+ `noblanks: true`::
34
+ Drop whitespace-only text nodes (libxml2 `XML_PARSE_NOBLANKS`
35
+ parity, issue #153) — the parse shape canon's pretty-printer
36
+ compares against `Nokogiri::XML(xml, &:noblanks)`.
37
+
33
38
  `readonly: true`::
34
39
  The binding memoizes every read and refuses mutations — the
35
40
  parse-and-read lifecycle for multi-pass consumers (comparison, diff,
@@ -62,13 +67,13 @@ implementation over one `leptris_node_traverse` call.
62
67
 
63
68
  == Known limitations (tracked upstream)
64
69
 
70
+ * Tab indentation honors the indent-unit string on leptris >= 1.9.45
71
+ (`indent_text:`), byte-identical to Nokogiri on element-only
72
+ trees; text-bearing leaves still emit spaces until the engine fix
73
+ (leptris/leptris#658).
65
74
  * A PI preceding the DOCTYPE reorders on serialization — libleptris
66
75
  normalizes DOCTYPE-first and the document node does not record its
67
76
  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
77
 
73
78
  Resolved with leptris-ruby 1.9.42 (leptris/leptris#636/#612): child
74
79
  PIs serialize on their own indented lines, DOCTYPE internal subsets
@@ -63,11 +63,15 @@ module Moxml
63
63
  include_decl = options.fetch(:declaration) do
64
64
  options[:no_declaration] ? false : document_has_declaration?(node)
65
65
  end
66
- xml = node.to_xml(
66
+ kwargs = {
67
67
  indent: options.fetch(:indent, 0),
68
68
  no_decl: !include_decl,
69
69
  encoding: options[:encoding],
70
- )
70
+ }
71
+ if INDENT_UNIT_SUPPORTED && options[:indent_text].is_a?(String)
72
+ kwargs[:indent_text] = options[:indent_text]
73
+ end
74
+ xml = node.to_xml(**kwargs)
71
75
  # Element output always ends with the close tag — but the
72
76
  # engine's serializer appends a stray trailing newline when
73
77
  # the element's last text child is non-ASCII (fixed engine
@@ -40,6 +40,15 @@ module Moxml
40
40
  LIBXML2_LAYOUT_PARITY =
41
41
  Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.42")
42
42
 
43
+ # leptris-ruby#109 (1.9.45): Element#to_xml takes the
44
+ # indent-unit string, so moxml's per-child document composition
45
+ # can carry `indent_text:` through. Text-bearing leaves still
46
+ # emit spaces until the engine fix (leptris/leptris#658); the
47
+ # boolean display form stays document-level and is never
48
+ # forwarded (the element face raises on it).
49
+ INDENT_UNIT_SUPPORTED =
50
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.45")
51
+
43
52
  NO_PARSE_ERRORS = [].freeze
44
53
  private_constant :NO_PARSE_ERRORS
45
54
 
@@ -81,7 +90,7 @@ module Moxml
81
90
  ::Leptris::XML::Document.parse(
82
91
  processed,
83
92
  readonly: options[:readonly] == true,
84
- options: dtdattr_parse_options(options),
93
+ options: parse_flags(options),
85
94
  )
86
95
  rescue ::Leptris::XML::ParseError => e
87
96
  # libleptris has no recovery mode that survives unclosed
@@ -104,11 +113,16 @@ module Moxml
104
113
  doc
105
114
  end
106
115
 
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).
110
- def dtdattr_parse_options(options)
111
- options[:dtdattr] == true ? ::Leptris::XML::ParseOptions.dtdattr : nil
116
+ # nil when no parse flag is requested — the binding treats a
117
+ # nil options hash as plain defaults (matching libxml2/
118
+ # Nokogiri semantics: blanks kept, no ATTLIST defaults).
119
+ # noblanks (issue #153) drops whitespace-only text nodes,
120
+ # libxml2 XML_PARSE_NOBLANKS parity.
121
+ def parse_flags(options)
122
+ flags = 0
123
+ flags |= ::Leptris::XML::ParseOptions::DTDATTR if options[:dtdattr] == true
124
+ flags |= ::Leptris::XML::ParseOptions::NOBLANKS if options[:noblanks] == true
125
+ flags.zero? ? nil : ::Leptris::XML::ParseOptions.new(flags)
112
126
  end
113
127
 
114
128
  def parse_errors(native_doc)
@@ -26,11 +26,13 @@ module Moxml
26
26
  ::Nokogiri::XML::DocumentFragment.parse(processed_xml) do |config|
27
27
  config.strict.nonet
28
28
  config.recover unless options[:strict]
29
+ config.noblanks if options[:noblanks]
29
30
  end
30
31
  else
31
32
  ::Nokogiri::XML(processed_xml, nil, "UTF-8") do |config|
32
33
  config.strict.nonet
33
34
  config.recover unless options[:strict]
35
+ config.noblanks if options[:noblanks]
34
36
  end
35
37
  end
36
38
  rescue ::Nokogiri::XML::SyntaxError => e
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.17"
4
+ VERSION = "0.5.18"
5
5
  end
@@ -185,6 +185,33 @@ RSpec.describe Moxml::Adapter::Leptris do
185
185
  expect(doc.to_xml).to include("<?renamed changed?>")
186
186
  end
187
187
 
188
+ it "matches raw Nokogiri with tab units on element-only trees" do
189
+ skip "requires the indent unit (leptris 1.9.45+)" unless described_class::INDENT_UNIT_SUPPORTED
190
+
191
+ source = %(<r><a><b/></a><c><d/><e/></c></r>)
192
+ target = Nokogiri::XML(source).to_xml(indent: 2, indent_text: "\t", encoding: "UTF-8")
193
+ output = ctx.parse(source).to_xml(
194
+ indent: 2, declaration: true, expand_empty: false,
195
+ encoding: "UTF-8", indent_text: "\t"
196
+ )
197
+ expect(output).to eq(target)
198
+ end
199
+
200
+ it "drops space-only text nodes with noblanks and matches Nokogiri (issue #153)" do
201
+ source = %(<a><t>1</t> <n/></a>)
202
+ recipe = {
203
+ indent: 2, declaration: true, expand_empty: false, encoding: "UTF-8"
204
+ }
205
+ target = Nokogiri::XML(source, &:noblanks).to_xml(indent: 2, encoding: "UTF-8")
206
+
207
+ doc = ctx.parse(source, noblanks: true)
208
+ expect(doc.root.children.to_a.select(&:text?)).to be_empty
209
+ expect(doc.to_xml(recipe)).to eq(target)
210
+
211
+ via_nokogiri = Moxml.new(:nokogiri).parse(source, noblanks: true).to_xml(recipe)
212
+ expect(via_nokogiri).to eq(target)
213
+ end
214
+
188
215
  it "reports the tracked native for a re-added document PI" do
189
216
  doc = ctx.parse("<?pi-src orig?><root/>")
190
217
  moved = doc.children.to_a[0]
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.17
4
+ version: 0.5.18
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-08-30 00:00:00.000000000 Z
11
+ date: 2026-08-31 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