exml/dom/mod.rs
1//! Implement [Document Object Model (DOM) Level 3 Core](https://www.w3.org/TR/DOM-Level-3-Core/).
2//!
3//! I will try to maintain compatibility with the libxml2 tree as much as possible.
4//!
5//! Currently, full implementation of the specification is not a goal.
6//! For example, I do not plan to provide a feature selection mechanism.
7//!
8//! # Note
9//! - The iterators that walk through the nodes of the DOM are not implemented.\
10//! Nodes are not restricted to only one source, and each node can modify the whole DOM tree.
11//! Therefore, it is impossible to implement iterators that is not invalidated.
12
13use node::{Node, NodeRef};
14
15pub mod attlistdecl;
16pub mod attr;
17pub mod character_data;
18pub mod document;
19pub mod document_fragment;
20pub mod document_type;
21pub mod dom_configuration;
22pub mod dom_error;
23pub mod dom_implementation;
24pub mod element;
25pub mod elementdecl;
26pub mod entity;
27pub mod entity_reference;
28pub mod name_list;
29pub mod named_node_map;
30pub mod node;
31pub mod node_list;
32pub mod notation;
33pub mod pi;
34pub mod user_data;
35
36/// This is the namespace for the special xml: prefix predefined in the
37/// XML Namespace specification.
38pub const XML_XML_NAMESPACE: &str = "http://www.w3.org/XML/1998/namespace";
39pub const XML_NS_NAMESPACE: &str = "http://www.w3.org/2000/xmlns/";
40
41/// Implementation of [DOMException](https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/DOM3-Core.html#core-ID-17189187)
42/// interface on [1.4 Fundamental Interfaces: Core Module](https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/DOM3-Core.html#core-ID-BBACDC08)
43///
44/// Although named “Exception”,
45/// it merely inherits its name from the specification and is in fact just an error code.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47pub enum DOMException {
48 /// If index or size is negative, or greater than the allowed value.
49 IndexSizeErr = 1,
50 /// If the specified range of text does not fit into a DOMString.
51 DOMStringSizeErr = 2,
52 /// If any Node is inserted somewhere it doesn't belong.
53 HierarchyRequestErr = 3,
54 /// If a Node is used in a different document than the one that created it
55 /// (that doesn't support it).
56 WrongDocumentErr = 4,
57 /// If an invalid or illegal character is specified, such as in an XML name.
58 InvalidCharacterErr = 5,
59 /// If data is specified for a Node which does not support data.
60 NoDataAllowedErr = 6,
61 /// If an attempt is made to modify an object where modifications are not allowed.
62 NoModificationAllowedErr = 7,
63 /// If an attempt is made to reference a Node in a context where it does not exist.
64 NotFoundErr = 8,
65 /// If the implementation does not support the requested type of object or operation.
66 NotSupportedErr = 9,
67 /// If an attempt is made to add an attribute that is already in use elsewhere.
68 InuseAttributeErr = 10,
69 /// If an attempt is made to use an object that is not, or is no longer, usable.
70 InvalidStateErr = 11,
71 /// If an invalid or illegal string is specified.
72 SyntaxErr = 12,
73 /// If an attempt is made to modify the type of the underlying object.
74 InvalidModificationErr = 13,
75 /// If an attempt is made to create or change an object in a way which is incorrect with
76 /// regard to namespaces.
77 NamespaceErr = 14,
78 /// If a parameter or an operation is not supported by the underlying object.
79 InvalidAccessErr = 15,
80 /// If a call to a method such as insertBefore or removeChild would make the Node invalid
81 /// with respect to "partial validity", this exception would be raised and the operation
82 /// would not be done. This code is used in [DOM Level 3 Validation]. Refer to this
83 /// specification for further information.
84 ValidationErr = 16,
85 /// If the type of an object is incompatible with the expected type of the parameter
86 /// associated to the object.
87 TypeMismatchErr = 17,
88}
89
90/// The two nodes are disconnected.\
91/// Order between disconnected nodes is always implementation-specific.
92const DOCUMENT_POSITION_DISCONNECTED: u16 = 0x01;
93/// The second node precedes the reference node.
94const DOCUMENT_POSITION_PRECEDING: u16 = 0x02;
95/// The node follows the reference node.
96const DOCUMENT_POSITION_FOLLOWING: u16 = 0x04;
97/// The node contains the reference node.\
98/// A node which contains is always preceding, too.
99const DOCUMENT_POSITION_CONTAINS: u16 = 0x08;
100/// The node is contained by the reference node.\
101/// A node which is contained is always following, too.
102const DOCUMENT_POSITION_CONTAINED_BY: u16 = 0x10;
103/// The determination of preceding versus following is implementation-specific.
104const DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: u16 = 0x20;
105
106/// Constants `DocumentPosition` in [Interface Node](https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/DOM3-Core.html#core-ID-1950641247).
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
108pub struct DocumentPosition(u16);
109
110impl DocumentPosition {
111 fn new() -> Self {
112 DocumentPosition(0)
113 }
114
115 fn set_flag(mut self, flag: u16) -> Self {
116 self.0 |= flag;
117 self
118 }
119
120 fn unset_flag(mut self, flag: u16) -> Self {
121 self.0 &= !flag;
122 self
123 }
124
125 pub fn is_same_node(self) -> bool {
126 self.0 == 0
127 }
128
129 pub fn is_disconnected(self) -> bool {
130 self.0 & DOCUMENT_POSITION_DISCONNECTED != 0
131 }
132
133 pub fn is_preceding(self) -> bool {
134 self.0 & DOCUMENT_POSITION_PRECEDING != 0
135 }
136
137 pub fn is_following(self) -> bool {
138 self.0 & DOCUMENT_POSITION_FOLLOWING != 0
139 }
140
141 pub fn is_contains(self) -> bool {
142 self.0 & DOCUMENT_POSITION_CONTAINS != 0
143 }
144
145 pub fn is_contained_by(self) -> bool {
146 self.0 & DOCUMENT_POSITION_CONTAINED_BY != 0
147 }
148
149 pub fn is_implementation_specific(self) -> bool {
150 self.0 & DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC != 0
151 }
152}
153
154impl From<DocumentPosition> for u16 {
155 fn from(value: DocumentPosition) -> Self {
156 value.0
157 }
158}
159
160/// Constants `NodeType` in [Interface Node](https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/DOM3-Core.html#core-ID-1950641247).
161#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
162pub enum NodeType {
163 Element = 1,
164 Attribute = 2,
165 Text = 3,
166 CDATASection = 4,
167 EntityReference = 5,
168 Entity = 6,
169 ProcessingInstruction = 7,
170 Comment = 8,
171 Document = 9,
172 DocumentType = 10,
173 DocumentFragment = 11,
174 Notation = 12,
175}
176
177/// Return `true` if `left` and `right` are allowed to be siblings.
178/// Otherwise, return `false`.
179///
180/// The following table is derived from the vertical constraints
181/// and is not directly described in the specification.
182///
183/// | NodeType | Description |
184/// | :-------------------- | :-------------------------------------------------------------------------------------------- |
185/// | Document | no siblings |
186/// | DocumentFragment | no siblings |
187/// | DocumentType | Element, ProcessingInstruction, Comment |
188/// | EntityReference | Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference |
189/// | Element | DocumentType, Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference |
190/// | Attr | no siblings |
191/// | ProcessingInstruction | DocumentType, Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference |
192/// | Comment | DocumentType, Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference |
193/// | Text | Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference |
194/// | CDATASection | Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference |
195/// | Entity | no siblings |
196/// | Notation | no siblings |
197fn check_horizontal_hierarchy(left: NodeType, right: NodeType) -> bool {
198 use NodeType::*;
199 match left {
200 Element | ProcessingInstruction | Comment => matches!(
201 right,
202 DocumentType
203 | Element
204 | Text
205 | Comment
206 | ProcessingInstruction
207 | CDATASection
208 | EntityReference
209 ),
210 Text | CDATASection | EntityReference => matches!(
211 right,
212 Element | Text | Comment | ProcessingInstruction | CDATASection | EntityReference
213 ),
214 DocumentType => matches!(right, Element | ProcessingInstruction | Comment),
215 _ => false,
216 }
217}
218
219/// Return `true` if `parent` and `child` are allowed to be parent and child.
220/// Otherwise, return `false`.
221///
222/// [1.1.1 The DOM Structure Model](https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/DOM3-Core.html#core-ID-1590626202)
223///
224/// | NodeType | Description |
225/// | :-------------------- | :---------------------------------------------------------------------------------------- |
226/// | Document | Element (maximum of one), ProcessingInstruction, Comment, DocumentType (maximum of one) |
227/// | DocumentFragment | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference |
228/// | DocumentType | no children |
229/// | EntityReference | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference |
230/// | Element | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference |
231/// | Attr | Text, EntityReference |
232/// | ProcessingInstruction | no children |
233/// | Comment | no children |
234/// | Text | no children |
235/// | CDATASection | no children |
236/// | Entity | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference |
237/// | Notation | no children |
238fn check_vertical_hierarchy(parent: NodeType, child: NodeType) -> bool {
239 use NodeType::*;
240 match parent {
241 Element | DocumentFragment | EntityReference | Entity => matches!(
242 child,
243 Element | Text | Comment | ProcessingInstruction | CDATASection | EntityReference
244 ),
245 Attribute => matches!(child, Text | EntityReference),
246 Document => matches!(
247 child,
248 Element | ProcessingInstruction | Comment | DocumentType
249 ),
250 _ => false,
251 }
252}
253
254/// Check if the nodes belong to the same document or not.
255fn check_owner_document_sameness(l: &impl Node, r: &impl Node) -> bool {
256 match (l.node_type(), r.node_type()) {
257 (NodeType::DocumentType, NodeType::DocumentType) => {
258 let ldoc = l.owner_document();
259 let rdoc = r.owner_document();
260 ldoc.is_some() == rdoc.is_some()
261 && ldoc
262 .zip(rdoc)
263 .is_none_or(|(l, r)| l.is_same_node(&r.into()))
264 }
265 (NodeType::DocumentType, _) if l.owner_document().is_none() => true,
266 (_, NodeType::DocumentType) if r.owner_document().is_none() => true,
267 (NodeType::Document, NodeType::Document) => l.is_same_node(&r.clone().into()),
268 (NodeType::Document, _) => r
269 .owner_document()
270 .is_some_and(|doc| l.is_same_node(&NodeRef::Document(doc))),
271 (_, NodeType::Document) => l
272 .owner_document()
273 .is_some_and(|doc| r.is_same_node(&NodeRef::Document(doc))),
274 _ => l
275 .owner_document()
276 .zip(r.owner_document())
277 .is_some_and(|(l, r)| l.is_same_node(&node::NodeRef::Document(r))),
278 }
279}
280
281fn check_no_modification_allowed_err(node: &impl Node) -> Result<(), DOMException> {
282 if node.is_read_only()
283 && node
284 .owner_document()
285 .is_some_and(|doc| doc.is_enabled_read_only_check())
286 {
287 Err(DOMException::NoModificationAllowedErr)
288 } else {
289 Ok(())
290 }
291}