moxml 0.5.123 → 0.5.125

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: aba5868bf7db2136823ceac2c98f825cad89b1c8f7b8e34d9ac1de2c7cbe1e61
4
- data.tar.gz: 3b8cdeb8e497d73d0c2d7db37c5f3af95e5a7bc165a6cbad8a68bb2b3cec2913
3
+ metadata.gz: 29ea7cba280de638af460bd398d4abff8d86cdccdfbb2bb228ed4fda643d362e
4
+ data.tar.gz: 551bfa7c9ba32d50fccb6d4eac05582794702789eebd05e40969f2864dcb8812
5
5
  SHA512:
6
- metadata.gz: 92aabda5d25c00d5c2d81fa3103ad4580f1e6774827057541659000cb3344e1e056cd922d4ce96a04975d6c29692fa087948a31c41de7d60cd5227f77aff382b
7
- data.tar.gz: 28cfbef33282d28a6eb0f94c248cbf36399cb8911430b468f9c67f890e2be684d8926d2670c346f54ab7aad9cfb0085f87bfc09dbeb9fed83c8b029e79997134
6
+ metadata.gz: a4717bd664d778f96a0a5362764fb82e01e87b6ea0bdf3708fffdcd42ffc1953c6e0ee3dd7cae74f1072d28e89d69339c6f2099540271500084aaa8e6ff337a7
7
+ data.tar.gz: a2c1825275657af73727ffb5981fb492459a63a5c32bf82926ae597ea2807e7c1fc09f490986f03128644e817c96fb26d0265dce9199d86750ec9b8bf43e8e49
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.9.321"
12
+ gem "leptris", "~> 1.9.338"
13
13
  gem "libxml-ruby", ">= 5.0"
14
14
  gem "nokogiri", "~> 1.18"
15
15
  gem "openssl", "~> 3.0"
@@ -84,6 +84,18 @@ module Moxml
84
84
  )
85
85
  end
86
86
 
87
+ # HTML serialization through the engine's HTML method
88
+ # (issue #354): void elements slash-less, raw text verbatim,
89
+ # no declaration. indent:/indent_text: thread the
90
+ # pretty-print knobs where the adapter's engine carries
91
+ # them. Adapters without an HTML serializer raise.
92
+ def serialize_html(_node, _options = {})
93
+ raise Moxml::AdapterError.new(
94
+ "HTML serialization is not supported by the #{name.split('::').last} adapter",
95
+ adapter: name, operation: "serialize_html",
96
+ )
97
+ end
98
+
87
99
  # Tolerant HTML4/5 parsing into the standard DOM (engine
88
100
  # issue leptris/leptris#659): implied end tags, void elements,
89
101
  # case-insensitive lowercased names, the HTML named-entity
@@ -103,6 +115,35 @@ module Moxml
103
115
  )
104
116
  end
105
117
 
118
+ # WHATWG fragment parsing with a context element (issue
119
+ # #353, the parse_html_fragment contract): body context by
120
+ # default; table contexts (td/th/tr/tbody/thead/tfoot/
121
+ # caption/colgroup/table) where the adapter's engine
122
+ # carries them. Returns the adapter's native fragment for
123
+ # HtmlFragment to wrap.
124
+ def parse_html_fragment(_html, _context_tag = :body, _context = nil)
125
+ raise Moxml::AdapterError.new(
126
+ "HTML fragment parsing is not supported by the #{name.split('::').last} adapter",
127
+ adapter: name, operation: "parse_html_fragment",
128
+ )
129
+ end
130
+
131
+ # The native fragment's top-level children (native nodes).
132
+ def fragment_children(_native_fragment)
133
+ raise Moxml::NotImplementedError.new(
134
+ "fragment_children not implemented",
135
+ feature: "fragment_children", adapter: name,
136
+ )
137
+ end
138
+
139
+ # The native fragment serialized as its children joined.
140
+ def serialize_fragment(_native_fragment)
141
+ raise Moxml::NotImplementedError.new(
142
+ "serialize_fragment not implemented",
143
+ feature: "serialize_fragment", adapter: name,
144
+ )
145
+ end
146
+
106
147
  # Parse XML using SAX (event-driven) parsing
