moxml 0.1.8 → 0.1.10
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/.rubocop_todo.yml +22 -39
- data/README.adoc +51 -20
- data/docs/_config.yml +3 -3
- data/docs/_guides/index.adoc +15 -7
- data/docs/_guides/modifying-xml.adoc +0 -1
- data/docs/_guides/node-api-consistency.adoc +572 -0
- data/docs/_guides/parsing-xml.adoc +0 -1
- data/docs/_guides/xml-declaration.adoc +450 -0
- data/docs/_pages/adapter-compatibility.adoc +1 -1
- data/docs/_pages/adapters/headed-ox.adoc +9 -9
- data/docs/_pages/adapters/index.adoc +0 -1
- data/docs/_pages/adapters/libxml.adoc +1 -2
- data/docs/_pages/adapters/nokogiri.adoc +1 -2
- data/docs/_pages/adapters/oga.adoc +1 -2
- data/docs/_pages/adapters/ox.adoc +2 -1
- data/docs/_pages/adapters/rexml.adoc +2 -3
- data/docs/_pages/best-practices.adoc +0 -1
- data/docs/_pages/compatibility.adoc +0 -1
- data/docs/_pages/configuration.adoc +0 -1
- data/docs/_pages/error-handling.adoc +0 -1
- data/docs/_pages/headed-ox-limitations.adoc +16 -0
- data/docs/_pages/installation.adoc +0 -1
- data/docs/_pages/node-api-reference.adoc +93 -4
- data/docs/_pages/performance.adoc +0 -1
- data/docs/_pages/quick-start.adoc +0 -1
- data/docs/_pages/thread-safety.adoc +0 -1
- data/docs/_references/document-api.adoc +0 -1
- data/docs/_tutorials/basic-usage.adoc +0 -1
- data/docs/_tutorials/builder-pattern.adoc +0 -1
- data/docs/_tutorials/namespace-handling.adoc +0 -1
- data/docs/_tutorials/xpath-queries.adoc +0 -1
- data/lib/moxml/adapter/customized_rexml/formatter.rb +2 -2
- data/lib/moxml/adapter/libxml.rb +34 -4
- data/lib/moxml/adapter/nokogiri.rb +50 -2
- data/lib/moxml/adapter/oga.rb +80 -3
- data/lib/moxml/adapter/ox.rb +70 -7
- data/lib/moxml/adapter/rexml.rb +45 -10
- data/lib/moxml/attribute.rb +6 -0
- data/lib/moxml/context.rb +18 -1
- data/lib/moxml/declaration.rb +9 -0
- data/lib/moxml/doctype.rb +33 -0
- data/lib/moxml/document.rb +14 -0
- data/lib/moxml/document_builder.rb +7 -0
- data/lib/moxml/element.rb +6 -0
- data/lib/moxml/error.rb +5 -5
- data/lib/moxml/node.rb +73 -1
- data/lib/moxml/processing_instruction.rb +6 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xpath/compiler.rb +2 -0
- data/lib/moxml/xpath/errors.rb +1 -1
- data/spec/integration/shared_examples/node_wrappers/declaration_behavior.rb +0 -3
- data/spec/moxml/declaration_preservation_spec.rb +217 -0
- data/spec/moxml/doctype_spec.rb +19 -3
- data/spec/performance/memory_usage_spec.rb +3 -2
- metadata +5 -3
- data/.ruby-version +0 -1
data/lib/moxml/adapter/ox.rb
CHANGED
|
@@ -348,6 +348,24 @@ module Moxml
|
|
|
348
348
|
end
|
|
349
349
|
|
|
350
350
|
def add_child(element, child)
|
|
351
|
+
# Special handling for declarations on Ox documents
|
|
352
|
+
if element.is_a?(::Ox::Document) && child.is_a?(::Ox::Instruct) && child.target == "xml"
|
|
353
|
+
# Transfer declaration attributes to document
|
|
354
|
+
element.attributes ||= {}
|
|
355
|
+
if child.attributes["version"]
|
|
356
|
+
element.attributes[:version] =
|
|
357
|
+
child.attributes["version"]
|
|
358
|
+
end
|
|
359
|
+
if child.attributes["encoding"]
|
|
360
|
+
element.attributes[:encoding] =
|
|
361
|
+
child.attributes["encoding"]
|
|
362
|
+
end
|
|
363
|
+
if child.attributes["standalone"]
|
|
364
|
+
element.attributes[:standalone] =
|
|
365
|
+
child.attributes["standalone"]
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
351
369
|
child.parent = element if child.respond_to?(:parent)
|
|
352
370
|
element.nodes ||= []
|
|
353
371
|
element.nodes << child
|
|
@@ -380,6 +398,15 @@ module Moxml
|
|
|
380
398
|
|
|
381
399
|
return unless parent(node)
|
|
382
400
|
|
|
401
|
+
# Special handling for declarations on Ox documents
|
|
402
|
+
if parent(node).is_a?(::Ox::Document) && node.is_a?(::Ox::Instruct) && node.target == "xml"
|
|
403
|
+
# Clear declaration attributes from document
|
|
404
|
+
doc = parent(node)
|
|
405
|
+
doc.attributes&.delete(:version)
|
|
406
|
+
doc.attributes&.delete(:encoding)
|
|
407
|
+
doc.attributes&.delete(:standalone)
|
|
408
|
+
end
|
|
409
|
+
|
|
383
410
|
parent(node).nodes.delete(unpatch_node(node))
|
|
384
411
|
end
|
|
385
412
|
|
|
@@ -483,6 +510,31 @@ module Moxml
|
|
|
483
510
|
end.values
|
|
484
511
|
end
|
|
485
512
|
|
|
513
|
+
# Doctype accessor methods
|
|
514
|
+
# Ox stores DOCTYPE as a string, so we parse it
|
|
515
|
+
def doctype_name(native)
|
|
516
|
+
# Parse: "name PUBLIC \"external_id\" \"system_id\"" or "name SYSTEM \"system_id\""
|
|
517
|
+
value = native.value.to_s.strip
|
|
518
|
+
# Extract the first word (the name)
|
|
519
|
+
value.split(/\s+/).first
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def doctype_external_id(native)
|
|
523
|
+
value = native.value.to_s
|
|
524
|
+
# Match PUBLIC "external_id"
|
|
525
|
+
match = value.match(/PUBLIC\s+"([^"]*)"/)
|
|
526
|
+
match ? match[1] : nil
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def doctype_system_id(native)
|
|
530
|
+
value = native.value.to_s
|
|
531
|
+
# Match the last quoted string (system_id)
|
|
532
|
+
# For PUBLIC: "name PUBLIC \"external_id\" \"system_id\""
|
|
533
|
+
# For SYSTEM: "name SYSTEM \"system_id\""
|
|
534
|
+
matches = value.scan(/"([^"]*)"/)
|
|
535
|
+
matches.last&.first
|
|
536
|
+
end
|
|
537
|
+
|
|
486
538
|
def xpath(node, expression, namespaces = {})
|
|
487
539
|
# Translate common XPath patterns to Ox locate() syntax
|
|
488
540
|
locate_expr = translate_xpath_to_locate(expression, namespaces)
|
|
@@ -524,13 +576,24 @@ module Moxml
|
|
|
524
576
|
def serialize(node, options = {})
|
|
525
577
|
output = ""
|
|
526
578
|
if node.is_a?(::Ox::Document)
|
|
527
|
-
#
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
579
|
+
# Check if we should include declaration
|
|
580
|
+
# Priority: explicit option > document attributes
|
|
581
|
+
should_include_decl = if options.key?(:no_declaration)
|
|
582
|
+
!options[:no_declaration]
|
|
583
|
+
else
|
|
584
|
+
# Check if document has declaration attributes
|
|
585
|
+
node[:version] || node[:encoding] || node[:standalone]
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# Only add declaration if should_include_decl is true
|
|
589
|
+
if should_include_decl
|
|
590
|
+
version = node[:version] || "1.0"
|
|
591
|
+
encoding = options[:encoding] || node[:encoding]
|
|
592
|
+
standalone = node[:standalone]
|
|
593
|
+
|
|
594
|
+
decl = create_native_declaration(version, encoding, standalone)
|
|
595
|
+
output = ::Ox.dump(::Ox::Document.new << decl).strip
|
|
596
|
+
end
|
|
534
597
|
end
|
|
535
598
|
|
|
536
599
|
ox_options = {
|
data/lib/moxml/adapter/rexml.rb
CHANGED
|
@@ -23,6 +23,7 @@ module Moxml
|
|
|
23
23
|
end
|
|
24
24
|
create_document
|
|
25
25
|
end
|
|
26
|
+
|
|
26
27
|
DocumentBuilder.new(Context.new(:rexml)).build(native_doc)
|
|
27
28
|
end
|
|
28
29
|
|
|
@@ -255,6 +256,12 @@ module Moxml
|
|
|
255
256
|
end
|
|
256
257
|
|
|
257
258
|
def add_child(element, child)
|
|
259
|
+
# Special handling for declarations on REXML documents
|
|
260
|
+
if element.is_a?(::REXML::Document) && child.is_a?(::REXML::XMLDecl)
|
|
261
|
+
# Set document's xml_decl directly
|
|
262
|
+
element.instance_variable_set(:@xml_declaration, child)
|
|
263
|
+
end
|
|
264
|
+
|
|
258
265
|
case child
|
|
259
266
|
when String
|
|
260
267
|
element.add_text(child)
|
|
@@ -279,6 +286,12 @@ module Moxml
|
|
|
279
286
|
end
|
|
280
287
|
|
|
281
288
|
def remove(node)
|
|
289
|
+
# Special handling for declarations on REXML documents
|
|
290
|
+
if node.is_a?(::REXML::XMLDecl) && node.parent.is_a?(::REXML::Document)
|
|
291
|
+
# Clear document's xml_declaration when removing declaration
|
|
292
|
+
node.parent.instance_variable_set(:@xml_declaration, nil)
|
|
293
|
+
end
|
|
294
|
+
|
|
282
295
|
node.remove
|
|
283
296
|
end
|
|
284
297
|
|
|
@@ -413,6 +426,19 @@ module Moxml
|
|
|
413
426
|
end
|
|
414
427
|
end
|
|
415
428
|
|
|
429
|
+
# Doctype accessor methods
|
|
430
|
+
def doctype_name(native)
|
|
431
|
+
native.name
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def doctype_external_id(native)
|
|
435
|
+
native.public
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def doctype_system_id(native)
|
|
439
|
+
native.system
|
|
440
|
+
end
|
|
441
|
+
|
|
416
442
|
# not used at the moment
|
|
417
443
|
# but may be useful when the xpath is upgraded to work with namespaces
|
|
418
444
|
def prepare_xpath_namespaces(node)
|
|
@@ -453,16 +479,25 @@ module Moxml
|
|
|
453
479
|
output = +""
|
|
454
480
|
|
|
455
481
|
if node.is_a?(::REXML::Document)
|
|
456
|
-
#
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
#
|
|
482
|
+
# Check if we should include declaration
|
|
483
|
+
# Priority: explicit option > check if document has xml_decl
|
|
484
|
+
should_include_decl = if options.key?(:no_declaration)
|
|
485
|
+
!options[:no_declaration]
|
|
486
|
+
else
|
|
487
|
+
# Include declaration only if document has xml_decl
|
|
488
|
+
!node.xml_decl.nil?
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Include XML declaration only if should_include_decl and xml_decl exists
|
|
492
|
+
if should_include_decl && node.xml_decl
|
|
493
|
+
decl = node.xml_decl
|
|
494
|
+
decl.encoding = options[:encoding] if options[:encoding]
|
|
495
|
+
output << "<?xml"
|
|
496
|
+
output << %( version="#{decl.version}") if decl.version
|
|
497
|
+
output << %( encoding="#{decl.encoding}") if decl.encoding
|
|
498
|
+
output << %( standalone="#{decl.standalone}") if decl.standalone
|
|
499
|
+
output << "?>"
|
|
500
|
+
end
|
|
466
501
|
|
|
467
502
|
# output << "\n"
|
|
468
503
|
node.doctype&.write(output)
|
data/lib/moxml/attribute.rb
CHANGED
|
@@ -10,6 +10,12 @@ module Moxml
|
|
|
10
10
|
adapter.set_attribute_name(@native, new_name)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# Returns the primary identifier for this attribute (its name)
|
|
14
|
+
# @return [String] the attribute name
|
|
15
|
+
def identifier
|
|
16
|
+
name
|
|
17
|
+
end
|
|
18
|
+
|
|
13
19
|
def value
|
|
14
20
|
@native.value
|
|
15
21
|
end
|
data/lib/moxml/context.rb
CHANGED
|
@@ -13,7 +13,24 @@ module Moxml
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def parse(xml, options = {})
|
|
16
|
-
|
|
16
|
+
# Detect if input has XML declaration
|
|
17
|
+
xml_string = if xml.respond_to?(:read)
|
|
18
|
+
xml.read.tap do
|
|
19
|
+
xml.rewind if xml.respond_to?(:rewind)
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
xml.to_s
|
|
23
|
+
end
|
|
24
|
+
has_declaration = xml_string.strip.start_with?("<?xml")
|
|
25
|
+
|
|
26
|
+
# Parse with adapter (without declaration info - adapters don't need it)
|
|
27
|
+
parsed_options = default_options.merge(options)
|
|
28
|
+
doc = config.adapter.parse(xml_string, parsed_options)
|
|
29
|
+
|
|
30
|
+
# Set declaration flag on Document wrapper (proper OOP)
|
|
31
|
+
doc.has_xml_declaration = has_declaration if doc.is_a?(Document)
|
|
32
|
+
|
|
33
|
+
doc
|
|
17
34
|
end
|
|
18
35
|
|
|
19
36
|
# Parse XML using SAX (event-driven) parsing
|
data/lib/moxml/declaration.rb
CHANGED
|
@@ -33,6 +33,15 @@ module Moxml
|
|
|
33
33
|
adapter.set_declaration_attribute(@native, "standalone", new_standalone)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
def remove
|
|
37
|
+
# Mark document as having no declaration when declaration is removed
|
|
38
|
+
# Store on native document so all wrappers see it
|
|
39
|
+
native_doc = adapter.document(@native)
|
|
40
|
+
native_doc&.instance_variable_set(:@moxml_has_declaration, false)
|
|
41
|
+
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
|
|
36
45
|
def declaration?
|
|
37
46
|
true
|
|
38
47
|
end
|
data/lib/moxml/doctype.rb
CHANGED
|
@@ -1,17 +1,50 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Moxml
|
|
4
|
+
# Represents an XML DOCTYPE declaration
|
|
5
|
+
#
|
|
6
|
+
# @note Doctype accessor methods are not fully implemented across all adapters.
|
|
7
|
+
# The availability of #name, #external_id, and #system_id depends on whether
|
|
8
|
+
# the specific adapter implements the corresponding adapter methods:
|
|
9
|
+
# - adapter.doctype_name(native)
|
|
10
|
+
# - adapter.doctype_external_id(native)
|
|
11
|
+
# - adapter.doctype_system_id(native)
|
|
12
|
+
#
|
|
13
|
+
# Most adapters do not currently implement these methods. If you need DOCTYPE
|
|
14
|
+
# information, consider using adapter-specific methods or parsing the serialized
|
|
15
|
+
# XML manually.
|
|
4
16
|
class Doctype < Node
|
|
17
|
+
# Returns the DOCTYPE name (root element name)
|
|
18
|
+
#
|
|
19
|
+
# @return [String, nil] the DOCTYPE name
|
|
20
|
+
# @raise [NotImplementedError] if the adapter doesn't implement doctype_name
|
|
5
21
|
def name
|
|
6
22
|
adapter.doctype_name(@native)
|
|
7
23
|
end
|
|
8
24
|
|
|
25
|
+
# Returns the DOCTYPE external ID
|
|
26
|
+
#
|
|
27
|
+
# @return [String, nil] the external ID
|
|
28
|
+
# @raise [NotImplementedError] if the adapter doesn't implement doctype_external_id
|
|
9
29
|
def external_id
|
|
10
30
|
adapter.doctype_external_id(@native)
|
|
11
31
|
end
|
|
12
32
|
|
|
33
|
+
# Returns the DOCTYPE system ID
|
|
34
|
+
#
|
|
35
|
+
# @return [String, nil] the system ID
|
|
36
|
+
# @raise [NotImplementedError] if the adapter doesn't implement doctype_system_id
|
|
13
37
|
def system_id
|
|
14
38
|
adapter.doctype_system_id(@native)
|
|
15
39
|
end
|
|
40
|
+
|
|
41
|
+
# Returns the primary identifier for this doctype
|
|
42
|
+
# Since DOCTYPE information is not reliably available across adapters,
|
|
43
|
+
# this returns nil.
|
|
44
|
+
#
|
|
45
|
+
# @return [nil]
|
|
46
|
+
def identifier
|
|
47
|
+
name
|
|
48
|
+
end
|
|
16
49
|
end
|
|
17
50
|
end
|
data/lib/moxml/document.rb
CHANGED
|
@@ -12,6 +12,17 @@ require_relative "doctype"
|
|
|
12
12
|
|
|
13
13
|
module Moxml
|
|
14
14
|
class Document < Node
|
|
15
|
+
attr_accessor :has_xml_declaration
|
|
16
|
+
|
|
17
|
+
def initialize(native, context)
|
|
18
|
+
super
|
|
19
|
+
@has_xml_declaration = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def document
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
15
26
|
def root=(element)
|
|
16
27
|
adapter.set_root(@native, element.native)
|
|
17
28
|
end
|
|
@@ -61,6 +72,9 @@ module Moxml
|
|
|
61
72
|
node = prepare_node(node)
|
|
62
73
|
|
|
63
74
|
if node.is_a?(Declaration)
|
|
75
|
+
# Mark that document now has a declaration
|
|
76
|
+
@has_xml_declaration = true
|
|
77
|
+
|
|
64
78
|
if children.empty?
|
|
65
79
|
adapter.add_child(@native, node.native)
|
|
66
80
|
else
|
|
@@ -12,6 +12,13 @@ module Moxml
|
|
|
12
12
|
def build(native_doc)
|
|
13
13
|
@current_doc = context.create_document(native_doc)
|
|
14
14
|
|
|
15
|
+
# Transfer has_declaration flag if present
|
|
16
|
+
if native_doc.respond_to?(:instance_variable_get) &&
|
|
17
|
+
native_doc.instance_variable_defined?(:@moxml_has_declaration)
|
|
18
|
+
has_declaration = native_doc.instance_variable_get(:@moxml_has_declaration)
|
|
19
|
+
@current_doc.has_xml_declaration = has_declaration
|
|
20
|
+
end
|
|
21
|
+
|
|
15
22
|
# Transfer DOCTYPE from parsed document if it exists
|
|
16
23
|
if native_doc.respond_to?(:instance_variable_get) &&
|
|
17
24
|
native_doc.instance_variable_defined?(:@moxml_doctype)
|
data/lib/moxml/element.rb
CHANGED
|
@@ -13,6 +13,12 @@ module Moxml
|
|
|
13
13
|
adapter.set_node_name(@native, value)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# Returns the primary identifier for this element (its tag name)
|
|
17
|
+
# @return [String] the element name
|
|
18
|
+
def identifier
|
|
19
|
+
name
|
|
20
|
+
end
|
|
21
|
+
|
|
16
22
|
# Returns the expanded name including namespace prefix
|
|
17
23
|
def expanded_name
|
|
18
24
|
if namespace_prefix && !namespace_prefix.empty?
|
data/lib/moxml/error.rb
CHANGED
|
@@ -40,7 +40,7 @@ module Moxml
|
|
|
40
40
|
msg = super
|
|
41
41
|
msg += "\n Expression: #{@expression}" if @expression
|
|
42
42
|
msg += "\n Adapter: #{@adapter}" if @adapter
|
|
43
|
-
msg += "\n Node: <#{@node.name}>" if @node.
|
|
43
|
+
msg += "\n Node: <#{@node.name}>" if @node.is_a?(Element) || @node.is_a?(Attribute)
|
|
44
44
|
msg += "\n Hint: Verify XPath syntax and ensure the adapter supports the expression"
|
|
45
45
|
msg
|
|
46
46
|
end
|
|
@@ -60,9 +60,9 @@ module Moxml
|
|
|
60
60
|
def to_s
|
|
61
61
|
msg = super
|
|
62
62
|
# Only add extra details if any were provided
|
|
63
|
-
has_details = @node.
|
|
63
|
+
has_details = (@node.is_a?(Element) || @node.is_a?(Attribute)) || @constraint || @value
|
|
64
64
|
if has_details
|
|
65
|
-
msg += "\n Node: <#{@node.name}>" if @node.
|
|
65
|
+
msg += "\n Node: <#{@node.name}>" if @node.is_a?(Element) || @node.is_a?(Attribute)
|
|
66
66
|
msg += "\n Constraint: #{@constraint}" if @constraint
|
|
67
67
|
msg += "\n Value: #{@value.inspect}" if @value
|
|
68
68
|
msg += "\n Hint: Ensure the value meets XML specification requirements"
|
|
@@ -119,7 +119,7 @@ module Moxml
|
|
|
119
119
|
|
|
120
120
|
def to_s
|
|
121
121
|
msg = super
|
|
122
|
-
msg += "\n Node: <#{@node.name}>" if @node.
|
|
122
|
+
msg += "\n Node: <#{@node.name}>" if @node.is_a?(Element) || @node.is_a?(Attribute)
|
|
123
123
|
msg += "\n Adapter: #{@adapter}" if @adapter
|
|
124
124
|
msg += "\n Format: #{@format}" if @format
|
|
125
125
|
msg += "\n Hint: Check that the node structure is valid for serialization"
|
|
@@ -160,7 +160,7 @@ module Moxml
|
|
|
160
160
|
def to_s
|
|
161
161
|
msg = super
|
|
162
162
|
msg += "\n Attribute: #{@attribute_name}" if @attribute_name
|
|
163
|
-
msg += "\n Element: <#{@element.name}>" if @element.
|
|
163
|
+
msg += "\n Element: <#{@element.name}>" if @element.is_a?(Element)
|
|
164
164
|
msg += "\n Value: #{@value.inspect}" if @value
|
|
165
165
|
msg += "\n Hint: Verify attribute name follows XML naming rules"
|
|
166
166
|
msg
|
data/lib/moxml/node.rb
CHANGED
|
@@ -73,7 +73,13 @@ module Moxml
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def to_xml(options = {})
|
|
76
|
-
|
|
76
|
+
# Determine if we should include XML declaration
|
|
77
|
+
# For Document nodes: check native then wrapper, unless explicitly overridden
|
|
78
|
+
# For other nodes: default to no declaration unless explicitly set
|
|
79
|
+
serialize_options = default_options.merge(options)
|
|
80
|
+
serialize_options[:no_declaration] = !should_include_declaration?(options)
|
|
81
|
+
|
|
82
|
+
adapter.serialize(@native, serialize_options)
|
|
77
83
|
end
|
|
78
84
|
|
|
79
85
|
def xpath(expression, namespaces = {})
|
|
@@ -180,6 +186,18 @@ module Moxml
|
|
|
180
186
|
end
|
|
181
187
|
end
|
|
182
188
|
|
|
189
|
+
# Returns the primary identifier for this node type
|
|
190
|
+
# For Element: the tag name
|
|
191
|
+
# For Attribute: the attribute name
|
|
192
|
+
# For ProcessingInstruction: the target
|
|
193
|
+
# For content nodes (Text, Comment, Cdata, Declaration): nil (no identifier)
|
|
194
|
+
# For Doctype: nil (not fully implemented across adapters)
|
|
195
|
+
#
|
|
196
|
+
# @return [String, nil] the node's primary identifier or nil
|
|
197
|
+
def identifier
|
|
198
|
+
nil
|
|
199
|
+
end
|
|
200
|
+
|
|
183
201
|
def self.wrap(node, context)
|
|
184
202
|
return nil if node.nil?
|
|
185
203
|
|
|
@@ -235,5 +253,59 @@ module Moxml
|
|
|
235
253
|
expand_empty: true,
|
|
236
254
|
}
|
|
237
255
|
end
|
|
256
|
+
|
|
257
|
+
def should_include_declaration?(options)
|
|
258
|
+
return options[:declaration] if options.key?(:declaration)
|
|
259
|
+
return options.fetch(:declaration, false) unless is_a?(Document)
|
|
260
|
+
|
|
261
|
+
# For Document nodes, check both wrapper flag and native state
|
|
262
|
+
# Wrapper flag is set by Context.parse for parsed documents
|
|
263
|
+
# Native state reflects programmatic changes (e.g., add/remove)
|
|
264
|
+
|
|
265
|
+
adapter_name = adapter.to_s.split("::").last
|
|
266
|
+
|
|
267
|
+
case adapter_name
|
|
268
|
+
when "Nokogiri"
|
|
269
|
+
# Nokogiri: if @xml_decl is explicitly set, use that state
|
|
270
|
+
# Otherwise, trust wrapper flag (for parsed documents)
|
|
271
|
+
if native.respond_to?(:instance_variable_defined?) &&
|
|
272
|
+
native.instance_variable_defined?(:@xml_decl)
|
|
273
|
+
# Explicitly set (programmatically added) - check if nil
|
|
274
|
+
!native.instance_variable_get(:@xml_decl).nil?
|
|
275
|
+
else
|
|
276
|
+
# Not set (parsed document) - trust wrapper flag
|
|
277
|
+
has_xml_declaration
|
|
278
|
+
end
|
|
279
|
+
when "Rexml"
|
|
280
|
+
# REXML: check @xml_declaration instance variable
|
|
281
|
+
# If not defined (parsed doc), trust wrapper flag
|
|
282
|
+
if native.respond_to?(:instance_variable_defined?) &&
|
|
283
|
+
native.instance_variable_defined?(:@xml_declaration)
|
|
284
|
+
# Explicitly set - check if nil
|
|
285
|
+
!native.instance_variable_get(:@xml_declaration).nil?
|
|
286
|
+
else
|
|
287
|
+
# Not set (parsed document) - trust wrapper flag
|
|
288
|
+
has_xml_declaration
|
|
289
|
+
end
|
|
290
|
+
when "Oga"
|
|
291
|
+
native.respond_to?(:xml_declaration) && !native.xml_declaration.nil?
|
|
292
|
+
when "Ox", "HeadedOx"
|
|
293
|
+
# Ox stores declaration in document attributes
|
|
294
|
+
native[:version] || native[:encoding] || native[:standalone]
|
|
295
|
+
when "Libxml"
|
|
296
|
+
# LibXML stores declaration wrapper as instance variable
|
|
297
|
+
if native.respond_to?(:instance_variable_defined?) &&
|
|
298
|
+
native.instance_variable_defined?(:@moxml_declaration)
|
|
299
|
+
# Explicitly set - check if nil
|
|
300
|
+
!native.instance_variable_get(:@moxml_declaration).nil?
|
|
301
|
+
else
|
|
302
|
+
# Not set - trust wrapper flag
|
|
303
|
+
has_xml_declaration
|
|
304
|
+
end
|
|
305
|
+
else
|
|
306
|
+
# Fallback - trust wrapper flag
|
|
307
|
+
has_xml_declaration
|
|
308
|
+
end
|
|
309
|
+
end
|
|
238
310
|
end
|
|
239
311
|
end
|
|
@@ -10,6 +10,12 @@ module Moxml
|
|
|
10
10
|
adapter.set_node_name(@native, new_target.to_s)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# Returns the primary identifier for this processing instruction (its target)
|
|
14
|
+
# @return [String] the PI target
|
|
15
|
+
def identifier
|
|
16
|
+
target
|
|
17
|
+
end
|
|
18
|
+
|
|
13
19
|
def content
|
|
14
20
|
adapter.processing_instruction_content(@native)
|
|
15
21
|
end
|
data/lib/moxml/version.rb
CHANGED
data/lib/moxml/xpath/compiler.rb
CHANGED
|
@@ -976,8 +976,10 @@ module Moxml
|
|
|
976
976
|
conversions << conversion.to_string(arg_var)
|
|
977
977
|
end
|
|
978
978
|
|
|
979
|
+
# rubocop:disable Style/RedundantSum, Performance/Sum
|
|
979
980
|
concatted = assigns.inject(:followed_by)
|
|
980
981
|
.followed_by(conversions.inject(:+))
|
|
982
|
+
# rubocop:enable Style/RedundantSum, Performance/Sum
|
|
981
983
|
|
|
982
984
|
block_given? ? concatted.empty?.if_false { yield concatted } : concatted
|
|
983
985
|
end
|
data/lib/moxml/xpath/errors.rb
CHANGED
|
@@ -36,7 +36,7 @@ module Moxml
|
|
|
36
36
|
|
|
37
37
|
def to_s
|
|
38
38
|
msg = super
|
|
39
|
-
msg += "\n Context node: <#{@context_node.name}>" if @context_node.
|
|
39
|
+
msg += "\n Context node: <#{@context_node.name}>" if @context_node.is_a?(Moxml::Element) || @context_node.is_a?(Moxml::Attribute)
|
|
40
40
|
msg += "\n Step: #{@step}" if @step
|
|
41
41
|
msg
|
|
42
42
|
end
|
|
@@ -98,9 +98,6 @@ RSpec.shared_examples "Moxml::Declaration" do
|
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
it "removes from document" do
|
|
101
|
-
if Moxml.new.config.adapter.name.match?(/Nokogiri|Rexml|Ox/)
|
|
102
|
-
pending("The document contains a default declaration")
|
|
103
|
-
end
|
|
104
101
|
doc.add_child(declaration)
|
|
105
102
|
declaration.remove
|
|
106
103
|
expect(doc.to_xml).not_to include("<?xml")
|