use core::fmt::{self, Display, Formatter, Write as _};
use super::ast::{BinaryOp, Expr, NodeTest, Step};
const XML_NAMESPACE: &str = "http://www.w3.org/XML/1998/namespace";
pub(crate) struct Render<'a> {
pub(crate) namespaces:
&'a [(alloc::string::String, alloc::string::String)],
}
impl Display for Expr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Render { namespaces: &[] }.expr(self, f)
}
}
impl Render<'_> {
pub(crate) fn expr(&self, e: &Expr, f: &mut Formatter<'_>) -> fmt::Result {
match e {
Expr::Literal(s) => literal(s, f),
Expr::Number(n) => number(*n, f),
Expr::Negate(inner) => self.negate(inner, f),
Expr::Variable(name) => {
f.write_char('$')?;
self.name(name.namespace.as_deref(), &name.local, f)
}
Expr::Path { absolute, steps } => self.path(*absolute, steps, f),
compound => self.compound(compound, f),
}
}
fn negate(&self, inner: &Expr, f: &mut Formatter<'_>) -> fmt::Result {
let Expr::Negate(operand) = inner else {
f.write_char('-')?;
return self.expr(inner, f);
};
f.write_str("--")?;
if matches!(**operand, Expr::Negate(_)) {
f.write_char('(')?;
self.expr(operand, f)?;
f.write_char(')')
} else {
self.expr(operand, f)
}
}
fn compound(&self, e: &Expr, f: &mut Formatter<'_>) -> fmt::Result {
match e {
Expr::Binary { op, lhs, rhs } => {
f.write_char('(')?;
self.operand(*op, lhs, f)?;
self.operator(*op, rhs, f)?;
f.write_char(')')
}
Expr::Chain { first, rest } => self.chain(first, rest, f),
Expr::Function { func, args } => self.call(func.name(), args, f),
Expr::Filter(filter) => {
self.filter(&filter.base, &filter.predicates, &filter.steps, f)
}
_ => unreachable!("not a compound expression"),
}
}
fn chain(
&self,
first: &Expr,
rest: &[(BinaryOp, Expr)],
f: &mut Formatter<'_>,
) -> fmt::Result {
f.write_char('(')?;
match rest.first() {
Some((op, _)) => self.operand(*op, first, f)?,
None => self.expr(first, f)?,
}
for (op, e) in rest {
self.operator(*op, e, f)?;
}
f.write_char(')')
}
fn operator(
&self,
op: BinaryOp,
rhs: &Expr,
f: &mut Formatter<'_>,
) -> fmt::Result {
write!(f, " {} ", op.symbol())?;
self.operand(op, rhs, f)
}
fn operand(
&self,
op: BinaryOp,
e: &Expr,
f: &mut Formatter<'_>,
) -> fmt::Result {
if (matches!(op, BinaryOp::Union) && matches!(e, Expr::Negate(_)))
|| ends_with_root_path(e)
{
f.write_char('(')?;
self.expr(e, f)?;
f.write_char(')')
} else {
self.expr(e, f)
}
}
fn call(
&self,
name: &str,
args: &[Expr],
f: &mut Formatter<'_>,
) -> fmt::Result {
f.write_str(name)?;
f.write_char('(')?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
self.expr(a, f)?;
}
f.write_char(')')
}
fn filter(
&self,
base: &Expr,
predicates: &[Expr],
steps: &[Step],
f: &mut Formatter<'_>,
) -> fmt::Result {
f.write_char('(')?;
self.expr(base, f)?;
f.write_char(')')?;
self.predicates(predicates, f)?;
for step in steps {
f.write_char('/')?;
self.step(step, f)?;
}
Ok(())
}
fn path(
&self,
absolute: bool,
steps: &[Step],
f: &mut Formatter<'_>,
) -> fmt::Result {
if absolute {
f.write_char('/')?;
}
for (i, step) in steps.iter().enumerate() {
if i > 0 {
f.write_char('/')?;
}
self.step(step, f)?;
}
Ok(())
}
fn step(&self, step: &Step, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(step.axis.name())?;
f.write_str("::")?;
self.node_test(&step.test, f)?;
self.predicates(&step.predicates, f)
}
fn predicates(&self, preds: &[Expr], f: &mut Formatter<'_>) -> fmt::Result {
for p in preds {
f.write_char('[')?;
self.expr(p, f)?;
f.write_char(']')?;
}
Ok(())
}
fn node_test(&self, test: &NodeTest, f: &mut Formatter<'_>) -> fmt::Result {
match test {
NodeTest::Wildcard => f.write_char('*'),
NodeTest::Name { namespace, local } => {
self.name(namespace.as_deref(), local, f)
}
NodeTest::AnyNamespace { local } => write!(f, "*:{local}"),
kind => kind_test(kind, f),
}
}
fn name(
&self,
namespace: Option<&str>,
local: &str,
f: &mut Formatter<'_>,
) -> fmt::Result {
let Some(uri) = namespace else {
return f.write_str(local);
};
if uri == XML_NAMESPACE {
return write!(f, "xml:{local}");
}
let bound = self.namespaces.iter().find(|(_, u)| u == uri);
match bound {
Some((prefix, _)) => write!(f, "{prefix}:{local}"),
None => write!(f, "{{{uri}}}{local}"),
}
}
}
fn ends_with_root_path(mut e: &Expr) -> bool {
while let Expr::Negate(inner) = e {
e = inner;
}
matches!(e, Expr::Path { absolute: true, steps } if steps.is_empty())
}
fn kind_test(test: &NodeTest, f: &mut Formatter<'_>) -> fmt::Result {
match test {
NodeTest::Text => f.write_str("text()"),
NodeTest::Comment => f.write_str("comment()"),
NodeTest::ProcessingInstruction(Some(target)) => {
f.write_str("processing-instruction(")?;
literal(target, f)?;
f.write_char(')')
}
NodeTest::ProcessingInstruction(None) => {
f.write_str("processing-instruction()")
}
_ => f.write_str("node()"),
}
}
fn literal(s: &str, f: &mut Formatter<'_>) -> fmt::Result {
if !s.contains('\'') {
return write!(f, "'{s}'");
}
if !s.contains('"') {
return write!(f, "\"{s}\"");
}
f.write_str("concat(")?;
for (i, piece) in s.split('\'').enumerate() {
if i > 0 {
f.write_str("\"'\", ")?;
}
write!(f, "'{piece}', ")?;
}
f.write_str("'')")
}
fn number(n: f64, f: &mut Formatter<'_>) -> fmt::Result {
if n.is_nan() {
return f.write_str("(0 div 0)");
}
if n.is_infinite() {
let sign = if n.is_sign_negative() { "-" } else { "" };
return write!(f, "{sign}{:0<310}", "1");
}
if n.is_sign_negative() {
return write!(f, "-{}", -n);
}
write!(f, "{n}")
}