1#![forbid(unsafe_code)]
4
5use rusty_xml_tree::{NodeId, NodeKind, XmlDoc};
6use std::cmp::Ordering;
7
8pub const XPATH_UNDEFINED: i32 = 0;
10pub const XPATH_NODESET: i32 = 1;
11pub const XPATH_BOOLEAN: i32 = 2;
12pub const XPATH_NUMBER: i32 = 3;
13pub const XPATH_STRING: i32 = 4;
14
15#[derive(Clone, Debug)]
16pub enum XPathObject {
17 Undefined,
18 NodeSet(Vec<NodeId>),
19 Boolean(bool),
20 Number(f64),
21 String(String),
22}
23
24impl XPathObject {
25 pub fn object_type(&self) -> i32 {
26 match self {
27 XPathObject::Undefined => XPATH_UNDEFINED,
28 XPathObject::NodeSet(_) => XPATH_NODESET,
29 XPathObject::Boolean(_) => XPATH_BOOLEAN,
30 XPathObject::Number(_) => XPATH_NUMBER,
31 XPathObject::String(_) => XPATH_STRING,
32 }
33 }
34}
35
36pub struct XmlXPathContext<'a> {
37 pub doc: &'a XmlDoc,
38 pub node: NodeId,
39 pub position: usize,
40 pub size: usize,
41 ns: Vec<(String, String)>,
42}
43
44impl<'a> XmlXPathContext<'a> {
45 #[doc(alias = "xmlXPathNewContext")]
47 pub fn xml_xpath_new_context(doc: &'a XmlDoc) -> Self {
48 Self {
49 node: doc.xml_doc_get_root_element().unwrap_or(NodeId::DOCUMENT),
50 doc,
51 position: 1,
52 size: 1,
53 ns: Vec::new(),
54 }
55 }
56
57 #[doc(alias = "xmlXPathSetContextNode")]
59 pub fn xml_xpath_set_context_node(&mut self, node: NodeId) {
60 self.node = node;
61 }
62
63 pub fn register_ns(&mut self, prefix: &str, href: &str) {
64 self.ns.push((prefix.to_string(), href.to_string()));
65 }
66}
67
68#[doc(alias = "xmlXPathIsNaN")]
70pub fn xml_xpath_is_nan(v: f64) -> bool {
71 v.is_nan()
72}
73
74#[doc(alias = "xmlXPathIsInf")]
76pub fn xml_xpath_is_inf(v: f64) -> i32 {
77 if v.is_infinite() {
78 if v.is_sign_positive() { 1 } else { -1 }
79 } else {
80 0
81 }
82}
83
84#[doc(alias = "xmlXPathOrderDocElems")]
86pub fn xml_xpath_order_doc_elems(doc: &XmlDoc) -> Vec<NodeId> {
87 let mut out = Vec::new();
88 fn walk(doc: &XmlDoc, id: NodeId, out: &mut Vec<NodeId>) {
89 out.push(id);
90 let mut a = doc.first_attr(id);
91 while let Some(x) = a {
92 out.push(x);
93 a = doc.next_sibling(x);
94 }
95 let mut c = doc.first_child(id);
96 while let Some(x) = c {
97 walk(doc, x, out);
98 c = doc.next_sibling(x);
99 }
100 }
101 walk(doc, NodeId::DOCUMENT, &mut out);
102 out
103}
104
105#[doc(alias = "xmlXPathCmpNodes")]
107pub fn xml_xpath_cmp_nodes(doc: &XmlDoc, a: NodeId, b: NodeId) -> i32 {
108 let order = xml_xpath_order_doc_elems(doc);
109 let ia = order.iter().position(|n| *n == a);
110 let ib = order.iter().position(|n| *n == b);
111 match (ia, ib) {
112 (Some(x), Some(y)) => x.cmp(&y) as i32,
113 _ => a.0.cmp(&b.0) as i32,
114 }
115}
116
117#[doc(alias = "xmlXPathEval")]
119pub fn xml_xpath_eval(expr: &str, ctx: &XmlXPathContext<'_>) -> Result<XPathObject, String> {
120 let mut p = Parser {
121 src: expr.trim(),
122 pos: 0,
123 };
124 let ast = p.parse_expr()?;
125 p.skip_ws();
126 if p.pos < p.src.len() && !p.src[p.pos..].chars().all(|c| c.is_whitespace()) {
127 return Err(format!("trailing junk in XPath: {}", &p.src[p.pos..]));
128 }
129 eval(&ast, ctx)
130}
131
132#[doc(alias = "xmlXPathCastToBoolean")]
134pub fn xml_xpath_cast_to_boolean(o: &XPathObject) -> bool {
135 match o {
136 XPathObject::Boolean(b) => *b,
137 XPathObject::Number(n) => *n != 0.0 && !n.is_nan(),
138 XPathObject::String(s) => !s.is_empty(),
139 XPathObject::NodeSet(v) => !v.is_empty(),
140 XPathObject::Undefined => false,
141 }
142}
143
144#[doc(alias = "xmlXPathCastToNumber")]
146pub fn xml_xpath_cast_to_number(o: &XPathObject, ctx: &XmlXPathContext<'_>) -> f64 {
147 match o {
148 XPathObject::Number(n) => *n,
149 XPathObject::Boolean(b) => {
150 if *b { 1.0 } else { 0.0 }
151 }
152 XPathObject::String(s) => xpath_number(s),
153 XPathObject::NodeSet(v) => xml_xpath_cast_to_number(&XPathObject::String(string_val(ctx, v)), ctx),
154 XPathObject::Undefined => f64::NAN,
155 }
156}
157
158#[doc(alias = "xmlXPathCastToString")]
160pub fn xml_xpath_cast_to_string(o: &XPathObject, ctx: &XmlXPathContext<'_>) -> String {
161 match o {
162 XPathObject::String(s) => s.clone(),
163 XPathObject::Boolean(b) => if *b { "true".into() } else { "false".into() },
164 XPathObject::Number(n) => number_to_string(*n),
165 XPathObject::NodeSet(v) => string_val(ctx, v),
166 XPathObject::Undefined => String::new(),
167 }
168}
169
170fn number_to_string(n: f64) -> String {
171 if n.is_nan() {
172 return "NaN".into();
173 }
174 if n.is_infinite() {
175 return if n.is_sign_positive() {
176 "Infinity".into()
177 } else {
178 "-Infinity".into()
179 };
180 }
181 if n == 0.0 {
182 return "0".into();
183 }
184 if n > i32::MIN as f64 && n < i32::MAX as f64 && n == (n as i32) as f64 {
185 return format!("{}", n as i32);
186 }
187 let abs = n.abs();
188 if abs > 1e9 || abs < 1e-5 {
189 let s = format!("{n:.14e}");
190 let Some(idx) = s.find('e') else { return s };
191 let mant = &s[..idx];
192 let es = &s[idx..];
193 let mut mant = mant.to_string();
194 if mant.contains('.') {
195 while mant.ends_with('0') {
196 mant.pop();
197 }
198 if mant.ends_with('.') {
199 mant.pop();
200 }
201 }
202 let es = if es.starts_with("e-") || es.starts_with("e+") {
203 es.to_string()
204 } else {
205 format!("e+{}", &es[1..])
206 };
207 format!("{mant}{es}")
208 } else {
209 let mut s = format!("{n}");
210 if let Some(rest) = s.strip_prefix("-") {
211 if rest.starts_with('.') {
212 s = format!("-0{rest}");
213 }
214 } else if s.starts_with('.') {
215 s = format!("0{s}");
216 }
217 s
218 }
219}
220
221fn dump_number(n: f64) -> String {
222 if n.is_nan() {
223 return "NaN".into();
224 }
225 if n.is_infinite() {
226 return if n.is_sign_positive() {
227 "Infinity".into()
228 } else {
229 "-Infinity".into()
230 };
231 }
232 if n == 0.0 {
233 return "0".into();
234 }
235 let exp = n.abs().log10().floor() as i32;
237 if exp < -4 || exp >= 6 {
238 let s = format!("{n:.5e}");
239 let Some(idx) = s.find('e') else { return s };
240 let mant = &s[..idx];
241 let es = &s[idx..];
242 let mut mant = mant.to_string();
243 if mant.contains('.') {
244 while mant.ends_with('0') {
245 mant.pop();
246 }
247 if mant.ends_with('.') {
248 mant.pop();
249 }
250 }
251 let es = if es.starts_with("e-") || es.starts_with("e+") {
252 es.to_string()
253 } else {
254 format!("e+{}", &es[1..])
255 };
256 format!("{mant}{es}")
257 } else {
258 format!("{n}")
259 }
260}
261
262fn xpath_number(s: &str) -> f64 {
263 let t = s.trim();
264 if t.is_empty() {
265 return f64::NAN;
266 }
267 t.parse::<f64>().unwrap_or(f64::NAN)
268}
269
270fn string_val(ctx: &XmlXPathContext<'_>, nodes: &[NodeId]) -> String {
271 let Some(&id) = nodes.first() else { return String::new() };
272 ctx.doc.xml_node_get_content(id)
273}
274
275pub fn xml_xpath_debug_dump(obj: &XPathObject, ctx: &XmlXPathContext<'_>) -> String {
277 match obj {
278 XPathObject::Undefined => "Object is empty (NULL)\n".into(),
279 XPathObject::Number(n) => format!("Object is a number : {}\n", dump_number(*n)),
280 XPathObject::Boolean(b) => format!(
281 "Object is a Boolean : {}\n",
282 if *b { "true" } else { "false" }
283 ),
284 XPathObject::String(s) => format!("Object is a string : {}\n", debug_string(s)),
285 XPathObject::NodeSet(v) => {
286 let mut s = format!("Object is a Node Set :\nSet contains {} nodes:\n", v.len());
287 for (i, id) in v.iter().enumerate() {
288 s.push_str(&format!("{}", i + 1));
289 dump_one(ctx.doc, *id, 1, &mut s);
290 }
291 s
292 }
293 }
294}
295
296fn debug_string(s: &str) -> String {
297 let mut out = String::new();
298 for (i, c) in s.chars().enumerate() {
299 if i >= 40 {
300 out.push_str("...");
301 break;
302 }
303 if c.is_whitespace() {
304 out.push(' ');
305 } else if (c as u32) >= 0x80 {
306 out.push_str(&format!("#{:X}", c as u32));
307 } else {
308 out.push(c);
309 }
310 }
311 out
312}
313
314fn spaces(depth: i32) -> String {
315 " ".repeat(depth.max(0) as usize)
316}
317
318fn dump_one(doc: &XmlDoc, id: NodeId, depth: i32, out: &mut String) {
319 match doc.kind(id) {
320 NodeKind::Document | NodeKind::HtmlDocument => {
321 out.push_str(&spaces(depth));
322 out.push_str(" /\n");
323 }
324 NodeKind::Element => {
325 out.push_str(&spaces(depth));
326 out.push_str("ELEMENT ");
327 out.push_str(&doc.qname(id));
328 out.push('\n');
329 for (pre, href) in doc.ns_defs(id) {
330 out.push_str(&spaces(depth + 1));
331 match pre {
332 Some(p) => out.push_str(&format!("namespace {p} href={href}\n")),
333 None => out.push_str(&format!("default namespace href={href}\n")),
334 }
335 }
336 let mut a = doc.first_attr(id);
337 while let Some(x) = a {
338 dump_attr(doc, x, depth + 1, out);
339 a = doc.next_sibling(x);
340 }
341 }
342 NodeKind::Attribute => dump_attr(doc, id, depth, out),
343 NodeKind::Text => {
344 out.push_str(&spaces(depth));
345 out.push_str("TEXT\n");
346 out.push_str(&spaces(depth + 1));
347 out.push_str("content=");
348 out.push_str(&debug_string(doc.content(id)));
349 out.push('\n');
350 }
351 NodeKind::CData => {
352 out.push_str(&spaces(depth));
353 out.push_str("CDATA_SECTION\n");
354 }
355 NodeKind::Comment => {
356 out.push_str(&spaces(depth));
357 out.push_str("COMMENT\n");
358 }
359 NodeKind::Pi => {
360 out.push_str(&spaces(depth));
361 out.push_str(&format!("PI {}\n", doc.name(id)));
362 }
363 _ => {
364 out.push_str(&spaces(depth));
365 out.push_str(&format!("{:?}\n", doc.kind(id)));
366 }
367 }
368}
369
370fn dump_attr(doc: &XmlDoc, id: NodeId, depth: i32, out: &mut String) {
371 out.push_str(&spaces(depth));
372 out.push_str("ATTRIBUTE ");
373 out.push_str(doc.name(id));
374 out.push('\n');
375 out.push_str(&spaces(depth + 1));
376 out.push_str("TEXT\n");
377 out.push_str(&spaces(depth + 2));
378 out.push_str("content=");
379 out.push_str(&debug_string(doc.content(id)));
380 out.push('\n');
381}
382
383pub fn xml_xpath_print_lint(obj: &XPathObject) -> Option<String> {
385 match obj {
386 XPathObject::Undefined => Some(String::new()),
387 XPathObject::Number(n) => Some(format!("{}\n", dump_number(*n))),
388 XPathObject::Boolean(b) => Some(format!("{}\n", if *b { "true" } else { "false" })),
389 XPathObject::String(s) => Some(format!("{s}\n")),
390 XPathObject::NodeSet(_) => None,
391 }
392}
393#[doc(alias = "xmlXPathCompile")]
394pub fn xml_xpath_compile(expr: &str) -> Result<String, String> {
395 let mut p = Parser {
396 src: expr.trim(),
397 pos: 0,
398 };
399 let _ = p.parse_expr()?;
400 Ok(expr.to_string())
401}
402
403#[doc(alias = "xmlXPathCompiledEval")]
405pub fn xml_xpath_compiled_eval(
406 expr: &str,
407 ctx: &XmlXPathContext<'_>,
408) -> Result<XPathObject, String> {
409 xml_xpath_eval(expr, ctx)
410}
411
412#[derive(Clone, Debug)]
415enum Expr {
416 Or(Box<Expr>, Box<Expr>),
417 And(Box<Expr>, Box<Expr>),
418 Eq(Box<Expr>, Box<Expr>, bool),
419 Rel(Box<Expr>, Box<Expr>, Ordering, bool),
420 Add(Box<Expr>, Box<Expr>, bool),
421 Mul(Box<Expr>, Box<Expr>, char),
422 Neg(Box<Expr>),
423 Union(Box<Expr>, Box<Expr>),
424 Path(PathExpr),
425 Steps { base: Box<Expr>, steps: Vec<Step> },
426 Filter(Box<Expr>, Vec<Expr>),
427 Literal(String),
428 Number(f64),
429 Var(String),
430 Fun { name: String, args: Vec<Expr> },
431}
432
433#[derive(Clone, Debug)]
434struct PathExpr {
435 abs: bool,
436 steps: Vec<Step>,
437}
438
439#[derive(Clone, Debug)]
440struct Step {
441 axis: Axis,
442 test: NodeTest,
443 preds: Vec<Expr>,
444}
445
446#[derive(Clone, Copy, Debug, PartialEq, Eq)]
447enum Axis {
448 Child,
449 Descendant,
450 Parent,
451 Ancestor,
452 FollowingSibling,
453 PrecedingSibling,
454 Following,
455 Preceding,
456 Attribute,
457 Namespace,
458 SelfAxis,
459 DescendantOrSelf,
460 AncestorOrSelf,
461}
462
463#[derive(Clone, Debug)]
464enum NodeTest {
465 Star,
466 Name(String, Option<String>),
467 Node,
468 Text,
469 Comment,
470 Pi(Option<String>),
471}
472
473struct Parser<'a> {
474 src: &'a str,
475 pos: usize,
476}
477
478impl<'a> Parser<'a> {
479 fn skip_ws(&mut self) {
480 while let Some(c) = self.src[self.pos..].chars().next() {
481 if c.is_whitespace() {
482 self.pos += c.len_utf8();
483 } else {
484 break;
485 }
486 }
487 }
488 fn peek(&self) -> Option<char> {
489 self.src[self.pos..].chars().next()
490 }
491 fn starts(&self, s: &str) -> bool {
492 self.src[self.pos..].starts_with(s)
493 }
494 fn bump(&mut self, n: usize) {
495 self.pos += n;
496 }
497 fn parse_expr(&mut self) -> Result<Expr, String> {
498 self.parse_or()
499 }
500 fn parse_or(&mut self) -> Result<Expr, String> {
501 let mut e = self.parse_and()?;
502 loop {
503 self.skip_ws();
504 if self.keyword("or") {
505 let r = self.parse_and()?;
506 e = Expr::Or(Box::new(e), Box::new(r));
507 } else {
508 break;
509 }
510 }
511 Ok(e)
512 }
513 fn parse_and(&mut self) -> Result<Expr, String> {
514 let mut e = self.parse_eq()?;
515 loop {
516 self.skip_ws();
517 if self.keyword("and") {
518 let r = self.parse_eq()?;
519 e = Expr::And(Box::new(e), Box::new(r));
520 } else {
521 break;
522 }
523 }
524 Ok(e)
525 }
526 fn parse_eq(&mut self) -> Result<Expr, String> {
527 let mut e = self.parse_rel()?;
528 loop {
529 self.skip_ws();
530 if self.starts("=") {
531 self.bump(1);
532 let r = self.parse_rel()?;
533 e = Expr::Eq(Box::new(e), Box::new(r), true);
534 } else if self.starts("!=") {
535 self.bump(2);
536 let r = self.parse_rel()?;
537 e = Expr::Eq(Box::new(e), Box::new(r), false);
538 } else {
539 break;
540 }
541 }
542 Ok(e)
543 }
544 fn parse_rel(&mut self) -> Result<Expr, String> {
545 let mut e = self.parse_add()?;
546 loop {
547 self.skip_ws();
548 if self.starts("<=") {
549 self.bump(2);
550 let r = self.parse_add()?;
551 e = Expr::Rel(Box::new(e), Box::new(r), Ordering::Less, true);
552 } else if self.starts(">=") {
553 self.bump(2);
554 let r = self.parse_add()?;
555 e = Expr::Rel(Box::new(e), Box::new(r), Ordering::Greater, true);
556 } else if self.starts("<") {
557 self.bump(1);
558 let r = self.parse_add()?;
559 e = Expr::Rel(Box::new(e), Box::new(r), Ordering::Less, false);
560 } else if self.starts(">") {
561 self.bump(1);
562 let r = self.parse_add()?;
563 e = Expr::Rel(Box::new(e), Box::new(r), Ordering::Greater, false);
564 } else {
565 break;
566 }
567 }
568 Ok(e)
569 }
570 fn parse_add(&mut self) -> Result<Expr, String> {
571 let mut e = self.parse_mul()?;
572 loop {
573 self.skip_ws();
574 if self.starts("+") {
575 self.bump(1);
576 let r = self.parse_mul()?;
577 e = Expr::Add(Box::new(e), Box::new(r), true);
578 } else if self.starts("-") && !self.is_name_start_after_minus() {
579 self.bump(1);
580 let r = self.parse_mul()?;
581 e = Expr::Add(Box::new(e), Box::new(r), false);
582 } else {
583 break;
584 }
585 }
586 Ok(e)
587 }
588 fn is_name_start_after_minus(&self) -> bool {
589 false
590 }
591 fn parse_mul(&mut self) -> Result<Expr, String> {
592 let mut e = self.parse_unary()?;
593 loop {
594 self.skip_ws();
595 if self.starts("*") && !self.looks_like_nametest_star() {
596 self.bump(1);
597 let r = self.parse_unary()?;
598 e = Expr::Mul(Box::new(e), Box::new(r), '*');
599 } else if self.keyword("div") {
600 let r = self.parse_unary()?;
601 e = Expr::Mul(Box::new(e), Box::new(r), 'd');
602 } else if self.keyword("mod") {
603 let r = self.parse_unary()?;
604 e = Expr::Mul(Box::new(e), Box::new(r), 'm');
605 } else {
606 break;
607 }
608 }
609 Ok(e)
610 }
611 fn looks_like_nametest_star(&self) -> bool {
612 false
613 }
614 fn parse_unary(&mut self) -> Result<Expr, String> {
615 self.skip_ws();
616 if self.starts("-") {
617 self.bump(1);
618 Ok(Expr::Neg(Box::new(self.parse_unary()?)))
619 } else {
620 self.parse_union()
621 }
622 }
623 fn parse_union(&mut self) -> Result<Expr, String> {
624 let mut e = self.parse_path()?;
625 loop {
626 self.skip_ws();
627 if self.starts("|") {
628 self.bump(1);
629 let r = self.parse_path()?;
630 e = Expr::Union(Box::new(e), Box::new(r));
631 } else {
632 break;
633 }
634 }
635 Ok(e)
636 }
637 fn parse_path(&mut self) -> Result<Expr, String> {
638 self.skip_ws();
639 if self.looks_like_primary() {
640 return self.parse_filter_or_primary();
641 }
642 let abs = if self.starts("//") {
643 self.bump(2);
644 let mut steps = vec![Step {
645 axis: Axis::DescendantOrSelf,
646 test: NodeTest::Node,
647 preds: vec![],
648 }];
649 steps.extend(self.parse_relative()?);
650 return Ok(Expr::Path(PathExpr { abs: true, steps }));
651 } else if self.starts("/") {
652 self.bump(1);
653 true
654 } else {
655 false
656 };
657 let steps = self.parse_relative()?;
658 if abs || !steps.is_empty() {
659 Ok(Expr::Path(PathExpr { abs, steps }))
660 } else {
661 self.parse_filter_or_primary()
662 }
663 }
664 fn looks_like_primary(&self) -> bool {
665 let s = self.src[self.pos..].trim_start();
666 match s.chars().next() {
667 Some('(' | '$' | '\'' | '"') => true,
668 Some(d) if d.is_ascii_digit() => true,
669 Some('.') => s[1..].chars().next().map(|x| x.is_ascii_digit()).unwrap_or(false),
670 Some(c) if c.is_ascii_alphabetic() || c == '_' => {
671 let i = s
672 .find(|ch: char| !(ch.is_ascii_alphanumeric() || "-._:".contains(ch)))
673 .unwrap_or(s.len());
674 s[i..].trim_start().starts_with('(')
675 }
676 _ => false,
677 }
678 }
679 fn parse_relative(&mut self) -> Result<Vec<Step>, String> {
680 let mut steps = Vec::new();
681 self.skip_ws();
682 if self.peek().is_none() || matches!(self.peek(), Some(')' | ',' | '|' | ']')) {
683 return Ok(steps);
684 }
685 if self.starts("/") && !self.starts("//") {
686 return Ok(steps);
687 }
688 steps.push(self.parse_step()?);
689 loop {
690 self.skip_ws();
691 if self.starts("//") {
692 self.bump(2);
693 steps.push(Step {
694 axis: Axis::DescendantOrSelf,
695 test: NodeTest::Node,
696 preds: vec![],
697 });
698 steps.push(self.parse_step()?);
699 } else if self.starts("/") {
700 self.bump(1);
701 steps.push(self.parse_step()?);
702 } else {
703 break;
704 }
705 }
706 Ok(steps)
707 }
708 fn parse_step(&mut self) -> Result<Step, String> {
709 self.skip_ws();
710 if self.starts("..") {
711 self.bump(2);
712 return Ok(Step {
713 axis: Axis::Parent,
714 test: NodeTest::Node,
715 preds: self.parse_preds()?,
716 });
717 }
718 if self.starts(".") {
719 self.bump(1);
720 return Ok(Step {
721 axis: Axis::SelfAxis,
722 test: NodeTest::Node,
723 preds: self.parse_preds()?,
724 });
725 }
726 if self.starts("@") {
727 self.bump(1);
728 let test = self.parse_node_test()?;
729 return Ok(Step {
730 axis: Axis::Attribute,
731 test,
732 preds: self.parse_preds()?,
733 });
734 }
735 let save = self.pos;
736 if let Some(axis) = self.try_axis() {
737 let test = self.parse_node_test()?;
738 return Ok(Step {
739 axis,
740 test,
741 preds: self.parse_preds()?,
742 });
743 }
744 self.pos = save;
745 let test = self.parse_node_test()?;
746 Ok(Step {
747 axis: Axis::Child,
748 test,
749 preds: self.parse_preds()?,
750 })
751 }
752 fn try_axis(&mut self) -> Option<Axis> {
753 self.skip_ws();
754 let names = [
755 ("descendant-or-self", Axis::DescendantOrSelf),
756 ("following-sibling", Axis::FollowingSibling),
757 ("preceding-sibling", Axis::PrecedingSibling),
758 ("ancestor-or-self", Axis::AncestorOrSelf),
759 ("descendant", Axis::Descendant),
760 ("attribute", Axis::Attribute),
761 ("following", Axis::Following),
762 ("namespace", Axis::Namespace),
763 ("preceding", Axis::Preceding),
764 ("ancestor", Axis::Ancestor),
765 ("parent", Axis::Parent),
766 ("child", Axis::Child),
767 ("self", Axis::SelfAxis),
768 ];
769 for (n, ax) in names {
770 if self.src[self.pos..].starts_with(n) {
771 let after = self.pos + n.len();
772 if self.src[after..].starts_with("::") {
773 self.pos = after + 2;
774 return Some(ax);
775 }
776 }
777 }
778 None
779 }
780 fn parse_node_test(&mut self) -> Result<NodeTest, String> {
781 self.skip_ws();
782 if self.starts("*") {
783 self.bump(1);
784 return Ok(NodeTest::Star);
785 }
786 if self.fn_test("node") {
787 return Ok(NodeTest::Node);
788 }
789 if self.fn_test("text") {
790 return Ok(NodeTest::Text);
791 }
792 if self.fn_test("comment") {
793 return Ok(NodeTest::Comment);
794 }
795 if self.src[self.pos..].starts_with("processing-instruction") {
796 self.pos += "processing-instruction".len();
797 self.skip_ws();
798 if !self.starts("(") {
799 return Err("expected (".into());
800 }
801 self.bump(1);
802 self.skip_ws();
803 let lit = if self.starts("'") || self.starts("\"") {
804 Some(self.parse_literal_raw()?)
805 } else {
806 None
807 };
808 self.skip_ws();
809 if self.starts(")") {
810 self.bump(1);
811 }
812 return Ok(NodeTest::Pi(lit));
813 }
814 let name = self.parse_qname()?;
815 let (prefix, local) = split_qname(&name);
816 Ok(NodeTest::Name(local, prefix))
817 }
818 fn fn_test(&mut self, n: &str) -> bool {
819 if self.src[self.pos..].starts_with(n) {
820 let after = self.pos + n.len();
821 let rest = &self.src[after..];
822 let trimmed = rest.trim_start();
823 if trimmed.starts_with("()") || trimmed.starts_with('(') {
824 if let Some(idx) = rest.find(')') {
826 self.pos = after + idx + 1;
827 return true;
828 }
829 }
830 }
831 false
832 }
833 fn parse_preds(&mut self) -> Result<Vec<Expr>, String> {
834 let mut v = Vec::new();
835 loop {
836 self.skip_ws();
837 if self.starts("[") {
838 self.bump(1);
839 v.push(self.parse_expr()?);
840 self.skip_ws();
841 if self.starts("]") {
842 self.bump(1);
843 } else {
844 return Err("expected ]".into());
845 }
846 } else {
847 break;
848 }
849 }
850 Ok(v)
851 }
852 fn parse_filter_or_primary(&mut self) -> Result<Expr, String> {
853 let mut e = self.parse_primary()?;
854 let preds = self.parse_preds()?;
855 if !preds.is_empty() {
856 e = Expr::Filter(Box::new(e), preds);
857 }
858 self.skip_ws();
859 if self.starts("/") {
860 let mut steps = Vec::new();
861 if self.starts("//") {
862 self.bump(2);
863 steps.push(Step {
864 axis: Axis::DescendantOrSelf,
865 test: NodeTest::Node,
866 preds: vec![],
867 });
868 } else {
869 self.bump(1);
870 }
871 steps.extend(self.parse_relative()?);
872 return Ok(Expr::Steps {
873 base: Box::new(e),
874 steps,
875 });
876 }
877 Ok(e)
878 }
879 fn parse_primary(&mut self) -> Result<Expr, String> {
880 self.skip_ws();
881 if self.starts("(") {
882 self.bump(1);
883 let e = self.parse_expr()?;
884 self.skip_ws();
885 if self.starts(")") {
886 self.bump(1);
887 }
888 return Ok(e);
889 }
890 if self.starts("$") {
891 self.bump(1);
892 return Ok(Expr::Var(self.parse_qname()?));
893 }
894 if self.starts("'") || self.starts("\"") {
895 return Ok(Expr::Literal(self.parse_literal_raw()?));
896 }
897 if self.peek().map(|c| c.is_ascii_digit() || c == '.').unwrap_or(false) {
898 return Ok(Expr::Number(self.parse_number()?));
899 }
900 let name = self.parse_qname()?;
901 self.skip_ws();
902 if self.starts("(") {
903 self.bump(1);
904 let mut args = Vec::new();
905 self.skip_ws();
906 if !self.starts(")") {
907 args.push(self.parse_expr()?);
908 loop {
909 self.skip_ws();
910 if self.starts(",") {
911 self.bump(1);
912 args.push(self.parse_expr()?);
913 } else {
914 break;
915 }
916 }
917 }
918 self.skip_ws();
919 if self.starts(")") {
920 self.bump(1);
921 }
922 return Ok(Expr::Fun { name, args });
923 }
924 let (prefix, local) = split_qname(&name);
926 Ok(Expr::Path(PathExpr {
927 abs: false,
928 steps: vec![Step {
929 axis: Axis::Child,
930 test: NodeTest::Name(local, prefix),
931 preds: vec![],
932 }],
933 }))
934 }
935 fn parse_literal_raw(&mut self) -> Result<String, String> {
936 let q = self.peek().ok_or("literal")?;
937 self.bump(1);
938 if let Some(end) = self.src[self.pos..].find(q) {
939 let s = self.src[self.pos..self.pos + end].to_string();
940 self.pos += end + 1;
941 Ok(s)
942 } else {
943 Err("unterminated string".into())
944 }
945 }
946 fn parse_number(&mut self) -> Result<f64, String> {
947 let start = self.pos;
948 while self.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
949 self.bump(1);
950 }
951 if self.peek() == Some('.') {
952 self.bump(1);
953 while self.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
954 self.bump(1);
955 }
956 }
957 if matches!(self.peek(), Some('e' | 'E')) {
958 self.bump(1);
959 if matches!(self.peek(), Some('+' | '-')) {
960 self.bump(1);
961 }
962 while self.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
963 self.bump(1);
964 }
965 }
966 let s = &self.src[start..self.pos];
967 Ok(s.parse::<f64>().unwrap_or(f64::NAN))
968 }
969 fn parse_qname(&mut self) -> Result<String, String> {
970 self.skip_ws();
971 let start = self.pos;
972 let first = self.peek().ok_or_else(|| format!("expected name at {}", &self.src[self.pos..]))?;
973 if !(first.is_ascii_alphabetic() || first == '_' || first == ':') {
974 return Err(format!("expected name at {}", &self.src[self.pos..]));
975 }
976 self.bump(first.len_utf8());
977 while let Some(c) = self.peek() {
978 if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' || c == ':' {
979 self.bump(c.len_utf8());
980 } else {
981 break;
982 }
983 }
984 Ok(self.src[start..self.pos].to_string())
985 }
986 fn keyword(&mut self, kw: &str) -> bool {
987 self.skip_ws();
988 if self.src[self.pos..].starts_with(kw) {
989 let after = self.pos + kw.len();
990 let next = self.src[after..].chars().next();
991 if next.map(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-').unwrap_or(false) {
992 return false;
993 }
994 self.pos = after;
995 true
996 } else {
997 false
998 }
999 }
1000}
1001
1002fn split_qname(n: &str) -> (Option<String>, String) {
1003 if let Some((p, l)) = n.split_once(':') {
1004 (Some(p.to_string()), l.to_string())
1005 } else {
1006 (None, n.to_string())
1007 }
1008}
1009
1010fn eval(expr: &Expr, ctx: &XmlXPathContext<'_>) -> Result<XPathObject, String> {
1013 match expr {
1014 Expr::Or(a, b) => {
1015 let x = xml_xpath_cast_to_boolean(&eval(a, ctx)?);
1016 if x {
1017 Ok(XPathObject::Boolean(true))
1018 } else {
1019 Ok(XPathObject::Boolean(xml_xpath_cast_to_boolean(&eval(b, ctx)?)))
1020 }
1021 }
1022 Expr::And(a, b) => {
1023 let x = xml_xpath_cast_to_boolean(&eval(a, ctx)?);
1024 if !x {
1025 Ok(XPathObject::Boolean(false))
1026 } else {
1027 Ok(XPathObject::Boolean(xml_xpath_cast_to_boolean(&eval(b, ctx)?)))
1028 }
1029 }
1030 Expr::Eq(a, b, eq) => {
1031 let l = eval(a, ctx)?;
1032 let r = eval(b, ctx)?;
1033 Ok(XPathObject::Boolean(compare_eq(&l, &r, ctx) == *eq))
1034 }
1035 Expr::Rel(a, b, ord, or_eq) => {
1036 let ln = xml_xpath_cast_to_number(&eval(a, ctx)?, ctx);
1037 let rn = xml_xpath_cast_to_number(&eval(b, ctx)?, ctx);
1038 let c = ln.partial_cmp(&rn);
1039 let ok = match c {
1040 Some(o) if o == *ord => true,
1041 Some(Ordering::Equal) if *or_eq => true,
1042 _ => false,
1043 };
1044 Ok(XPathObject::Boolean(ok))
1045 }
1046 Expr::Add(a, b, plus) => {
1047 let ln = xml_xpath_cast_to_number(&eval(a, ctx)?, ctx);
1048 let rn = xml_xpath_cast_to_number(&eval(b, ctx)?, ctx);
1049 Ok(XPathObject::Number(if *plus { ln + rn } else { ln - rn }))
1050 }
1051 Expr::Mul(a, b, op) => {
1052 let ln = xml_xpath_cast_to_number(&eval(a, ctx)?, ctx);
1053 let rn = xml_xpath_cast_to_number(&eval(b, ctx)?, ctx);
1054 Ok(XPathObject::Number(match op {
1055 '*' => ln * rn,
1056 'd' => ln / rn,
1057 _ => ln % rn,
1058 }))
1059 }
1060 Expr::Neg(a) => Ok(XPathObject::Number(-xml_xpath_cast_to_number(&eval(a, ctx)?, ctx))),
1061 Expr::Union(a, b) => {
1062 let mut ns = nodeset(eval(a, ctx)?);
1063 ns.extend(nodeset(eval(b, ctx)?));
1064 Ok(XPathObject::NodeSet(sort_unique(ctx.doc, ns)))
1065 }
1066 Expr::Path(p) => eval_path(p, ctx),
1067 Expr::Steps { base, steps } => {
1068 let mut nodes = nodeset(eval(base, ctx)?);
1069 for step in steps {
1070 let mut next = Vec::new();
1071 for n in nodes {
1072 next.extend(axis_nodes(ctx.doc, n, step.axis, &step.test, &ctx.ns));
1073 }
1074 nodes = filter_preds(ctx, sort_unique(ctx.doc, next), &step.preds)?;
1075 }
1076 Ok(XPathObject::NodeSet(nodes))
1077 }
1078 Expr::Filter(e, preds) => {
1079 let o = eval(e, ctx)?;
1080 let ns = nodeset(o);
1081 Ok(XPathObject::NodeSet(filter_preds(ctx, ns, preds)?))
1082 }
1083 Expr::Literal(s) => Ok(XPathObject::String(s.clone())),
1084 Expr::Number(n) => Ok(XPathObject::Number(*n)),
1085 Expr::Var(_) => Err("variables not bound".into()),
1086 Expr::Fun { name, args } => eval_fun(name, args, ctx),
1087 }
1088}
1089
1090fn compare_eq(l: &XPathObject, r: &XPathObject, ctx: &XmlXPathContext<'_>) -> bool {
1091 match (l, r) {
1092 (XPathObject::Boolean(_), _) | (_, XPathObject::Boolean(_)) => {
1093 xml_xpath_cast_to_boolean(l) == xml_xpath_cast_to_boolean(r)
1094 }
1095 (XPathObject::Number(_), _) | (_, XPathObject::Number(_)) => {
1096 xml_xpath_cast_to_number(l, ctx) == xml_xpath_cast_to_number(r, ctx)
1097 }
1098 _ => xml_xpath_cast_to_string(l, ctx) == xml_xpath_cast_to_string(r, ctx),
1099 }
1100}
1101
1102fn nodeset(o: XPathObject) -> Vec<NodeId> {
1103 match o {
1104 XPathObject::NodeSet(v) => v,
1105 _ => vec![],
1106 }
1107}
1108
1109fn sort_unique(doc: &XmlDoc, mut v: Vec<NodeId>) -> Vec<NodeId> {
1110 let order = xml_xpath_order_doc_elems(doc);
1111 v.sort_by_key(|n| order.iter().position(|x| x == n).unwrap_or(usize::MAX));
1112 v.dedup();
1113 v
1114}
1115
1116fn eval_path(path: &PathExpr, ctx: &XmlXPathContext<'_>) -> Result<XPathObject, String> {
1117 let mut nodes = if path.abs {
1118 vec![NodeId::DOCUMENT]
1119 } else {
1120 vec![ctx.node]
1121 };
1122 if path.steps.is_empty() && path.abs {
1123 return Ok(XPathObject::NodeSet(nodes));
1124 }
1125 for step in &path.steps {
1126 let mut next = Vec::new();
1127 for n in nodes {
1128 next.extend(axis_nodes(ctx.doc, n, step.axis, &step.test, &ctx.ns));
1129 }
1130 nodes = filter_preds(ctx, sort_unique(ctx.doc, next), &step.preds)?;
1131 }
1132 Ok(XPathObject::NodeSet(nodes))
1133}
1134
1135fn filter_preds(
1136 ctx: &XmlXPathContext<'_>,
1137 nodes: Vec<NodeId>,
1138 preds: &[Expr],
1139) -> Result<Vec<NodeId>, String> {
1140 let mut cur = nodes;
1141 for pred in preds {
1142 let size = cur.len();
1143 let mut kept = Vec::new();
1144 for (i, n) in cur.iter().enumerate() {
1145 let c2 = XmlXPathContext {
1146 doc: ctx.doc,
1147 node: *n,
1148 position: i + 1,
1149 size,
1150 ns: ctx.ns.clone(),
1151 };
1152 let v = eval(pred, &c2)?;
1153 let pass = match v {
1154 XPathObject::Number(num) => (num as usize) == c2.position,
1155 other => xml_xpath_cast_to_boolean(&other),
1156 };
1157 if pass {
1158 kept.push(*n);
1159 }
1160 }
1161 cur = kept;
1162 }
1163 Ok(cur)
1164}
1165
1166fn axis_nodes(
1167 doc: &XmlDoc,
1168 n: NodeId,
1169 axis: Axis,
1170 test: &NodeTest,
1171 ns: &[(String, String)],
1172) -> Vec<NodeId> {
1173 let mut raw = Vec::new();
1174 match axis {
1175 Axis::SelfAxis => raw.push(n),
1176 Axis::Child => {
1177 let mut c = doc.first_child(n);
1178 while let Some(x) = c {
1179 raw.push(x);
1180 c = doc.next_sibling(x);
1181 }
1182 }
1183 Axis::Attribute => {
1184 let mut a = doc.first_attr(n);
1185 while let Some(x) = a {
1186 raw.push(x);
1187 a = doc.next_sibling(x);
1188 }
1189 }
1190 Axis::Parent => {
1191 if let Some(p) = doc.parent(n) {
1192 raw.push(p);
1193 }
1194 }
1195 Axis::Ancestor => {
1196 let mut p = doc.parent(n);
1197 while let Some(x) = p {
1198 raw.push(x);
1199 p = doc.parent(x);
1200 }
1201 }
1202 Axis::AncestorOrSelf => {
1203 raw.push(n);
1204 let mut p = doc.parent(n);
1205 while let Some(x) = p {
1206 raw.push(x);
1207 p = doc.parent(x);
1208 }
1209 }
1210 Axis::Descendant => collect_desc(doc, n, &mut raw, false),
1211 Axis::DescendantOrSelf => collect_desc(doc, n, &mut raw, true),
1212 Axis::FollowingSibling => {
1213 let mut s = doc.next_sibling(n);
1214 while let Some(x) = s {
1215 raw.push(x);
1216 s = doc.next_sibling(x);
1217 }
1218 }
1219 Axis::PrecedingSibling => {
1220 let mut s = doc.prev_sibling(n);
1221 let mut v = Vec::new();
1222 while let Some(x) = s {
1223 v.push(x);
1224 s = doc.prev_sibling(x);
1225 }
1226 v.reverse();
1227 raw.extend(v);
1228 }
1229 Axis::Following => {
1230 let mut cur = n;
1231 loop {
1232 if let Some(s) = doc.next_sibling(cur) {
1233 collect_desc(doc, s, &mut raw, true);
1234 let mut t = doc.next_sibling(s);
1235 while let Some(x) = t {
1236 collect_desc(doc, x, &mut raw, true);
1237 t = doc.next_sibling(x);
1238 }
1239 cur = s;
1240 if let Some(p) = doc.parent(cur) {
1242 cur = p;
1243 continue;
1244 }
1245 } else if let Some(p) = doc.parent(cur) {
1246 cur = p;
1247 continue;
1248 }
1249 break;
1250 }
1251 }
1252 Axis::Preceding => {
1253 let order = xml_xpath_order_doc_elems(doc);
1255 let mut ancestors = Vec::new();
1256 let mut p = Some(n);
1257 while let Some(x) = p {
1258 ancestors.push(x);
1259 p = doc.parent(x);
1260 }
1261 if let Some(idx) = order.iter().position(|x| *x == n) {
1262 for &id in &order[..idx] {
1263 if !ancestors.contains(&id) {
1264 raw.push(id);
1265 }
1266 }
1267 }
1268 }
1269 Axis::Namespace => {
1270 for (pre, _) in doc.ns_defs(n) {
1271 let dummy = n; let _ = (pre, dummy);
1273 }
1274 }
1275 }
1276 raw.into_iter()
1277 .filter(|id| node_test(doc, *id, test, ns, axis))
1278 .collect()
1279}
1280
1281fn collect_desc(doc: &XmlDoc, n: NodeId, out: &mut Vec<NodeId>, include_self: bool) {
1282 if include_self {
1283 out.push(n);
1284 }
1285 let mut c = doc.first_child(n);
1286 while let Some(x) = c {
1287 collect_desc(doc, x, out, true);
1288 c = doc.next_sibling(x);
1289 }
1290}
1291
1292fn node_test(
1293 doc: &XmlDoc,
1294 id: NodeId,
1295 test: &NodeTest,
1296 ns: &[(String, String)],
1297 axis: Axis,
1298) -> bool {
1299 match test {
1300 NodeTest::Node => true,
1301 NodeTest::Star => {
1302 if axis == Axis::Attribute {
1303 doc.kind(id) == NodeKind::Attribute
1304 } else {
1305 doc.kind(id) == NodeKind::Element
1306 }
1307 }
1308 NodeTest::Text => doc.kind(id) == NodeKind::Text || doc.kind(id) == NodeKind::CData,
1309 NodeTest::Comment => doc.kind(id) == NodeKind::Comment,
1310 NodeTest::Pi(t) => {
1311 doc.kind(id) == NodeKind::Pi && t.as_deref().map(|x| x == doc.name(id)).unwrap_or(true)
1312 }
1313 NodeTest::Name(local, prefix) => {
1314 let kind_ok = if axis == Axis::Attribute {
1315 doc.kind(id) == NodeKind::Attribute
1316 } else {
1317 doc.kind(id) == NodeKind::Element
1318 };
1319 if !kind_ok || doc.name(id) != local {
1320 return false;
1321 }
1322 if let Some(p) = prefix {
1323 let href = ns.iter().find(|(a, _)| a == p).map(|(_, h)| h.as_str());
1324 match href {
1325 Some(h) => doc.ns_uri(id) == Some(h),
1326 None => doc.prefix(id) == Some(p.as_str()),
1327 }
1328 } else {
1329 true
1330 }
1331 }
1332 }
1333}
1334
1335fn eval_fun(name: &str, args: &[Expr], ctx: &XmlXPathContext<'_>) -> Result<XPathObject, String> {
1336 let ev = |i: usize| eval(&args[i], ctx);
1337 let local = name.rsplit(':').next().unwrap_or(name);
1338 match local {
1339 "true" => Ok(XPathObject::Boolean(true)),
1340 "false" => Ok(XPathObject::Boolean(false)),
1341 "not" => Ok(XPathObject::Boolean(!xml_xpath_cast_to_boolean(&ev(0)?))),
1342 "boolean" => Ok(XPathObject::Boolean(xml_xpath_cast_to_boolean(&ev(0)?))),
1343 "number" => {
1344 let o = if args.is_empty() {
1345 XPathObject::NodeSet(vec![ctx.node])
1346 } else {
1347 ev(0)?
1348 };
1349 Ok(XPathObject::Number(xml_xpath_cast_to_number(&o, ctx)))
1350 }
1351 "string" => {
1352 let o = if args.is_empty() {
1353 XPathObject::NodeSet(vec![ctx.node])
1354 } else {
1355 ev(0)?
1356 };
1357 Ok(XPathObject::String(xml_xpath_cast_to_string(&o, ctx)))
1358 }
1359 "last" => Ok(XPathObject::Number(ctx.size as f64)),
1360 "position" => Ok(XPathObject::Number(ctx.position as f64)),
1361 "count" => Ok(XPathObject::Number(nodeset(ev(0)?).len() as f64)),
1362 "local-name" => {
1363 let ns = if args.is_empty() {
1364 vec![ctx.node]
1365 } else {
1366 nodeset(ev(0)?)
1367 };
1368 Ok(XPathObject::String(
1369 ns.first().map(|id| ctx.doc.name(*id).to_string()).unwrap_or_default(),
1370 ))
1371 }
1372 "name" => {
1373 let ns = if args.is_empty() {
1374 vec![ctx.node]
1375 } else {
1376 nodeset(ev(0)?)
1377 };
1378 Ok(XPathObject::String(
1379 ns.first().map(|id| ctx.doc.qname(*id)).unwrap_or_default(),
1380 ))
1381 }
1382 "namespace-uri" => {
1383 let ns = if args.is_empty() {
1384 vec![ctx.node]
1385 } else {
1386 nodeset(ev(0)?)
1387 };
1388 Ok(XPathObject::String(
1389 ns.first()
1390 .and_then(|id| ctx.doc.ns_uri(*id).map(str::to_string))
1391 .unwrap_or_default(),
1392 ))
1393 }
1394 "concat" => {
1395 let mut s = String::new();
1396 for a in args {
1397 s.push_str(&xml_xpath_cast_to_string(&eval(a, ctx)?, ctx));
1398 }
1399 Ok(XPathObject::String(s))
1400 }
1401 "starts-with" => {
1402 let a = xml_xpath_cast_to_string(&ev(0)?, ctx);
1403 let b = xml_xpath_cast_to_string(&ev(1)?, ctx);
1404 Ok(XPathObject::Boolean(a.starts_with(&b)))
1405 }
1406 "contains" => {
1407 let a = xml_xpath_cast_to_string(&ev(0)?, ctx);
1408 let b = xml_xpath_cast_to_string(&ev(1)?, ctx);
1409 Ok(XPathObject::Boolean(a.contains(&b)))
1410 }
1411 "substring-before" => {
1412 let a = xml_xpath_cast_to_string(&ev(0)?, ctx);
1413 let b = xml_xpath_cast_to_string(&ev(1)?, ctx);
1414 Ok(XPathObject::String(
1415 a.split_once(&b).map(|(x, _)| x.to_string()).unwrap_or_default(),
1416 ))
1417 }
1418 "substring-after" => {
1419 let a = xml_xpath_cast_to_string(&ev(0)?, ctx);
1420 let b = xml_xpath_cast_to_string(&ev(1)?, ctx);
1421 Ok(XPathObject::String(
1422 a.split_once(&b).map(|(_, x)| x.to_string()).unwrap_or_default(),
1423 ))
1424 }
1425 "substring" => {
1426 let s = xml_xpath_cast_to_string(&ev(0)?, ctx);
1427 let start = xpath_round(xml_xpath_cast_to_number(&ev(1)?, ctx));
1428 let end = if args.len() > 2 {
1429 start + xpath_round(xml_xpath_cast_to_number(&ev(2)?, ctx))
1430 } else {
1431 f64::INFINITY
1432 };
1433 let out: String = s
1434 .chars()
1435 .enumerate()
1436 .filter(|(i, _)| {
1437 let pos = (*i as f64) + 1.0;
1438 pos >= start && pos < end
1439 })
1440 .map(|(_, c)| c)
1441 .collect();
1442 Ok(XPathObject::String(out))
1443 }
1444 "string-length" => {
1445 let s = if args.is_empty() {
1446 ctx.doc.xml_node_get_content(ctx.node)
1447 } else {
1448 xml_xpath_cast_to_string(&ev(0)?, ctx)
1449 };
1450 Ok(XPathObject::Number(s.chars().count() as f64))
1451 }
1452 "normalize-space" => {
1453 let s = if args.is_empty() {
1454 ctx.doc.xml_node_get_content(ctx.node)
1455 } else {
1456 xml_xpath_cast_to_string(&ev(0)?, ctx)
1457 };
1458 Ok(XPathObject::String(
1459 s.split_whitespace().collect::<Vec<_>>().join(" "),
1460 ))
1461 }
1462 "translate" => {
1463 let s = xml_xpath_cast_to_string(&ev(0)?, ctx);
1464 let from: Vec<char> = xml_xpath_cast_to_string(&ev(1)?, ctx).chars().collect();
1465 let to: Vec<char> = xml_xpath_cast_to_string(&ev(2)?, ctx).chars().collect();
1466 let out: String = s
1467 .chars()
1468 .filter_map(|c| {
1469 if let Some(i) = from.iter().position(|x| *x == c) {
1470 to.get(i).copied()
1471 } else {
1472 Some(c)
1473 }
1474 })
1475 .collect();
1476 Ok(XPathObject::String(out))
1477 }
1478 "floor" => Ok(XPathObject::Number(
1479 xml_xpath_cast_to_number(&ev(0)?, ctx).floor(),
1480 )),
1481 "ceiling" => Ok(XPathObject::Number(
1482 xml_xpath_cast_to_number(&ev(0)?, ctx).ceil(),
1483 )),
1484 "round" => {
1485 let n = xml_xpath_cast_to_number(&ev(0)?, ctx);
1486 Ok(XPathObject::Number(xpath_round(n)))
1487 }
1488 "sum" => {
1489 let ns = nodeset(ev(0)?);
1490 let mut t = 0.0;
1491 for id in ns {
1492 t += xpath_number(&ctx.doc.xml_node_get_content(id));
1493 }
1494 Ok(XPathObject::Number(t))
1495 }
1496 "id" => {
1497 let ids = xml_xpath_cast_to_string(&ev(0)?, ctx);
1498 let mut found = Vec::new();
1499 let order = xml_xpath_order_doc_elems(ctx.doc);
1500 for tok in ids.split_whitespace() {
1501 for id in &order {
1502 if ctx.doc.kind(*id) == NodeKind::Element
1503 && ctx.doc.xml_get_prop(*id, "id").as_deref() == Some(tok)
1504 {
1505 found.push(*id);
1506 }
1507 }
1508 }
1509 Ok(XPathObject::NodeSet(sort_unique(ctx.doc, found)))
1510 }
1511 "lang" => {
1512 let want = xml_xpath_cast_to_string(&ev(0)?, ctx).to_ascii_lowercase();
1513 let mut cur = Some(ctx.node);
1514 let mut lang = None;
1515 while let Some(id) = cur {
1516 if let Some(l) = ctx.doc.xml_get_prop(id, "lang") {
1517 lang = Some(l);
1518 break;
1519 }
1520 cur = ctx.doc.parent(id);
1521 }
1522 let ok = lang
1523 .map(|l| {
1524 let l = l.to_ascii_lowercase();
1525 l == want || l.starts_with(&format!("{want}-"))
1526 })
1527 .unwrap_or(false);
1528 Ok(XPathObject::Boolean(ok))
1529 }
1530 _ => Err(format!("unknown function {name}")),
1531 }
1532}
1533
1534fn xpath_round(n: f64) -> f64 {
1535 if n.is_nan() || n.is_infinite() {
1536 return n;
1537 }
1538 if n == 0.0 {
1539 return n;
1540 }
1541 if n >= 0.0 {
1543 (n + 0.5).floor()
1544 } else {
1545 let f = (n.abs() + 0.5).floor();
1546 if (n.abs() - n.abs().floor()) == 0.5 {
1547 -n.abs().floor()
1548 } else {
1549 -f
1550 }
1551 }
1552}