moxml 0.5.47 → 0.5.48

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: 83f4ac58d2c22de225ec143ff3e78eb95e3460863b8fb1d67e8230a872bfad2a
4
- data.tar.gz: c7401f4154a32e534a1c6a53f067a25406b16056de651eec8e768ac04c602dcc
3
+ metadata.gz: fbe45e0b5017c94650ed10dde4e726843ad5a820005ac4a1f653d5747ef04f71
4
+ data.tar.gz: 14f003f4f15ff1b9a40429654a7435485cb20ca62e88f6b9dc21e1120e9c85dd
5
5
  SHA512:
6
- metadata.gz: d0ad7a04cbac82f8195be3be4318ee7d381974a812c7c2fce45110778875abfcb0be7ebb0c8e19339ddf2c2d8d9f4bd575254062b492a9cf4ad35072d8fe67fd
7
- data.tar.gz: 301188cc44df6af9339502db3694651cc2a69238e3c222f6c0cda92393e72b80d5353cca335671be6b6560d5d64da4c32ad3bc4484f91d12239ddebf677e6aec
6
+ metadata.gz: b6bcf2d9d9b6a34bbd56afc5076421c22e107ac33e9a8adc06ebe0329b7cab59e038ace45e0cb2d14debe8ba16c8cd606af49d8994bf59137e51d2679487295d
7
+ data.tar.gz: 22ea33b5bc99974709cc8cbcf670dc39fdce033fcce310353cda9aa15568129a283055a8e8975f211d67a8167fa6f87597db0d81e4935f77864a6d58ccc1a9dd
@@ -133,6 +133,14 @@ module Moxml
133
133
  # Protocol-level native equality; engines with more than one
134
134
  # wrapper class over one C node override (leptris native
135
135
  # read layer). Shared adapter examples compare through this.
136
+ # Extend-in-place capability (#230): an adapter whose native
137
+ # objects can carry the contract modules directly returns the
138
+ # extended native here (its @native is itself); the default
139
+ # mints a wrapper shell.
140
+ def wrap_native(_node, _type, _context)
141
+ nil
142
+ end
143
+
136
144
  def same_node?(one, other)
137
145
  one == other
138
146
  end
@@ -85,6 +85,24 @@ module Moxml
85
85
  defined?(::Leptris::XML::NativeNode) &&
86
86
  Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.163.5")
87
87
 
88
+ # Identity mode (#230 stage 2): the contract modules extended
89
+ # onto natives shadow the C surface (Element#[] over
90
+ # NativeNode#[]), so the adapter's native fast paths speak to
91
+ # the native layer through these bound entries — bind_call is
92
+ # the same C dispatch without the shadow lookup.
93
+ if NATIVE_READ_LAYER
94
+ NN = ::Leptris::XML::NativeNode
95
+ NN_ATTRIBUTE = NN.instance_method(:attribute)
96
+ NN_NAME = NN.instance_method(:name)
97
+ NN_PARENT = NN.instance_method(:parent)
98
+ NN_NEXT_SIBLING = NN.instance_method(:next_sibling)
99
+ NN_CONTENT = NN.instance_method(:content)
100
+ NN_CHILDREN = NN.instance_method(:children)
101
+ NN_NODE_TYPE = NN.instance_method(:node_type)
102
+ NN_DOCUMENT = NN.instance_method(:document)
103
+ NN_ADD_CHILD = NN.instance_method(:add_child)
104
+ end
105
+
88
106
  # 1.9.163.2 moved the native bulk children into C
89
107
  # (Native.bulk_children) and fixed the 512 truncation
90
108
  # (leptris-ruby#202). The 162.6-163.1 window carries the
@@ -136,6 +154,28 @@ module Moxml
136
154
  @native_doc_roots = ObjectSpace::WeakMap.new
137
155
 
138
156
  class << self
157
+ # Extend-in-place mint (#230 stage 2): a TypedData native
158
+ # carries the contract modules on itself — the wrapper layer's
159
+ # 3-4 Ruby frames collapse to the module call over the C
160
+ # method. @native is self; every adapter method that accepts
161
+ # a native already handles NativeNode receivers.
162
+ # Extend-in-place mint (#230 stage 2): disabled by
163
+ # measurement. Extending TypedData natives with the
164
+ # contract modules works correctly (full battery + lm
165
+ # 5422/0 under identity mode) but each extend allocates a
166
+ # singleton plus per-module iclasses — the consumer
167
+ # pipeline regressed 4017us -> 16350us with 6x the
168
+ # allocations on a 900-node walk. The per-read frame
169
+ # savings (~50ns) cannot repay that mint cost. Revisit
170
+ # when the binding grows klass-injection
171
+ # (NativeNode.from(doc, ptr, klass:) minting the
172
+ # contract-carrying subclass in C) — asked upstream in the
173
+ # #230 thread. Wrappers mint through the Wrappers:: shells
174
+ # meanwhile.
175
+ def wrap_native(_node, _type, _context)
176
+ nil
177
+ end
178
+
139
179
  def record_native_doc(root_native, doc)
