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}