107
148
  #
108
149
  # SAX parsing provides a memory-efficient way to process XML
@@ -161,6 +161,27 @@ module Moxml
161
161
  ::Leptris::XML::Document.method_defined?(:absorb) &&
162
162
  Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.311.3")
163
163
 
164
+ # Document#to_html indent kwargs (issue #354, engine
165
+ # leptris#1637 via leptris-ruby#421): the nested HTML layout
166
+ # (indent_html ext option — libxml2 format-mode shape). The
167
+ # parameter probe pairs the version gate: a pre-#421 binding's
168
+ # to_html takes no kwargs and would raise ArgumentError from
169
+ # the pass-through.
170
+ HTML_SERIALIZE_INDENT =
171
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.338.0") &&
172
+ defined?(::Leptris::XML::Document) &&
173
+ ::Leptris::XML::Document.method_defined?(:to_html) &&
174
+ ::Leptris::XML::Document.instance_method(:to_html)
175
+ .parameters.any? { |_, key| key == :indent }
176
+
177
+ # Leptris::HTML.parse_fragment (leptris-ruby#422, engine
178
+ # in-body drops leptris#1638): the WHATWG fragment semantics
179
+ # composed over the binding's WHATWG lane. The face probe
180
+ # gates the ride — older bindings raise a named error.
181
+ HTML_FRAGMENT =
182
+ defined?(::Leptris::HTML) &&
183
+ ::Leptris::HTML.respond_to?(:parse_fragment)
184
+
164
185
  # Markup-accumulating builder face (leptris-ruby#374 lever 2,
165
186
  # binding 1.9.313): the DSL accumulates markup in a plain
166
187
  # String and ONE native crossing (parse + attach) flushes the
@@ -1077,6 +1098,45 @@ module Moxml
1077
1098
  end
1078
1099
  end
1079
1100
 
1101
+ def serialize_html(node, options = {})
1102
+ kwargs = options.slice(:indent, :indent_text).compact
1103
+ if kwargs.empty?
1104
+ return node.to_html
1105
+ end
1106
+ unless HTML_SERIALIZE_INDENT
1107
+ raise Moxml::AdapterError.new(
1108
+ "HTML serialization with indent options requires leptris >= 1.9.338.0 " \
1109
+ "(got #{::Leptris::VERSION})",
1110
+ adapter: name, operation: "serialize_html",
1111
+ )
1112
+ end
1113
+ node.to_html(**kwargs)
1114
+ end
1115
+
1116
+ def parse_html_fragment(html, context_tag = :body, _context = nil)
1117
+ unless HTML_FRAGMENT
1118
+ raise Moxml::AdapterError.new(
1119
+ "HTML fragment parsing requires leptris >= 1.9.338.0 " \
1120
+ "(got #{::Leptris::VERSION})",
1121
+ adapter: name, operation: "parse_html_fragment",
1122
+ )
1123
+ end
1124
+ begin
1125
+ ::Leptris::HTML.parse_fragment(html, context: context_tag)
1126
+ rescue ::ArgumentError => e
1127
+ raise Moxml::AdapterError.new(e.message,
1128
+ adapter: name, operation: "parse_html_fragment")
1129
+ end
1130
+ end
1131
+
1132
+ def fragment_children(native_fragment)
1133
+ native_fragment.children
1134
+ end
1135
+
1136
+ def serialize_fragment(native_fragment)
1137
+ native_fragment.to_html
1138
+ end
1139
+
1080
1140
  def parse_html(html, _options = {}, _context = nil)
1081
1141
  html_string = html.is_a?(IO) || html.is_a?(StringIO) ? html.read : html.to_s
1082
1142
  native_doc = begin
@@ -159,6 +159,40 @@ module Moxml
159
159
  Wrappers::Document.new(native_doc, _context || Context.new(:nokogiri))
160
160
  end
161
161
 