140
180
  @native_doc_roots[root_native] = doc
141
181
  end
@@ -165,7 +205,7 @@ module Moxml
165
205
  def doc_for(node)
166
206
  # The 1.9.163.5 native layer exposes the owning document
167
207
  # directly; the root climb serves older bindings.
168
- return node.document if NATIVE_MUTATIONS_COHERENT
208
+ return NN_DOCUMENT.bind_call(node) if NATIVE_MUTATIONS_COHERENT
169
209
 
170
210
  current = node
171
211
  current = current.parent while current&.parent
@@ -201,7 +241,7 @@ module Moxml
201
241
  ::Leptris::XML::FFI::C14N_1_0, nil,
202
242
  mode: ::Leptris::XML::FFI::C14N_MODE_CANONICAL
203
243
  )
204
- wrapper = Moxml::Document.new(native_doc, Moxml::Context.new(:leptris))
244
+ wrapper = Moxml::Wrappers::Document.new(native_doc, Moxml::Context.new(:leptris))
205
245
  reference = Moxml::C14n::Inclusive10.new.canonicalize(wrapper.root)
206
246
  native == reference
207
247
  rescue StandardError
@@ -333,7 +373,14 @@ module Moxml
333
373
  # document-plus-attachment probe here cost more than the
334
374
  # read itself).
335
375
  def bare_attr_value(element, name)
336
- value = element[name.to_s]
376
+ # Exact-name C read: bare names target no-namespace
377
+ # attributes (the documented contract — a bare "y" never
378
+ # resolves a "p:y" sibling).
379
+ value = if NATIVE_READ_LAYER && element.is_a?(NN)
380
+ NN_ATTRIBUTE.bind_call(element, name.to_s)
381
+ else
382
+ element[name.to_s]
383
+ end
337
384
  if !NATIVE_STRINGS_UTF8 && NATIVE_READ_LAYER &&
338
385
  element.is_a?(::Leptris::XML::NativeNode) &&
339
386
  value.is_a?(String)
@@ -376,7 +423,7 @@ module Moxml
376
423
  create_document
377
424
  end
378
425
  ctx = _context || Context.new(:leptris)
379
- doc = Document.new(native_doc, ctx)
426
+ doc = Wrappers::Document.new(native_doc, ctx)
380
427
 
381
428
  if options[:noblanks] && !ENGINE_NOBLANKS_SAFE
382
429
  # The engine's DROP_WS_TEXT trims boundary whitespace of
@@ -451,7 +498,7 @@ module Moxml
451
498
  end
452
499
  attachments.set(native_doc, :entity_markers, false)
453
500
  bump_serialize_generation
454
- Document.new(native_doc, _context || Context.new(:leptris))
501
+ Wrappers::Document.new(native_doc, _context || Context.new(:leptris))
455
502
  end
456
503
 
457
504
  # nil when no parse flag is requested — the binding treats a
@@ -711,13 +758,15 @@ module Moxml
711
758
  end
712
759
 
713
760
  def processing_instruction_target(node)
761
+ return to_binding(node).target if NATIVE_READ_LAYER && node.is_a?(NN)
762
+
714
763
  node.target
715
764
  end
716
765
 
717
766
  def node_type(node)
718
767
  if NATIVE_READ_LAYER &&
719
768
  node.is_a?(::Leptris::XML::NativeNode)
720
- type = node.node_type
769
+ type = NN_NODE_TYPE.bind_call(node)
721
770
  # The native layer spells PIs :pi; the wrapper contract
722
771
  # (node_type_map) says :processing_instruction.
723
772
  return :processing_instruction if type == :pi
@@ -765,11 +814,12 @@ module Moxml
765
814
 
766
815
  if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
767
816
  # PIs expose no name through the native layer.
768
- return to_binding(node).target.to_s if node.node_type == :pi
817
+ return to_binding(node).target.to_s if NN_NODE_TYPE.bind_call(node) == :pi
769
818
 
770
- return node.name if NATIVE_STRINGS_UTF8
819
+ name = NN_NAME.bind_call(node)
820
+ return name if NATIVE_STRINGS_UTF8
771
821
 
772
- node.name.dup.force_encoding(Encoding::UTF_8)
822
+ name.dup.force_encoding(Encoding::UTF_8)
773
823
  end
774
824
 
775
825
  node.name.to_s.dup.force_encoding("UTF-8")
@@ -808,7 +858,7 @@ module Moxml
808
858
  # the wrapper's memo supplies the flag, so deriving it
809
859
  # here costs no C parent climb per call.
810
860
  natives = if NATIVE_BULK_FIXED
811
- node.children.to_a
861
+ NN_CHILDREN.bind_call(node).to_a
812
862
  else
813
863
  bulk_native_children(node)
814
864
  end
@@ -864,7 +914,7 @@ module Moxml
864
914
 
865
915
  def parent(node)
