1use sup_xml_tree::dom::{Attribute as ArenaAttr, Document as ArenaDoc, Node as ArenaNode, NodeKind as ArenaKind};
10
11use super::index::{DocIndexLike, NodeId, XPathNodeKind};
12
13pub struct INode<'doc> {
15 pub kind: INodeKind<'doc>,
16 pub parent: Option<NodeId>,
17 pub attr_start: usize,
19 pub attr_end: usize,
20 pub ns_start: usize,
25 pub ns_end: usize,
26 pub content_children: Vec<NodeId>,
28}
29
30pub enum INodeKind<'doc> {
33 Document,
34 Element(&'doc ArenaNode<'doc>),
35 Attribute(&'doc ArenaAttr<'doc>),
36 Text(&'doc ArenaNode<'doc>),
37 Comment(&'doc ArenaNode<'doc>),
38 CData(&'doc ArenaNode<'doc>),
39 PI(&'doc ArenaNode<'doc>),
40 Namespace { prefix: Option<&'doc str>, uri: &'doc str },
46}
47
48const XML_NS_PREFIX: &str = "xml";
52const XML_NS_URI: &str = "http://www.w3.org/XML/1998/namespace";
53
54pub struct DocIndex<'doc> {
55 pub nodes: Vec<INode<'doc>>,
56 id_attrs: std::sync::Arc<std::collections::HashMap<String, Vec<String>>>,
62 idref_attrs: std::sync::Arc<std::collections::HashMap<String, Vec<String>>>,
68 synthetic: std::cell::RefCell<Vec<String>>,
75 rtfs: elsa::FrozenVec<Box<super::rtf::RtfIndex>>,
83 synthetic_wraps: std::cell::RefCell<std::collections::HashSet<NodeId>>,
93 rtf_node_types: std::cell::RefCell<std::collections::HashMap<NodeId, Box<(String, String)>>>,
101}
102
103pub const SYNTHETIC_TEXT_BASE: NodeId = 1_usize << (usize::BITS - 1);
109
110
111impl<'doc> DocIndex<'doc> {
112 pub fn build(doc: &'doc ArenaDoc) -> Self {
114 let mut idx = Self {
115 nodes: Vec::new(),
116 id_attrs: doc.id_attributes().clone(),
117 idref_attrs: doc.idref_attributes().clone(),
118 synthetic: std::cell::RefCell::new(Vec::new()),
119 rtfs: elsa::FrozenVec::new(),
120 synthetic_wraps: std::cell::RefCell::new(std::collections::HashSet::new()),
121 rtf_node_types: std::cell::RefCell::new(std::collections::HashMap::new()),
122 };
123 idx.nodes.push(INode {
125 kind: INodeKind::Document,
126 parent: None,
127 attr_start: 0, attr_end: 0,
128 ns_start: 0, ns_end: 0,
129 content_children: Vec::new(),
130 });
131 let mut top: Vec<NodeId> = Vec::new();
135 let mut cur: Option<&'doc ArenaNode<'doc>> = Some(doc.first_sibling());
136 while let Some(n) = cur {
137 top.extend(idx.add_node(n, 0));
138 cur = n.next_sibling.get();
139 }
140 idx.nodes[0].content_children = top;
141 idx
142 }
143
144 pub fn add_document(&mut self, doc: &'doc ArenaDoc) -> NodeId {
154 let doc_id = self.nodes.len();
155 self.nodes.push(INode {
156 kind: INodeKind::Document,
157 parent: None,
158 attr_start: 0, attr_end: 0,
159 ns_start: 0, ns_end: 0,
160 content_children: Vec::new(),
161 });
162 let mut top: Vec<NodeId> = Vec::new();
163 let mut cur: Option<&'doc ArenaNode<'doc>> = Some(doc.first_sibling());
164 while let Some(n) = cur {
165 top.extend(self.add_node(n, doc_id));
166 cur = n.next_sibling.get();
167 }
168 self.nodes[doc_id].content_children = top;
169 if !doc.id_attributes().is_empty() {
174 let mut merged = (*self.id_attrs).clone();
175 for (elem, ids) in doc.id_attributes().iter() {
176 merged.entry(elem.clone())
177 .or_insert_with(Vec::new)
178 .extend(ids.iter().cloned());
179 }
180 self.id_attrs = std::sync::Arc::new(merged);
181 }
182 if !doc.idref_attributes().is_empty() {
183 let mut merged = (*self.idref_attrs).clone();
184 for (elem, refs) in doc.idref_attributes().iter() {
185 merged.entry(elem.clone())
186 .or_insert_with(Vec::new)
187 .extend(refs.iter().cloned());
188 }
189 self.idref_attrs = std::sync::Arc::new(merged);
190 }
191 doc_id
192 }
193
194 fn add_node(&mut self, node: &'doc ArenaNode<'doc>, parent: NodeId) -> Vec<NodeId> {
195 match node.kind {
196 ArenaKind::Element => {
197 let id = self.nodes.len();
198 self.nodes.push(INode {
199 kind: INodeKind::Element(node),
200 parent: Some(parent),
201 attr_start: 0, attr_end: 0,
202 ns_start: 0, ns_end: 0,
203 content_children: Vec::new(),
204 });
205 let ns_start = self.nodes.len();
213 for (prefix, uri) in collect_in_scope_namespaces(node) {
214 self.nodes.push(INode {
215 kind: INodeKind::Namespace { prefix, uri },
216 parent: Some(id),
217 attr_start: 0, attr_end: 0,
218 ns_start: 0, ns_end: 0,
219 content_children: Vec::new(),
220 });
221 }
222 let ns_end = self.nodes.len();
223 self.nodes[id].ns_start = ns_start;
224 self.nodes[id].ns_end = ns_end;
225
226 let attr_start = self.nodes.len();
227 for attr in node.attributes() {
228 let name = attr.name();
234 if name == "xmlns" || name.starts_with("xmlns:") { continue; }
235 self.nodes.push(INode {
236 kind: INodeKind::Attribute(attr),
237 parent: Some(id),
238 attr_start: 0, attr_end: 0,
239 ns_start: 0, ns_end: 0,
240 content_children: Vec::new(),
241 });
242 }
243 let attr_end = self.nodes.len();
244 self.nodes[id].attr_start = attr_start;
245 self.nodes[id].attr_end = attr_end;
246
247 let mut children = Vec::new();
248 for child in node.children() {
249 children.extend(self.add_node(child, id));
250 }
251 self.nodes[id].content_children = children;
252 vec![id]
253 }
254 ArenaKind::Text | ArenaKind::Comment | ArenaKind::CData | ArenaKind::Pi => {
255 let id = self.nodes.len();
256 let kind = match node.kind {
257 ArenaKind::Text => INodeKind::Text(node),
258 ArenaKind::Comment => INodeKind::Comment(node),
259 ArenaKind::CData => INodeKind::CData(node),
260 ArenaKind::Pi => INodeKind::PI(node),
261 _ => unreachable!(),
262 };
263 self.nodes.push(INode {
264 kind,
265 parent: Some(parent),
266 attr_start: 0, attr_end: 0,
267 ns_start: 0, ns_end: 0,
268 content_children: Vec::new(),
269 });
270 vec![id]
271 }
272 ArenaKind::Attribute => unreachable!("Attribute kind never appears on a Node"),
274 ArenaKind::Document => unreachable!("Document kind never appears on a Node"),
275 ArenaKind::EntityRef => vec![],
279 ArenaKind::DocumentFragment => vec![],
285 ArenaKind::DtdDecl => vec![],
288 ArenaKind::Dtd => vec![],
289 }
290 }
291
292 pub fn string_value(&self, id: NodeId) -> String {
294 if let Some((rtf, local)) = self.rtf_at(id) {
295 return rtf.string_value(local);
296 }
297 if is_synthetic(id) {
298 let i = id & !SYNTHETIC_TEXT_BASE;
299 return self.synthetic.borrow().get(i).cloned().unwrap_or_default();
300 }
301 match &self.nodes[id].kind {
302 INodeKind::Document | INodeKind::Element(_) => {
303 let mut s = String::new();
304 self.concat_text(id, &mut s);
305 s
306 }
307 INodeKind::Attribute(a) => a.value().to_string(),
308 INodeKind::Text(n) | INodeKind::CData(n) => n.content().to_string(),
309 INodeKind::Comment(n) => n.content().to_string(),
310 INodeKind::PI(n) => n.content().to_string(),
311 INodeKind::Namespace { uri, .. } => (*uri).to_string(),
313 }
314 }
315
316 fn concat_text(&self, id: NodeId, out: &mut String) {
317 for &child in &self.nodes[id].content_children {
318 match &self.nodes[child].kind {
319 INodeKind::Text(n) | INodeKind::CData(n) => out.push_str(n.content()),
320 INodeKind::Element(_) => self.concat_text(child, out),
321 _ => {}
322 }
323 }
324 }
325
326 pub fn node_name(&self, id: NodeId) -> &str {
327 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.node_name(local); }
328 if is_synthetic(id) { return ""; }
329 match &self.nodes[id].kind {
330 INodeKind::Element(n) => n.name(),
331 INodeKind::Attribute(a) => a.name(),
332 INodeKind::PI(n) => n.name(),
333 INodeKind::Namespace { prefix, .. } => prefix.unwrap_or(""),
336 _ => "",
337 }
338 }
339
340 pub fn local_name(&self, id: NodeId) -> &str {
341 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.local_name(local); }
342 if is_synthetic(id) { return ""; }
343 if let INodeKind::Namespace { prefix, .. } = &self.nodes[id].kind {
346 return prefix.unwrap_or("");
347 }
348 let full = self.node_name(id);
349 match full.split_once(':') {
350 Some((_, local)) => local,
351 None => full,
352 }
353 }
354
355 pub fn namespace_uri(&self, id: NodeId) -> &str {
356 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.namespace_uri(local); }
357 if is_synthetic(id) { return ""; }
358 match &self.nodes[id].kind {
359 INodeKind::Element(n) => n.namespace.get().map(|ns| ns.href()).unwrap_or(""),
360 INodeKind::Attribute(a) => a.namespace.get().map(|ns| ns.href()).unwrap_or(""),
361 _ => "",
365 }
366 }
367
368 pub fn namespace_prefix(&self, id: NodeId) -> Option<&str> {
369 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.namespace_prefix(local); }
370 if is_synthetic(id) { return None; }
371 match &self.nodes[id].kind {
372 INodeKind::Element(n) => n.namespace.get().and_then(|ns| ns.prefix()),
373 INodeKind::Attribute(a) => a.namespace.get().and_then(|ns| ns.prefix()),
374 _ => None,
375 }
376 }
377}
378
379fn collect_in_scope_namespaces<'doc>(
395 el: &'doc ArenaNode<'doc>,
396) -> Vec<(Option<&'doc str>, &'doc str)> {
397 let mut out: Vec<(Option<&'doc str>, &'doc str)> = Vec::new();
399 let mut seen_default = false;
400 let mut seen_xml = false;
401
402 let mut cur: Option<&'doc ArenaNode<'doc>> = Some(el);
403 while let Some(n) = cur {
404 for (prefix, uri) in n.ns_declarations() {
405 let already = out.iter().any(|(p, _)| *p == prefix)
406 || (prefix.is_none() && seen_default);
407 if already {
408 continue;
410 }
411 if prefix.is_none() {
412 seen_default = true;
413 }
414 if uri.is_empty() {
415 continue;
419 }
420 if prefix == Some(XML_NS_PREFIX) {
421 seen_xml = true;
422 }
423 out.push((prefix, uri));
424 }
425 cur = n.parent.get();
426 }
427
428 if !seen_xml {
429 out.push((Some(XML_NS_PREFIX), XML_NS_URI));
430 }
431 out
432}
433
434impl<'doc> DocIndex<'doc> {
437 pub fn allocate_rtf_text_nodes_inherent(&self, values: Vec<String>) -> Vec<NodeId> {
450 let mut store = self.synthetic.borrow_mut();
451 let start = store.len();
452 store.extend(values);
453 (start..store.len())
454 .map(|i| SYNTHETIC_TEXT_BASE | i)
455 .collect()
456 }
457
458}
459
460#[inline(always)]
465pub fn is_synthetic_id(id: NodeId) -> bool { is_synthetic(id) }
466
467pub(crate) fn is_synthetic(id: NodeId) -> bool {
468 id & SYNTHETIC_TEXT_BASE != 0
469}
470
471impl<'doc> DocIndex<'doc> {
474 pub fn start_rtf(&self) -> super::rtf::RtfBuilder {
480 let slot = self.rtfs.len();
481 super::rtf::RtfBuilder::new(slot)
482 }
483
484 pub fn mark_synthetic_wrap(&self, root: NodeId) {
488 self.synthetic_wraps.borrow_mut().insert(root);
489 }
490
491 pub fn is_synthetic_wrap(&self, id: NodeId) -> bool {
497 self.synthetic_wraps.borrow().contains(&id)
498 }
499
500 pub fn finish_rtf(&self, mut builder: super::rtf::RtfBuilder) -> NodeId {
504 let host_index = builder.host_index;
505 if !builder.typed_nodes.is_empty() {
510 let mut tbl = self.rtf_node_types.borrow_mut();
511 for (id, ty) in builder.typed_nodes.drain(..) {
512 tbl.insert(id, ty);
513 }
514 }
515 let rtf = builder.build();
516 self.rtfs.push(Box::new(rtf));
520 super::rtf::encode_rtf_id(host_index, 0)
521 }
522
523 pub fn rtf_node_type(&self, id: NodeId) -> Option<(String, String)> {
526 self.rtf_node_types.borrow().get(&id).map(|b| (**b).clone())
527 }
528
529 pub fn graft_dynamic_document(
546 &self,
547 doc: &sup_xml_tree::dom::Document,
548 ) -> NodeId {
549 let mut builder = self.start_rtf();
550 let root = builder.add_document();
551 let mut cur = Some(doc.first_sibling());
555 while let Some(n) = cur {
556 graft_node(&mut builder, root, n);
557 cur = n.next_sibling.get();
558 }
559 self.finish_rtf(builder)
560 }
561}
562
563fn graft_node(
567 builder: &mut super::rtf::RtfBuilder,
568 parent: NodeId,
569 node: &sup_xml_tree::dom::Node<'_>,
570) {
571 use sup_xml_tree::dom::NodeKind;
572 match node.kind {
573 NodeKind::Element => {
574 let name = node.name();
575 let prefix = name.rfind(':').map(|i| &name[..i]);
576 let uri = node.namespace.get().map(|ns| ns.href()).unwrap_or("");
577 let elem = builder.add_element(parent, name, uri, prefix);
578 let mut attrs = node.attributes().peekable();
581 if attrs.peek().is_some() {
582 builder.start_attrs(elem);
583 for a in attrs {
584 let an = a.name();
585 let aprefix = an.rfind(':').map(|i| &an[..i]);
586 let auri = a.namespace.get().map(|ns| ns.href()).unwrap_or("");
587 builder.add_attribute(elem, an, auri, aprefix, a.value());
588 }
589 }
590 for child in node.children() {
591 graft_node(builder, elem, child);
592 }
593 }
594 NodeKind::Text | NodeKind::CData => {
595 builder.add_text(parent, node.content());
596 }
597 NodeKind::Comment => {
598 builder.add_comment(parent, node.content());
599 }
600 NodeKind::Pi => {
601 builder.add_pi(parent, node.name(), node.content());
602 }
603 _ => {}
608 }
609}
610
611impl<'doc> DocIndex<'doc> {
614 #[inline(always)]
620 fn rtf_at(&self, id: NodeId) -> Option<(&super::rtf::RtfIndex, NodeId)> {
621 if !super::rtf::is_rtf_id(id) { return None; }
622 let (host_i, local) = super::rtf::decode_rtf_id(id);
623 self.rtfs.get(host_i).map(|r| (r, local))
624 }
625}
626
627impl<'doc> DocIndexLike for DocIndex<'doc> {
628 fn graft_dynamic_document(
629 &self,
630 doc: &sup_xml_tree::dom::Document,
631 ) -> Option<NodeId> {
632 Some(DocIndex::graft_dynamic_document(self, doc))
633 }
634 fn children(&self, id: NodeId) -> &[NodeId] {
635 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.children(local); }
636 if is_synthetic(id) { return &[]; }
637 &self.nodes[id].content_children
638 }
639 fn parent(&self, id: NodeId) -> Option<NodeId> {
640 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.parent(local); }
641 if is_synthetic(id) { return None; }
642 self.nodes[id].parent
643 }
644 fn attr_range(&self, id: NodeId) -> std::ops::Range<NodeId> {
645 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.attr_range(local); }
646 if is_synthetic(id) { return 0..0; }
647 self.nodes[id].attr_start..self.nodes[id].attr_end
648 }
649 fn ns_range(&self, id: NodeId) -> std::ops::Range<NodeId> {
650 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.ns_range(local); }
651 if is_synthetic(id) { return 0..0; }
652 self.nodes[id].ns_start..self.nodes[id].ns_end
653 }
654 fn kind(&self, id: NodeId) -> XPathNodeKind {
655 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.kind(local); }
656 if is_synthetic(id) { return XPathNodeKind::Text; }
657 match self.nodes[id].kind {
658 INodeKind::Document => XPathNodeKind::Document,
659 INodeKind::Element(_) => XPathNodeKind::Element,
660 INodeKind::Attribute(_) => XPathNodeKind::Attribute,
661 INodeKind::Text(_) => XPathNodeKind::Text,
662 INodeKind::Comment(_) => XPathNodeKind::Comment,
663 INodeKind::CData(_) => XPathNodeKind::CData,
664 INodeKind::PI(_) => XPathNodeKind::PI,
665 INodeKind::Namespace { .. } => XPathNodeKind::Namespace,
666 }
667 }
668 fn pi_target(&self, id: NodeId) -> &str {
669 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.pi_target(local); }
670 if is_synthetic(id) { return ""; }
671 match &self.nodes[id].kind {
672 INodeKind::PI(n) => n.name(),
673 _ => "",
674 }
675 }
676 fn string_value(&self, id: NodeId) -> String {
677 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.string_value(local); }
678 if is_synthetic(id) {
679 let i = id & !SYNTHETIC_TEXT_BASE;
680 return self.synthetic.borrow().get(i).cloned().unwrap_or_default();
681 }
682 DocIndex::string_value(self, id)
683 }
684 fn node_name(&self, id: NodeId) -> &str {
685 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.node_name(local); }
686 if is_synthetic(id) { return ""; }
687 DocIndex::node_name(self, id)
688 }
689 fn local_name(&self, id: NodeId) -> &str {
690 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.local_name(local); }
691 if is_synthetic(id) { return ""; }
692 DocIndex::local_name(self, id)
693 }
694 fn namespace_prefix(&self, id: NodeId) -> Option<&str> {
695 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.namespace_prefix(local); }
696 if is_synthetic(id) { return None; }
697 DocIndex::namespace_prefix(self, id)
698 }
699 fn namespace_uri(&self, id: NodeId) -> &str {
700 if let Some((rtf, local)) = self.rtf_at(id) { return rtf.namespace_uri(local); }
701 if is_synthetic(id) { return ""; }
702 DocIndex::namespace_uri(self, id)
703 }
704 fn is_element(&self, id: NodeId) -> bool {
705 if let Some((rtf, local)) = self.rtf_at(id) {
706 return matches!(rtf.kind(local), XPathNodeKind::Element);
707 }
708 if is_synthetic(id) { return false; }
709 matches!(self.nodes[id].kind, INodeKind::Element(_))
710 }
711 fn allocate_rtf_text_nodes(&self, values: Vec<String>) -> Option<Vec<NodeId>> {
712 Some(DocIndex::allocate_rtf_text_nodes_inherent(self, values))
713 }
714 fn rtf_builder(&self) -> Option<super::rtf::RtfBuilder> {
715 Some(DocIndex::start_rtf(self))
716 }
717 fn finish_rtf(&self, builder: super::rtf::RtfBuilder) -> Option<NodeId> {
718 Some(DocIndex::finish_rtf(self, builder))
719 }
720 fn rtf_node_type(&self, id: NodeId) -> Option<(String, String)> {
721 DocIndex::rtf_node_type(self, id)
722 }
723 fn is_synthetic_wrap(&self, id: NodeId) -> bool {
724 DocIndex::is_synthetic_wrap(self, id)
725 }
726 fn is_id_attribute(&self, attr_id: NodeId) -> bool {
727 if super::rtf::is_rtf_id(attr_id) { return false; }
728 if is_synthetic(attr_id) { return false; }
729 let INodeKind::Attribute(_) = self.nodes[attr_id].kind else { return false; };
730 let local = self.local_name(attr_id);
731 if self.node_name(attr_id) == "xml:id" || local == "id" {
735 return true;
736 }
737 if self.id_attrs.is_empty() { return false; }
740 let Some(owner) = self.parent(attr_id) else { return false; };
741 let owner_name = self.node_name(owner);
742 let attr_name = self.node_name(attr_id);
743 self.id_attrs.get(owner_name)
744 .is_some_and(|ids| ids.iter().any(|n| n == attr_name || n == local))
745 }
746 fn is_idref_attribute(&self, attr_id: NodeId) -> bool {
747 if super::rtf::is_rtf_id(attr_id) { return false; }
748 if is_synthetic(attr_id) { return false; }
749 let INodeKind::Attribute(_) = self.nodes[attr_id].kind else { return false; };
750 if self.idref_attrs.is_empty() { return false; }
753 let Some(owner) = self.parent(attr_id) else { return false; };
754 let owner_name = self.node_name(owner);
755 let attr_name = self.node_name(attr_id);
756 let local = self.local_name(attr_id);
757 self.idref_attrs.get(owner_name)
758 .is_some_and(|refs| refs.iter().any(|n| n == attr_name || n == local))
759 }
760}
761
762#[cfg(test)]
763mod tests {
764 use super::*;
765 use crate::xpath::index::DocIndexLike;
766 use crate::{parse_str, ParseOptions};
767
768 fn idx_of(xml: &str) -> (sup_xml_tree::dom::Document, ()) {
769 let opts = ParseOptions { namespace_aware: true, ..ParseOptions::default() };
779 let doc = parse_str(xml, &opts).unwrap();
780 (doc, ())
781 }
782
783 fn find_kind(idx: &DocIndex<'_>, want: XPathNodeKind) -> NodeId {
786 for id in 0..idx.nodes.len() {
787 if idx.kind(id) == want { return id; }
788 }
789 panic!("no node of kind {want:?}");
790 }
791
792 #[test]
795 fn pi_node_indexing_and_accessors() {
796 let (doc, _) = idx_of(r#"<r><?xml-stylesheet href="s.xsl"?></r>"#);
797 let idx = DocIndex::build(&doc);
798 let pi_id = find_kind(&idx, XPathNodeKind::PI);
799 assert_eq!(idx.node_name(pi_id), "xml-stylesheet");
800 assert_eq!(idx.local_name(pi_id), "xml-stylesheet");
801 assert_eq!(idx.namespace_uri(pi_id), "");
802 assert_eq!(idx.namespace_prefix(pi_id), None);
803 assert_eq!(idx.string_value(pi_id), r#"href="s.xsl""#);
805 assert_eq!(<DocIndex<'_> as DocIndexLike>::pi_target(&idx, pi_id), "xml-stylesheet");
806 }
807
808 #[test]
809 fn pi_target_returns_empty_for_non_pi_nodes() {
810 let (doc, _) = idx_of("<r/>");
811 let idx = DocIndex::build(&doc);
812 let elem_id = find_kind(&idx, XPathNodeKind::Element);
813 assert_eq!(<DocIndex<'_> as DocIndexLike>::pi_target(&idx, elem_id), "");
814 }
815
816 #[test]
819 fn cdata_node_indexed_and_kinded() {
820 let (doc, _) = idx_of("<r><![CDATA[raw <data>]]></r>");
821 let idx = DocIndex::build(&doc);
822 let cd_id = find_kind(&idx, XPathNodeKind::CData);
823 assert_eq!(idx.string_value(cd_id), "raw <data>");
825 assert_eq!(idx.kind(cd_id), XPathNodeKind::CData);
827 }
828
829 #[test]
832 fn comment_string_value() {
833 let (doc, _) = idx_of("<r><!-- hello --></r>");
834 let idx = DocIndex::build(&doc);
835 let c_id = find_kind(&idx, XPathNodeKind::Comment);
836 assert_eq!(idx.string_value(c_id), " hello ");
837 }
838
839 #[test]
842 fn attribute_string_value_returns_attr_value() {
843 let (doc, _) = idx_of(r#"<r id="x"/>"#);
846 let idx = DocIndex::build(&doc);
847 let attr_id = find_kind(&idx, XPathNodeKind::Attribute);
848 assert_eq!(idx.string_value(attr_id), "x");
849 assert_eq!(idx.namespace_uri(attr_id), "");
851 }
852
853 #[test]
854 fn unprefixed_attribute_has_no_namespace_uri_or_prefix() {
855 let (doc, _) = idx_of(r#"<r id="x"/>"#);
856 let idx = DocIndex::build(&doc);
857 let attr_id = find_kind(&idx, XPathNodeKind::Attribute);
858 assert_eq!(idx.namespace_uri(attr_id), "");
859 assert_eq!(idx.namespace_prefix(attr_id), None);
860 }
861
862 #[test]
865 fn text_node_has_no_namespace() {
866 let (doc, _) = idx_of("<r>hello</r>");
867 let idx = DocIndex::build(&doc);
868 let txt_id = find_kind(&idx, XPathNodeKind::Text);
869 assert_eq!(idx.namespace_uri(txt_id), "");
870 assert_eq!(idx.namespace_prefix(txt_id), None);
871 }
872
873 #[test]
876 fn explicit_xmlns_xml_declaration_is_honored() {
877 let (doc, _) = idx_of(
880 r#"<r xmlns:xml="http://www.w3.org/XML/1998/namespace"/>"#,
881 );
882 let idx = DocIndex::build(&doc);
883 let r_id = find_kind(&idx, XPathNodeKind::Element);
884 let ns_range = idx.ns_range(r_id);
885 let xml_count = ns_range
886 .filter(|&nid| idx.node_name(nid) == "xml")
887 .count();
888 assert_eq!(xml_count, 1, "xml prefix should appear exactly once");
889 }
890
891 #[test]
894 fn namespace_node_local_and_node_name_match_prefix() {
895 let (doc, _) = idx_of(r#"<r xmlns:ns="urn:n"/>"#);
896 let idx = DocIndex::build(&doc);
897 let r_id = find_kind(&idx, XPathNodeKind::Element);
898 let ns_range = idx.ns_range(r_id);
899 let mut found_ns = false;
901 for nid in ns_range {
902 if idx.node_name(nid) == "ns" {
903 found_ns = true;
904 assert_eq!(idx.local_name(nid), "ns");
905 assert_eq!(idx.string_value(nid), "urn:n");
906 assert_eq!(idx.namespace_uri(nid), "");
907 }
908 }
909 assert!(found_ns, "ns binding not found among namespace nodes");
910 }
911
912 #[test]
913 fn default_namespace_node_has_empty_name() {
914 let (doc, _) = idx_of(r#"<r xmlns="urn:default"/>"#);
915 let idx = DocIndex::build(&doc);
916 let r_id = find_kind(&idx, XPathNodeKind::Element);
917 let ns_range = idx.ns_range(r_id);
918 let mut found = false;
919 for nid in ns_range {
920 if idx.string_value(nid) == "urn:default" {
922 found = true;
923 assert_eq!(idx.node_name(nid), "");
924 assert_eq!(idx.local_name(nid), "");
925 }
926 }
927 assert!(found);
928 }
929
930 #[test]
931 fn xmlns_undeclaration_removes_default() {
932 let (doc, _) = idx_of(
934 r#"<r xmlns="urn:default"><c xmlns=""/></r>"#,
935 );
936 let idx = DocIndex::build(&doc);
937 let inner = (0..idx.nodes.len())
939 .find(|&id|
940 idx.kind(id) == XPathNodeKind::Element
941 && idx.local_name(id) == "c"
942 )
943 .expect("c element");
944 let ns_range = idx.ns_range(inner);
945 let default_count = ns_range
946 .filter(|&nid| idx.node_name(nid) == "")
947 .count();
948 assert_eq!(default_count, 0);
951 }
952
953 #[test]
956 fn doc_index_like_thunks_forward_correctly() {
957 let (doc, _) = idx_of(r#"<r xmlns:ns="urn:n" id="x"><a/></r>"#);
958 let idx = DocIndex::build(&doc);
959 let r_id = find_kind(&idx, XPathNodeKind::Element);
961 let attr_id = find_kind(&idx, XPathNodeKind::Attribute);
962 assert!(<DocIndex<'_> as DocIndexLike>::ns_range(&idx, r_id).len() >= 2);
964 assert!(<DocIndex<'_> as DocIndexLike>::is_element(&idx, r_id));
965 assert!(!<DocIndex<'_> as DocIndexLike>::is_element(&idx, attr_id));
966 assert_eq!(<DocIndex<'_> as DocIndexLike>::namespace_prefix(&idx, attr_id), None);
967 assert!(!<DocIndex<'_> as DocIndexLike>::string_value(&idx, r_id).is_empty()
969 || true); }
971}