1use std::collections::BTreeMap;
9
10use crate::core::{
11 Document, ElementData, ErrorKind, NamespaceUri, NodeId, NodeKind, QName, Span, XmlError,
12 XmlResult,
13};
14use crate::security::QuerySecurityConfig;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Query {
18 steps: Vec<QueryStep>,
19 source: String,
20}
21
22impl Query {
23 pub fn parse(source: &str) -> XmlResult<Self> {
24 Parser::new(source).parse()
25 }
26
27 pub fn evaluate(&self, document: &Document) -> XmlResult<QueryResult> {
28 self.evaluate_with_context(document, &NamespaceContext::default())
29 }
30
31 pub fn evaluate_with_context(
32 &self,
33 document: &Document,
34 namespaces: &NamespaceContext,
35 ) -> XmlResult<QueryResult> {
36 self.evaluate_with_options(document, namespaces, &QuerySecurityConfig::default())
37 }
38
39 pub fn evaluate_with_options(
40 &self,
41 document: &Document,
42 namespaces: &NamespaceContext,
43 security: &QuerySecurityConfig,
44 ) -> XmlResult<QueryResult> {
45 let Some(root) = document.root() else {
46 return Ok(QueryResult::default());
47 };
48 let mut evaluator = Evaluator::new(document, namespaces, security);
49 evaluator.evaluate(root, &self.steps)
50 }
51
52 pub fn source(&self) -> &str {
53 &self.source
54 }
55}
56
57#[derive(Debug, Clone, Default, PartialEq, Eq)]
58pub struct QueryResult {
59 values: Vec<QueryValue>,
60}
61
62impl QueryResult {
63 pub fn values(&self) -> &[QueryValue] {
64 &self.values
65 }
66
67 pub fn nodes(&self) -> Vec<NodeId> {
68 self.values
69 .iter()
70 .filter_map(|value| match value {
71 QueryValue::Node(id) => Some(*id),
72 _ => None,
73 })
74 .collect()
75 }
76
77 pub fn strings(&self) -> Vec<&str> {
78 self.values
79 .iter()
80 .filter_map(|value| match value {
81 QueryValue::Text(value) | QueryValue::Attribute { value, .. } => {
82 Some(value.as_str())
83 }
84 QueryValue::Node(_) => None,
85 })
86 .collect()
87 }
88
89 pub fn len(&self) -> usize {
90 self.values.len()
91 }
92
93 pub fn is_empty(&self) -> bool {
94 self.values.is_empty()
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum QueryValue {
100 Node(NodeId),
101 Text(String),
102 Attribute { name: QName, value: String },
103}
104
105#[derive(Debug, Clone, Default, PartialEq, Eq)]
106pub struct NamespaceContext {
107 aliases: BTreeMap<String, NamespaceUri>,
108}
109
110impl NamespaceContext {
111 pub fn new() -> Self {
112 Self::default()
113 }
114
115 pub fn with_alias(
116 mut self,
117 alias: impl Into<String>,
118 uri: impl Into<String>,
119 ) -> XmlResult<Self> {
120 self.aliases.insert(alias.into(), NamespaceUri::new(uri)?);
121 Ok(self)
122 }
123
124 pub fn resolve(&self, alias: &str) -> Option<&NamespaceUri> {
125 self.aliases.get(alias)
126 }
127}
128
129pub trait DocumentQueryExt {
130 fn query(&self, source: &str) -> XmlResult<QueryResult>;
131 fn query_with_context(
132 &self,
133 source: &str,
134 namespaces: &NamespaceContext,
135 ) -> XmlResult<QueryResult>;
136}
137
138impl DocumentQueryExt for Document {
139 fn query(&self, source: &str) -> XmlResult<QueryResult> {
140 Query::parse(source)?.evaluate(self)
141 }
142
143 fn query_with_context(
144 &self,
145 source: &str,
146 namespaces: &NamespaceContext,
147 ) -> XmlResult<QueryResult> {
148 Query::parse(source)?.evaluate_with_context(self, namespaces)
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
153enum QueryStep {
154 Root,
155 Child(NodeTest),
156 Descendant(NodeTest),
157 Attribute(NameTest),
158 Text,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
162struct NodeTest {
163 name: NameTest,
164 predicate: Option<Predicate>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
168struct Predicate {
169 attribute: NameTest,
170 value: String,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
174struct NameTest {
175 prefix: Option<String>,
176 local: String,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
180enum TokenKind {
181 Slash,
182 DoubleSlash,
183 At,
184 LBracket,
185 RBracket,
186 Eq,
187 LParen,
188 RParen,
189 Name(String),
190 String(String),
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194struct Token {
195 kind: TokenKind,
196 position: usize,
197}
198
199fn lex(source: &str) -> XmlResult<Vec<Token>> {
200 let bytes = source.as_bytes();
201 let mut position = 0;
202 let mut tokens = Vec::new();
203
204 while position < bytes.len() {
205 match bytes[position] {
206 b'/' if bytes.get(position + 1) == Some(&b'/') => {
207 tokens.push(Token {
208 kind: TokenKind::DoubleSlash,
209 position,
210 });
211 position += 2;
212 }
213 b'/' => {
214 tokens.push(Token {
215 kind: TokenKind::Slash,
216 position,
217 });
218 position += 1;
219 }
220 b'@' => {
221 tokens.push(Token {
222 kind: TokenKind::At,
223 position,
224 });
225 position += 1;
226 }
227 b'[' => {
228 tokens.push(Token {
229 kind: TokenKind::LBracket,
230 position,
231 });
232 position += 1;
233 }
234 b']' => {
235 tokens.push(Token {
236 kind: TokenKind::RBracket,
237 position,
238 });
239 position += 1;
240 }
241 b'=' => {
242 tokens.push(Token {
243 kind: TokenKind::Eq,
244 position,
245 });
246 position += 1;
247 }
248 b'(' => {
249 tokens.push(Token {
250 kind: TokenKind::LParen,
251 position,
252 });
253 position += 1;
254 }
255 b')' => {
256 tokens.push(Token {
257 kind: TokenKind::RParen,
258 position,
259 });
260 position += 1;
261 }
262 b'\'' | b'"' => {
263 let quote = bytes[position];
264 let start = position;
265 position += 1;
266 let value_start = position;
267 while position < bytes.len() && bytes[position] != quote {
268 position += 1;
269 }
270 if position >= bytes.len() {
271 return Err(query_error(source, start, "unterminated string literal"));
272 }
273 let value = source[value_start..position].to_owned();
274 tokens.push(Token {
275 kind: TokenKind::String(value),
276 position: start,
277 });
278 position += 1;
279 }
280 ch if ch.is_ascii_whitespace() => position += 1,
281 _ => {
282 let start = position;
283 while position < bytes.len() && is_name_byte(bytes[position]) {
284 position += 1;
285 }
286 if start == position {
287 return Err(query_error(
288 source,
289 position,
290 format!("unexpected character `{}`", bytes[position] as char),
291 ));
292 }
293 tokens.push(Token {
294 kind: TokenKind::Name(source[start..position].to_owned()),
295 position: start,
296 });
297 }
298 }
299 }
300
301 Ok(tokens)
302}
303
304fn is_name_byte(byte: u8) -> bool {
305 byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.' | b':')
306}
307
308struct Parser<'a> {
309 source: &'a str,
310 tokens: Vec<Token>,
311 position: usize,
312}
313
314impl<'a> Parser<'a> {
315 fn new(source: &'a str) -> Self {
316 Self {
317 source,
318 tokens: lex(source).unwrap_or_default(),
319 position: 0,
320 }
321 }
322
323 fn parse(mut self) -> XmlResult<Query> {
324 self.tokens = lex(self.source)?;
325 if self.tokens.is_empty() {
326 return Err(query_error(self.source, 0, "query cannot be empty"));
327 }
328 self.expect_slash_like_start()?;
329 let mut steps = vec![QueryStep::Root];
330
331 while !self.is_eof() {
332 let axis = self.consume_axis()?;
333 let step = self.parse_step(axis)?;
334 steps.push(step);
335 }
336
337 Ok(Query {
338 steps,
339 source: self.source.to_owned(),
340 })
341 }
342
343 fn expect_slash_like_start(&mut self) -> XmlResult<()> {
344 match self.peek_kind() {
345 Some(TokenKind::Slash | TokenKind::DoubleSlash) => Ok(()),
346 _ => Err(query_error(
347 self.source,
348 self.peek_position(),
349 "query must start with `/` or `//`",
350 )),
351 }
352 }
353
354 fn consume_axis(&mut self) -> XmlResult<Axis> {
355 match self.next_kind() {
356 Some(TokenKind::Slash) => Ok(Axis::Child),
357 Some(TokenKind::DoubleSlash) => Ok(Axis::Descendant),
358 _ => Err(query_error(
359 self.source,
360 self.peek_position(),
361 "expected `/` or `//`",
362 )),
363 }
364 }
365
366 fn parse_step(&mut self, axis: Axis) -> XmlResult<QueryStep> {
367 if self.consume_at() {
368 let name = self.parse_name()?;
369 return Ok(QueryStep::Attribute(name));
370 }
371
372 let name = self.parse_name()?;
373 if name.prefix.is_none() && name.local == "text" && self.consume_lparen() {
374 self.expect_rparen()?;
375 return Ok(QueryStep::Text);
376 }
377
378 let predicate = if self.consume_lbracket() {
379 Some(self.parse_predicate()?)
380 } else {
381 None
382 };
383 let test = NodeTest { name, predicate };
384 Ok(match axis {
385 Axis::Child => QueryStep::Child(test),
386 Axis::Descendant => QueryStep::Descendant(test),
387 })
388 }
389
390 fn parse_predicate(&mut self) -> XmlResult<Predicate> {
391 if !self.consume_at() {
392 return Err(query_error(
393 self.source,
394 self.peek_position(),
395 "predicate must select an attribute with `@`",
396 ));
397 }
398 let attribute = self.parse_name()?;
399 self.expect_eq()?;
400 let value = self.parse_string()?;
401 self.expect_rbracket()?;
402 Ok(Predicate { attribute, value })
403 }
404
405 fn parse_name(&mut self) -> XmlResult<NameTest> {
406 match self.next() {
407 Some(Token {
408 kind: TokenKind::Name(name),
409 position,
410 }) => name_test(self.source, position, &name),
411 Some(token) => Err(query_error(
412 self.source,
413 token.position,
414 "expected XML name in query step",
415 )),
416 None => Err(query_error(
417 self.source,
418 self.source.len(),
419 "expected XML name in query step",
420 )),
421 }
422 }
423
424 fn parse_string(&mut self) -> XmlResult<String> {
425 match self.next() {
426 Some(Token {
427 kind: TokenKind::String(value),
428 ..
429 }) => Ok(value),
430 Some(token) => Err(query_error(
431 self.source,
432 token.position,
433 "expected string literal",
434 )),
435 None => Err(query_error(
436 self.source,
437 self.source.len(),
438 "expected string literal",
439 )),
440 }
441 }
442
443 fn consume_at(&mut self) -> bool {
444 self.consume(|kind| matches!(kind, TokenKind::At))
445 }
446
447 fn consume_lparen(&mut self) -> bool {
448 self.consume(|kind| matches!(kind, TokenKind::LParen))
449 }
450
451 fn consume_lbracket(&mut self) -> bool {
452 self.consume(|kind| matches!(kind, TokenKind::LBracket))
453 }
454
455 fn expect_rparen(&mut self) -> XmlResult<()> {
456 self.expect(|kind| matches!(kind, TokenKind::RParen), "expected `)`")
457 }
458
459 fn expect_rbracket(&mut self) -> XmlResult<()> {
460 self.expect(|kind| matches!(kind, TokenKind::RBracket), "expected `]`")
461 }
462
463 fn expect_eq(&mut self) -> XmlResult<()> {
464 self.expect(|kind| matches!(kind, TokenKind::Eq), "expected `=`")
465 }
466
467 fn expect(&mut self, matches: impl FnOnce(&TokenKind) -> bool, message: &str) -> XmlResult<()> {
468 match self.next() {
469 Some(token) if matches(&token.kind) => Ok(()),
470 Some(token) => Err(query_error(self.source, token.position, message)),
471 None => Err(query_error(self.source, self.source.len(), message)),
472 }
473 }
474
475 fn consume(&mut self, matches: impl FnOnce(&TokenKind) -> bool) -> bool {
476 if self
477 .tokens
478 .get(self.position)
479 .is_some_and(|token| matches(&token.kind))
480 {
481 self.position += 1;
482 true
483 } else {
484 false
485 }
486 }
487
488 fn next_kind(&mut self) -> Option<TokenKind> {
489 self.next().map(|token| token.kind)
490 }
491
492 fn next(&mut self) -> Option<Token> {
493 let token = self.tokens.get(self.position).cloned();
494 if token.is_some() {
495 self.position += 1;
496 }
497 token
498 }
499
500 fn peek_kind(&self) -> Option<&TokenKind> {
501 self.tokens.get(self.position).map(|token| &token.kind)
502 }
503
504 fn peek_position(&self) -> usize {
505 self.tokens
506 .get(self.position)
507 .map(|token| token.position)
508 .unwrap_or(self.source.len())
509 }
510
511 fn is_eof(&self) -> bool {
512 self.position >= self.tokens.len()
513 }
514}
515
516#[derive(Debug, Clone, Copy, PartialEq, Eq)]
517enum Axis {
518 Child,
519 Descendant,
520}
521
522fn name_test(source: &str, position: usize, raw: &str) -> XmlResult<NameTest> {
523 let mut parts = raw.split(':');
524 let first = parts.next().expect("split always yields one part");
525 match (parts.next(), parts.next()) {
526 (Some(local), None) if !first.is_empty() && !local.is_empty() => Ok(NameTest {
527 prefix: Some(first.to_owned()),
528 local: local.to_owned(),
529 }),
530 (None, None) if !first.is_empty() => Ok(NameTest {
531 prefix: None,
532 local: first.to_owned(),
533 }),
534 _ => Err(query_error(source, position, "invalid qualified name")),
535 }
536}
537
538struct Evaluator<'a> {
539 document: &'a Document,
540 namespaces: &'a NamespaceContext,
541 security: &'a QuerySecurityConfig,
542 steps: usize,
543}
544
545impl<'a> Evaluator<'a> {
546 fn new(
547 document: &'a Document,
548 namespaces: &'a NamespaceContext,
549 security: &'a QuerySecurityConfig,
550 ) -> Self {
551 Self {
552 document,
553 namespaces,
554 security,
555 steps: 0,
556 }
557 }
558
559 fn evaluate(&mut self, root: NodeId, steps: &[QueryStep]) -> XmlResult<QueryResult> {
560 let mut values = match steps.get(1) {
561 Some(step) => self.apply_first_step(root, step)?,
562 None => vec![QueryValue::Node(root)],
563 };
564 for step in steps.iter().skip(2) {
565 values = self.apply_step(values, step)?;
566 }
567 Ok(QueryResult { values })
568 }
569
570 fn apply_step(
571 &mut self,
572 values: Vec<QueryValue>,
573 step: &QueryStep,
574 ) -> XmlResult<Vec<QueryValue>> {
575 let mut next = Vec::new();
576 for value in values {
577 let QueryValue::Node(node_id) = value else {
578 continue;
579 };
580 match step {
581 QueryStep::Root => next.push(QueryValue::Node(node_id)),
582 QueryStep::Child(test) => {
583 for child in self.element_children(node_id)? {
584 if self.node_matches(child, test)? {
585 next.push(QueryValue::Node(child));
586 }
587 }
588 }
589 QueryStep::Descendant(test) => {
590 for descendant in self.descendants(node_id)? {
591 if self.node_matches(descendant, test)? {
592 next.push(QueryValue::Node(descendant));
593 }
594 }
595 }
596 QueryStep::Attribute(name) => {
597 if let Some(element) = self.element(node_id)? {
598 for attribute in element.attributes() {
599 if self.name_matches(attribute.name(), name)? {
600 next.push(QueryValue::Attribute {
601 name: attribute.name().clone(),
602 value: attribute.value().to_owned(),
603 });
604 }
605 }
606 }
607 }
608 QueryStep::Text => {
609 for child in self.element_children(node_id)? {
610 if let NodeKind::Text(value) = self.document.node(child)?.kind() {
611 next.push(QueryValue::Text(value.clone()));
612 }
613 }
614 }
615 }
616 }
617 Ok(next)
618 }
619
620 fn apply_first_step(&mut self, root: NodeId, step: &QueryStep) -> XmlResult<Vec<QueryValue>> {
621 Ok(match step {
622 QueryStep::Root => vec![QueryValue::Node(root)],
623 QueryStep::Child(test) if self.node_matches(root, test)? => {
624 vec![QueryValue::Node(root)]
625 }
626 QueryStep::Descendant(test) => {
627 let mut matches = Vec::new();
628 if self.node_matches(root, test)? {
629 matches.push(QueryValue::Node(root));
630 }
631 matches.extend(
632 self.descendants(root)?
633 .into_iter()
634 .filter_map(|node| match self.node_matches(node, test) {
635 Ok(true) => Some(Ok(QueryValue::Node(node))),
636 Ok(false) => None,
637 Err(error) => Some(Err(error)),
638 })
639 .collect::<XmlResult<Vec<_>>>()?,
640 );
641 matches
642 }
643 QueryStep::Attribute(_) | QueryStep::Text => Vec::new(),
644 QueryStep::Child(_) => Vec::new(),
645 })
646 }
647
648 fn element_children(&mut self, node_id: NodeId) -> XmlResult<Vec<NodeId>> {
649 self.bump()?;
650 match self.document.node(node_id)?.kind() {
651 NodeKind::Element(element) => Ok(element.children().to_vec()),
652 _ => Ok(Vec::new()),
653 }
654 }
655
656 fn descendants(&mut self, node_id: NodeId) -> XmlResult<Vec<NodeId>> {
657 let mut descendants = Vec::new();
658 let mut stack = self.element_children(node_id)?;
659 stack.reverse();
660 while let Some(current) = stack.pop() {
661 self.bump()?;
662 descendants.push(current);
663 let mut children = self.element_children(current)?;
664 children.reverse();
665 stack.extend(children);
666 }
667 Ok(descendants)
668 }
669
670 fn node_matches(&mut self, node_id: NodeId, test: &NodeTest) -> XmlResult<bool> {
671 self.bump()?;
672 let Some(element) = self.element(node_id)? else {
673 return Ok(false);
674 };
675 if !self.name_matches(element.name(), &test.name)? {
676 return Ok(false);
677 }
678 match &test.predicate {
679 Some(predicate) => self.predicate_matches(element, predicate),
680 None => Ok(true),
681 }
682 }
683
684 fn predicate_matches(&self, element: &ElementData, predicate: &Predicate) -> XmlResult<bool> {
685 for attribute in element.attributes() {
686 if self.name_matches(attribute.name(), &predicate.attribute)?
687 && attribute.value() == predicate.value
688 {
689 return Ok(true);
690 }
691 }
692 Ok(false)
693 }
694
695 fn name_matches(&self, name: &QName, test: &NameTest) -> XmlResult<bool> {
696 if name.local() != test.local {
697 return Ok(false);
698 }
699 match &test.prefix {
700 Some(prefix) => {
701 let uri = self.namespaces.resolve(prefix).ok_or_else(|| {
702 XmlError::new(
703 ErrorKind::UnknownNamespacePrefix,
704 format!("namespace alias `{prefix}` is not declared"),
705 )
706 })?;
707 Ok(name.namespace_uri().is_some_and(|name_uri| name_uri == uri))
708 }
709 None => Ok(name.prefix().is_none() && name.namespace_uri().is_none()),
710 }
711 }
712
713 fn element(&self, node_id: NodeId) -> XmlResult<Option<&ElementData>> {
714 Ok(match self.document.node(node_id)?.kind() {
715 NodeKind::Element(element) => Some(element),
716 _ => None,
717 })
718 }
719
720 fn bump(&mut self) -> XmlResult<()> {
721 self.steps += 1;
722 self.security.check_steps(self.steps)
723 }
724}
725
726fn query_error(source: &str, position: usize, message: impl Into<String>) -> XmlError {
727 XmlError::new(ErrorKind::Query, message).with_span(span_for_byte(source, position))
728}
729
730fn span_for_byte(source: &str, byte_position: usize) -> Span {
731 let mut line = 1;
732 let mut column = 1;
733 for (index, ch) in source.char_indices() {
734 if index >= byte_position {
735 break;
736 }
737 if ch == '\n' {
738 line += 1;
739 column = 1;
740 } else {
741 column += 1;
742 }
743 }
744 Span::new(line, column)
745}
746
747#[cfg(test)]
748mod tests {
749 use super::*;
750 use crate::core::{Attribute, NamespaceDeclaration};
751 use crate::parser;
752
753 fn sample_document() -> XmlResult<Document> {
754 let mut document = Document::new();
755 let root = document.add_root_element(QName::qualified("doc", "Root", "urn:doc")?)?;
756 document
757 .add_namespace_declaration(root, NamespaceDeclaration::prefixed("doc", "urn:doc")?)?;
758
759 let first = document.add_element(root, QName::qualified("doc", "Item", "urn:doc")?)?;
760 document.add_attribute(first, Attribute::new(QName::new("code")?, "A1"))?;
761 document.add_attribute(
762 first,
763 Attribute::new(QName::qualified("doc", "kind", "urn:doc")?, "primary"),
764 )?;
765 let first_name = document.add_element(first, QName::new("Name")?)?;
766 document.add_text(first_name, "Alpha")?;
767
768 let second = document.add_element(root, QName::qualified("doc", "Item", "urn:doc")?)?;
769 document.add_attribute(second, Attribute::new(QName::new("code")?, "B2"))?;
770 let second_name = document.add_element(second, QName::new("Name")?)?;
771 document.add_text(second_name, "Beta")?;
772
773 let note = document.add_element(root, QName::new("Note")?)?;
774 document.add_text(note, "Loose")?;
775
776 Ok(document)
777 }
778
779 fn ns() -> NamespaceContext {
780 NamespaceContext::new()
781 .with_alias("d", "urn:doc")
782 .expect("namespace alias")
783 }
784
785 #[test]
786 fn query_lexer_tokenizes_path() -> XmlResult<()> {
787 let tokens = lex("/d:Root//Name[@code='A1']/text()")?;
788
789 assert!(matches!(tokens[0].kind, TokenKind::Slash));
790 assert!(tokens
791 .iter()
792 .any(|token| matches!(token.kind, TokenKind::DoubleSlash)));
793 assert!(tokens
794 .iter()
795 .any(|token| matches!(token.kind, TokenKind::String(_))));
796 Ok(())
797 }
798
799 #[test]
800 fn query_parser_builds_absolute_path() -> XmlResult<()> {
801 let query = Query::parse("/Root/Child")?;
802
803 assert_eq!(query.steps.len(), 3);
804 assert_eq!(query.source(), "/Root/Child");
805 Ok(())
806 }
807
808 #[test]
809 fn query_evaluator_selects_absolute_path() -> XmlResult<()> {
810 let document = sample_document()?;
811 let result = document.query_with_context("/d:Root/d:Item", &ns())?;
812
813 assert_eq!(result.len(), 2);
814 Ok(())
815 }
816
817 #[test]
818 fn query_evaluator_selects_descendants() -> XmlResult<()> {
819 let document = sample_document()?;
820 let result = document.query("//Name")?;
821
822 assert_eq!(result.len(), 2);
823 Ok(())
824 }
825
826 #[test]
827 fn query_evaluator_selects_attribute() -> XmlResult<()> {
828 let document = sample_document()?;
829 let result = document.query_with_context("/d:Root/d:Item/@code", &ns())?;
830
831 assert_eq!(result.strings(), vec!["A1", "B2"]);
832 Ok(())
833 }
834
835 #[test]
836 fn query_evaluator_selects_text() -> XmlResult<()> {
837 let document = sample_document()?;
838 let result = document.query_with_context("/d:Root/d:Item/Name/text()", &ns())?;
839
840 assert_eq!(result.strings(), vec!["Alpha", "Beta"]);
841 Ok(())
842 }
843
844 #[test]
845 fn query_evaluator_filters_by_attribute_predicate() -> XmlResult<()> {
846 let document = sample_document()?;
847 let result =
848 document.query_with_context("/d:Root/d:Item[@code='A1']/Name/text()", &ns())?;
849
850 assert_eq!(result.strings(), vec!["Alpha"]);
851 Ok(())
852 }
853
854 #[test]
855 fn query_namespaces_alias_filters_by_namespaced_attribute_predicate() -> XmlResult<()> {
856 let document = sample_document()?;
857 let result =
858 document.query_with_context("/d:Root/d:Item[@d:kind='primary']/Name/text()", &ns())?;
859
860 assert_eq!(result.strings(), vec!["Alpha"]);
861 Ok(())
862 }
863
864 #[test]
865 fn query_namespaces_default_namespace_requires_alias() -> XmlResult<()> {
866 let document =
867 parser::parse_str(r#"<Root xmlns="urn:default"><Child>value</Child></Root>"#)?;
868 let namespaces = NamespaceContext::new().with_alias("d", "urn:default")?;
869
870 assert!(document.query("/Root")?.is_empty());
871 assert_eq!(
872 document
873 .query_with_context("/d:Root/d:Child/text()", &namespaces)?
874 .strings(),
875 vec!["value"]
876 );
877 Ok(())
878 }
879
880 #[test]
881 fn query_compiled_query_can_be_reused() -> XmlResult<()> {
882 let document = sample_document()?;
883 let query = Query::parse("//Name/text()")?;
884
885 assert_eq!(query.evaluate(&document)?.strings(), vec!["Alpha", "Beta"]);
886 assert_eq!(query.evaluate(&document)?.strings(), vec!["Alpha", "Beta"]);
887 Ok(())
888 }
889
890 #[test]
891 fn query_valid_cases_cover_mvp_surface() -> XmlResult<()> {
892 let document = sample_document()?;
893 let namespace = ns();
894 let cases = [
895 ("/d:Root", 1),
896 ("/d:Root/d:Item", 2),
897 ("/d:Root/Note", 1),
898 ("//d:Item", 2),
899 ("//Name", 2),
900 ("//Name/text()", 2),
901 ("/d:Root/d:Item/@code", 2),
902 ("/d:Root/d:Item[@code='B2']", 1),
903 ("/d:Root/d:Item[@code='B2']/Name/text()", 1),
904 ("/d:Root/d:Item/@d:kind", 1),
905 ];
906
907 for (source, expected_len) in cases {
908 assert_eq!(
909 document.query_with_context(source, &namespace)?.len(),
910 expected_len,
911 "query {source}"
912 );
913 }
914 Ok(())
915 }
916
917 #[test]
918 fn query_invalid_cases_have_structured_errors_with_span() {
919 let invalid = ["", "Root", "/", "/Root[", "/Root[@id]", "/Root/text("];
920
921 for source in invalid {
922 let error = Query::parse(source).expect_err("query must fail");
923 assert_eq!(error.kind(), &ErrorKind::Query);
924 assert!(error.span().is_some());
925 }
926 }
927
928 #[test]
929 fn query_namespace_alias_must_be_declared() {
930 let document = sample_document().expect("document");
931 let error = document
932 .query("/d:Root")
933 .expect_err("missing namespace alias must fail");
934
935 assert_eq!(error.kind(), &ErrorKind::UnknownNamespacePrefix);
936 }
937
938 #[test]
939 fn query_security_limits_steps() {
940 let document = sample_document().expect("document");
941 let query = Query::parse("//Name").expect("query");
942 let security = QuerySecurityConfig::default()
943 .with_limits(crate::security::SecurityLimits::default().with_max_query_steps(1));
944 let error = query
945 .evaluate_with_options(&document, &NamespaceContext::default(), &security)
946 .expect_err("query step limit must fail");
947
948 assert_eq!(error.kind(), &ErrorKind::Parse);
949 }
950}