866
916
  if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
867
- parent = node.parent
917
+ parent = NN_PARENT.bind_call(node)
868
918
  return parent if parent
869
919
 
870
920
  doc = doc_for(node)
@@ -987,7 +1037,7 @@ module Moxml
987
1037
  end
988
1038
 
989
1039
  def next_sibling(node)
990
- return node.next_sibling if NATIVE_READ_LAYER &&
1040
+ return NN_NEXT_SIBLING.bind_call(node) if NATIVE_READ_LAYER &&
991
1041
  node.is_a?(::Leptris::XML::NativeNode)
992
1042
 
993
1043
  node.next_sibling if node.is_a?(::Leptris::XML::Node)
@@ -1114,6 +1164,12 @@ module Moxml
1114
1164
  bump_serialize_generation
1115
1165
  return child
1116
1166
  end
1167
+ if NATIVE_MUTATIONS_COHERENT && parent.is_a?(NN) && child.is_a?(NN)
1168
+ doc = NN_DOCUMENT.bind_call(parent)
1169
+ child = doc.create_text_node(child) if child.is_a?(String)
1170
+ return NN_ADD_CHILD.bind_call(parent, child)
1171
+ end
1172
+
1117
1173
  child = parent.document.create_text_node(child) if child.is_a?(String)
1118
1174
  parent.add_child(child)
1119
1175
  end
@@ -1200,9 +1256,10 @@ module Moxml
1200
1256
 
1201
1257
  def text_content(node)
1202
1258
  if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
1203
- return node.content if NATIVE_STRINGS_UTF8
1259
+ content = NN_CONTENT.bind_call(node)
1260
+ return content if NATIVE_STRINGS_UTF8
1204
1261
 
1205
- return node.content.dup.force_encoding(Encoding::UTF_8)
1262
+ content.dup.force_encoding(Encoding::UTF_8)
1206
1263
  end
1207
1264
 
1208
1265
  # Frequency-ordered: elements dominate reads; they paid
@@ -1246,26 +1303,35 @@ module Moxml
1246
1303
  end
1247
1304
 
1248
1305
  def cdata_content(node)
1306
+ return NN_CONTENT.bind_call(node) if NATIVE_READ_LAYER && node.is_a?(NN)
1307
+
1249
1308
  node.content
1250
1309
  end
1251
1310
 
1252
1311
  def set_cdata_content(node, content)
1312
+ node = to_binding(node) if NATIVE_READ_LAYER
1253
1313
  node.content = content.to_s
1254
1314
  end
1255
1315
 
1256
1316
  def comment_content(node)
1317
+ return NN_CONTENT.bind_call(node) if NATIVE_READ_LAYER && node.is_a?(NN)
1318
+
1257
1319
  node.content
1258
1320
  end
1259
1321
 
1260
1322
  def set_comment_content(node, content)
1323
+ node = to_binding(node) if NATIVE_READ_LAYER
1261
1324
  node.content = content.to_s
1262
1325
  end
1263
1326
 
1264
1327
  def processing_instruction_content(node)
1328
+ return NN_CONTENT.bind_call(node) if NATIVE_READ_LAYER && node.is_a?(NN)
1329
+
1265
1330
  node.content
1266
1331
  end
1267
1332
 
1268
1333
  def set_processing_instruction_content(node, content)
1334
+ node = to_binding(node) if NATIVE_READ_LAYER
1269
1335
  node.data = content.to_s
1270
1336
  end
1271
1337
 
@@ -152,7 +152,7 @@ module Moxml
152
152
  # any new parse-time logic only has to be added to this one
153
153
  # path; the restoration walk is just one of potentially several
154
154
  # post-processing steps and doesn't fork the construction.
155
- doc = Document.new(native_doc, ctx)
155
+ doc = Wrappers::Document.new(native_doc, ctx)
156
156
  Entity::Restorer.new(doc).run if ctx.config.restore_entities
157
157
  doc
158
158
  end
@@ -80,7 +80,7 @@ module Moxml
80
80
  # Use provided context if available, otherwise create new one
81
81
  ctx = _context || Context.new(:nokogiri)
82
82
  attachments.set(native_doc, :entity_markers, entity_markers)
83
- Document.new(native_doc, ctx)
83
+ Wrappers::Document.new(native_doc, ctx)
84
84
  end
85
85
 
86
86
  # Tolerant HTML parsing through libxml2's HTML mode: implied
@@ -90,7 +90,7 @@ module Moxml
90
90
  def parse_html(html, _options = {}, _context = nil)
91
91
  html_string = html.is_a?(IO) || html.is_a?(StringIO) ? html.read : html.to_s
92
92
  native_doc = ::Nokogiri::HTML(html_string)
93
- Document.new(native_doc, _context || Context.new(:nokogiri))
93
+ Wrappers::Document.new(native_doc, _context || Context.new(:nokogiri))
94
94
  end
95
95
 
96
96
  # Nokogiri parses with `config.recover` unless strict, so
