moxml 0.5.31 → 0.5.32

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: 32f1034201de38bed35a6b1496d10f96a580f0e4c92517c2253ded0631331439
4
- data.tar.gz: bb3d697a73fbac699e00b51905ef446f9837b9ee649785bb1ed4df2027870e78
3
+ metadata.gz: 627111c496efca4687eabeeb78989d611d3d308adc5ed32443c7058da51c57c9
4
+ data.tar.gz: 86d1cc6c471d07084e0d2e0de9412b286b5486b202f9fe677b7417f514c1fe55
5
5
  SHA512:
6
- metadata.gz: 3a854ab201001d88494c37d79b163bbf4246880702f5c7d61cf52fd41128459be2a1e8c4601c894852b6aa73e33efca3c471ebfdbf9a126120afa698ab28aff1
7
- data.tar.gz: 531a60b0d52e84f4f0df5a026d51b5eeed850b56da1c1eea23297c2c2fb331efa4047a9dc73ef21a430998035aad7a58fe8adb7905047f2001f5b941d07353fc
6
+ metadata.gz: f1108c98dc8af92913605dd08b84eb50bbfec92a5dd286e15656c3ecd57937ce56bc186c9393a740f6bd8a8316f06fb059db226f20ffc9f0b84cfb433e1a532d
7
+ data.tar.gz: 02d109a32659b30fc34ac5d6d3f01bc0d32d9e1ca6a38cfdc2fe3880a02c779ce5ff41fa2f0eda04adc2a9ccd6702f372d29b4d2a6fe33faeb0456cb8852d26b
@@ -74,10 +74,17 @@ so script bodies with markup characters round-trip through a strict
74
74
  XML reparse. The adapter stands the entity-marker pipeline down for
75
75
  HTML parses — the engine decodes HTML entities directly into text.
76
76
 