162
+ # HTML method pass-through: the engine's own bytes (libxml2
163
+ # HTML serializer; the HTML5/gumbo serializer ignores indent
164
+ # kwargs — pass-through parity with direct Nokogiri calls,
165
+ # which is the byte contract of this adapter's to_html).
166
+ def serialize_html(node, options = {})
167
+ kwargs = {}
168
+ kwargs[:indent] = options[:indent] if options[:indent]
169
+ kwargs[:indent_text] = options[:indent_text] if options[:indent_text]
170
+ kwargs[:encoding] = options[:encoding] if options[:encoding]
171
+ node.to_html(**kwargs)
172
+ end
173
+
174
+ # WHATWG fragment parse through Nokogiri::HTML5.fragment
175
+ # (issue #353) — the same tree construction the leptris
176
+ # lane composes over its WHATWG engine.
177
+ def parse_html_fragment(html, context_tag = :body, _context = nil)
178
+ unless context_tag == :body
179
+ raise Moxml::AdapterError.new(
180
+ "nokogiri fragment contexts beyond :body are not supported " \
181
+ "(got #{context_tag.inspect})",
182
+ adapter: name, operation: "parse_html_fragment",
183
+ )
184
+ end
185
+ ::Nokogiri::HTML5.fragment(html)
186
+ end
187
+
188
+ def fragment_children(native_fragment)
189
+ native_fragment.children.to_a
190
+ end
191
+
192
+ def serialize_fragment(native_fragment)
193
+ native_fragment.children.map(&:to_html).join
194
+ end
195
+
162
196
  # Nokogiri parses with `config.recover` unless strict, so
163
197
  # recoverable syntax errors land on `doc.errors` instead of
164
198
  # raising (issue #147).
data/lib/moxml/context.rb CHANGED
@@ -133,6 +133,29 @@ module Moxml
133
133
  config.adapter.parse_html(html, options, self)
134
134
  end
135
135
 
136
+ # WHATWG fragment parse (issue #353): the parsed top-level
137
+ # nodes with fragment semantics — the context element's
138
+ # insertion mode governs tree construction (:body default keeps
139
+ # head-swept elements at fragment top level; the table
140
+ # contexts take the in-table rules). Returns an
141
+ # Moxml::HtmlFragment whose #children are the moxml-wrapped
142
+ # top-level nodes.
143
+ #
144
+ # @param html [String, IO] the fragment source
145
+ # @param context_tag [Symbol] :body (default) or a table
146
+ # context (:td, :th, :tr, :tbody, :thead, :tfoot, :caption,
147
+ # :colgroup, :table)
148
+ # @return [Moxml::HtmlFragment]
149
+ # @raise [Moxml::AdapterError] on adapters without fragment
150
+ # support or an unknown context tag
151
+ def parse_html_fragment(html, context_tag: :body)
152
+ html_string = html.is_a?(IO) || html.is_a?(StringIO) ? html.read : html.to_s
153
+ HtmlFragment.new(
154
+ config.adapter.parse_html_fragment(html_string, context_tag, self),
155
+ self,
156
+ )
157
+ end
158
+
136
159
  # Compile an XSLT stylesheet with this context's adapter
137
160
  # (implementation-agnostic; nokogiri/libxslt and leptris ship
138
161
  # engines, leptris covering XSLT 2.0/3.0 per its engine). Apply
@@ -119,6 +119,22 @@ module Moxml
119
119
  adapter.parse_errors(@native)
120
120
  end
121
121
 
122
+ # HTML serialization (issue #354): documents parsed through
123
+ # parse_html — and XML documents alike — serialize through the
124
+ # adapter's HTML method: void elements slash-less, raw text
125
+ # (script/style) verbatim, no XML declaration.
126
+ #
127
+ # indent: N (spaces per level) and indent_text: unit (e.g. "\t")
128
+ # thread the pretty-print knobs on adapters that carry them
129
+ # (leptris >= the #354 ride; nokogiri passes them to the
130
+ # engine). Adapters without the knob raise Moxml::AdapterError.
131
+ def to_html(options = {})
132
+ result = adapter.serialize_html(@native, options)
133
+ apply_line_ending(result,
134
+ options.fetch(:line_ending, Config::LINE_ENDING_LF))
135
+ result
136
+ end
137
+
122
138
  def create_element(name)
123
139
  Wrappers::Element.new(adapter.create_element(name, owner_doc: @native), context)