@@ -54,7 +54,7 @@ module Moxml
54
54
  end
55
55
 
56
56
  ctx = _context || Context.new(:oga)
57
- doc = Document.new(native_doc, ctx)
57
+ doc = Wrappers::Document.new(native_doc, ctx)
58
58
  Entity::Restorer.new(doc).run if ctx.config.restore_entities
59
59
  doc
60
60
  end
@@ -68,7 +68,7 @@ module Moxml
68
68
  end
69
69
 
70
70
  ctx = _context || Context.new(context_adapter_name)
71
- Document.new(native_doc, ctx)
71
+ Wrappers::Document.new(native_doc, ctx)
72
72
  end
73
73
 
74
74
  # SAX parsing implementation for Ox
@@ -49,7 +49,7 @@ module Moxml
49
49
  end
50
50
 
51
51
  ctx = _context || Context.new(:rexml)
52
- doc = Document.new(native_doc, ctx)
52
+ doc = Wrappers::Document.new(native_doc, ctx)
53
53
  Entity::Restorer.new(doc).run if ctx.config.restore_entities
54
54
  doc
55
55
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class Attribute < Node
4
+ module Attribute
5
+ include Node
6
+
5
7
  # Immutable between name= writes (which clear it via
6
8
  # clear_native_memo!), unlike element names that can change via
7
9
  # native adoption; the memo removes an adapter read per access.
@@ -64,7 +66,7 @@ module Moxml
64
66
 
65
67
  def namespace
66
68
  ns = adapter.namespace(@native)
67
- ns && Namespace.new(ns, context)
69
+ ns && Wrappers::Namespace.new(ns, context)
68
70
  end
69
71
 
70
72
  def namespace=(ns)
data/lib/moxml/cdata.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class Cdata < Node
4
+ module Cdata
5
+ include Node
6
+
5
7
  def content
6
8
  adapter.cdata_content(@native)
7
9
  end
data/lib/moxml/comment.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class Comment < Node
4
+ module Comment
5
+ include Node
6
+
5
7
  def content
6
8
  adapter.comment_content(@native)
7
9
  end
data/lib/moxml/context.rb CHANGED
@@ -77,7 +77,7 @@ module Moxml
77
77
  end
78
78
 
79
79
  def create_document(native_doc = nil)
80
- Document.new(config.adapter.create_document(native_doc), self)
80
+ Wrappers::Document.new(config.adapter.create_document(native_doc), self)
81
81
  end
82
82
 
83
83
  def parse(xml, options = {})
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class Declaration < Node
4
+ module Declaration
5
+ include Node
6
+
5
7
  ALLOWED_VERSIONS = ["1.0", "1.1"].freeze
6
8
  ALLOWED_STANDALONE = %w[yes no].freeze
7
9
  ALLOWED_ATTRIBUTES = %w[version encoding standalone].freeze
data/lib/moxml/doctype.rb CHANGED
@@ -13,7 +13,9 @@ module Moxml
13
13
  # Most adapters do not currently implement these methods. If you need DOCTYPE
14
14
  # information, consider using adapter-specific methods or parsing the serialized
15
15
  # XML manually.
16
- class Doctype < Node
16
+ module Doctype
17
+ include Node
18
+
17
19
  # Returns the DOCTYPE name (root element name)
18
20
  #
19
21
  # @return [String, nil] the DOCTYPE name
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class Document < Node
4
+ module Document
5
+ include Node
6
+
5
7
  attr_accessor :has_xml_declaration
6
8
 
7
9
  def initialize(native, context, adapter = nil, node_type = nil)
@@ -61,30 +63,30 @@ module Moxml
61
63
  end
62
64
 
63
65
  def create_element(name)
64
- Element.new(adapter.create_element(name, owner_doc: @native), context)
66
+ Wrappers::Element.new(adapter.create_element(name, owner_doc: @native), context)
65
67
  end
66
68
 
67
69
  def create_text(content)
68
- Text.new(adapter.create_text(content, owner_doc: @native), context)
70
+ Wrappers::Text.new(adapter.create_text(content, owner_doc: @native), context)
69
71
  end
70
72
 
71
73
  def create_cdata(content)
72
- Cdata.new(adapter.create_cdata(content, owner_doc: @native), context)
74
+ Wrappers::Cdata.new(adapter.create_cdata(content, owner_doc: @native), context)
73
75
  end
74
76
 
75
77
  def create_comment(content)
76
- Comment.new(adapter.create_comment(content, owner_doc: @native), context)
78
+ Wrappers::Comment.new(adapter.create_comment(content, owner_doc: @native), context)
77
79
  end
78
80
 
79
81
  def create_doctype(name, external_id, system_id)
