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