1use crate::ids::TypeKey;
7use crate::namespace::qname::QualifiedName;
8use crate::schema::model::DerivationSet;
9use crate::types::value::XmlValue;
10use crate::types::{ItemType, NameTest, SequenceType};
11use crate::xpath::ast::{ItemTypeNode, KindTest};
12use crate::xpath::cast::type_matches;
13use crate::xpath::iterator::XmlItem;
14
15use super::context::XPathContext;
16use super::{DomNavigator, DomNodeType};
17
18#[derive(Debug, Clone)]
20pub enum NodeTest {
21 Name(NameTest),
23 Type(SequenceType),
25}
26
27impl NodeTest {
28 pub fn matches<N: DomNavigator>(&self, nav: &N, ctx: &XPathContext<'_>) -> bool {
29 match self {
30 NodeTest::Name(test) => matches_name_test(test, nav, ctx),
31 NodeTest::Type(seq) => matches_sequence_type(seq, nav, ctx),
32 }
33 }
34}
35
36pub fn matches_name_test<N: DomNavigator>(
37 test: &NameTest,
38 nav: &N,
39 ctx: &XPathContext<'_>,
40) -> bool {
41 if nav.node_type() != DomNodeType::Element && nav.node_type() != DomNodeType::Attribute {
42 return false;
43 }
44
45 match test {
46 NameTest::Wildcard => true,
47 NameTest::NamespaceWildcard(local_id) => {
48 match ctx.resolve_name(*local_id) {
50 Some(local) => nav.local_name() == local,
51 None => false,
52 }
53 }
54 NameTest::LocalWildcard(ns_id) => {
55 match ctx.resolve_name(*ns_id) {
57 Some(ns) => nav.namespace_uri() == ns,
58 None => false,
59 }
60 }
61 NameTest::QName(qname) => qname_matches(qname, nav, ctx),
62 }
63}
64
65pub fn matches_sequence_type<N: DomNavigator>(
66 sequence: &SequenceType,
67 nav: &N,
68 ctx: &XPathContext<'_>,
69) -> bool {
70 matches_item_type(&sequence.item_type, nav, ctx)
71}
72
73fn matches_item_type<N: DomNavigator>(
74 item_type: &ItemType,
75 nav: &N,
76 ctx: &XPathContext<'_>,
77) -> bool {
78 match item_type {
79 ItemType::AnyItem | ItemType::AnyNode => true,
80 ItemType::Document(None) => nav.node_type() == DomNodeType::Root,
81 ItemType::Document(Some(inner)) => match_document_with_inner(inner, nav, ctx),
82 ItemType::Element(name_test, schema_type) => {
83 if nav.node_type() != DomNodeType::Element {
84 return false;
85 }
86 if let Some(test) = name_test {
87 if !matches_name_test(test, nav, ctx) {
88 return false;
89 }
90 }
91 if let Some(expected) = schema_type {
92 if let Some(actual) = nav.schema_type() {
94 if let Some(schema_set) = ctx.schema_set {
95 if !schema_set.is_type_derived_from(
98 TypeKey::Simple(actual),
99 TypeKey::Simple(*expected),
100 DerivationSet::empty(),
101 ) {
102 return false;
103 }
104 } else {
105 if actual != *expected {
107 return false;
108 }
109 }
110 } else {
111 return false;
113 }
114 }
115 true
116 }
117 ItemType::Attribute(name_test, schema_type) => {
118 if nav.node_type() != DomNodeType::Attribute {
119 return false;
120 }
121 if let Some(test) = name_test {
122 if !matches_name_test(test, nav, ctx) {
123 return false;
124 }
125 }
126 if let Some(expected) = schema_type {
127 if let Some(actual) = nav.schema_type() {
129 if let Some(schema_set) = ctx.schema_set {
130 if !schema_set.is_type_derived_from(
132 TypeKey::Simple(actual),
133 TypeKey::Simple(*expected),
134 DerivationSet::empty(),
135 ) {
136 return false;
137 }
138 } else {
139 if actual != *expected {
141 return false;
142 }
143 }
144 } else {
145 return false;
147 }
148 }
149 true
150 }
151 ItemType::SchemaElement(name) => {
152 if nav.node_type() != DomNodeType::Element {
153 return false;
154 }
155 if !qname_matches(name, nav, ctx) {
157 return false;
158 }
159 if let Some(schema_set) = ctx.schema_set {
161 let ns_id = name.namespace_uri;
163 let Some(elem_key) = schema_set.lookup_element(ns_id, name.local_name) else {
164 return false;
166 };
167 let Some(elem_data) = schema_set.arenas.elements.get(elem_key) else {
168 return false;
169 };
170 if let Some(expected_type) = elem_data.resolved_type {
172 let Some(actual_type) = nav.schema_type() else {
173 return false;
175 };
176 return schema_set.is_type_derived_from(
178 TypeKey::Simple(actual_type),
179 expected_type,
180 DerivationSet::empty(),
181 );
182 }
183 return true;
185 }
186 true
188 }
189 ItemType::SchemaAttribute(name) => {
190 if nav.node_type() != DomNodeType::Attribute {
191 return false;
192 }
193 if !qname_matches(name, nav, ctx) {
195 return false;
196 }
197 if let Some(schema_set) = ctx.schema_set {
199 let ns_id = name.namespace_uri;
201 let Some(attr_key) = schema_set.lookup_attribute(ns_id, name.local_name) else {
202 return false;
204 };
205 let Some(attr_data) = schema_set.arenas.attributes.get(attr_key) else {
206 return false;
207 };
208 if let Some(expected_type) = attr_data.resolved_type {
210 let Some(actual_type) = nav.schema_type() else {
211 return false;
213 };
214 return schema_set.is_type_derived_from(
216 TypeKey::Simple(actual_type),
217 expected_type,
218 DerivationSet::empty(),
219 );
220 }
221 return true;
223 }
224 true
226 }
227 ItemType::Text => nav.node_type().is_text_like(),
228 ItemType::Comment => nav.node_type() == DomNodeType::Comment,
229 ItemType::ProcessingInstruction(target) => {
230 nav.node_type() == DomNodeType::ProcessingInstruction
231 && target.as_ref().is_none_or(|name| nav.local_name() == name)
232 }
233 ItemType::NamespaceNode => nav.node_type() == DomNodeType::Namespace,
234 ItemType::AtomicType(_) | ItemType::SchemaAtomicType(_) => false,
235 }
236}
237
238fn match_document_with_inner<N: DomNavigator>(
239 inner: &ItemType,
240 nav: &N,
241 ctx: &XPathContext<'_>,
242) -> bool {
243 if nav.node_type() != DomNodeType::Root {
244 return false;
245 }
246
247 let mut cursor = nav.clone();
248 if !cursor.move_to_first_child() {
249 return false;
250 }
251
252 loop {
253 if matches_item_type(inner, &cursor, ctx) {
254 return true;
255 }
256 if !cursor.move_to_next_sibling() {
257 break;
258 }
259 }
260
261 false
262}
263
264fn qname_matches<N: DomNavigator>(qname: &QualifiedName, nav: &N, ctx: &XPathContext<'_>) -> bool {
265 let local = match ctx.resolve_name(qname.local_name) {
266 Some(local) => local,
267 None => return false,
268 };
269 let ns = match qname.namespace_uri {
270 Some(id) => match ctx.resolve_name(id) {
271 Some(ns) => ns,
272 None => return false,
273 },
274 None => String::new(),
275 };
276
277 nav.local_name() == local && nav.namespace_uri() == ns
278}
279
280pub fn matches_item_type_node<N: DomNavigator>(
300 item: &XmlItem<N>,
301 item_type: &ItemTypeNode,
302 resolved_atomic_type: Option<&QualifiedName>,
303 ctx: &XPathContext<'_>,
304) -> bool {
305 match item_type {
306 ItemTypeNode::Item => {
307 true
309 }
310 ItemTypeNode::Atomic(_) => {
311 match item {
313 XmlItem::Node(_) => false,
314 XmlItem::Atomic(value) => {
315 if let Some(qname) = resolved_atomic_type {
317 matches_atomic_type(value, qname, ctx)
318 } else {
319 false
321 }
322 }
323 }
324 }
325 ItemTypeNode::Kind(kind_test) => {
326 match item {
328 XmlItem::Node(nav) => matches_kind_test(nav, kind_test, ctx),
329 XmlItem::Atomic(_) => false,
330 }
331 }
332 }
333}
334
335fn matches_atomic_type(value: &XmlValue, qname: &QualifiedName, ctx: &XPathContext<'_>) -> bool {
337 use crate::namespace::table::well_known;
338 use crate::xpath::cast::resolved_type_to_type_code;
339
340 match qname.namespace_uri {
342 Some(ns_id) if ns_id == well_known::XS_NAMESPACE => {}
343 _ => return false,
344 }
345
346 let target_type = match resolved_type_to_type_code(qname, ctx.names) {
348 Ok(tc) => tc,
349 Err(_) => return false,
350 };
351
352 type_matches(value.type_code, target_type)
354}
355
356pub fn matches_kind_test<N: DomNavigator>(
360 nav: &N,
361 kind_test: &KindTest,
362 ctx: &XPathContext<'_>,
363) -> bool {
364 match kind_test {
365 KindTest::AnyKind => {
366 true
368 }
369 KindTest::Text => nav.node_type().is_text_like(),
370 KindTest::Comment => nav.node_type() == DomNodeType::Comment,
371 KindTest::ProcessingInstruction(target) => {
372 if nav.node_type() != DomNodeType::ProcessingInstruction {
373 return false;
374 }
375 match target {
376 None => true,
377 Some(name) => nav.local_name() == *name,
378 }
379 }
380 KindTest::Document(inner) => {
381 if nav.node_type() != DomNodeType::Root {
382 return false;
383 }
384 match inner {
385 None => true,
386 Some(inner_kind) => {
387 let mut cursor = nav.clone();
389 if !cursor.move_to_first_child() {
390 return false;
391 }
392 loop {
393 if matches_kind_test(&cursor, inner_kind, ctx) {
394 return true;
395 }
396 if !cursor.move_to_next_sibling() {
397 break;
398 }
399 }
400 false
401 }
402 }
403 }
404 KindTest::Element(elem_test) => {
405 if nav.node_type() != DomNodeType::Element {
406 return false;
407 }
408 if let Some(ref qname) = elem_test.name {
410 if !ast_qname_matches(qname, nav, ctx) {
411 return false;
412 }
413 }
414 true
416 }
417 KindTest::Attribute(attr_test) => {
418 if nav.node_type() != DomNodeType::Attribute {
419 return false;
420 }
421 if let Some(ref qname) = attr_test.name {
423 if !ast_qname_matches(qname, nav, ctx) {
424 return false;
425 }
426 }
427 true
429 }
430 KindTest::SchemaElement(name) => {
431 if nav.node_type() != DomNodeType::Element {
432 return false;
433 }
434 use crate::xpath::functions::qname::parse_lexical_qname;
436 let Ok((prefix_opt, local_name)) = parse_lexical_qname(name) else {
437 return false; };
439 if nav.local_name() != local_name {
441 return false;
442 }
443 let expected_ns = if let Some(prefix) = &prefix_opt {
445 ctx.resolve_prefix(prefix).unwrap_or_default()
446 } else {
447 ctx.default_element_ns
448 .and_then(|id| ctx.names.try_resolve(id))
449 .unwrap_or_default()
450 };
451 if nav.namespace_uri() != expected_ns {
453 return false;
454 }
455 if let Some(schema_set) = ctx.schema_set {
457 let Some(local_id) = ctx.names.get(&local_name) else {
459 return false;
460 };
461 let ns_id = if expected_ns.is_empty() {
463 None
464 } else {
465 ctx.names.get(&expected_ns)
466 };
467 let Some(elem_key) = schema_set.lookup_element(ns_id, local_id) else {
469 return false;
470 };
471 let Some(elem_data) = schema_set.arenas.elements.get(elem_key) else {
472 return false;
473 };
474 if let Some(expected_type) = elem_data.resolved_type {
476 let Some(actual_type) = nav.schema_type() else {
477 return false;
478 };
479 return schema_set.is_type_derived_from(
480 TypeKey::Simple(actual_type),
481 expected_type,
482 DerivationSet::empty(),
483 );
484 }
485 return true;
487 }
488 true
490 }
491 KindTest::SchemaAttribute(name) => {
492 if nav.node_type() != DomNodeType::Attribute {
493 return false;
494 }
495 use crate::xpath::functions::qname::parse_lexical_qname;
497 let Ok((prefix_opt, local_name)) = parse_lexical_qname(name) else {
498 return false; };
500 if nav.local_name() != local_name {
502 return false;
503 }
504 let expected_ns = if let Some(prefix) = &prefix_opt {
506 ctx.resolve_prefix(prefix).unwrap_or_default()
507 } else {
508 String::new() };
510 if nav.namespace_uri() != expected_ns {
512 return false;
513 }
514 if let Some(schema_set) = ctx.schema_set {
516 let Some(local_id) = ctx.names.get(&local_name) else {
518 return false;
519 };
520 let ns_id = if expected_ns.is_empty() {
522 None
523 } else {
524 ctx.names.get(&expected_ns)
525 };
526 let Some(attr_key) = schema_set.lookup_attribute(ns_id, local_id) else {
528 return false;
529 };
530 let Some(attr_data) = schema_set.arenas.attributes.get(attr_key) else {
531 return false;
532 };
533 if let Some(expected_type) = attr_data.resolved_type {
535 let Some(actual_type) = nav.schema_type() else {
536 return false;
537 };
538 return schema_set.is_type_derived_from(
539 TypeKey::Simple(actual_type),
540 expected_type,
541 DerivationSet::empty(),
542 );
543 }
544 return true;
546 }
547 true
549 }
550 }
551}
552
553fn ast_qname_matches<N: DomNavigator>(
555 qname: &crate::xpath::ast::QName,
556 nav: &N,
557 ctx: &XPathContext<'_>,
558) -> bool {
559 if nav.local_name() != qname.local {
562 return false;
563 }
564
565 if qname.prefix.is_empty() {
567 nav.namespace_uri().is_empty()
569 } else {
570 match ctx.resolve_prefix(&qname.prefix) {
572 Some(ns_uri) => nav.namespace_uri() == ns_uri,
573 None => false,
574 }
575 }
576}
577
578#[cfg(test)]
579#[path = "node_test_tests.rs"]
580mod tests;