80
- Doctype.new(
82
+ Wrappers::Doctype.new(
81
83
  adapter.create_doctype(name, external_id, system_id),
82
84
  context,
83
85
  )
84
86
  end
85
87
 
86
88
  def create_processing_instruction(target, content)
87
- ProcessingInstruction.new(
89
+ Wrappers::ProcessingInstruction.new(
88
90
  adapter.create_processing_instruction(target, content),
89
91
  context,
90
92
  )
@@ -93,11 +95,11 @@ module Moxml
93
95
  def create_declaration(version = "1.0", encoding = "UTF-8",
94
96
  standalone = nil)
95
97
  decl = adapter.create_declaration(version, encoding, standalone)
96
- Declaration.new(decl, context)
98
+ Wrappers::Declaration.new(decl, context)
97
99
  end
98
100
 
99
101
  def create_entity_reference(name)
100
- EntityReference.new(adapter.create_entity_reference(name), context)
102
+ Wrappers::EntityReference.new(adapter.create_entity_reference(name), context)
101
103
  end
102
104
 
103
105
  def add_child(node)
data/lib/moxml/element.rb CHANGED
@@ -120,7 +120,7 @@ module Moxml
120
120
  # mint, so the first name/value/attribute? access skips the
121
121
  # context hop and the adapter's type probe.
122
122
  @attributes ||= adapter.attributes(@native).map do |attr|
123
- a = Attribute.new(attr, context, adapter, :attribute)
123
+ a = Wrappers::Attribute.new(attr, context, adapter, :attribute)
124
124
  a.parent_node = self
125
125
  a
126
126
  end
@@ -182,7 +182,7 @@ module Moxml
182
182
  # it's NOT the same as namespaces.first
183
183
  def namespace
184
184
  ns = adapter.namespace(@native)
185
- ns && Namespace.new(ns, context)
185
+ ns && Wrappers::Namespace.new(ns, context)
186
186
  end
187
187
 
188
188
  # add the prefix to the element name
@@ -216,7 +216,7 @@ module Moxml
216
216
  return @namespace_definitions unless @namespace_definitions.nil?
217
217
 
218
218
  @namespace_definitions = adapter.namespace_definitions(@native).map do |ns|
219
- Namespace.new(ns, context)
219
+ Wrappers::Namespace.new(ns, context)
220
220
  end
221
221
  end
222
222
 
@@ -225,7 +225,7 @@ module Moxml
225
225
  # scope. The shape materialize records carry (issue #138).
226
226
  def declared_namespaces
227
227
  adapter.namespace_definitions(@native).map do |ns|
228
- wrapper = Namespace.new(ns, context)
228
+ wrapper = Wrappers::Namespace.new(ns, context)
229
229
  [wrapper.prefix, wrapper.uri]
230
230
  end
231
231
  end
@@ -236,7 +236,7 @@ module Moxml
236
236
  generation = context.namespace_scope_generation
237
237
  if @in_scope_namespaces.nil? || @in_scope_generation != generation
238
238
  @in_scope_namespaces = adapter.in_scope_namespaces(@native).map do |ns|
239
- Namespace.new(ns, context)
239
+ Wrappers::Namespace.new(ns, context)
240
240
  end
241
241
  @in_scope_generation = generation
242
242
  end
@@ -363,7 +363,8 @@ module Moxml
363
363
  end
364
364
  end
365
365
 
366
- class Element < Node
366
+ module Element
367
+ include Node
367
368
  include ElementBehavior
368
369
  end
369
370
  end
@@ -78,12 +78,12 @@ module Moxml
78
78
  def append_chunk(parent, type, payload)
79
79
  case type
80
80
  when :text
81
- parent.add_child(::Moxml::Text.new(
81
+ parent.add_child(::Moxml::Wrappers::Text.new(
82
82
  @adapter.create_native_text(payload), @ctx
83
83
  ))
84
84
  when :eref
85
85
  parent.add_child(
86
- ::Moxml::EntityReference.new(
86
+ ::Moxml::Wrappers::EntityReference.new(
87
87
  @adapter.create_native_entity_reference(payload),
88
88
  @ctx,
89
89
  ),
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class EntityReference < Node
4
+ module EntityReference
5
+ include Node
6
+
5
7
  def content
6
8
  ""
7
9
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class Namespace < Node
4
+ module Namespace
5
+ include Node
6
+
5
7
  def prefix
6
8
  adapter.namespace_prefix(@native)
7
9
  end
data/lib/moxml/node.rb CHANGED
@@ -20,15 +20,22 @@ module Moxml
20
20
  # otherwise pay the context hop and the type probe again on its
21
21
  # first access.
22
22
  def initialize(native, context, adapter = nil, node_type = nil)
23
+ prime_contract(native, context, adapter, node_type)
24
+ end
25
+
26
+ # Update native reference after identity-changing operations
27
+ # (e.g., LibXML doc.root= creates a new Ruby wrapper)
28
+ # Contract priming shared by the constructor and the extend-in-place
29
+ # mint (#230): an extended native IS its own @native.
30
+ def prime_contract(native, context, adapter = nil, node_type = nil)
23
31
  @context = context
24
32
  @native = native
25
33
  @parent_node = nil
26
34
  @adapter = adapter
27
35
  @node_type_cached = node_type
36
+ self
28
37
  end
29
38
 
30
- # Update native reference after identity-changing operations
31
- # (e.g., LibXML doc.root= creates a new Ruby wrapper)
32
39
  def refresh_native!(new_native)
33
40
  unless new_native.equal?(@native)
34
41
  context.unregister_wrapper(@native)
@@ -48,7 +55,7 @@ module Moxml
48
55
  end
49
56
 
50
57
  def document
51
- Document.wrap(adapter.document(@native), context)
58
+ Moxml::Node.wrap(adapter.document(@native), context)
52
59
  end
53
60
 
54
61
  def parent
@@ -255,7 +262,7 @@ module Moxml
255
262
  return nil unless element?
256
263
 
257
264
  ns = adapter.namespace(@native)
258
- ns && Namespace.new(ns, context)
265
+ ns && Wrappers::Namespace.new(ns, context)
259
266
  end
260
267
 
261
268
  # Returns all namespace definitions on this node
@@ -264,7 +271,7 @@ module Moxml
264
271
  return [] unless element?
265
272
 
266
273
  adapter.namespace_definitions(@native).map do |ns|
267
- Namespace.new(ns, context)
274
+ Wrappers::Namespace.new(ns, context)
268
275
  end
269
276
  end
270
277
 
@@ -414,7 +421,7 @@ module Moxml
414
421
  # Native equality goes through the adapter: engines with a
415
422
  # native read layer hand out two wrapper classes over one C
416
423
  # node, and raw native == is false across that seam.
417
- self.class == other.class &&
424
+ other.is_a?(Moxml::Node) &&
418
425
  adapter.same_node?(@native, other.native)
419
426
  end
420
427
 
@@ -486,7 +493,7 @@ module Moxml
486
493
 
487
494
  def prepare_node(node)
488
495
  case node
489
- when String then Text.new(adapter.create_text(node), context)
496
+ when String then Moxml::Node.wrap(adapter.create_text(node), context)
490
497
  when Node then node
491
498
  else
492
499
  raise Moxml::DocumentStructureError.new(
@@ -512,11 +519,35 @@ module Moxml
512
519
  end
513
520
  end
514
521
 
515
- class Node
522
+ module Node
516
523
  include NodeBehavior
517
524
 
525
+ # Instantiation shells per type — the contract constants
526
+ # (Element, Text, ...) are modules since issue #230, so wrappers
527
+ # mint through Wrappers::*.
518
528
  def self.node_type_map
519
529
  @node_type_map ||= {
530
+ element: Wrappers::Element,
531
+ text: Wrappers::Text,
532
+ cdata: Wrappers::Cdata,
533
+ comment: Wrappers::Comment,
534
+ processing_instruction: Wrappers::ProcessingInstruction,
535
+ document: Wrappers::Document,
536
+ declaration: Wrappers::Declaration,
537
+ doctype: Wrappers::Doctype,
538
+ attribute: Wrappers::Attribute,
539
+ entity_reference: Wrappers::EntityReference,
540
+ }.freeze
541
+ end
542
+
543
+ # The contract module for a type — what an in-place extended
544
+ # native carries instead of a wrapper shell.
545
+ def self.contract_module(type)
546
+ contract_modules[type]
547
+ end
548
+
549
+ def self.contract_modules
550
+ @contract_modules ||= {
520
551
  element: Element,
521
552
  text: Text,
522
553
  cdata: Cdata,
@@ -538,8 +569,17 @@ module Moxml
538
569
 
539
570
  adapter = adapter(context)
540
571
  type = adapter.node_type(node)
541
- klass = node_type_map[type] || self
542
572
 
573
+ # Extend-in-place (issue #230): adapters whose natives can
574
+ # carry the contract modules directly (leptris TypedData) mint
575
+ # the native itself as the wrapper — @native is self.
576
+ if (extended = adapter.wrap_native(node, type, context))
577
+ extended.prime_contract(node, context, adapter, type)
578
+ context.register_wrapper(node, extended)
579
+ return extended
580
+ end
581
+
582
+ klass = node_type_map[type] || Wrappers::Node
543
583
  klass.new(node, context, adapter, type)
544
584
  .tap { |wrapper| context.register_wrapper(node, wrapper) }
545
585
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class ProcessingInstruction < Node
4
+ module ProcessingInstruction
5
+ include Node
6
+
5
7
  def target
6
8
  adapter.processing_instruction_target(@native)
7
9
  end
data/lib/moxml/text.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class Text < Node
4
+ module Text
5
+ include Node
6
+
5
7
  def content
6
8
  text = raw_content
7
9
  entity_bearing? ? adapter.restore_entities(text) : text
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.47"
4
+ VERSION = "0.5.48"
5
5
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ # Instantiation shells (issue #230): the node-type constants are
5
+ # contract MODULES so leptris TypedData natives can carry them in
6
+ # place — wrappers are minted through these shells for native
7
+ # objects that cannot take the modules in place (binding-layer
8
+ # natives on every adapter, and all natives on non-leptris ones).
9
+ module Wrappers
10
+ Node = Class.new do
11
+ include Moxml::Node
12
+ end
13
+ Element = Class.new(Node) do
14
+ include Moxml::Element
15
+ end
16
+ Text = Class.new(Node) do
17
+ include Moxml::Text
18
+ end
19
+ Cdata = Class.new(Node) do
20
+ include Moxml::Cdata
21
+ end
22
+ Comment = Class.new(Node) do
23
+ include Moxml::Comment
24
+ end
25
+ ProcessingInstruction = Class.new(Node) do
26
+ include Moxml::ProcessingInstruction
27
+ end
28
+ Document = Class.new(Node) do
29
+ include Moxml::Document
30
+ end
31
+ Declaration = Class.new(Node) do
32
+ include Moxml::Declaration
33
+ end
34
+ Doctype = Class.new(Node) do
35
+ include Moxml::Doctype
36
+ end
37
+ Attribute = Class.new(Node) do
38
+ include Moxml::Attribute
39
+ end
40
+ EntityReference = Class.new(Node) do
41
+ include Moxml::EntityReference
42
+ end
43
+ Namespace = Class.new(Node) do
44
+ include Moxml::Namespace
45
+ end
46
+ end
47
+ end
data/lib/moxml.rb CHANGED
@@ -67,6 +67,7 @@ module Moxml
67
67
  autoload :Config, "moxml/config"
68
68
  autoload :Context, "moxml/context"
69
69
  autoload :Node, "moxml/node"
70
+ autoload :Wrappers, "moxml/wrappers"
70
71
  autoload :NodeSet, "moxml/node_set"
71
72
  autoload :LazyNodeSet, "moxml/lazy_node_set"
72
73
  autoload :Document, "moxml/document"
@@ -22,9 +22,8 @@
22
22
  describe "fragment parsing (issue #188)" do
23
23
  it "returns the fragment's top-level nodes uniformly" do
24
24
  nodes = ctx.parse_fragment(%(<a x="1">t1</a><b/>tail))
25
- expect(nodes.map(&:class)).to eq(
26
- [Moxml::Element, Moxml::Element, Moxml::Text],
27
- )
25
+ expect(nodes.map(&:element?)).to eq([true, true, false])
26
+ expect(nodes.map(&:text?)).to eq([false, false, true])
28
27
  expect(nodes[0]["x"]).to eq("1")
29
28
  expect(nodes[0].text).to eq("t1")
30
29
  expect(nodes[2].content).to eq("tail")
@@ -76,12 +76,12 @@ RSpec.shared_examples "Entity Reference Whitespace Preservation" do
76
76
  end
77
77
 
78
78
  children = doc.root.children
79
- types = children.map(&:class)
79
+ types = children.map(&:entity_reference?)
80
80
 
81
81
  expect(types).to eq([
82
- Moxml::EntityReference,
83
- Moxml::Text,
84
- Moxml::EntityReference,
82
+ true,
83
+ false,
84
+ true,
85
85
  ])
86
86
  expect(children[1].content).to eq(" ")
87
87
  end
@@ -93,10 +93,14 @@ RSpec.describe Moxml::Adapter::Leptris do
93
93
  doc = ctx.parse('<?xml version="1.0"?><?pi-prolog before?><root/><?pi-epilog after?><!-- tail -->')
94
94
  kids = doc.children.to_a
95
95
 
96
- expect(kids.map(&:class)).to eq(
97
- [Moxml::ProcessingInstruction, Moxml::Element,
98
- Moxml::ProcessingInstruction, Moxml::Comment],
99
- )
96
+ shape = kids.map do |k|
97
+ if k.processing_instruction?
98
+ :pi
99
+ else
100
+ k.element? ? :element : :comment
101
+ end
102
+ end
103
+ expect(shape).to eq(%i[pi element pi comment])
100
104
  expect(kids.select(&:processing_instruction?).map(&:target)).to eq(%w[pi-prolog pi-epilog])
101
105
  expect(kids[0].content).to eq("before")
102
106
  expect(doc.to_xml.index("pi-epilog")).to be > doc.to_xml.index("</root>")
@@ -606,14 +610,15 @@ RSpec.describe Moxml::Adapter::Leptris do
606
610
  it "skips the marker split for entity-free documents" do
607
611
  doc = ctx.parse("<root>\n <a>text</a>\n <b/>\n</root>")
608
612
  kids = doc.root.children.to_a
609
- expect(kids.map(&:class)).to eq([Moxml::Text, Moxml::Element, Moxml::Text, Moxml::Element, Moxml::Text])
613
+ shape = kids.map { |k| k.text? ? :text : :element }
614
+ expect(shape).to eq(%i[text element text element text])
610
615
  expect(described_class.entity_bearing?(doc.root.native)).to be(false)
611
616
  end
612
617
 
613
618
  it "splits markers when the source carries entities" do
614
619
  doc = ctx.parse("<root><a>pre&nbsp;post</a></root>")
615
620
  kids = doc.at_xpath("//a").children.to_a
616
- expect(kids.map(&:class)).to include(Moxml::EntityReference)
621
+ expect(kids.map { |k| k.is_a?(Moxml::EntityReference) }).to include(true)
617
622
  expect(described_class.entity_bearing?(doc.root.native)).to be(true)
618
623
  end
619
624
 
@@ -631,7 +636,7 @@ RSpec.describe Moxml::Adapter::Leptris do
631
636
  er = doc.create_entity_reference("nbsp")
632
637
  doc.at_xpath("//a").add_child(er)
633
638
  expect(described_class.entity_bearing?(doc.root.native)).to be(true)
634
- expect(doc.at_xpath("//a").children.to_a.map(&:class)).to include(Moxml::EntityReference)
639
+ expect(doc.at_xpath("//a").children.to_a.map { |k| k.is_a?(Moxml::EntityReference) }).to include(true)
635
640
  end
636
641
  end
637
642
 
@@ -702,11 +707,11 @@ RSpec.describe Moxml::Adapter::Leptris do
702
707
  expect(decl.digest).to be_nil
703
708
  end
704
709
  # comments and PIs hash in C
705
- kinds = doc.root.children.map { |n| [n.class, n.digest] }
706
- comment = kinds.find { |n, _| n == Moxml::Comment }
707
- pi = kinds.find { |n, _| n == Moxml::ProcessingInstruction }
708
- expect(comment[1]).to be_a(Integer)
709
- expect(pi[1]).to be_a(Integer)
710
+ kinds = doc.root.children.map { |n| [n.is_a?(Moxml::Comment), n.is_a?(Moxml::ProcessingInstruction), n.digest] }
711
+ comment = kinds.find { |c, _, _| c }
712
+ pi = kinds.find { |_, p, _| p }
713
+ expect(comment[2]).to be_a(Integer)
714
+ expect(pi[2]).to be_a(Integer)
710
715
  end
711
716
  end
712
717
  end
@@ -113,7 +113,7 @@ RSpec.describe Moxml::Adapter::Libxml do
113
113
  doc = context.parse("<root><a/><b/></root>")
114
114
  a = doc.root.children.first
115
115
  b = doc.root.children.last
116
- eref = Moxml::EntityReference.new(
116
+ eref = Moxml::Wrappers::EntityReference.new(
117
117
  adapter.create_native_entity_reference("amp"), context
118
118
  )
119
119
  a.add_child(eref)
@@ -125,7 +125,7 @@ RSpec.describe Moxml::Adapter::Libxml do
125
125
  it "returns [refs, sequence] when both are registered for the element" do
126
126
  doc = context.parse("<root><a>text</a></root>")
127
127
  a = doc.root.children.first
128
- eref = Moxml::EntityReference.new(
128
+ eref = Moxml::Wrappers::EntityReference.new(
129
129
  adapter.create_native_entity_reference("amp"), context
130
130
  )
131
131
  a.add_child(eref)
@@ -144,7 +144,7 @@ RSpec.describe Moxml::Adapter::Libxml do
144
144
  it "preserves normal child indentation when entity refs are present" do
145
145
  doc = context.parse("<root><a><b/></a></root>")
146
146
  a = doc.root.children.first
147
- eref = Moxml::EntityReference.new(
147
+ eref = Moxml::Wrappers::EntityReference.new(
148
148
  adapter.create_native_entity_reference("amp"), context
149
149
  )
150
150
  a.add_child(eref)
@@ -45,7 +45,7 @@ RSpec.describe Moxml::NodeSet do
45
45
  end
46
46
 
47
47
  it "wraps through the Enumerable surface" do
48
- expect(doc.root.children.map(&:class).uniq).to eq([Moxml::Element])
48
+ expect(doc.root.children.map { |c| c.is_a?(Moxml::Element) }.uniq).to eq([true])
49
49
  end
50
50
  end
51
51
  end
@@ -84,7 +84,7 @@ RSpec.describe Moxml::Node do
84
84
 
85
85
  describe "#content" do
86
86
  it "returns empty string on base Node" do
87
- expect(described_class.new(nil, context).content).to eq("")
87
+ expect(Moxml::Wrappers::Node.new(nil, context).content).to eq("")
88
88
  end
89
89
 
90
90
  it "returns text on Element" do
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.47
4
+ version: 0.5.48
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -271,6 +271,7 @@ files:
271
271
  - lib/moxml/signature/verifier.rb
272
272
  - lib/moxml/text.rb
273
273
  - lib/moxml/version.rb
274
+ - lib/moxml/wrappers.rb
274
275
  - lib/moxml/xml_emitter.rb
275
276
  - lib/moxml/xml_utils.rb
276
277
  - lib/moxml/xml_utils/encoder.rb