1use std::cmp::Ordering;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum NodeKind {
12 Root,
13 Element,
14 Attribute,
15 Namespace,
16 ProcessingInstruction,
17 Comment,
18 Text,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ExpandedName {
24 pub namespace_uri: Option<String>,
25 pub local_name: String,
26}
27
28pub trait Node<'a>: Copy + Eq + 'a {
41 fn kind(self) -> NodeKind;
43
44 fn parent(self) -> Option<Self>;
48
49 fn children(self) -> impl Iterator<Item = Self> + 'a;
52
53 fn attributes(self) -> impl Iterator<Item = Self> + 'a;
56
57 fn namespaces(self) -> impl Iterator<Item = Self> + 'a;
60
61 fn expanded_name(self) -> Option<ExpandedName>;
65
66 fn string_value(self) -> String;
70
71 fn document_order(self, other: Self) -> Ordering;
79}
80
81pub trait Document {
83 type N<'a>: Node<'a>
84 where
85 Self: 'a;
86
87 fn root(&self) -> Self::N<'_>;
88}
89
90#[cfg(test)]
94pub(crate) mod fixture {
95 use super::{Document, ExpandedName, Node, NodeKind};
96 use std::cmp::Ordering;
97
98 #[derive(Debug)]
99 struct NodeData {
100 kind: NodeKind,
101 name: Option<ExpandedName>,
102 text: Option<String>,
103 parent: Option<usize>,
104 children: Vec<usize>,
105 attributes: Vec<usize>,
106 namespaces: Vec<usize>,
107 }
108
109 impl NodeData {
110 fn new(kind: NodeKind, parent: Option<usize>) -> Self {
111 NodeData {
112 kind,
113 name: None,
114 text: None,
115 parent,
116 children: Vec::new(),
117 attributes: Vec::new(),
118 namespaces: Vec::new(),
119 }
120 }
121 }
122
123 #[derive(Debug)]
124 struct Arena {
125 nodes: Vec<NodeData>,
126 }
127
128 struct Builder {
133 nodes: Vec<NodeData>,
134 }
135
136 impl Builder {
137 fn push(&mut self, data: NodeData) -> usize {
138 let idx = self.nodes.len();
139 self.nodes.push(data);
140 idx
141 }
142
143 fn new_root(&mut self) -> usize {
144 self.push(NodeData::new(NodeKind::Root, None))
145 }
146
147 fn add_element(&mut self, parent: usize, local: &str) -> usize {
148 let mut data = NodeData::new(NodeKind::Element, Some(parent));
149 data.name = Some(ExpandedName {
150 namespace_uri: None,
151 local_name: local.to_string(),
152 });
153 let idx = self.push(data);
154 self.nodes[parent].children.push(idx);
155 idx
156 }
157
158 fn add_namespace(&mut self, owner: usize, prefix: &str, uri: &str) -> usize {
159 let mut data = NodeData::new(NodeKind::Namespace, Some(owner));
160 data.name = Some(ExpandedName {
161 namespace_uri: None,
162 local_name: prefix.to_string(),
163 });
164 data.text = Some(uri.to_string());
165 let idx = self.push(data);
166 self.nodes[owner].namespaces.push(idx);
167 idx
168 }
169
170 fn add_attribute(&mut self, owner: usize, local: &str, value: &str) -> usize {
171 let mut data = NodeData::new(NodeKind::Attribute, Some(owner));
172 data.name = Some(ExpandedName {
173 namespace_uri: None,
174 local_name: local.to_string(),
175 });
176 data.text = Some(value.to_string());
177 let idx = self.push(data);
178 self.nodes[owner].attributes.push(idx);
179 idx
180 }
181
182 fn add_text(&mut self, parent: usize, text: &str) -> usize {
183 let mut data = NodeData::new(NodeKind::Text, Some(parent));
184 data.text = Some(text.to_string());
185 let idx = self.push(data);
186 self.nodes[parent].children.push(idx);
187 idx
188 }
189
190 fn add_comment(&mut self, parent: usize, text: &str) -> usize {
191 let mut data = NodeData::new(NodeKind::Comment, Some(parent));
192 data.text = Some(text.to_string());
193 let idx = self.push(data);
194 self.nodes[parent].children.push(idx);
195 idx
196 }
197
198 fn add_pi(&mut self, parent: usize, target: &str, content: &str) -> usize {
199 let mut data = NodeData::new(NodeKind::ProcessingInstruction, Some(parent));
200 data.name = Some(ExpandedName {
201 namespace_uri: None,
202 local_name: target.to_string(),
203 });
204 data.text = Some(content.to_string());
205 let idx = self.push(data);
206 self.nodes[parent].children.push(idx);
207 idx
208 }
209 }
210
211 pub(crate) struct TestDoc {
212 arena: Arena,
213 }
214
215 impl TestDoc {
216 pub(crate) fn node(&self, idx: usize) -> TestNode<'_> {
217 TestNode {
218 arena: &self.arena,
219 idx,
220 }
221 }
222 }
223
224 impl Document for TestDoc {
225 type N<'a> = TestNode<'a>;
226
227 fn root(&self) -> Self::N<'_> {
228 self.node(0)
229 }
230 }
231
232 #[derive(Clone, Copy, Debug)]
233 pub(crate) struct TestNode<'a> {
234 arena: &'a Arena,
235 idx: usize,
236 }
237
238 impl<'a> PartialEq for TestNode<'a> {
239 fn eq(&self, other: &Self) -> bool {
240 std::ptr::eq(self.arena, other.arena) && self.idx == other.idx
241 }
242 }
243
244 impl<'a> Eq for TestNode<'a> {}
245
246 impl<'a> Node<'a> for TestNode<'a> {
247 fn kind(self) -> NodeKind {
248 self.arena.nodes[self.idx].kind
249 }
250
251 fn parent(self) -> Option<Self> {
252 self.arena.nodes[self.idx].parent.map(|p| TestNode {
253 arena: self.arena,
254 idx: p,
255 })
256 }
257
258 fn children(self) -> impl Iterator<Item = Self> + 'a {
259 let arena = self.arena;
260 self.arena.nodes[self.idx]
261 .children
262 .clone()
263 .into_iter()
264 .map(move |i| TestNode { arena, idx: i })
265 }
266
267 fn attributes(self) -> impl Iterator<Item = Self> + 'a {
268 let arena = self.arena;
269 self.arena.nodes[self.idx]
270 .attributes
271 .clone()
272 .into_iter()
273 .map(move |i| TestNode { arena, idx: i })
274 }
275
276 fn namespaces(self) -> impl Iterator<Item = Self> + 'a {
277 let arena = self.arena;
278 self.arena.nodes[self.idx]
279 .namespaces
280 .clone()
281 .into_iter()
282 .map(move |i| TestNode { arena, idx: i })
283 }
284
285 fn expanded_name(self) -> Option<ExpandedName> {
286 self.arena.nodes[self.idx].name.clone()
287 }
288
289 fn string_value(self) -> String {
290 let data = &self.arena.nodes[self.idx];
291 match data.kind {
292 NodeKind::Root | NodeKind::Element => {
293 let mut out = String::new();
294 collect_text(self.arena, self.idx, &mut out);
295 out
296 }
297 NodeKind::Attribute
298 | NodeKind::Namespace
299 | NodeKind::ProcessingInstruction
300 | NodeKind::Comment
301 | NodeKind::Text => data.text.clone().unwrap_or_default(),
302 }
303 }
304
305 fn document_order(self, other: Self) -> Ordering {
306 self.idx.cmp(&other.idx)
307 }
308 }
309
310 fn collect_text(arena: &Arena, idx: usize, out: &mut String) {
311 for &child in &arena.nodes[idx].children {
312 match arena.nodes[child].kind {
313 NodeKind::Text => out.push_str(arena.nodes[child].text.as_deref().unwrap_or("")),
314 NodeKind::Element => collect_text(arena, child, out),
315 _ => {}
316 }
317 }
318 }
319
320 pub(crate) struct Fixture {
342 pub doc: TestDoc,
343 pub html: usize,
344 pub html_ns: usize,
345 pub html_lang: usize,
346 pub html_id: usize,
347 pub body: usize,
348 pub body_ns: usize,
349 pub body_class: usize,
350 pub text0: usize,
351 pub span: usize,
352 pub span_attr: usize,
353 pub text1: usize,
354 pub comment: usize,
355 pub pi: usize,
356 pub text2: usize,
357 }
358
359 pub(crate) fn build() -> Fixture {
360 let mut b = Builder { nodes: Vec::new() };
361 let root = b.new_root();
362 let html = b.add_element(root, "html");
363 let html_ns = b.add_namespace(html, "xml", "http://www.w3.org/XML/1998/namespace");
364 let html_lang = b.add_attribute(html, "lang", "en");
365 let html_id = b.add_attribute(html, "id", "root-el");
366 let body = b.add_element(html, "body");
367 let body_ns = b.add_namespace(body, "x", "urn:x");
368 let body_class = b.add_attribute(body, "class", "main");
369 let text0 = b.add_text(body, "Hello ");
370 let span = b.add_element(body, "span");
371 let span_attr = b.add_attribute(span, "data-x", "1");
372 let text1 = b.add_text(span, "World");
373 let comment = b.add_comment(body, "note");
374 let pi = b.add_pi(body, "proc", "data");
375 let text2 = b.add_text(body, "!");
376 Fixture {
377 doc: TestDoc {
378 arena: Arena { nodes: b.nodes },
379 },
380 html,
381 html_ns,
382 html_lang,
383 html_id,
384 body,
385 body_ns,
386 body_class,
387 text0,
388 span,
389 span_attr,
390 text1,
391 comment,
392 pi,
393 text2,
394 }
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::fixture::build;
401 use super::{Node, NodeKind};
402
403 #[test]
404 fn string_value_text() {
405 let f = build();
406 assert_eq!(f.doc.node(f.text0).string_value(), "Hello ");
407 }
408
409 #[test]
410 fn string_value_comment() {
411 let f = build();
412 assert_eq!(f.doc.node(f.comment).string_value(), "note");
413 }
414
415 #[test]
416 fn string_value_processing_instruction() {
417 let f = build();
418 assert_eq!(f.doc.node(f.pi).string_value(), "data");
419 }
420
421 #[test]
422 fn string_value_attribute() {
423 let f = build();
424 assert_eq!(f.doc.node(f.html_lang).string_value(), "en");
425 }
426
427 #[test]
428 fn string_value_namespace() {
429 let f = build();
430 assert_eq!(
431 f.doc.node(f.html_ns).string_value(),
432 "http://www.w3.org/XML/1998/namespace"
433 );
434 }
435
436 #[test]
437 fn string_value_element_concatenates_nested_descendant_text_in_document_order() {
438 let f = build();
439 assert_eq!(f.doc.node(f.span).string_value(), "World");
442 assert_eq!(f.doc.node(f.body).string_value(), "Hello World!");
443 assert_eq!(f.doc.node(f.html).string_value(), "Hello World!");
444 }
445
446 #[test]
447 fn string_value_root_concatenates_all_descendant_text() {
448 let f = build();
449 assert_eq!(f.doc.node(0).string_value(), "Hello World!");
450 }
451
452 #[test]
453 fn expanded_name_per_kind() {
454 let f = build();
455 assert!(f.doc.node(0).expanded_name().is_none()); assert_eq!(
457 f.doc.node(f.html).expanded_name().unwrap().local_name,
458 "html"
459 );
460 assert_eq!(
461 f.doc.node(f.html_lang).expanded_name().unwrap().local_name,
462 "lang"
463 );
464 assert_eq!(
465 f.doc.node(f.html_ns).expanded_name().unwrap().local_name,
466 "xml"
467 );
468 assert_eq!(f.doc.node(f.pi).expanded_name().unwrap().local_name, "proc");
469 assert!(f.doc.node(f.comment).expanded_name().is_none());
470 assert!(f.doc.node(f.text0).expanded_name().is_none());
471 }
472
473 #[test]
474 fn kind_per_node() {
475 let f = build();
476 assert_eq!(f.doc.node(0).kind(), NodeKind::Root);
477 assert_eq!(f.doc.node(f.html).kind(), NodeKind::Element);
478 assert_eq!(f.doc.node(f.html_lang).kind(), NodeKind::Attribute);
479 assert_eq!(f.doc.node(f.html_ns).kind(), NodeKind::Namespace);
480 assert_eq!(f.doc.node(f.pi).kind(), NodeKind::ProcessingInstruction);
481 assert_eq!(f.doc.node(f.comment).kind(), NodeKind::Comment);
482 assert_eq!(f.doc.node(f.text0).kind(), NodeKind::Text);
483 }
484
485 #[test]
486 fn document_order_matches_tree_structure() {
487 let f = build();
488 use std::cmp::Ordering;
489 let root = f.doc.node(0);
490 let html = f.doc.node(f.html);
491 let body = f.doc.node(f.body);
492 let text0 = f.doc.node(f.text0);
493 assert_eq!(root.document_order(html), Ordering::Less);
494 assert_eq!(html.document_order(body), Ordering::Less);
495 assert_eq!(body.document_order(text0), Ordering::Less);
496 assert_eq!(html.document_order(html), Ordering::Equal);
497 }
498}