124
140
  end
data/lib/moxml/element.rb CHANGED
@@ -184,22 +184,55 @@ module Moxml
184
184
 
185
185
  # Nokogiri-compatible: children= replaces the entire child list
186
186
  def children=(node_or_nodes)
187
+ source_docs = nil
187
188
  natives = case node_or_nodes
188
189
  when String
189
190
  wrapper = "_moxml_children_#{Process.pid}_#{object_id}"
190
- context.parse("<#{wrapper}>#{node_or_nodes}</#{wrapper}>")
191
- .root.children.map(&:native)
192
- when Moxml::Node then [node_or_nodes.native]
193
- when Array, NodeSet then node_or_nodes.map(&:native)
191
+ wrapper_doc = context.parse("<#{wrapper}>#{node_or_nodes}</#{wrapper}>")
192
+ # #355: engines that move/copy across documents by
193
+ # reference (leptris absorb) leave the attached
194
+ # nodes pool-owned by the SOURCE document; the
195
+ # wrapper is otherwise unreferenced and its free
196
+ # can dangle the attached subtree (windows GC
197
+ # timing). Retain it for this element's lifetime.
198
+ source_docs = [wrapper_doc]
199
+ wrapper_doc.root.children.map(&:native)
200
+ when Moxml::Node
201
+ retain_children_source(node_or_nodes.document)
202
+ [node_or_nodes.native]
203
+ when Array, NodeSet
204
+ node_or_nodes.each { |n| retain_children_source(n.document) }
205
+ node_or_nodes.map(&:native)
194
206
  else
195
207
  raise ArgumentError, "children= accepts String, Node, or Array"
196
208
  end
197
209
  adapter.replace_children(@native, natives)
210
+ retain_children_source_docs(source_docs) if source_docs
198
211
  @text = nil
199
212
  context.bump_children_generation
200
213
  invalidate_children_cache!
201
214
  end
202
215
 
216
+ # #355: keep every distinct source document whose nodes were
217
+ # attached here reachable for as long as this element lives —
218
+ # absorbed/moved pools must not be freed out from under the
219
+ # tree. Accumulates: a pool absorbed by this document stays
220
+ # referenced even after later children= calls replace the
221
+ # nodes that came from it.
222
+ def retain_children_source(source_doc)
223
+ retain_children_source_docs([source_doc])
224
+ end
225
+
226
+ def retain_children_source_docs(docs)
227
+ docs = docs.compact.uniq
228
+ docs.delete_if { |d| d.equal?(document) }
229
+ return if docs.empty?
230
+
231
+ existing = adapter.attachments.get(@native, :children_source_docs)
232
+ merged = existing ? (existing + docs).uniq : docs
233
+ adapter.attachments.set(@native, :children_source_docs, merged)
234
+ end
235
+
203
236
  def remove_attribute(name)
204
237
  # Both remove paths end in invalidate_attribute_cache!, which
205
238
  # bumps the generation.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ # An HTML fragment (issue #353): the parsed top-level nodes of a
5
+ # WHATWG fragment parse with a context element (body by default).
6
+ # Not a Node — no parent, no document of its own; the adapter's
7
+ # native fragment owns the parsed tree's lifetime.
8
+ class HtmlFragment
9
+ include Enumerable
10
+
11
+ def initialize(native_fragment, context)
12
+ @native_fragment = native_fragment
13
+ @context = context
14
+ end
15
+
16
+ # The top-level nodes, moxml-wrapped, in tree order.
17
+ def children
18
+ children_natives.map { |n| Node.wrap(n, @context) }
19
+ end
20
+
21
+ def each(&block)
22
+ children.each(&block)
23
+ end
24
+
25
+ def size
26
+ children_natives.size
27
+ end
28
+
29
+ def empty?
30
+ children_natives.empty?
31
+ end
32
+
33
+ def to_html(_options = {})
34
+ @context.config.adapter.serialize_fragment(@native_fragment)
35
+ end
36
+
37
+ private
38
+
39
+ def children_natives
40
+ @context.config.adapter.fragment_children(@native_fragment)
41
+ end
42
+ end
43
+ 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.123"
4
+ VERSION = "0.5.125"
5
5
  end
