moxml 0.1.9 → 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 +3 -3
- data/README.adoc +51 -20
- data/docs/_guides/index.adoc +14 -12
- data/docs/_guides/node-api-consistency.adoc +572 -0
- data/docs/_guides/xml-declaration.adoc +5 -5
- data/docs/_pages/adapters/rexml.adoc +1 -1
- data/docs/_pages/node-api-reference.adoc +93 -3
- data/lib/moxml/adapter/libxml.rb +15 -1
- data/lib/moxml/adapter/nokogiri.rb +13 -0
- data/lib/moxml/adapter/oga.rb +13 -0
- data/lib/moxml/adapter/ox.rb +25 -0
- data/lib/moxml/adapter/rexml.rb +13 -0
- data/lib/moxml/attribute.rb +6 -0
- data/lib/moxml/doctype.rb +33 -0
- data/lib/moxml/element.rb +6 -0
- data/lib/moxml/error.rb +5 -5
- data/lib/moxml/node.rb +12 -0
- data/lib/moxml/processing_instruction.rb +6 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xpath/errors.rb +1 -1
- data/spec/moxml/doctype_spec.rb +19 -3
- metadata +3 -3
- data/.ruby-version +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 07caeff68b2413d8ccaab1e827b151c4b98381f76b0f1feb63cec118be54ae05
|
|
4
|
+
data.tar.gz: 76e3eb23022b6d49e5c265467966d29d6ad12617f2a00af3b34680b32f96b8ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e0a41270e30bca0664d5e4bf32019d82a262cbfe6c2d001879d8e6259bf06e80a24df2372698ecd651835e88b27be2b7f2552b0d26aaf40aa514de7a870bf5e8
|
|
7
|
+
data.tar.gz: 7ade8a4b671b3eb3fe06d2b909783f61bd5172d1645e3c1e9c6344d3e3fb01b5b1c32b8da09b39fedc2b73d04e52edb19956bf00240fbb145798f4be9c598058
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2025-11-
|
|
3
|
+
# on 2025-11-26 03:06:16 UTC using RuboCop version 1.80.0.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
|
8
8
|
|
|
9
|
-
# Offense count:
|
|
9
|
+
# Offense count: 203
|
|
10
10
|
# This cop supports safe autocorrection (--autocorrect).
|
|
11
11
|
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
|
|
12
12
|
# URISchemes: http, https
|
|
@@ -91,7 +91,7 @@ Metrics/MethodLength:
|
|
|
91
91
|
Metrics/ParameterLists:
|
|
92
92
|
Max: 7
|
|
93
93
|
|
|
94
|
-
# Offense count:
|
|
94
|
+
# Offense count: 43
|
|
95
95
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
|
96
96
|
Metrics/PerceivedComplexity:
|
|
97
97
|
Enabled: false
|
data/README.adoc
CHANGED
|
@@ -26,6 +26,8 @@ Key features:
|
|
|
26
26
|
|
|
27
27
|
== Supported XML libraries
|
|
28
28
|
|
|
29
|
+
=== General
|
|
30
|
+
|
|
29
31
|
Moxml supports the following XML libraries:
|
|
30
32
|
|
|
31
33
|
REXML:: https://github.com/ruby/rexml[REXML], a pure Ruby XML parser
|
|
@@ -110,7 +112,9 @@ section.
|
|
|
110
112
|
NOTE: HeadedOx provides full XPath 1.0 support via a pure Ruby XPath engine
|
|
111
113
|
layered on top of Ox's C parser. See HeadedOx documentation for details.
|
|
112
114
|
|
|
113
|
-
NOTE: Ox/HeadedOx SAX: Only core events supported (start_element, end_element,
|
|
115
|
+
NOTE: Ox/HeadedOx SAX: Only core events supported (start_element, end_element,
|
|
116
|
+
characters, errors). No separate CDATA, comment, or processing instruction
|
|
117
|
+
events.
|
|
114
118
|
|
|
115
119
|
== Adapter comparison
|
|
116
120
|
|
|
@@ -356,7 +360,7 @@ NOTE: Ox/HeadedOx SAX: Only core events supported (start_element, end_element, c
|
|
|
356
360
|
| ✅ Full
|
|
357
361
|
| ✅ Full
|
|
358
362
|
| ✅ Full
|
|
359
|
-
|
|
|
363
|
+
| ✅ Full
|
|
360
364
|
| ✅ Full
|
|
361
365
|
| ✅ Full
|
|
362
366
|
|
|
@@ -400,13 +404,12 @@ NOTE: Ox/HeadedOx SAX: Only core events supported (start_element, end_element, c
|
|
|
400
404
|
| ✅ Yes
|
|
401
405
|
| ✅ Yes
|
|
402
406
|
|===
|
|
403
|
-
|
|
407
|
+
+
|
|
404
408
|
^1^ Ox/HeadedOx: Text node replacement may fail in some cases due to internal node structure +
|
|
405
409
|
^2^ Ox: `//book[@id]` works (returns all book elements), but doesn't filter by attribute existence +
|
|
406
410
|
^3^ HeadedOx: Full XPath 1.0 with all 27 functions and 6 axes. Pure Ruby XPath engine on Ox's C parser. 99.20% pass rate. See link:docs/headed-ox.adoc[] +
|
|
407
411
|
^4^ Ox: Use `.find { |el| el["id"] == "123" }` instead of XPath attribute value predicates +
|
|
408
|
-
^5^
|
|
409
|
-
^6^ HeadedOx limitations: Namespace introspection and 7 axes not implemented. See link:docs/HEADED_OX_LIMITATIONS.md[]
|
|
412
|
+
^5^ HeadedOx limitations: Namespace introspection and 7 axes not implemented. See link:docs/HEADED_OX_LIMITATIONS.md[]
|
|
410
413
|
|
|
411
414
|
=== Adapter selection guide
|
|
412
415
|
|
|
@@ -453,10 +456,13 @@ NOTE: Ox/HeadedOx SAX: Only core events supported (start_element, end_element, c
|
|
|
453
456
|
* Need more XPath capabilities than standard Ox provides
|
|
454
457
|
* Memory efficiency is important but XPath features are required
|
|
455
458
|
|
|
456
|
-
CAUTION: Ox's custom XPath engine supports common patterns but
|
|
459
|
+
CAUTION: Ox's custom XPath engine supports common patterns but cannot handle
|
|
457
460
|
complex XPath expressions. Test thoroughly if your use case requires advanced
|
|
458
461
|
XPath.
|
|
459
462
|
|
|
463
|
+
TODO: We should throw errors when unsupported XPath features are used with Ox
|
|
464
|
+
or HeadedOx to prevent silent failures.
|
|
465
|
+
|
|
460
466
|
|
|
461
467
|
== Getting started
|
|
462
468
|
|
|
@@ -643,6 +649,23 @@ For complete SAX documentation including all handler types, event methods, adapt
|
|
|
643
649
|
|
|
644
650
|
For complete node API reference including traversal methods, manipulation, queries, type checking, and node information, see link:docs/_pages/node-api-reference.adoc[Node API Reference].
|
|
645
651
|
|
|
652
|
+
=== Node identity
|
|
653
|
+
|
|
654
|
+
Moxml provides a consistent `#identifier` method across all node types to safely identify nodes:
|
|
655
|
+
|
|
656
|
+
[source,ruby]
|
|
657
|
+
----
|
|
658
|
+
element = doc.at_xpath("//book")
|
|
659
|
+
puts element.identifier # => "book"
|
|
660
|
+
|
|
661
|
+
attr = element.attribute("id")
|
|
662
|
+
puts attr.identifier # => "id"
|
|
663
|
+
----
|
|
664
|
+
|
|
665
|
+
The `#identifier` method returns the primary identifier for each node type (tag name for elements, attribute name for attributes, target for processing instructions, or `nil` for content nodes).
|
|
666
|
+
|
|
667
|
+
IMPORTANT: Always use type-safe patterns when working with mixed node types. See the link:docs/_guides/node-api-consistency.adoc[Node API Consistency Guide] for complete documentation on safe coding patterns, API surface by node type, and migration guidelines.
|
|
668
|
+
|
|
646
669
|
|
|
647
670
|
== Advanced features
|
|
648
671
|
|
|
@@ -694,7 +717,8 @@ rescue Moxml::Error => e
|
|
|
694
717
|
end
|
|
695
718
|
----
|
|
696
719
|
|
|
697
|
-
For complete error class hierarchy, error types, best practices, and debugging
|
|
720
|
+
For complete error class hierarchy, error types, best practices, and debugging
|
|
721
|
+
techniques, see link:docs/_pages/error-handling.adoc[Error Handling Guide].
|
|
698
722
|
|
|
699
723
|
|
|
700
724
|
== Configuration
|
|
@@ -717,22 +741,31 @@ context = Moxml.new do |config|
|
|
|
717
741
|
end
|
|
718
742
|
----
|
|
719
743
|
|
|
720
|
-
For all configuration options, adapter selection, serialization options, and
|
|
744
|
+
For all configuration options, adapter selection, serialization options, and
|
|
745
|
+
environment-based configuration, see
|
|
746
|
+
link:docs/_pages/configuration.adoc[Configuration Guide].
|
|
721
747
|
|
|
722
748
|
|
|
723
749
|
|
|
724
750
|
== Thread safety
|
|
725
751
|
|
|
726
|
-
For complete information on thread-safe patterns, context management, and
|
|
752
|
+
For complete information on thread-safe patterns, context management, and
|
|
753
|
+
concurrent processing, see the link:docs/_pages/thread-safety.adoc[Thread Safety
|
|
754
|
+
Guide].
|
|
727
755
|
|
|
728
756
|
|
|
729
757
|
== Performance considerations
|
|
730
758
|
|
|
731
|
-
For detailed performance optimization strategies, memory management best
|
|
759
|
+
For detailed performance optimization strategies, memory management best
|
|
760
|
+
practices, and efficient querying patterns, see the
|
|
761
|
+
link:docs/_pages/performance.adoc[Performance Considerations Guide].
|
|
732
762
|
|
|
733
763
|
== Best practices
|
|
734
764
|
|
|
735
|
-
For comprehensive best practices covering XPath queries, adapter selection,
|
|
765
|
+
For comprehensive best practices covering XPath queries, adapter selection,
|
|
766
|
+
error handling, namespace handling, memory management, thread safety,
|
|
767
|
+
performance optimization, and testing strategies, see
|
|
768
|
+
link:docs/_pages/best-practices.adoc[Best Practices Guide].
|
|
736
769
|
|
|
737
770
|
|
|
738
771
|
== Specific adapter limitations
|
|
@@ -756,11 +789,13 @@ The Ox adapter provides maximum parsing speed but has XPath limitations.
|
|
|
756
789
|
doc.xpath("//book").find { |book| book["id"] == "123" }
|
|
757
790
|
----
|
|
758
791
|
|
|
759
|
-
For complete Ox adapter documentation including all limitations and workarounds,
|
|
792
|
+
For complete Ox adapter documentation including all limitations and workarounds,
|
|
793
|
+
see link:docs/_pages/adapters/ox.adoc[Ox Adapter Guide].
|
|
760
794
|
|
|
761
795
|
=== HeadedOx adapter
|
|
762
796
|
|
|
763
|
-
The HeadedOx adapter combines Ox's fast C-based XML parsing with Moxml's
|
|
797
|
+
The HeadedOx adapter combines Ox's fast C-based XML parsing with Moxml's
|
|
798
|
+
comprehensive pure Ruby XPath 1.0 engine.
|
|
764
799
|
|
|
765
800
|
**Status:** Production-ready v1.2 (99.20% pass rate, 1,992/2,008 tests)
|
|
766
801
|
|
|
@@ -796,12 +831,6 @@ For complete HeadedOx documentation including architecture, XPath capabilities,
|
|
|
796
831
|
|
|
797
832
|
==== LibXML adapter
|
|
798
833
|
|
|
799
|
-
*DOCTYPE Limitations:*
|
|
800
|
-
|
|
801
|
-
* DOCTYPE parsing works
|
|
802
|
-
* DOCTYPE round-trip preservation is limited
|
|
803
|
-
* DOCTYPE cannot be reliably re-serialized after parsing
|
|
804
|
-
|
|
805
834
|
*Performance:*
|
|
806
835
|
|
|
807
836
|
* Serialization speed: ~120 ips (slower than target)
|
|
@@ -819,7 +848,9 @@ limitations. Use these adapters when you need full XPath and namespace support.
|
|
|
819
848
|
|
|
820
849
|
== Development and testing
|
|
821
850
|
|
|
822
|
-
For complete information on development setup, testing strategies, benchmarking,
|
|
851
|
+
For complete information on development setup, testing strategies, benchmarking,
|
|
852
|
+
and coverage reporting, see the
|
|
853
|
+
link:docs/_guides/development-testing.adoc[Development and Testing Guide].
|
|
823
854
|
|
|
824
855
|
== Contributing
|
|
825
856
|
|
data/docs/_guides/index.adoc
CHANGED
|
@@ -10,23 +10,25 @@ Task-oriented guides for common XML processing operations with Moxml.
|
|
|
10
10
|
=== Document operations
|
|
11
11
|
|
|
12
12
|
link:parsing-xml[Parsing XML]::
|
|
13
|
-
Parse XML from strings, files, and IO streams
|
|
13
|
+
Parse XML from strings, files, and IO streams
|
|
14
14
|
|
|
15
|
-
link:
|
|
16
|
-
|
|
15
|
+
link:working-with-documents[Working with Documents]::
|
|
16
|
+
Create and manipulate XML documents
|
|
17
17
|
|
|
18
|
-
link:xml
|
|
19
|
-
|
|
18
|
+
link:modifying-xml[Modifying XML]::
|
|
19
|
+
Edit elements, attributes, and content
|
|
20
20
|
|
|
21
|
-
link:
|
|
22
|
-
|
|
23
|
-
manipulation.
|
|
21
|
+
link:sax-parsing[SAX Parsing]::
|
|
22
|
+
Memory-efficient event-driven parsing
|
|
24
23
|
|
|
25
|
-
link:
|
|
26
|
-
|
|
24
|
+
link:advanced-features[Advanced Features]::
|
|
25
|
+
Namespaces, XPath, and more
|
|
26
|
+
|
|
27
|
+
link:node-api-consistency[Node API Consistency]::
|
|
28
|
+
Understanding node types and their APIs
|
|
27
29
|
|
|
28
|
-
link:
|
|
29
|
-
|
|
30
|
+
link:development-testing[Development & Testing]::
|
|
31
|
+
Contributing to Moxml
|
|
30
32
|
|
|
31
33
|
=== Querying and traversal
|
|
32
34
|
|
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
= Node API consistency guide
|
|
2
|
+
:toc:
|
|
3
|
+
:toclevels: 3
|
|
4
|
+
|
|
5
|
+
== Overview
|
|
6
|
+
|
|
7
|
+
This guide documents the API surface of all node types in Moxml, providing clear
|
|
8
|
+
expectations for developers about which methods are available on which node
|
|
9
|
+
types.
|
|
10
|
+
|
|
11
|
+
== Node Type Hierarchy
|
|
12
|
+
|
|
13
|
+
[source]
|
|
14
|
+
----
|
|
15
|
+
Node (abstract base)
|
|
16
|
+
├── Document
|
|
17
|
+
├── Element
|
|
18
|
+
├── Attribute
|
|
19
|
+
├── Text
|
|
20
|
+
├── Cdata
|
|
21
|
+
├── Comment
|
|
22
|
+
├── ProcessingInstruction
|
|
23
|
+
├── Declaration
|
|
24
|
+
└── Doctype
|
|
25
|
+
----
|
|
26
|
+
|
|
27
|
+
== Common Node Methods
|
|
28
|
+
|
|
29
|
+
All node types inherit these methods from `Moxml::Node`:
|
|
30
|
+
|
|
31
|
+
=== Document Navigation
|
|
32
|
+
|
|
33
|
+
[cols="1,3,1"]
|
|
34
|
+
|===
|
|
35
|
+
| Method | Description | Always Available?
|
|
36
|
+
|
|
37
|
+
| `#document`
|
|
38
|
+
| Returns the containing document
|
|
39
|
+
| ✅ Yes
|
|
40
|
+
|
|
41
|
+
| `#parent`
|
|
42
|
+
| Returns the parent node
|
|
43
|
+
| ✅ Yes
|
|
44
|
+
|
|
45
|
+
| `#children`
|
|
46
|
+
| Returns a NodeSet of child nodes
|
|
47
|
+
| ✅ Yes (may be empty)
|
|
48
|
+
|
|
49
|
+
| `#next_sibling`
|
|
50
|
+
| Returns the next sibling node
|
|
51
|
+
| ✅ Yes (may be nil)
|
|
52
|
+
|
|
53
|
+
| `#previous_sibling`
|
|
54
|
+
| Returns the previous sibling node
|
|
55
|
+
| ✅ Yes (may be nil)
|
|
56
|
+
|===
|
|
57
|
+
|
|
58
|
+
=== Tree Manipulation
|
|
59
|
+
|
|
60
|
+
[cols="1,3,1"]
|
|
61
|
+
|===
|
|
62
|
+
| Method | Description | Always Available?
|
|
63
|
+
|
|
64
|
+
| `#add_child(node)`
|
|
65
|
+
| Adds a child node
|
|
66
|
+
| ✅ Yes
|
|
67
|
+
|
|
68
|
+
| `#add_previous_sibling(node)`
|
|
69
|
+
| Adds a sibling before this node
|
|
70
|
+
| ✅ Yes
|
|
71
|
+
|
|
72
|
+
| `#add_next_sibling(node)`
|
|
73
|
+
| Adds a sibling after this node
|
|
74
|
+
| ✅ Yes
|
|
75
|
+
|
|
76
|
+
| `#remove`
|
|
77
|
+
| Removes this node from the tree
|
|
78
|
+
| ✅ Yes
|
|
79
|
+
|
|
80
|
+
| `#replace(node)`
|
|
81
|
+
| Replaces this node with another
|
|
82
|
+
| ✅ Yes
|
|
83
|
+
|===
|
|
84
|
+
|
|
85
|
+
=== Serialization
|
|
86
|
+
|
|
87
|
+
[cols="1,3,1"]
|
|
88
|
+
|===
|
|
89
|
+
| Method | Description | Always Available?
|
|
90
|
+
|
|
91
|
+
| `#to_xml(options = {})`
|
|
92
|
+
| Serializes node to XML string
|
|
93
|
+
| ✅ Yes
|
|
94
|
+
|
|
95
|
+
| `#clone` / `#dup`
|
|
96
|
+
| Creates a deep copy of the node
|
|
97
|
+
| ✅ Yes
|
|
98
|
+
|===
|
|
99
|
+
|
|
100
|
+
=== XPath Queries
|
|
101
|
+
|
|
102
|
+
[cols="1,3,1"]
|
|
103
|
+
|===
|
|
104
|
+
| Method | Description | Always Available?
|
|
105
|
+
|
|
106
|
+
| `#xpath(expression, ns = {})`
|
|
107
|
+
| Returns NodeSet matching XPath
|
|
108
|
+
| ✅ Yes (adapter-dependent)
|
|
109
|
+
|
|
110
|
+
| `#at_xpath(expression, ns = {})`
|
|
111
|
+
| Returns first node matching XPath
|
|
112
|
+
| ✅ Yes (adapter-dependent)
|
|
113
|
+
|===
|
|
114
|
+
|
|
115
|
+
=== Type Checking
|
|
116
|
+
|
|
117
|
+
[cols="1,3,1"]
|
|
118
|
+
|===
|
|
119
|
+
| Method | Description | Always Available?
|
|
120
|
+
|
|
121
|
+
| `#element?`
|
|
122
|
+
| Returns true if node is an Element
|
|
123
|
+
| ✅ Yes
|
|
124
|
+
|
|
125
|
+
| `#text?`
|
|
126
|
+
| Returns true if node is a Text node
|
|
127
|
+
| ✅ Yes
|
|
128
|
+
|
|
129
|
+
| `#cdata?`
|
|
130
|
+
| Returns true if node is a CDATA section
|
|
131
|
+
| ✅ Yes
|
|
132
|
+
|
|
133
|
+
| `#comment?`
|
|
134
|
+
| Returns true if node is a Comment
|
|
135
|
+
| ✅ Yes
|
|
136
|
+
|
|
137
|
+
| `#processing_instruction?`
|
|
138
|
+
| Returns true if node is a PI
|
|
139
|
+
| ✅ Yes
|
|
140
|
+
|
|
141
|
+
| `#document?`
|
|
142
|
+
| Returns true if node is a Document
|
|
143
|
+
| ✅ Yes
|
|
144
|
+
|
|
145
|
+
| `#declaration?`
|
|
146
|
+
| Returns true if node is a Declaration
|
|
147
|
+
| ✅ Yes
|
|
148
|
+
|
|
149
|
+
| `#doctype?`
|
|
150
|
+
| Returns true if node is a Doctype
|
|
151
|
+
| ✅ Yes
|
|
152
|
+
|
|
153
|
+
| `#attribute?`
|
|
154
|
+
| Returns true if node is an Attribute
|
|
155
|
+
| ✅ Yes
|
|
156
|
+
|===
|
|
157
|
+
|
|
158
|
+
== Node Type-Specific APIs
|
|
159
|
+
|
|
160
|
+
=== Document
|
|
161
|
+
|
|
162
|
+
The root document node.
|
|
163
|
+
|
|
164
|
+
==== Additional Methods
|
|
165
|
+
|
|
166
|
+
[cols="1,3,1"]
|
|
167
|
+
|===
|
|
168
|
+
| Method | Description | Always Available?
|
|
169
|
+
|
|
170
|
+
| `#root`
|
|
171
|
+
| Returns the root element
|
|
172
|
+
| ✅ Yes
|
|
173
|
+
|
|
174
|
+
| `#root=(element)`
|
|
175
|
+
| Sets the root element
|
|
176
|
+
| ✅ Yes
|
|
177
|
+
|
|
178
|
+
| `#encoding`
|
|
179
|
+
| Returns document encoding
|
|
180
|
+
| ✅ Yes
|
|
181
|
+
|
|
182
|
+
| `#create_element(name, content = nil)`
|
|
183
|
+
| Creates a new element
|
|
184
|
+
| ✅ Yes
|
|
185
|
+
|
|
186
|
+
| `#create_text(content)`
|
|
187
|
+
| Creates a new text node
|
|
188
|
+
| ✅ Yes
|
|
189
|
+
|
|
190
|
+
| `#create_comment(content)`
|
|
191
|
+
| Creates a new comment
|
|
192
|
+
| ✅ Yes
|
|
193
|
+
|
|
194
|
+
| `#create_cdata(content)`
|
|
195
|
+
| Creates a new CDATA section
|
|
196
|
+
| ✅ Yes
|
|
197
|
+
|
|
198
|
+
| `#create_processing_instruction(target, content)`
|
|
199
|
+
| Creates a new processing instruction
|
|
200
|
+
| ✅ Yes
|
|
201
|
+
|
|
202
|
+
| `#create_declaration(version, encoding, standalone)`
|
|
203
|
+
| Creates a new XML declaration
|
|
204
|
+
| ✅ Yes (adapter-dependent)
|
|
205
|
+
|===
|
|
206
|
+
|
|
207
|
+
=== Element
|
|
208
|
+
|
|
209
|
+
Elements are the primary structural nodes with tag names, attributes, and children.
|
|
210
|
+
|
|
211
|
+
==== Identity Methods
|
|
212
|
+
|
|
213
|
+
[cols="1,3,1"]
|
|
214
|
+
|===
|
|
215
|
+
| Method | Description | Always Available?
|
|
216
|
+
|
|
217
|
+
| `#name`
|
|
218
|
+
| Returns the element tag name
|
|
219
|
+
| ✅ Yes
|
|
220
|
+
|
|
221
|
+
| `#name=(value)`
|
|
222
|
+
| Sets the element tag name
|
|
223
|
+
| ✅ Yes
|
|
224
|
+
|
|
225
|
+
| `#identifier`
|
|
226
|
+
| Returns the primary identifier (same as #name)
|
|
227
|
+
| ✅ Yes
|
|
228
|
+
|===
|
|
229
|
+
|
|
230
|
+
==== Namespace Methods
|
|
231
|
+
|
|
232
|
+
[cols="1,3,1"]
|
|
233
|
+
|===
|
|
234
|
+
| Method | Description | Always Available?
|
|
235
|
+
|
|
236
|
+
| `#namespace`
|
|
237
|
+
| Returns the element's namespace
|
|
238
|
+
| ✅ Yes (may be nil)
|
|
239
|
+
|
|
240
|
+
| `#namespace=(ns_or_hash)`
|
|
241
|
+
| Sets the element's namespace
|
|
242
|
+
| ✅ Yes
|
|
243
|
+
|
|
244
|
+
| `#namespace_prefix`
|
|
245
|
+
| Returns the namespace prefix
|
|
246
|
+
| ✅ Yes (may be nil)
|
|
247
|
+
|
|
248
|
+
| `#namespace_uri`
|
|
249
|
+
| Returns the namespace URI
|
|
250
|
+
| ✅ Yes (may be nil)
|
|
251
|
+
|
|
252
|
+
| `#namespaces`
|
|
253
|
+
| Returns all namespace definitions
|
|
254
|
+
| ✅ Yes
|
|
255
|
+
|
|
256
|
+
| `#add_namespace(prefix, uri)`
|
|
257
|
+
| Adds a namespace definition
|
|
258
|
+
| ✅ Yes
|
|
259
|
+
|===
|
|
260
|
+
|
|
261
|
+
==== Attribute Methods
|
|
262
|
+
|
|
263
|
+
[cols="1,3,1"]
|
|
264
|
+
|===
|
|
265
|
+
| Method | Description | Always Available?
|
|
266
|
+
|
|
267
|
+
| `#[](name)`
|
|
268
|
+
| Gets attribute value
|
|
269
|
+
| ✅ Yes
|
|
270
|
+
|
|
271
|
+
| `#[]=(name, value)`
|
|
272
|
+
| Sets attribute value
|
|
273
|
+
| ✅ Yes
|
|
274
|
+
|
|
275
|
+
| `#attribute(name)`
|
|
276
|
+
| Returns Attribute object
|
|
277
|
+
| ✅ Yes (may be nil)
|
|
278
|
+
|
|
279
|
+
| `#attributes`
|
|
280
|
+
| Returns array of all attributes
|
|
281
|
+
| ✅ Yes
|
|
282
|
+
|
|
283
|
+
| `#remove_attribute(name)`
|
|
284
|
+
| Removes an attribute
|
|
285
|
+
| ✅ Yes
|
|
286
|
+
|===
|
|
287
|
+
|
|
288
|
+
==== Content Methods
|
|
289
|
+
|
|
290
|
+
[cols="1,3,1"]
|
|
291
|
+
|===
|
|
292
|
+
| Method | Description | Always Available?
|
|
293
|
+
|
|
294
|
+
| `#text`
|
|
295
|
+
| Returns text content
|
|
296
|
+
| ✅ Yes
|
|
297
|
+
|
|
298
|
+
| `#text=(content)`
|
|
299
|
+
| Sets text content
|
|
300
|
+
| ✅ Yes
|
|
301
|
+
|
|
302
|
+
| `#inner_text`
|
|
303
|
+
| Returns inner text (concatenated)
|
|
304
|
+
| ✅ Yes
|
|
305
|
+
|
|
306
|
+
| `#inner_xml`
|
|
307
|
+
| Returns inner XML as string
|
|
308
|
+
| ✅ Yes
|
|
309
|
+
|
|
310
|
+
| `#inner_xml=(xml)`
|
|
311
|
+
| Sets inner XML from string
|
|
312
|
+
| ✅ Yes
|
|
313
|
+
|===
|
|
314
|
+
|
|
315
|
+
=== Attribute
|
|
316
|
+
|
|
317
|
+
Attributes are name-value pairs attached to elements.
|
|
318
|
+
|
|
319
|
+
==== Identity Methods
|
|
320
|
+
|
|
321
|
+
[cols="1,3,1"]
|
|
322
|
+
|===
|
|
323
|
+
| Method | Description | Always Available?
|
|
324
|
+
|
|
325
|
+
| `#name`
|
|
326
|
+
| Returns the attribute name
|
|
327
|
+
| ✅ Yes
|
|
328
|
+
|
|
329
|
+
| `#name=(new_name)`
|
|
330
|
+
| Sets the attribute name
|
|
331
|
+
| ✅ Yes
|
|
332
|
+
|
|
333
|
+
| `#identifier`
|
|
334
|
+
| Returns the primary identifier (same as #name)
|
|
335
|
+
| ✅ Yes
|
|
336
|
+
|===
|
|
337
|
+
|
|
338
|
+
==== Value Methods
|
|
339
|
+
|
|
340
|
+
[cols="1,3,1"]
|
|
341
|
+
|===
|
|
342
|
+
| Method | Description | Always Available?
|
|
343
|
+
|
|
344
|
+
| `#value`
|
|
345
|
+
| Returns the attribute value
|
|
346
|
+
| ✅ Yes
|
|
347
|
+
|
|
348
|
+
| `#value=(new_value)`
|
|
349
|
+
| Sets the attribute value
|
|
350
|
+
| ✅ Yes
|
|
351
|
+
|
|
352
|
+
| `#text`
|
|
353
|
+
| Alias for #value (XPath compatibility)
|
|
354
|
+
| ✅ Yes
|
|
355
|
+
|===
|
|
356
|
+
|
|
357
|
+
==== Relationship Methods
|
|
358
|
+
|
|
359
|
+
[cols="1,3,1"]
|
|
360
|
+
|===
|
|
361
|
+
| Method | Description | Always Available?
|
|
362
|
+
|
|
363
|
+
| `#element`
|
|
364
|
+
| Returns the owning element
|
|
365
|
+
| ✅ Yes
|
|
366
|
+
|
|
367
|
+
| `#namespace`
|
|
368
|
+
| Returns the attribute's namespace
|
|
369
|
+
| ✅ Yes (may be nil)
|
|
370
|
+
|===
|
|
371
|
+
|
|
372
|
+
=== Text
|
|
373
|
+
|
|
374
|
+
Text nodes contain character data.
|
|
375
|
+
|
|
376
|
+
==== Content Methods
|
|
377
|
+
|
|
378
|
+
[cols="1,3,1"]
|
|
379
|
+
|===
|
|
380
|
+
| Method | Description | Always Available?
|
|
381
|
+
|
|
382
|
+
| `#content`
|
|
383
|
+
| Returns the text content
|
|
384
|
+
| ✅ Yes
|
|
385
|
+
|
|
386
|
+
| `#content=(text)`
|
|
387
|
+
| Sets the text content
|
|
388
|
+
| ✅ Yes
|
|
389
|
+
|
|
390
|
+
| `#text`
|
|
391
|
+
| Alias for #content
|
|
392
|
+
| ✅ Yes
|
|
393
|
+
|===
|
|
394
|
+
|
|
395
|
+
==== Identity Methods
|
|
396
|
+
|
|
397
|
+
[cols="1,3,1"]
|
|
398
|
+
|===
|
|
399
|
+
| Method | Description | Always Available?
|
|
400
|
+
|
|
401
|
+
| `#identifier`
|
|
402
|
+
| Returns nil (content nodes have no identifier)
|
|
403
|
+
| ✅ Yes
|
|
404
|
+
|===
|
|
405
|
+
|
|
406
|
+
=== Cdata
|
|
407
|
+
|
|
408
|
+
CDATA sections contain character data that should not be parsed.
|
|
409
|
+
|
|
410
|
+
==== Content Methods
|
|
411
|
+
|
|
412
|
+
[cols="1,3,1"]
|
|
413
|
+
|===
|
|
414
|
+
| Method | Description | Always Available?
|
|
415
|
+
|
|
416
|
+
| `#content`
|
|
417
|
+
| Returns the CDATA content
|
|
418
|
+
| ✅ Yes
|
|
419
|
+
|
|
420
|
+
| `#content=(text)`
|
|
421
|
+
| Sets the CDATA content
|
|
422
|
+
| ✅ Yes
|
|
423
|
+
|
|
424
|
+
| `#text`
|
|
425
|
+
| Alias for #content
|
|
426
|
+
| ✅ Yes
|
|
427
|
+
|===
|
|
428
|
+
|
|
429
|
+
==== Identity Methods
|
|
430
|
+
|
|
431
|
+
[cols="1,3,1"]
|
|
432
|
+
|===
|
|
433
|
+
| Method | Description | Always Available?
|
|
434
|
+
|
|
435
|
+
| `#identifier`
|
|
436
|
+
| Returns nil (content nodes have no identifier)
|
|
437
|
+
| ✅ Yes
|
|
438
|
+
|===
|
|
439
|
+
|
|
440
|
+
=== Comment
|
|
441
|
+
|
|
442
|
+
Comment nodes contain XML comments.
|
|
443
|
+
|
|
444
|
+
==== Content Methods
|
|
445
|
+
|
|
446
|
+
[cols="1,3,1"]
|
|
447
|
+
|===
|
|
448
|
+
| Method | Description | Always Available?
|
|
449
|
+
|
|
450
|
+
| `#content`
|
|
451
|
+
| Returns the comment text
|
|
452
|
+
| ✅ Yes
|
|
453
|
+
|
|
454
|
+
| `#content=(text)`
|
|
455
|
+
| Sets the comment text
|
|
456
|
+
| ✅ Yes
|
|
457
|
+
|
|
458
|
+
| `#text`
|
|
459
|
+
| Alias for #content
|
|
460
|
+
| ✅ Yes
|
|
461
|
+
|===
|
|
462
|
+
|
|
463
|
+
==== Identity Methods
|
|
464
|
+
|
|
465
|
+
[cols="1,3,1"]
|
|
466
|
+
|===
|
|
467
|
+
| Method | Description | Always Available?
|
|
468
|
+
|
|
469
|
+
| `#identifier`
|
|
470
|
+
| Returns nil (content nodes have no identifier)
|
|
471
|
+
| ✅ Yes
|
|
472
|
+
|===
|
|
473
|
+
|
|
474
|
+
=== ProcessingInstruction
|
|
475
|
+
|
|
476
|
+
Processing instructions provide directives to applications.
|
|
477
|
+
|
|
478
|
+
==== Identity Methods
|
|
479
|
+
|
|
480
|
+
[cols="1,3,1"]
|
|
481
|
+
|===
|
|
482
|
+
| Method | Description | Always Available?
|
|
483
|
+
|
|
484
|
+
| `#target`
|
|
485
|
+
| Returns the PI target
|
|
486
|
+
| ✅ Yes
|
|
487
|
+
|
|
488
|
+
| `#target=(new_target)`
|
|
489
|
+
| Sets the PI target
|
|
490
|
+
| ✅ Yes
|
|
491
|
+
|
|
492
|
+
| `#identifier`
|
|
493
|
+
| Returns the primary identifier (same as #target)
|
|
494
|
+
| ✅ Yes
|
|
495
|
+
|===
|
|
496
|
+
|
|
497
|
+
==== Content Methods
|
|
498
|
+
|
|
499
|
+
[cols="1,3,1"]
|
|
500
|
+
|===
|
|
501
|
+
| Method | Description | Always Available?
|
|
502
|
+
|
|
503
|
+
| `#content`
|
|
504
|
+
| Returns the PI content
|
|
505
|
+
| ✅ Yes
|
|
506
|
+
|
|
507
|
+
| `#content=(new_content)`
|
|
508
|
+
| Sets the PI content
|
|
509
|
+
| ✅ Yes
|
|
510
|
+
|===
|
|
511
|
+
|
|
512
|
+
=== Declaration
|
|
513
|
+
|
|
514
|
+
XML declarations specify document metadata.
|
|
515
|
+
|
|
516
|
+
==== Property Methods
|
|
517
|
+
|
|
518
|
+
[cols="1,3,1"]
|
|
519
|
+
|===
|
|
520
|
+
| Method | Description | Always Available?
|
|
521
|
+
|
|
522
|
+
| `#version`
|
|
523
|
+
| Returns XML version (e.g., "1.0")
|
|
524
|
+
| ✅ Yes
|
|
525
|
+
|
|
526
|
+
| `#version=(new_version)`
|
|
527
|
+
| Sets XML version
|
|
528
|
+
| ✅ Yes
|
|
529
|
+
|
|
530
|
+
| `#encoding`
|
|
531
|
+
| Returns character encoding
|
|
532
|
+
| ✅ Yes (may be nil)
|
|
533
|
+
|
|
534
|
+
| `#encoding=(new_encoding)`
|
|
535
|
+
| Sets character encoding
|
|
536
|
+
| ✅ Yes
|
|
537
|
+
|
|
538
|
+
| `#standalone`
|
|
539
|
+
| Returns standalone status
|
|
540
|
+
| ✅ Yes (may be nil)
|
|
541
|
+
|
|
542
|
+
| `#standalone=(new_standalone)`
|
|
543
|
+
| Sets standalone status
|
|
544
|
+
| ✅ Yes
|
|
545
|
+
|===
|
|
546
|
+
|
|
547
|
+
==== Identity Methods
|
|
548
|
+
|
|
549
|
+
[cols="1,3,1"]
|
|
550
|
+
|===
|
|
551
|
+
| Method | Description | Always Available?
|
|
552
|
+
|
|
553
|
+
| `#identifier`
|
|
554
|
+
| Returns nil (declarations have no identifier)
|
|
555
|
+
| ✅ Yes
|
|
556
|
+
|===
|
|
557
|
+
|
|
558
|
+
=== Doctype
|
|
559
|
+
|
|
560
|
+
Document type declarations specify DTD information.
|
|
561
|
+
|
|
562
|
+
WARNING: Doctype accessor methods are **not fully implemented** across all adapters. The availability of these methods depends on the specific adapter being used.
|
|
563
|
+
|
|
564
|
+
==== Identity Methods
|
|
565
|
+
|
|
566
|
+
[cols="1,3,1"]
|
|
567
|
+
|===
|
|
568
|
+
|Doctype |`#name` |✅ Yes |Returns DOCTYPE name (root element)
|
|
569
|
+
|Doctype |`#external_id` |✅ Yes |Returns PUBLIC identifier
|
|
570
|
+
|Doctype |`#system_id` |✅ Yes |Returns SYSTEM identifier (DTD URI)
|
|
571
|
+
|Doctype |`#identifier` |✅ Yes |Returns DOCTYPE name (same as `#name`)
|
|
572
|
+
|===
|
|
@@ -439,12 +439,12 @@ doc.to_xml # Use automatic preservation
|
|
|
439
439
|
Controls whether XML declaration is included in serialized output:
|
|
440
440
|
|
|
441
441
|
* `true`: Always include declaration
|
|
442
|
-
* `false`: Never include declaration
|
|
442
|
+
* `false`: Never include declaration
|
|
443
443
|
* Not specified: Use `has_xml_declaration` value (automatic preservation)
|
|
444
444
|
|
|
445
445
|
== See Also
|
|
446
446
|
|
|
447
|
-
* link:parsing-xml
|
|
448
|
-
* link:modifying-xml
|
|
449
|
-
* link:../adapters/index
|
|
450
|
-
* link:../best-practices
|
|
447
|
+
* link:parsing-xml[Parsing XML] - How to parse XML documents
|
|
448
|
+
* link:modifying-xml[Modifying XML] - Working with parsed documents
|
|
449
|
+
* link:../adapters/index[Adapters] - Adapter-specific behavior
|
|
450
|
+
* link:../best-practices[Best Practices] - General XML processing guidelines
|
|
@@ -283,7 +283,7 @@ end
|
|
|
283
283
|
=== References
|
|
284
284
|
|
|
285
285
|
* link:https://github.com/ruby/rexml[REXML on GitHub]
|
|
286
|
-
* link:https://ruby-doc.org/stdlib/libdoc/rexml/rdoc/REXML
|
|
286
|
+
* link:https://ruby-doc.org/stdlib/libdoc/rexml/rdoc/REXML[REXML documentation]
|
|
287
287
|
|
|
288
288
|
=== See also
|
|
289
289
|
|
|
@@ -1,9 +1,77 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Node API
|
|
3
|
-
|
|
2
|
+
title: Node API Reference
|
|
3
|
+
:toc:
|
|
4
|
+
:toclevels: 3
|
|
4
5
|
---
|
|
5
6
|
|
|
6
|
-
== Node API
|
|
7
|
+
== Node API Reference
|
|
8
|
+
|
|
9
|
+
This reference documents the API of all node types in Moxml. For a guide on API consistency and safe coding patterns, see the link:../guides/node-api-consistency[Node API Consistency Guide].
|
|
10
|
+
|
|
11
|
+
== Node Identity: The #identifier Method
|
|
12
|
+
|
|
13
|
+
All node types in Moxml support the `#identifier` method, which returns the primary identifier for a node:
|
|
14
|
+
|
|
15
|
+
[cols="1,2,1"]
|
|
16
|
+
|===
|
|
17
|
+
| Node Type | #identifier Returns | Example
|
|
18
|
+
|
|
19
|
+
| Element
|
|
20
|
+
| The tag name
|
|
21
|
+
| `"book"`, `"title"`
|
|
22
|
+
|
|
23
|
+
| Attribute
|
|
24
|
+
| The attribute name
|
|
25
|
+
| `"id"`, `"class"`
|
|
26
|
+
|
|
27
|
+
| ProcessingInstruction
|
|
28
|
+
| The PI target
|
|
29
|
+
| `"xml-stylesheet"`
|
|
30
|
+
|
|
31
|
+
| Text, Comment, Cdata
|
|
32
|
+
| `nil` (no identifier)
|
|
33
|
+
| `nil`
|
|
34
|
+
|
|
35
|
+
| Declaration
|
|
36
|
+
| `nil` (no identifier)
|
|
37
|
+
| `nil`
|
|
38
|
+
|
|
39
|
+
| Document
|
|
40
|
+
| `nil` (no identifier)
|
|
41
|
+
| `nil`
|
|
42
|
+
|===
|
|
43
|
+
|
|
44
|
+
**Example usage:**
|
|
45
|
+
|
|
46
|
+
[source,ruby]
|
|
47
|
+
----
|
|
48
|
+
element = doc.at_xpath("//book")
|
|
49
|
+
puts element.identifier # => "book"
|
|
50
|
+
|
|
51
|
+
attr = element.attribute("id")
|
|
52
|
+
puts attr.identifier # => "id"
|
|
53
|
+
|
|
54
|
+
pi = doc.children.find { |n| n.processing_instruction? }
|
|
55
|
+
puts pi.identifier # => "xml-stylesheet"
|
|
56
|
+
|
|
57
|
+
text = element.children.find { |n| n.text? }
|
|
58
|
+
puts text.identifier # => nil
|
|
59
|
+
----
|
|
60
|
+
|
|
61
|
+
**Safe iteration over mixed nodes:**
|
|
62
|
+
|
|
63
|
+
[source,ruby]
|
|
64
|
+
----
|
|
65
|
+
doc.root.children.each do |node|
|
|
66
|
+
if id = node.identifier
|
|
67
|
+
puts "#{node.class.name.split('::').last}: #{id}"
|
|
68
|
+
else
|
|
69
|
+
puts "#{node.class.name.split('::').last}: (no identifier)"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
----
|
|
73
|
+
|
|
74
|
+
== Common Node Methods
|
|
7
75
|
|
|
8
76
|
== XML objects and their methods
|
|
9
77
|
|
|
@@ -47,3 +115,25 @@ See also:
|
|
|
47
115
|
|
|
48
116
|
* link:../guides/working-with-documents[Working with documents guide]
|
|
49
117
|
* link:../guides/advanced-features[Advanced features guide]
|
|
118
|
+
=== Doctype nodes
|
|
119
|
+
|
|
120
|
+
Doctype nodes represent DOCTYPE declarations in XML documents.
|
|
121
|
+
|
|
122
|
+
[source,ruby]
|
|
123
|
+
----
|
|
124
|
+
doctype = doc.create_doctype("html", "-//W3C//DTD HTML 4.01//EN",
|
|
125
|
+
"http://www.w3.org/TR/html4/strict.dtd")
|
|
126
|
+
doctype.name # => "html"
|
|
127
|
+
doctype.external_id # => "-//W3C//DTD HTML 4.01//EN"
|
|
128
|
+
doctype.system_id # => "http://www.w3.org/TR/html4/strict.dtd"
|
|
129
|
+
doctype.identifier # => "html"
|
|
130
|
+
----
|
|
131
|
+
|
|
132
|
+
*Available methods:*
|
|
133
|
+
|
|
134
|
+
* `name` - Returns the DOCTYPE name (root element name)
|
|
135
|
+
* `external_id` - Returns the PUBLIC identifier (or nil)
|
|
136
|
+
* `system_id` - Returns the SYSTEM identifier (DTD URI, or nil)
|
|
137
|
+
* `identifier` - Returns the primary identifier (same as `name`)
|
|
138
|
+
|
|
139
|
+
All Doctype accessor methods are fully implemented across all 6 adapters.
|
data/lib/moxml/adapter/libxml.rb
CHANGED
|
@@ -788,6 +788,20 @@ module Moxml
|
|
|
788
788
|
end
|
|
789
789
|
end
|
|
790
790
|
|
|
791
|
+
# Doctype accessor methods
|
|
792
|
+
def doctype_name(native)
|
|
793
|
+
# LibXML uses DoctypeWrapper which stores the values
|
|
794
|
+
native.name
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
def doctype_external_id(native)
|
|
798
|
+
native.external_id
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def doctype_system_id(native)
|
|
802
|
+
native.system_id
|
|
803
|
+
end
|
|
804
|
+
|
|
791
805
|
def xpath(node, expression, namespaces = nil)
|
|
792
806
|
native_node = unpatch_node(node)
|
|
793
807
|
return [] unless native_node
|
|
@@ -1149,7 +1163,7 @@ module Moxml
|
|
|
1149
1163
|
# Add namespace definitions (only on this element, not ancestors)
|
|
1150
1164
|
if elem.respond_to?(:namespaces)
|
|
1151
1165
|
seen_ns = {}
|
|
1152
|
-
elem.namespaces.
|
|
1166
|
+
elem.namespaces.each do |ns|
|
|
1153
1167
|
prefix = ns.prefix
|
|
1154
1168
|
uri = ns.href
|
|
1155
1169
|
next if seen_ns.key?(prefix)
|
|
@@ -321,6 +321,19 @@ module Moxml
|
|
|
321
321
|
node.namespace_definitions
|
|
322
322
|
end
|
|
323
323
|
|
|
324
|
+
# Doctype accessor methods
|
|
325
|
+
def doctype_name(native)
|
|
326
|
+
native.name
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def doctype_external_id(native)
|
|
330
|
+
native.external_id
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def doctype_system_id(native)
|
|
334
|
+
native.system_id
|
|
335
|
+
end
|
|
336
|
+
|
|
324
337
|
def xpath(node, expression, namespaces = nil)
|
|
325
338
|
node.xpath(expression, namespaces).to_a
|
|
326
339
|
rescue ::Nokogiri::XML::XPath::SyntaxError => e
|
data/lib/moxml/adapter/oga.rb
CHANGED
|
@@ -365,6 +365,19 @@ module Moxml
|
|
|
365
365
|
node.namespaces.values
|
|
366
366
|
end
|
|
367
367
|
|
|
368
|
+
# Doctype accessor methods
|
|
369
|
+
def doctype_name(native)
|
|
370
|
+
native.name
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def doctype_external_id(native)
|
|
374
|
+
native.public_id
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def doctype_system_id(native)
|
|
378
|
+
native.system_id
|
|
379
|
+
end
|
|
380
|
+
|
|
368
381
|
def xpath(node, expression, namespaces = nil)
|
|
369
382
|
node.xpath(expression, {},
|
|
370
383
|
namespaces: namespaces&.transform_keys(&:to_s)).to_a
|
data/lib/moxml/adapter/ox.rb
CHANGED
|
@@ -510,6 +510,31 @@ module Moxml
|
|
|
510
510
|
end.values
|
|
511
511
|
end
|
|
512
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
|
+
|
|
513
538
|
def xpath(node, expression, namespaces = {})
|
|
514
539
|
# Translate common XPath patterns to Ox locate() syntax
|
|
515
540
|
locate_expr = translate_xpath_to_locate(expression, namespaces)
|
data/lib/moxml/adapter/rexml.rb
CHANGED
|
@@ -426,6 +426,19 @@ module Moxml
|
|
|
426
426
|
end
|
|
427
427
|
end
|
|
428
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
|
+
|
|
429
442
|
# not used at the moment
|
|
430
443
|
# but may be useful when the xpath is upgraded to work with namespaces
|
|
431
444
|
def prepare_xpath_namespaces(node)
|
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/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/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
|
@@ -186,6 +186,18 @@ module Moxml
|
|
|
186
186
|
end
|
|
187
187
|
end
|
|
188
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
|
+
|
|
189
201
|
def self.wrap(node, context)
|
|
190
202
|
return nil if node.nil?
|
|
191
203
|
|
|
@@ -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/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
|
data/spec/moxml/doctype_spec.rb
CHANGED
|
@@ -8,7 +8,6 @@ RSpec.describe Moxml::Doctype do
|
|
|
8
8
|
|
|
9
9
|
describe "#name" do
|
|
10
10
|
it "returns doctype name" do
|
|
11
|
-
skip "Doctype accessor methods not yet implemented in all adapters"
|
|
12
11
|
doctype = doc.create_doctype("root", nil, "test.dtd")
|
|
13
12
|
expect(doctype.name).to eq("root")
|
|
14
13
|
end
|
|
@@ -16,15 +15,32 @@ RSpec.describe Moxml::Doctype do
|
|
|
16
15
|
|
|
17
16
|
describe "#system_id" do
|
|
18
17
|
it "returns system identifier" do
|
|
19
|
-
skip "Doctype accessor methods not yet implemented in all adapters"
|
|
20
18
|
doctype = doc.create_doctype("root", nil, "test.dtd")
|
|
21
19
|
expect(doctype.system_id).to eq("test.dtd")
|
|
22
20
|
end
|
|
23
21
|
end
|
|
24
22
|
|
|
23
|
+
describe "#external_id" do
|
|
24
|
+
it "returns external identifier when present" do
|
|
25
|
+
doctype = doc.create_doctype("html", "-//W3C//DTD HTML 4.01//EN", "http://www.w3.org/TR/html4/strict.dtd")
|
|
26
|
+
expect(doctype.external_id).to eq("-//W3C//DTD HTML 4.01//EN")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "returns nil when not present" do
|
|
30
|
+
doctype = doc.create_doctype("root", nil, "test.dtd")
|
|
31
|
+
expect(doctype.external_id).to be_nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe "#identifier" do
|
|
36
|
+
it "returns the doctype name" do
|
|
37
|
+
doctype = doc.create_doctype("html", nil, nil)
|
|
38
|
+
expect(doctype.identifier).to eq("html")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
25
42
|
describe "creation" do
|
|
26
43
|
it "creates a doctype" do
|
|
27
|
-
skip "Doctype accessor methods not yet implemented in all adapters"
|
|
28
44
|
doctype = doc.create_doctype("html", nil, nil)
|
|
29
45
|
expect(doctype).to be_a(described_class)
|
|
30
46
|
expect(doctype.name).to eq("html")
|
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.1.
|
|
4
|
+
version: 0.1.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-26 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
|
|
@@ -29,7 +29,6 @@ files:
|
|
|
29
29
|
- ".rspec"
|
|
30
30
|
- ".rubocop.yml"
|
|
31
31
|
- ".rubocop_todo.yml"
|
|
32
|
-
- ".ruby-version"
|
|
33
32
|
- Gemfile
|
|
34
33
|
- LICENSE.md
|
|
35
34
|
- README.adoc
|
|
@@ -44,6 +43,7 @@ files:
|
|
|
44
43
|
- docs/_guides/development-testing.adoc
|
|
45
44
|
- docs/_guides/index.adoc
|
|
46
45
|
- docs/_guides/modifying-xml.adoc
|
|
46
|
+
- docs/_guides/node-api-consistency.adoc
|
|
47
47
|
- docs/_guides/parsing-xml.adoc
|
|
48
48
|
- docs/_guides/sax-parsing.adoc
|
|
49
49
|
- docs/_guides/working-with-documents.adoc
|
data/.ruby-version
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3.0.7@moxml
|