moxml 0.5.3 → 0.5.4
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/lib/moxml/adapter/base.rb +8 -10
- data/lib/moxml/adapter/leptris.rb +32 -4
- data/lib/moxml/adapter/oga.rb +1 -1
- data/lib/moxml/context.rb +3 -1
- data/lib/moxml/document_builder.rb +22 -0
- data/lib/moxml/node.rb +4 -2
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml.rb +1 -0
- data/spec/moxml/adapter/leptris_spec.rb +35 -0
- data/spec/moxml/document_builder_spec.rb +39 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9c1c880ebd136229eb81109ecbfc504a27b9bc101805688231525587a1601e8
|
|
4
|
+
data.tar.gz: c09ed1d4c857d7a78006f4c2ade2bcb662bac233578858e7aeb0ca0b0d2f09f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d334d7e9789817b8891a78b77cf2bbaab05af83252a432cdc4bffb6728c37938a9f430098561ab8a8fa2db77f9ec0f089fc563922a1ceab046f26e7b2a1e01ed
|
|
7
|
+
data.tar.gz: 1feb278e326095475c68bea001bd68f3ac76e8423fdf021bc3a54b765b4e9069ec5cf403c282687fb3843b6ca6411868d9208e6a50a5ea071ca5e443485358f0
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -5,16 +5,6 @@ module Moxml
|
|
|
5
5
|
class Base
|
|
6
6
|
# include XmlUtils
|
|
7
7
|
|
|
8
|
-
# Entity round-trip pipeline lives in Moxml::Entity; these
|
|
9
|
-
# constants and methods stay on the adapter protocol so
|
|
10
|
-
# adapters and wrappers keep their calling convention.
|
|
11
|
-
ENTITY_MARKER = Entity::MARKER
|
|
12
|
-
ENTITY_NAME_PATTERN = Entity::NAME_PATTERN
|
|
13
|
-
ENTITY_NAME_RE = Entity::NAME_RE
|
|
14
|
-
ENTITY_MARKER_RE = Entity::MARKER_RE
|
|
15
|
-
SERIALIZED_ENTITY_MARKER_RE = Entity::SERIALIZED_MARKER_RE
|
|
16
|
-
STANDARD_ENTITIES = Entity::STANDARD_ENTITIES
|
|
17
|
-
|
|
18
8
|
class << self
|
|
19
9
|
include XmlUtils
|
|
20
10
|
|
|
@@ -183,6 +173,14 @@ namespace_validation_mode: :strict)
|
|
|
183
173
|
node
|
|
184
174
|
end
|
|
185
175
|
|
|
176
|
+
# Whether the subtree at native can contain entity markers.
|
|
177
|
+
# Marker-tracking adapters override this so the post-serialize
|
|
178
|
+
# restore can skip its full-output scans on marker-free
|
|
179
|
+
# documents; the default stays conservative.
|
|
180
|
+
def entity_bearing?(_native)
|
|
181
|
+
true
|
|
182
|
+
end
|
|
183
|
+
|
|
186
184
|
# Check if the native document has an XML declaration
|
|
187
185
|
# @param native_doc the native document object
|
|
188
186
|
# @param wrapper [Moxml::Document] the wrapper with has_xml_declaration flag
|
|
@@ -46,6 +46,7 @@ module Moxml
|
|
|
46
46
|
doc = Document.new(native_doc, ctx)
|
|
47
47
|
|
|
48
48
|
record_source_declaration(native_doc, processed)
|
|
49
|
+
attachments.set(native_doc, :entity_markers, processed.include?(Entity::MARKER))
|
|
49
50
|
|
|
50
51
|
doc
|
|
51
52
|
end
|
|
@@ -207,6 +208,21 @@ module Moxml
|
|
|
207
208
|
node.target
|
|
208
209
|
end
|
|
209
210
|
|
|
211
|
+
# Marker presence is a document-level fact: parse records it,
|
|
212
|
+
# the ER builder path flips it, and the split/restore scans
|
|
213
|
+
# consult it. Customized natives only exist as split products
|
|
214
|
+
# of marker-bearing text, so they always report true.
|
|
215
|
+
def entity_bearing?(native)
|
|
216
|
+
case native
|
|
217
|
+
when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
|
|
218
|
+
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment
|
|
219
|
+
true
|
|
220
|
+
else
|
|
221
|
+
doc = native.document
|
|
222
|
+
doc.nil? || attachments.get(doc, :entity_markers) != false
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
210
226
|
def node_type(node)
|
|
211
227
|
case node
|
|
212
228
|
when ::Leptris::XML::Document then :document
|
|
@@ -257,7 +273,17 @@ module Moxml
|
|
|
257
273
|
CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment
|
|
258
274
|
[]
|
|
259
275
|
else
|
|
260
|
-
|
|
276
|
+
natives = node.children.to_a
|
|
277
|
+
# Parse records whether the preprocessed source held any
|
|
278
|
+
# entity markers, and the ER builder path flips the flag
|
|
279
|
+
# when it mints one. A false flag lets traversal skip the
|
|
280
|
+
# marker split — including the per-text content fetch that
|
|
281
|
+
# dominates cold children cost. Cross-document moves of
|
|
282
|
+
# marker-bearing text into an entity-free document degrade
|
|
283
|
+
# to literal text.
|
|
284
|
+
return natives if attachments.get(node.document, :entity_markers) == false
|
|
285
|
+
|
|
286
|
+
split_entity_markers(natives, node)
|
|
261
287
|
end
|
|
262
288
|
end
|
|
263
289
|
|
|
@@ -385,6 +411,7 @@ module Moxml
|
|
|
385
411
|
if child.is_a?(CustomizedLeptris::EntityReference)
|
|
386
412
|
marker = parent.document.create_text_node("#{Entity::MARKER}#{child.name};")
|
|
387
413
|
parent.add_child(marker)
|
|
414
|
+
attachments.set(parent.document, :entity_markers, true)
|
|
388
415
|
return child
|
|
389
416
|
end
|
|
390
417
|
child = parent.document.create_text_node(child) if child.is_a?(String)
|
|
@@ -698,9 +725,10 @@ module Moxml
|
|
|
698
725
|
end
|
|
699
726
|
|
|
700
727
|
def serialize(node, options = {})
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
728
|
+
# Entity restoration belongs to the wrapper layer
|
|
729
|
+
# (Node#to_xml runs adapter.restore_entities for every
|
|
730
|
+
# adapter); doing it here scanned the output a second time.
|
|
731
|
+
normalize_serialization(raw_serialize(node, options), options)
|
|
704
732
|
end
|
|
705
733
|
|
|
706
734
|
def raw_serialize(node, options)
|
data/lib/moxml/adapter/oga.rb
CHANGED
data/lib/moxml/context.rb
CHANGED
|
@@ -60,7 +60,9 @@ module Moxml
|
|
|
60
60
|
xml.rewind if xml.is_a?(IO) || xml.is_a?(StringIO)
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
|
-
|
|
63
|
+
# Allocation-free declaration sniff: String#strip would copy the
|
|
64
|
+
# whole buffer just to test its prefix.
|
|
65
|
+
has_declaration = xml_string.match?(/\A[ \t\r\n\f\v\0]*<\?xml/)
|
|
64
66
|
|
|
65
67
|
# Parse with adapter, passing self (context) so adapter can use our config
|
|
66
68
|
parsed_options = default_options.merge(options)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
# Deprecated compatibility facade retained for 0.2-era callers.
|
|
5
|
+
#
|
|
6
|
+
# DocumentBuilder was the parse entry point before 0.3.0 retired it
|
|
7
|
+
# in favor of Context#parse; deleting the constant broke downstream
|
|
8
|
+
# gems that referenced it directly (issue #124). It now delegates to
|
|
9
|
+
# Context#parse, so `build(xml_string)` returns a parsed document —
|
|
10
|
+
# unlike the 0.2-era behavior of silently returning an empty one.
|
|
11
|
+
class DocumentBuilder
|
|
12
|
+
attr_reader :context
|
|
13
|
+
|
|
14
|
+
def initialize(context)
|
|
15
|
+
@context = context
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def build(xml)
|
|
19
|
+
context.parse(xml)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/moxml/node.rb
CHANGED
|
@@ -111,8 +111,10 @@ module Moxml
|
|
|
111
111
|
result = adapter.serialize(@native, serialize_options)
|
|
112
112
|
result = apply_line_ending(result, serialize_options[:line_ending])
|
|
113
113
|
|
|
114
|
-
# Restore entity markers to named entity references
|
|
115
|
-
adapter.
|
|
114
|
+
# Restore entity markers to named entity references; skipped
|
|
115
|
+
# when the adapter knows the document carries no markers.
|
|
116
|
+
result = adapter.restore_entities(result) if adapter.entity_bearing?(@native)
|
|
117
|
+
result
|
|
116
118
|
end
|
|
117
119
|
|
|
118
120
|
def xpath(expression, namespaces = {})
|
data/lib/moxml/version.rb
CHANGED
data/lib/moxml.rb
CHANGED
|
@@ -80,6 +80,7 @@ module Moxml
|
|
|
80
80
|
autoload :Namespace, "moxml/namespace"
|
|
81
81
|
autoload :Doctype, "moxml/doctype"
|
|
82
82
|
autoload :EntityReference, "moxml/entity_reference"
|
|
83
|
+
autoload :DocumentBuilder, "moxml/document_builder"
|
|
83
84
|
autoload :Builder, "moxml/builder"
|
|
84
85
|
autoload :Entity, "moxml/entity"
|
|
85
86
|
autoload :EntityRegistry, "moxml/entity_registry"
|
|
@@ -80,4 +80,39 @@ RSpec.describe Moxml::Adapter::Leptris do
|
|
|
80
80
|
expect { doc.xpath("//item[") }.to raise_error(Moxml::XPathError)
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
|
+
|
|
84
|
+
describe "entity-marker tracking" do
|
|
85
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
86
|
+
|
|
87
|
+
it "skips the marker split for entity-free documents" do
|
|
88
|
+
doc = ctx.parse("<root>\n <a>text</a>\n <b/>\n</root>")
|
|
89
|
+
kids = doc.root.children.to_a
|
|
90
|
+
expect(kids.map(&:class)).to eq([Moxml::Text, Moxml::Element, Moxml::Text, Moxml::Element, Moxml::Text])
|
|
91
|
+
expect(described_class.entity_bearing?(doc.root.native)).to be(false)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "splits markers when the source carries entities" do
|
|
95
|
+
doc = ctx.parse("<root><a>pre post</a></root>")
|
|
96
|
+
kids = doc.at_xpath("//a").children.to_a
|
|
97
|
+
expect(kids.map(&:class)).to include(Moxml::EntityReference)
|
|
98
|
+
expect(described_class.entity_bearing?(doc.root.native)).to be(true)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "returns serialized markup for entity-free documents" do
|
|
102
|
+
# to_xml must not return nil when the restore scan is skipped
|
|
103
|
+
doc = ctx.parse("<root><a>text</a></root>")
|
|
104
|
+
expect(doc.to_xml).to include("<root>")
|
|
105
|
+
expect(doc.at_xpath("//a").to_xml).to eq("<a>text</a>")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "flips the flag when the builder mints an entity reference" do
|
|
109
|
+
doc = ctx.parse("<root><a/></root>")
|
|
110
|
+
expect(described_class.entity_bearing?(doc.root.native)).to be(false)
|
|
111
|
+
|
|
112
|
+
er = doc.create_entity_reference("nbsp")
|
|
113
|
+
doc.at_xpath("//a").add_child(er)
|
|
114
|
+
expect(described_class.entity_bearing?(doc.root.native)).to be(true)
|
|
115
|
+
expect(doc.at_xpath("//a").children.to_a.map(&:class)).to include(Moxml::EntityReference)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
83
118
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moxml::DocumentBuilder do
|
|
6
|
+
# Issue #124: 0.3.0 deleted the class and NameError'd downstream
|
|
7
|
+
# callers (lutaml-xsd parses XML through this constructor shape).
|
|
8
|
+
describe ".new with a context" do
|
|
9
|
+
it "builds a document from XML content" do
|
|
10
|
+
context = Moxml::Context.new
|
|
11
|
+
builder = described_class.new(context)
|
|
12
|
+
doc = builder.build(<<~XML)
|
|
13
|
+
<person xmlns="http://example.com/test" id="123">
|
|
14
|
+
<name>John Doe</name>
|
|
15
|
+
</person>
|
|
16
|
+
XML
|
|
17
|
+
|
|
18
|
+
expect(doc).to be_a(Moxml::Document)
|
|
19
|
+
expect(doc.root.name).to eq("person")
|
|
20
|
+
expect(doc.root["id"]).to eq("123")
|
|
21
|
+
expect(doc.root.children.select(&:element?).map(&:name)).to eq(["name"])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "exposes the context it was created with" do
|
|
25
|
+
context = Moxml::Context.new
|
|
26
|
+
builder = described_class.new(context)
|
|
27
|
+
|
|
28
|
+
expect(builder.context).to equal(context)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "accepts an explicit adapter, matching Context.new(adapter)" do
|
|
32
|
+
context = Moxml::Context.new(:nokogiri)
|
|
33
|
+
doc = described_class.new(context).build("<root><child/></root>")
|
|
34
|
+
|
|
35
|
+
expect(doc.context.config.adapter_name).to eq(:nokogiri)
|
|
36
|
+
expect(doc.root.children.select(&:element?).map(&:name)).to eq(["child"])
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
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.
|
|
4
|
+
version: 0.5.4
|
|
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-
|
|
11
|
+
date: 2026-08-27 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
|
|
@@ -186,6 +186,7 @@ files:
|
|
|
186
186
|
- lib/moxml/declaration.rb
|
|
187
187
|
- lib/moxml/doctype.rb
|
|
188
188
|
- lib/moxml/document.rb
|
|
189
|
+
- lib/moxml/document_builder.rb
|
|
189
190
|
- lib/moxml/element.rb
|
|
190
191
|
- lib/moxml/entity.rb
|
|
191
192
|
- lib/moxml/entity/reference.rb
|
|
@@ -443,6 +444,7 @@ files:
|
|
|
443
444
|
- spec/moxml/declaration_preservation_spec.rb
|
|
444
445
|
- spec/moxml/declaration_spec.rb
|
|
445
446
|
- spec/moxml/doctype_spec.rb
|
|
447
|
+
- spec/moxml/document_builder_spec.rb
|
|
446
448
|
- spec/moxml/document_spec.rb
|
|
447
449
|
- spec/moxml/element_spec.rb
|
|
448
450
|
- spec/moxml/entity_preservation_spec.rb
|