data/lib/moxml.rb CHANGED
@@ -83,6 +83,7 @@ module Moxml
83
83
  autoload :Doctype, "moxml/doctype"
84
84
  autoload :EntityReference, "moxml/entity_reference"
85
85
  autoload :DocumentBuilder, "moxml/document_builder"
86
+ autoload :HtmlFragment, "moxml/html_fragment"
86
87
  autoload :Builder, "moxml/builder"
87
88
  autoload :Entity, "moxml/entity"
88
89
  autoload :EntityRegistry, "moxml/entity_registry"
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # #355: children= attaches nodes whose engine storage may be
6
+ # pool-owned by a SOURCE document (leptris absorbs/moves across
7
+ # documents by reference). The String branch's wrapper document is
8
+ # otherwise unreferenced — collected, its free can dangle the
9
+ # attached subtree (windows GC timing, standoc#1272). The element
10
+ # retains every distinct foreign source document for its lifetime.
11
+ RSpec.describe "children= source retention (#355)" do
12
+ %i[leptris nokogiri].each do |adapter_name|
13
+ describe "on #{adapter_name}" do
14
+ let(:ctx) { Moxml.new(adapter_name) }
15
+
16
+ it "retains the String branch's wrapper document" do
17
+ el = ctx.parse("<bibitem><t>old</t></bibitem>").at("//bibitem")
18
+ el.children = "<contributor><role>author</role></contributor>"
19
+ sources = ctx.config.adapter.attachments.get(el.native, :children_source_docs)
20
+ expect(sources).not_to be_nil
21
+ expect(sources.size).to eq(1)
22
+ GC.start
23
+ expect(el.to_xml).to include("author")
24
+ end
25
+
26
+ it "retains cross-document sources from the Array branch" do
27
+ el = ctx.parse("<r/>").root
28
+ other = ctx.parse("<other><deep>v</deep></other>")
29
+ el.children = other.root.children.to_a
30
+ sources = ctx.config.adapter.attachments.get(el.native, :children_source_docs)
31
+ expect(sources).to eq([other])
32
+ GC.start
33
+ expect(el.to_xml).to include("deep")
34
+ end
35
+
36
+ it "accumulates distinct sources across repeated children= calls" do
37
+ el = ctx.parse("<r/>").root
38
+ other_a = ctx.parse("<a><x/></a>")
39
+ other_b = ctx.parse("<b><y/></b>")
40
+ el.children = other_a.root.children.to_a
41
+ el.children = other_b.root.children.to_a
42
+ sources = ctx.config.adapter.attachments.get(el.native, :children_source_docs)
43
+ expect(sources).to contain_exactly(other_a, other_b)
44
+ end
45
+
46
+ it "does not retain the element's own document" do
47
+ doc = ctx.parse("<r><x/></r>")
48
+ el = doc.root
49
+ el.children = el.children.to_a
50
+ expect(
51
+ ctx.config.adapter.attachments.get(el.native, :children_source_docs),
52
+ ).to be_nil
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Context#parse_html_fragment (#353): WHATWG fragment semantics on
6
+ # the moxml surface — head-swept elements stay at fragment top
7
+ # level, table-structure tags follow the in-body rules, whitespace
8
+ # is preserved. The leptris lane composes over the binding's
9
+ # fragment face (leptris-ruby#422); nokogiri maps to
10
+ # Nokogiri::HTML5.fragment.
11
+ RSpec.describe "Context#parse_html_fragment" do
12
+ describe "nokogiri" do
13
+ let(:ctx) { Moxml.new(:nokogiri) }
14
+
15
+ it "keeps head-swept elements at fragment top level" do
16
+ frag = ctx.parse_html_fragment("<title>T</title><p>x</p>")
17
+ expect(frag.children.map(&:name)).to eq(%w[title p])
18
+ expect(frag.children.first).to be_element
19
+ end
20
+
21
+ it "drops table-structure elements, keeping their text" do
22
+ frag = ctx.parse_html_fragment("<td>cell</td>")
23
+ expect(frag.children.map(&:name)).to eq(["text"])
24
+ expect(frag.children.first.text).to eq("cell")
25
+ end
26
+
27
+ it "preserves whitespace-only text" do
28
+ frag = ctx.parse_html_fragment(" <p>lead ws</p> ")
29
+ expect(frag.children.map(&:name).first).to eq("text")
30
+ end
31
+
32
+ it "is enumerable and serializes as children joined" do
33
+ frag = ctx.parse_html_fragment("<p>a</p>tail")
34
+ expect(frag.size).to eq(2)
35
+ expect(frag.to_html).to eq("<p>a</p>tail")
36
+ expect(frag).not_to be_empty
37
+ expect(ctx.parse_html_fragment("")).to be_empty
38
+ end
39
+
40
+ it "wraps children with the context" do
41
+ frag = ctx.parse_html_fragment("<p>x</p>")
42
+ expect(frag.children.first.context).to eq(ctx)
43
+ end
44
+ end
45
+
46
+ describe "leptris" do
47
+ let(:ctx) { Moxml.new(:leptris) }
48
+
49
+ before do
50
+ unless Moxml::Adapter::Leptris::HTML_FRAGMENT
51
+ skip "binding #{Leptris::VERSION} predates the #353 fragment face"
52
+ end
53
+ end
54
+
55
+ it "keeps head-swept elements at fragment top level" do
56
+ frag = ctx.parse_html_fragment("<title>T</title><p>x</p>")
57
+ expect(frag.children.map(&:name)).to eq(%w[title p])
58
+ end
59
+
60
+ it "parses inside a table context" do
61
+ frag = ctx.parse_html_fragment("<b>x</b>", context_tag: :td)
62
+ expect(frag.children.map(&:name)).to eq(%w[b])
63
+ end
64
+
65
+ it "raises a named error for unknown context tags" do
66
+ expect { ctx.parse_html_fragment("<p/>", context_tag: :nope) }
67
+ .to raise_error(Moxml::AdapterError, /table context/)
68
+ end
69
+
70
+ it "drops table-structure elements, keeping their text" do
71
+ frag = ctx.parse_html_fragment("<td>cell</td>")
72
+ expect(frag.children.map(&:name)).to eq(["text"])
73
+ expect(frag.children.first.text).to eq("cell")
74
+ end
75
+
76
+ it "serializes as children joined" do
77
+ expect(ctx.parse_html_fragment("<p>a</p>tail").to_html).to eq("<p>a</p>tail")
78
+ end
79
+ end
80
+
81
+ describe "adapters without fragment support" do
82
+ it "raise a named AdapterError" do
83
+ expect { Moxml.new(:rexml).parse_html_fragment("<p>x</p>") }
84
+ .to raise_error(Moxml::AdapterError, /HTML fragment parsing/)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Document#to_html (issue #354): HTML-method serialization with the
6
+ # pretty-print knobs. The byte contract is per-engine pass-through —
7
+ # the leptris nested layout (engine leptris#1637) and Nokogiri's own
8
+ # serializer bytes — not cross-adapter byte equality.
9
+ RSpec.describe "Moxml::Document#to_html" do
10
+ let(:src) do
11
+ "<html><head><title>T</title></head><body><div id=\"a\"><p>x</p>" \
12
+ "<span>y</span></div><script>if (a < b) { c(); }</script></body></html>"
13
+ end
14
+
15
+ describe "nokogiri" do
16
+ let(:doc) { Moxml.new(:nokogiri).parse_html(src) }
17
+
18
+ it "serializes the engine's HTML bytes by default" do
19
+ expect(doc.to_html).to eq(Nokogiri::HTML(src).to_html)
20
+ end
21
+
22
+ it "threads indent through to the engine" do
23
+ expect(doc.to_html(indent: 2))
24
+ .to eq(Nokogiri::HTML(src).to_html(indent: 2))
25
+ end
26
+
27
+ it "threads the indent unit through to the engine" do
28
+ expect(doc.to_html(indent: 1, indent_text: "\t"))
29
+ .to eq(Nokogiri::HTML(src).to_html(indent: 1, indent_text: "\t"))
30
+ end
31
+ end
32
+
33
+ describe "leptris" do
34
+ let(:doc) { Moxml.new(:leptris).parse_html(src) }
35
+
36
+ it "serializes single-line without kwargs" do
37
+ expect(doc.to_html).to eq(doc.native.to_html)
38
+ expect(doc.to_html).to start_with("<html>")
39
+ end
40
+
41
+ context "with the indent ride armed (binding >= 1.9.338.0)" do
42
+ before do
43
+ unless Moxml::Adapter::Leptris::HTML_SERIALIZE_INDENT
44
+ skip "binding #{Leptris::VERSION} predates the #354 ride"
45
+ end
46
+ end
47
+
48
+ it "nests block children with space leads" do
49
+ expect(doc.to_html(indent: 2)).to eq(
50
+ "<html>\n " \
51
+ "<head><title>T</title></head>\n " \
52
+ "<body>\n " \
53
+ "<div id=\"a\">\n " \
54
+ "<p>x</p>\n " \
55
+ "<span>y</span>\n " \
56
+ "</div>\n " \
57
+ "<script>if (a < b) { c(); }</script>\n " \
58
+ "</body>\n" \
59
+ "</html>",
60
+ )
61
+ end
62
+
63
+ it "nests with the indent_text unit" do
64
+ expect(doc.to_html(indent: 1, indent_text: "\t")).to include(
65
+ "<body>\n\t\t<div id=\"a\">\n\t\t\t<p>x</p>",
66
+ )
67
+ end
68
+ end
69
+
70
+ context "when the binding predates the ride" do
71
+ before do
72
+ if Moxml::Adapter::Leptris::HTML_SERIALIZE_INDENT
73
+ skip "binding #{Leptris::VERSION} carries the ride"
74
+ end
75
+ end
76
+
77
+ it "raises a named AdapterError instead of silently degrading" do
78
+ expect { doc.to_html(indent: 2) }.to raise_error(Moxml::AdapterError, /indent/)
79
+ end
80
+ end
81
+ end
82
+
83
+ describe "adapters without an HTML serializer" do
84
+ it "raise a named AdapterError" do
85
+ doc = Moxml.new(:rexml).parse("<r><a>x</a></r>")
86
+ expect { doc.to_html }
87
+ .to raise_error(Moxml::AdapterError, /HTML serialization/)
88
+ end
89
+ end
90
+ 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.123
4
+ version: 0.5.125
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -214,6 +214,7 @@ files:
214
214
  - lib/moxml/entity_registry.rb