77
- Measured on 1.9.105 (named-entity decoding fixed, engine #848):
77
+ Measured on 1.9.105+ (named-entity decoding fixed, engine #848):
78
78
  parse 2.5-3.5x, serialize 4.1x, xpath 6-29x faster than Nokogiri —
79
79
  uniformly, including entity-laden pages.
80
80
 
81
+ Since 1.9.118/1.9.121 the engine also handles WHATWG foreign
82
+ content — embedded SVG and MathML in HTML parse into the tree
83
+ (preserving `foreignObject` subtrees that Nokogiri drops), the
84
+ source HTML doctype round-trips, and `template`/`colgroup` place
85
+ correctly. Foreign-content camelCase names are currently
86
+ lowercased (engine conformance note leptris/leptris#1013).
87
+
81
88
  Adapters without an engine HTML mode (Ox, Oga, REXML, LibXML) raise
82
89
  `Moxml::AdapterError`; Nokogiri parses through libxml2's HTML mode.
83
90
 
@@ -64,6 +64,14 @@ module Moxml
64
64
  texts << child
65
65
  attachments.set(doc, :document_text, texts)
66
66
  child
67
+ when ::Leptris::XML::Comment
68
+ # The tree model supports document comments (they parse
69
+ # and serialize, libleptris 1.9.3 #578) but the engine
70
+ # has no add entry yet (leptris/leptris#1032).
71
+ raise Moxml::NotImplementedError.new(
72
+ "Adding document-level comments requires an engine entry (leptris/leptris#1032)",
73
+ feature: "add_document_child", adapter: "Leptris",
74
+ )
67
75
  else
68
76
  raise Moxml::DocumentStructureError.new(
69
77
  "Unsupported document child: #{child.class}",
@@ -33,7 +33,13 @@ module Moxml
33
33
  # Entity restoration belongs to the wrapper layer
34
34
  # (Node#to_xml runs adapter.restore_entities for every
35
35
  # adapter); doing it here scanned the output a second time.
36
- xml = normalize_serialization(raw_serialize(node, options), options)
36
+ xml = if native_expand?(node, options)
37
+ opts = options.dup
38
+ opts[:__expand_handled_natively] = true
39
+ normalize_serialization(raw_serialize(node, opts), opts)
40
+ else
41
+ normalize_serialization(raw_serialize(node, options), options)
42
+ end
37
43
  # The binding's FFI strings come back binary-tagged; the
38
44
  # engine encoded the bytes per this option, so tag them.
39
45
  xml.force_encoding(options[:encoding]) if options[:encoding]
@@ -47,7 +53,32 @@ module Moxml
47
53
 
48
54
  # The binding's element face with all-default options — the
49
55
  # frozen splat keeps the hot argless path allocation-free.
56
+ # Native expand-empty (engine #882, libleptris 1.9.95,
57
+ # bindings 1.9.144): empty elements emit <a></a> through a
58
+ # C-side ext entry — the Ruby full-output regex rewrite and
59
+ # its "/>" probe scan drop out of the element path. The
60
+ # document face does not expose the option yet; documents
61
+ # keep the Ruby pass.
62
+ EXPAND_EMPTY_NATIVE =
63
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.144")
64
+
50
65
  ELEMENT_DEFAULT_KWARGS = { indent: 0, no_decl: true, encoding: nil }.freeze
66
+ ELEMENT_EXPAND_KWARGS =
67
+ if EXPAND_EMPTY_NATIVE
68
+ { indent: 0, no_decl: true, encoding: nil, expand_empty: true }.freeze
69
+ else
70
+ ELEMENT_DEFAULT_KWARGS
71
+ end.freeze
72
+
73
+ # Elements (not documents) with expand_empty and NO
74
+ # indent-unit string: the C ext entry handles expansion. The
75
+ # element unit serializer does not take the flag — those keep
76
+ # the Ruby pass.
77
+ def native_expand?(node, options)
78
+ EXPAND_EMPTY_NATIVE && options[:expand_empty] &&
79
+ !node.is_a?(::Leptris::XML::Document) &&
80
+ !options[:indent_text].is_a?(String)
81
+ end
51
82
 
52
83
  def raw_serialize(node, options)
53
84
  # CDATA must precede Text in this chain: CDATA < Text in the
@@ -79,10 +110,12 @@ module Moxml
79
110
  # per call; the wrapper force-tags the string either way.
80
111
  indent = options.fetch(:indent, 0)
81
112
  encoding = options[:encoding] == "UTF-8" ? nil : options[:encoding]
113
+ native_expand = native_expand?(node, options)
82
114
  xml = if indent.zero? && encoding.nil?
83
- node.to_xml(**ELEMENT_DEFAULT_KWARGS)
115
+ node.to_xml(**(native_expand ? ELEMENT_EXPAND_KWARGS : ELEMENT_DEFAULT_KWARGS))
84
116
  else
85
117
  kwargs = { indent: indent, no_decl: true, encoding: encoding }
118
+ kwargs[:expand_empty] = true if native_expand
86
119
  if INDENT_UNIT_SUPPORTED && options[:indent_text].is_a?(String)
87
120
  kwargs[:indent_text] = options[:indent_text]
88
121
  end
@@ -119,7 +152,9 @@ module Moxml
119
152
  # The libxml2-layout serializer (>= 1.9.42) keeps attribute
120
153
  # apostrophes literal; older engines escaped them.
121
154
  needs_apos = !LIBXML2_LAYOUT_PARITY && xml.include?("&apos;")
122
- needs_expand = options[:expand_empty] && xml.include?("/>")
155
+ needs_expand = options[:expand_empty] &&
156
+ !options[:__expand_handled_natively] &&
157
+ xml.include?("/>")
123
158
  # Corruption guards: the 1-char ampersand probe is ~1µs
124
159
  # (memchr-class); the raw-< scan runs only on builds that
125
160
  # still carry the parse race (leptris-ruby#131).
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.31"
4
+ VERSION = "0.5.32"
5
5
  end
@@ -374,6 +374,37 @@ RSpec.describe Moxml::Adapter::Leptris do
374
374
  expect(reparsed.errors).to be_empty
375
375
  end
376
376
 
377
+ it "preserves foreign content (SVG/MathML) in HTML documents" do
378
+ doc = ctx.parse_html(%(<p>a</p><svg viewBox="0 0 1 1"><circle r="1"/></svg><math><mi>a</mi></math>))
379
+ body = doc.root.children.find(&:element?)
380
+ names = body.children.select(&:element?).map(&:name)
381
+ expect(names).to include("svg", "math")
382
+ expect(body.at_xpath(".//circle")["r"]).to eq("1")
383
+ expect(body.at_xpath(".//mi").text).to eq("a")
384
+ end
385
+
386
+ it "preserves foreignObject content Nokogiri drops (name lowercased)" do
387
+ # WHATWG keeps foreign-content camelCase (foreignObject,
388
+ # viewBox); the engine currently lowercases like it does HTML
389
+ # names, but preserves the subtree — Nokogiri drops it
390
+ # entirely. Pins current behavior; engine conformance note
391
+ # filed for the adjust-tables.
392
+ doc = ctx.parse_html(%(<svg><foreignObject><p>x</p></foreignObject></svg>))
393
+ out = doc.to_xml
394
+ expect(out).to include("<foreignobject>")
395
+ expect(out).to include("<p>x</p>")
396
+ end
397
+
398
+ it "round-trips an HTML doctype with external identifiers" do
399
+ doc = ctx.parse_html(%(<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "x.dtd"><p>a</p>))
400
+ expect(doc.to_xml).to include(%(<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "x.dtd">))
401
+ end
402
+
403
+ it "preserves template element placement" do
404
+ doc = ctx.parse_html(%(<div><template><p>t</p></template></div>))
405
+ expect(doc.at_xpath("//template/p")&.text).to eq("t")
406
+ end
407
+
377
408
  it "round-trips a parsed tree through mutation" do
378
409
  doc = ctx.parse_html(%(<ul><li>a<li>b</ul>))
379
410
  doc.at_xpath("//ul").add_child(doc.create_element("li"))
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.31
4
+ version: 0.5.32
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-09-12 00:00:00.000000000 Z
11
+ date: 2026-09-13 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