moxml 0.5.114 → 0.5.115
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 +12 -0
- data/lib/moxml/adapter/leptris.rb +31 -0
- data/lib/moxml/builder.rb +183 -0
- data/lib/moxml/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3269b76bcc581101e3b7f6219c58fc5010752ab1af4f4bb35b8e505989f08e04
|
|
4
|
+
data.tar.gz: 1c255835027897f446de2bb88b175a5b71432636b2a7ab3810480323865a4dac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12d97fd4ae488aee81c5e04e5b55be1336bea7bad60f14fc2481ee9fdd80689edac7dfc3113a6dc65be605a555d388d10e89566a206993c84438d0fcbd9e8d41
|
|
7
|
+
data.tar.gz: 1bf50772dde870a02dd80c1d4d686d2d47149b1d4b9210113bed8e2732dec6c48291105ee26969c92c1d46d6f7086931516c0f6110ce868d662cb2bfa1ea2871
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -13,6 +13,18 @@ module Moxml
|
|
|
13
13
|
class << self
|
|
14
14
|
include XmlUtils
|
|
15
15
|
|
|
16
|
+
# Binding-level markup-accumulating builder face
|
|
17
|
+
# (leptris 1.9.313, #374 lever 2): nil = the adapter has no
|
|
18
|
+
# face and the Builder takes the per-node tree path.
|
|
19
|
+
def builder_face(_native_document)
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# A built subtree carried entity references as marker text —
|
|
24
|
+
# arm the document's marker restoration (the entity
|
|
25
|
+
# preservation contract).
|
|
26
|
+
def mark_entity_markers(_native_document); end
|
|
27
|
+
|
|
16
28
|
def preprocess_entities(xml)
|
|
17
29
|
Entity.preprocess_entities(xml)
|
|
18
30
|
end
|
|
@@ -161,6 +161,16 @@ 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
|
+
# Markup-accumulating builder face (leptris-ruby#374 lever 2,
|
|
165
|
+
# binding 1.9.313): the DSL accumulates markup in a plain
|
|
166
|
+
# String and ONE native crossing (parse + attach) flushes the
|
|
167
|
+
# whole subtree. See Adapter::Base#builder_face.
|
|
168
|
+
NATIVE_BUILDER =
|
|
169
|
+
defined?(::Leptris::XML::Builder) &&
|
|
170
|
+
defined?(::Leptris::XML::Document) &&
|
|
171
|
+
::Leptris::XML::Document.method_defined?(:build) &&
|
|
172
|
+
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.313.0")
|
|
173
|
+
|
|
164
174
|
# XSLT 1.0/2.0/3.0 through the engine's compiled-stylesheet
|
|
165
175
|
# faces (Moxml::XSLT contract). Top-level parameters are not
|
|
166
176
|
# threaded through the faces yet (leptris-ruby#360) — non-empty
|
|
@@ -208,6 +218,27 @@ module Moxml
|
|
|
208
218
|
# (and on installs without the native layer — the constant
|
|
209
219
|
# check short-circuits), a Node.wrap over the shared C
|
|
210
220
|
# pointer for NativeNodes.
|
|
221
|
+
def builder_face(native_document)
|
|
222
|
+
return nil unless NATIVE_BUILDER && NATIVE_READ_LAYER
|
|
223
|
+
|
|
224
|
+
doc = if native_document.is_a?(NN)
|
|
225
|
+
to_binding(native_document)
|
|
226
|
+
else
|
|
227
|
+
native_document
|
|
228
|
+
end
|
|
229
|
+
::Leptris::XML::Builder.new(doc)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def mark_entity_markers(native_document)
|
|
233
|
+
doc = if native_document.is_a?(NN)
|
|
234
|
+
to_binding(native_document)
|
|
235
|
+
else
|
|
236
|
+
native_document
|
|
237
|
+
end
|
|
238
|
+
attachments.set(doc, :entity_markers, true)
|
|
239
|
+
bump_serialize_generation
|
|
240
|
+
end
|
|
241
|
+
|
|
211
242
|
def to_binding(node)
|
|
212
243
|
return node unless NATIVE_READ_LAYER &&
|
|
213
244
|
node.is_a?(::Leptris::XML::NativeNode)
|
data/lib/moxml/builder.rb
CHANGED
|
@@ -11,14 +11,40 @@ module Moxml
|
|
|
11
11
|
@context = context
|
|
12
12
|
@current = @document = context.create_document
|
|
13
13
|
@namespaces = {}
|
|
14
|
+
# Markup-accumulating face (leptris 1.9.313, #374 lever 2):
|
|
15
|
+
# leaf DSL ops write markup into one String and a single
|
|
16
|
+
# native crossing flushes the whole subtree at build end.
|
|
17
|
+
# Tree-only ops (declaration/doctype/PI/entity/namespace)
|
|
18
|
+
# flush first and run on the document; the face retires after
|
|
19
|
+
# its first flush.
|
|
20
|
+
@face = context.config.adapter.builder_face(@document.native)
|
|
21
|
+
@face_stack = []
|
|
22
|
+
@face_counts = Hash.new(0)
|
|
23
|
+
@face_deferred = []
|
|
24
|
+
@face_entity_markers = false
|
|
14
25
|
end
|
|
15
26
|
|
|
16
27
|
def build(&block)
|
|
17
28
|
instance_eval(&block)
|
|
29
|
+
flush_face
|
|
18
30
|
@document
|
|
19
31
|
end
|
|
20
32
|
|
|
33
|
+
# Element ops inside a face-accumulated subtree cannot touch the
|
|
34
|
+
# tree yet (nothing exists until the flush): PIs ride the raw
|
|
35
|
+
# hatch in exact position; namespace/entity/declaration/doctype
|
|
36
|
+
# defer to the flush and re-target their element by position (an
|
|
37
|
+
# element's namespace set is unordered, so deferral is exact;
|
|
38
|
+
# deferred children land at their element's end).
|
|
39
|
+
def face_defer(operation, args)
|
|
40
|
+
@face_deferred << [operation, args, @face_stack.dup]
|
|
41
|
+
end
|
|
42
|
+
|
|
21
43
|
def declaration(version: "1.0", encoding: "UTF-8", standalone: nil)
|
|
44
|
+
args = [version, encoding, standalone]
|
|
45
|
+
return face_defer(:declaration, args) if @face && @face_stack.any?
|
|
46
|
+
|
|
47
|
+
flush_face
|
|
22
48
|
@current.add_child(
|
|
23
49
|
@document.create_declaration(version, encoding, standalone),
|
|
24
50
|
)
|
|
@@ -41,34 +67,65 @@ module Moxml
|
|
|
41
67
|
end
|
|
42
68
|
|
|
43
69
|
def text(content)
|
|
70
|
+
return face_content { @face.text(content) } if @face
|
|
71
|
+
|
|
44
72
|
@current.add_child(@document.create_text(content))
|
|
45
73
|
end
|
|
46
74
|
|
|
47
75
|
def cdata(content)
|
|
76
|
+
return face_content { @face.cdata(content) } if @face
|
|
77
|
+
|
|
48
78
|
@current.add_child(@document.create_cdata(content))
|
|
49
79
|
end
|
|
50
80
|
|
|
51
81
|
def comment(content)
|
|
82
|
+
return face_content { @face.comment(content) } if @face
|
|
83
|
+
|
|
52
84
|
@current.add_child(@document.create_comment(content))
|
|
53
85
|
end
|
|
54
86
|
|
|
55
87
|
def entity_reference(name)
|
|
88
|
+
# In-position through the marker face: the reference lands as
|
|
89
|
+
# marker text exactly where the DSL puts it, and the flush
|
|
90
|
+
# arms the document's entity-marker restoration.
|
|
91
|
+
if @face && @face_stack.any?
|
|
92
|
+
@face.raw("#{Entity::MARKER}#{name};")
|
|
93
|
+
@face_entity_markers = true
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
flush_face
|
|
56
98
|
@current.add_child(@document.create_entity_reference(name))
|
|
57
99
|
end
|
|
58
100
|
|
|
59
101
|
def processing_instruction(target, content)
|
|
102
|
+
if @face
|
|
103
|
+
if @face_stack.empty?
|
|
104
|
+
flush_face
|
|
105
|
+
else
|
|
106
|
+
@face.raw("<?#{target} #{content}?>")
|
|
107
|
+
return
|
|
108
|
+
end
|
|
109
|
+
end
|
|
60
110
|
@current.add_child(
|
|
61
111
|
@document.create_processing_instruction(target, content),
|
|
62
112
|
)
|
|
63
113
|
end
|
|
64
114
|
|
|
65
115
|
def namespace(prefix, uri)
|
|
116
|
+
return face_defer(:namespace, [prefix, uri]) if @face && @face_stack.any?
|
|
117
|
+
|
|
118
|
+
flush_face
|
|
66
119
|
@current.add_namespace(prefix, uri)
|
|
67
120
|
@namespaces[prefix] = uri
|
|
68
121
|
end
|
|
69
122
|
|
|
70
123
|
# Convenience method for DOCTYPE
|
|
71
124
|
def doctype(name, external_id = nil, system_id = nil)
|
|
125
|
+
args = [name, external_id, system_id]
|
|
126
|
+
return face_defer(:doctype, args) if @face && @face_stack.any?
|
|
127
|
+
|
|
128
|
+
flush_face
|
|
72
129
|
@current.add_child(
|
|
73
130
|
@document.create_doctype(name, external_id, system_id),
|
|
74
131
|
)
|
|
@@ -109,13 +166,139 @@ module Moxml
|
|
|
109
166
|
true
|
|
110
167
|
end
|
|
111
168
|
|
|
169
|
+
FACE_NAME = /\A[a-zA-Z_][\w.-]*\z/
|
|
170
|
+
FACE_ESCAPE = { "&" => "&", "<" => "<", ">" => ">",
|
|
171
|
+
'"' => """ }.freeze
|
|
172
|
+
FACE_ESCAPE_RE = /[&<>"]/
|
|
173
|
+
|
|
112
174
|
private
|
|
113
175
|
|
|
176
|
+
# One face.element per DSL element — the face opens/closes the
|
|
177
|
+
# markup around the block itself, so nesting is free. Prefixed
|
|
178
|
+
# names (p:tag) fall outside the face's name shape and ride the
|
|
179
|
+
# raw hatch with manual escaping. Returns no wrapper: nothing
|
|
180
|
+
# exists until the flush.
|
|
181
|
+
def face_element(tag_name, attrs, text_content, block, eval_block)
|
|
182
|
+
unless FACE_NAME.match?(tag_name)
|
|
183
|
+
return face_raw_element(tag_name, attrs, text_content, block,
|
|
184
|
+
eval_block)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
face_attrs = {}
|
|
188
|
+
attrs.each do |key, value|
|
|
189
|
+
k = key.to_s
|
|
190
|
+
if k == "xmlns" || k.start_with?("xmlns:")
|
|
191
|
+
face_attrs[k] = value.to_s
|
|
192
|
+
@namespaces[k.delete_prefix("xmlns:")] = value.to_s
|
|
193
|
+
else
|
|
194
|
+
face_attrs[k] = value
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
ordinal = @face_counts[@face_stack.length]
|
|
198
|
+
@face_counts[@face_stack.length] = ordinal + 1
|
|
199
|
+
@face_stack << ordinal
|
|
200
|
+
begin
|
|
201
|
+
@face.element(tag_name, face_attrs) do
|
|
202
|
+
@face.text(text_content) if text_content
|
|
203
|
+
run_builder_block(block, eval_block) if block
|
|
204
|
+
end
|
|
205
|
+
ensure
|
|
206
|
+
@face_stack.pop
|
|
207
|
+
@face_counts.delete(@face_stack.length + 1)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def face_raw_element(tag_name, attrs, text_content, block, eval_block)
|
|
212
|
+
open = "<#{tag_name}"
|
|
213
|
+
attrs.each do |key, value|
|
|
214
|
+
k = key.to_s
|
|
215
|
+
if k == "xmlns" || k.start_with?("xmlns:")
|
|
216
|
+
@namespaces[k.delete_prefix("xmlns:")] = value.to_s
|
|
217
|
+
end
|
|
218
|
+
open << " " << k << '="' << face_escape(value) << '"'
|
|
219
|
+
end
|
|
220
|
+
ordinal = @face_counts[@face_stack.length]
|
|
221
|
+
@face_counts[@face_stack.length] = ordinal + 1
|
|
222
|
+
@face_stack << ordinal
|
|
223
|
+
begin
|
|
224
|
+
if block || text_content
|
|
225
|
+
@face.raw("#{open}>")
|
|
226
|
+
@face.text(text_content) if text_content
|
|
227
|
+
run_builder_block(block, eval_block) if block
|
|
228
|
+
@face.raw("</#{tag_name}>")
|
|
229
|
+
else
|
|
230
|
+
@face.raw("#{open}/>")
|
|
231
|
+
end
|
|
232
|
+
ensure
|
|
233
|
+
@face_stack.pop
|
|
234
|
+
@face_counts.delete(@face_stack.length + 1)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Content at document level has no root to attach to and the
|
|
239
|
+
# Document face flush demands exactly one root — retire the face
|
|
240
|
+
# and take the tree path (legacy semantics: doc-level text is a
|
|
241
|
+
# document child). Inside a subtree the text accumulates in
|
|
242
|
+
# position; @current never leaves the document in face mode, so
|
|
243
|
+
# the face stack is the nesting test.
|
|
244
|
+
def face_content
|
|
245
|
+
return yield if @face && @face_stack.any?
|
|
246
|
+
|
|
247
|
+
flush_face if @face
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def run_builder_block(block, eval_block)
|
|
251
|
+
eval_block ? instance_eval(&block) : block.call
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def face_escape(value)
|
|
255
|
+
value.to_s.gsub(FACE_ESCAPE_RE, FACE_ESCAPE)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def flush_face
|
|
259
|
+
return unless @face
|
|
260
|
+
|
|
261
|
+
face = @face
|
|
262
|
+
@face = nil
|
|
263
|
+
face.flush
|
|
264
|
+
if @face_entity_markers
|
|
265
|
+
@context.config.adapter.mark_entity_markers(@document.native)
|
|
266
|
+
end
|
|
267
|
+
apply_face_deferred
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Deferred tree-ops re-target their element by position: the
|
|
271
|
+
# stack records each element's ordinal among its parent's
|
|
272
|
+
# elements, and the flushed tree preserves markup order.
|
|
273
|
+
def apply_face_deferred
|
|
274
|
+
@face_deferred.each do |op, args, stack|
|
|
275
|
+
target = @document.root
|
|
276
|
+
stack[1..].each do |ordinal|
|
|
277
|
+
target = target.elements.to_a[ordinal]
|
|
278
|
+
end
|
|
279
|
+
case op
|
|
280
|
+
when :namespace
|
|
281
|
+
target.add_namespace(*args)
|
|
282
|
+
@namespaces[args[0]] = args[1]
|
|
283
|
+
when :declaration
|
|
284
|
+
target.add_child(@document.create_declaration(*args))
|
|
285
|
+
when :doctype
|
|
286
|
+
target.add_child(@document.create_doctype(*args))
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
@face_deferred = []
|
|
290
|
+
end
|
|
291
|
+
|
|
114
292
|
# Single method for all element creation.
|
|
115
293
|
# eval_block: true → instance_eval (build DSL context)
|
|
116
294
|
# eval_block: false → yield (preserves caller's self)
|
|
117
295
|
def create_element_node(tag_name, attrs = {}, text_content: nil,
|
|
118
296
|
block: nil, eval_block: true)
|
|
297
|
+
if @face
|
|
298
|
+
face_element(tag_name, attrs, text_content, block, eval_block)
|
|
299
|
+
return nil
|
|
300
|
+
end
|
|
301
|
+
|
|
119
302
|
el = @document.create_element(tag_name)
|
|
120
303
|
|
|
121
304
|
attrs.each do |key, value|
|
data/lib/moxml/version.rb
CHANGED