215
215
  - lib/moxml/entity_registry_opal_data.rb
216
216
  - lib/moxml/error.rb
217
+ - lib/moxml/html_fragment.rb
217
218
  - lib/moxml/lazy_node_set.rb
218
219
  - lib/moxml/materializer.rb
219
220
  - lib/moxml/namespace.rb
@@ -482,6 +483,7 @@ files:
482
483
  - spec/moxml/c14n/w3c_examples_spec.rb
483
484
  - spec/moxml/c14n/xml_attributes_spec.rb
484
485
  - spec/moxml/cdata_spec.rb
486
+ - spec/moxml/children_source_retention_spec.rb
485
487
  - spec/moxml/comment_spec.rb
486
488
  - spec/moxml/config_spec.rb
487
489
  - spec/moxml/context_spec.rb
@@ -500,6 +502,7 @@ files:
500
502
  - spec/moxml/entity_registry_spec.rb
501
503
  - spec/moxml/entity_spec.rb
502
504
  - spec/moxml/error_spec.rb
505
+ - spec/moxml/html_fragment_spec.rb
503
506
  - spec/moxml/lazy_parse_spec.rb
504
507
  - spec/moxml/materializer_spec.rb
505
508
  - spec/moxml/moxml_spec.rb
@@ -543,6 +546,7 @@ files:
543
546
  - spec/moxml/struct_plan_spec.rb
544
547
  - spec/moxml/struct_plan_typed_spec.rb
545
548
  - spec/moxml/text_spec.rb
549
+ - spec/moxml/to_html_spec.rb
546
550
  - spec/moxml/version_spec.rb
547
551
  - spec/moxml/xml_emitter_spec.rb
548
552
  - spec/moxml/xml_utils/.gitkeep