use std::collections::HashMap;
use std::rc::Rc;
use crate::item::{Item, Node, NodeType, Sequence};
use crate::output::{OutputDefinition, OutputSpec};
use crate::parser::avt::parse as parse_avt;
use crate::parser::xpath::parse;
use crate::pattern::{Branch, Pattern};
use crate::transform::callable::{ActualParameters, Callable, FormalParameters};
use crate::transform::context::{Context, ContextBuilder};
use crate::transform::numbers::{Level, Numbering};
use crate::transform::template::Template;
use crate::transform::{
Axis, Grouping, KindTest, NameTest, NodeMatch, NodeTest, Order, Transform, WildcardOrName,
WildcardOrNamespaceUri, in_scope_namespaces,
};
use crate::value::Value;
use crate::xdmerror::*;
use qualname::{NamespaceUri, NcName, QName};
use std::convert::TryFrom;
use std::sync::LazyLock;
use url::Url;
static XSLTNS: LazyLock<Option<NamespaceUri>> =
LazyLock::new(|| Some(NamespaceUri::try_from("http://www.w3.org/1999/XSL/Transform").unwrap()));
static XRUSTNS: LazyLock<Option<NamespaceUri>> =
LazyLock::new(|| Some(NamespaceUri::try_from("http://github.com/ballsteve/xrust").unwrap()));
static XSLSTYLESHEET: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("stylesheet").unwrap(), XSLTNS.clone())
});
static XSLTRANSFORM: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("transform").unwrap(), XSLTNS.clone()));
static XSLOUTPUT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("output").unwrap(), XSLTNS.clone()));
static XSLINCLUDE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("include").unwrap(), XSLTNS.clone()));
static XRUSTIMPORT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("import").unwrap(), XRUSTNS.clone()));
static XSLIMPORT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("import").unwrap(), XSLTNS.clone()));
static XSLATTRIBUTESET: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("attribute-set").unwrap(), XSLTNS.clone())
});
static XSLATTRIBUTE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("attribute").unwrap(), XSLTNS.clone()));
static XSLTEMPLATE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("template").unwrap(), XSLTNS.clone()));
static XSLKEY: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("key").unwrap(), XSLTNS.clone()));
static XSLPARAM: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("param").unwrap(), XSLTNS.clone()));
static XSLFUNCTION: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("function").unwrap(), XSLTNS.clone()));
static XSLVARIABLE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("variable").unwrap(), XSLTNS.clone()));
static XSLVALUEOF: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("value-of").unwrap(), XSLTNS.clone()));
static XSLTEXT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("text").unwrap(), XSLTNS.clone()));
static XSLAPPLYTEMPLATES: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("apply-templates").unwrap(), XSLTNS.clone())
});
static XSLAPPLYIMPORTS: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("apply-imports").unwrap(), XSLTNS.clone())
});
static XSLSEQUENCE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("sequence").unwrap(), XSLTNS.clone()));
static XSLIF: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("if").unwrap(), XSLTNS.clone()));
static XSLCHOOSE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("choose").unwrap(), XSLTNS.clone()));
static XSLWHEN: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("when").unwrap(), XSLTNS.clone()));
static XSLOTHERWISE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("otherwise").unwrap(), XSLTNS.clone()));
static XSLFOREACH: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("for-each").unwrap(), XSLTNS.clone()));
static XSLFOREACHGROUP: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("for-each-group").unwrap(), XSLTNS.clone())
});
static XSLCOPY: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("copy").unwrap(), XSLTNS.clone()));
static XSLCOPYOF: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("copy-of").unwrap(), XSLTNS.clone()));
static XSLCALLTEMPLATE: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("call-template").unwrap(), XSLTNS.clone())
});
static XSLWITHPARAM: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("with-param").unwrap(), XSLTNS.clone())
});
static XSLELEMENT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("element").unwrap(), XSLTNS.clone()));
static XSLCOMMENT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("comment").unwrap(), XSLTNS.clone()));
static XSLPROCESSINGINSTRUCTION: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(
NcName::try_from("processing-instruction").unwrap(),
XSLTNS.clone(),
)
});
static XSLMESSAGE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("message").unwrap(), XSLTNS.clone()));
static XSLNUMBER: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("number").unwrap(), XSLTNS.clone()));
static XSLDECIMALFORMAT: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("decimal-format").unwrap(), XSLTNS.clone())
});
static XSLSORT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("sort").unwrap(), XSLTNS.clone()));
static XSLSTRIPSPACE: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("strip-space").unwrap(), XSLTNS.clone())
});
static XSLPRESERVESPACE: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("preserve-space").unwrap(), XSLTNS.clone())
});
static ATTRINDENT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("indent").unwrap(), None));
static ATTRHREF: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("href").unwrap(), None));
static ATTRNAME: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("name").unwrap(), None));
static ATTRMATCH: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("match").unwrap(), None));
static ATTRMODE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("mode").unwrap(), None));
static ATTRPRIORITY: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("priority").unwrap(), None));
static ATTRUSE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("use").unwrap(), None));
static ATTRSELECT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("select").unwrap(), None));
static ATTRDOE: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(NcName::try_from("disable-output-escaping").unwrap(), None)
});
static ATTRTEST: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("test").unwrap(), None));
static ATTRGROUPBY: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("group-by").unwrap(), None));
static ATTRGROUPADJACENT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("group-adjacent").unwrap(), None));
static ATTRGROUPSTARTINGWITH: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("group-starting-with").unwrap(), None));
static ATTRGROUPENDINGWITH: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("group-ending-with").unwrap(), None));
static XSLATTRUSEATTRIBUTESETS: LazyLock<QName> = LazyLock::new(|| {
QName::new_from_parts(
NcName::try_from("use-attribute-sets").unwrap(),
XSLTNS.clone(),
)
});
static ATTRTERMINATE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("terminate").unwrap(), None));
static ATTRVALUE: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("value").unwrap(), None));
static ATTRLEVEL: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("level").unwrap(), None));
static ATTRCOUNT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("count").unwrap(), None));
static ATTRFROM: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("from").unwrap(), None));
static ATTRFORMAT: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("format").unwrap(), None));
static ATTRORDER: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("order").unwrap(), None));
static ATTRELEMENTS: LazyLock<QName> =
LazyLock::new(|| QName::new_from_parts(NcName::try_from("elements").unwrap(), None));
pub trait XSLT: Node {
fn transform<N: Node, F, G>(
&self,
src: Rc<Item<N>>,
b: Option<Url>,
f: F,
g: G,
) -> Result<Sequence<N>, Error>
where
F: Fn(&str) -> Result<N, Error>,
G: Fn(&Url) -> Result<String, Error>;
}
pub fn from_document<N: Node, F, G>(
styledoc: N,
base: Option<Url>,
f: F,
g: G,
) -> Result<Context<N>, Error>
where
F: Fn(&str) -> Result<N, Error>,
G: Fn(&Url) -> Result<String, Error>,
{
let mut rnit = styledoc.child_iter();
let stylenode = match rnit.next() {
Some(root) => {
if !(root
.name()
.is_some_and(|rn| rn == *XSLSTYLESHEET || rn == *XSLTRANSFORM))
{
return Result::Err(Error::new(
ErrorKind::TypeError,
String::from("not an XSLT stylesheet"),
));
} else {
root
}
}
None => {
return Result::Err(Error::new(
ErrorKind::TypeError,
String::from("document does not have document element"),
));
}
};
if rnit.next().is_some() {
return Result::Err(Error::new(
ErrorKind::TypeError,
String::from("extra element: not an XSLT stylesheet"),
));
}
strip_whitespace(
styledoc.clone(),
true,
&vec![NodeTest::Name(NameTest::Wildcard(
WildcardOrNamespaceUri::Wildcard,
WildcardOrName::Wildcard,
))],
&vec![NodeTest::Name(NameTest::Name(XSLTEXT.clone()))],
)?;
let mut od = OutputDefinition::new();
if let Some(c) = stylenode
.child_iter()
.find(|c| !(c.is_element() && c.name().is_some_and(|cn| cn == *XSLOUTPUT)))
{
let b: bool = matches!(
c.get_attribute(&ATTRINDENT).to_string().as_str(),
"yes" | "true" | "1"
);
od.set_indent(b);
};
stylenode
.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLINCLUDE))
.try_for_each(|mut c| {
let h = c.get_attribute(&ATTRHREF);
let url = match base.clone().map_or_else(
|| Url::parse(h.to_string().as_str()),
|full| full.join(h.to_string().as_str()),
) {
Ok(u) => u,
Err(_) => {
return Result::Err(Error::new(
ErrorKind::Unknown,
format!(
"unable to parse href URL \"{}\" baseurl \"{}\"",
h,
base.clone()
.map_or(String::from("--no base--"), |b| b.to_string())
),
));
}
};
let xml = g(&url)?;
let module = f(xml.as_str().trim())?;
let moddoc = module.first_child().unwrap();
moddoc.child_iter().try_for_each(|mc| {
c.insert_before(mc)?;
Ok::<(), Error>(())
})?;
c.pop()?;
Ok(())
})?;
stylenode
.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLIMPORT))
.try_for_each(|mut c| {
let h = c.get_attribute(&ATTRHREF);
let url = match base.clone().map_or_else(
|| Url::parse(h.to_string().as_str()),
|full| full.join(h.to_string().as_str()),
) {
Ok(u) => u,
Err(_) => {
return Result::Err(Error::new(
ErrorKind::Unknown,
format!(
"unable to parse href URL \"{}\" baseurl \"{}\"",
h,
base.clone()
.map_or(String::from("--no base--"), |b| b.to_string())
),
));
}
};
let xml = g(&url)?;
let module = f(xml.as_str().trim())?;
let moddoc = module.first_child().unwrap();
moddoc.child_iter().try_for_each(|mc| {
if mc.node_type() == NodeType::Element {
let newnode = mc.deep_copy()?;
let newat =
styledoc.new_attribute(XRUSTIMPORT.clone(), Rc::new(Value::from(1)))?;
newnode.add_attribute(newat)?;
c.insert_before(newnode)?;
} else {
let newnode = mc.deep_copy()?;
c.insert_before(newnode)?;
}
Ok::<(), Error>(())
})?;
c.pop()?;
Ok::<(), Error>(())
})?;
let mut attr_sets: HashMap<QName, Vec<Transform<N>>> = HashMap::new();
stylenode
.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLATTRIBUTESET))
.try_for_each(|c| {
let name = c.get_attribute(&ATTRNAME);
let eqname = c.to_qname(name.to_string())?;
if eqname.to_string().is_empty() {
return Err(Error::new(
ErrorKind::DynamicAbsent,
"attribute sets must have a name",
));
}
let mut attrs = vec![];
c.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLATTRIBUTE))
.try_for_each(|a| {
attrs.push(to_transform(a, &attr_sets)?);
Ok(())
})?;
attr_sets.insert(eqname, attrs);
Ok(())
})?;
let mut templates: Vec<Template<N>> = vec![];
stylenode
.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLTEMPLATE))
.filter(|c| c.get_attribute_node(&ATTRMATCH).is_some())
.try_for_each(|c| {
let m = c.get_attribute(&ATTRMATCH);
let pat = Pattern::try_from((m.to_string(), c.clone())).map_err(|e| {
Error::new(
e.kind,
format!(
"Error parsing match pattern \"{}\": {}",
m.to_string(),
e.message
),
)
})?;
if pat.is_err() {
return Err(pat.get_err().unwrap());
}
if let Pattern::Selection(Branch::Error(e)) = pat {
return Err(e.clone());
}
let mut body = vec![];
let mode = c.get_attribute_node(&ATTRMODE);
c.child_iter().try_for_each(|d| {
body.push(to_transform(d, &attr_sets)?);
Ok::<(), Error>(())
})?;
let pr = c.get_attribute(&ATTRPRIORITY);
let prio: f64 = match pr.to_string().as_str() {
"" => {
match &pat {
Pattern::Predicate(p) => match p {
Transform::Empty => -1.0,
_ => 1.0,
},
Pattern::Selection(s) => {
if let Branch::Error(e) = s {
return Err(e.clone());
}
let (t, nt, q) = s.terminal_node_test();
match (t, nt) {
(Axis::SelfAttribute, _) => -0.5,
(Axis::SelfAxis, Axis::Parent)
| (Axis::SelfAxis, Axis::Ancestor)
| (Axis::SelfAxis, Axis::AncestorOrSelf) => match q {
NodeTest::Name(nm) => match nm {
NameTest::Wildcard(_, _) => -0.5,
_ => 0.0,
},
NodeTest::Kind(_kt) => -0.5,
},
_ => 0.5,
}
}
_ => -1.0,
}
}
_ => pr.to_string().parse::<f64>().unwrap(), };
let mut import: usize = 0;
let im = c.get_attribute(&XRUSTIMPORT);
if im.to_string() != "" {
import = im.to_int()? as usize
}
let mut qmode = None;
if let Some(modenode) = mode {
qmode = Some(modenode.to_qname(modenode.to_string())?)
}
templates.push(Template::new(
pat,
Transform::SequenceItems(body),
Some(prio),
vec![import],
None,
qmode,
m.to_string(),
));
Ok::<(), Error>(())
})?;
let mut keys = vec![];
stylenode
.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLKEY))
.try_for_each(|c| {
let name = c.get_attribute(&ATTRNAME);
let m = c.get_attribute(&ATTRMATCH);
let pat = Pattern::try_from(m.to_string())?;
let u = c.get_attribute(&ATTRUSE);
keys.push((
name,
pat,
parse::<N>(&u.to_string(), Some(c.clone()), None)?,
));
Ok(())
})?;
let mut newctxt = ContextBuilder::new()
.template(Template::new(
Pattern::try_from("/")?,
Transform::ApplyTemplates(
Box::new(Transform::Step(NodeMatch::new(
Axis::Child,
NodeTest::Kind(KindTest::Any),
))),
None,
vec![],
),
None,
vec![0],
None,
None,
String::from("/"),
))
.template(Template::new(
Pattern::try_from("child::*")?,
Transform::ApplyTemplates(
Box::new(Transform::Step(NodeMatch::new(
Axis::Child,
NodeTest::Kind(KindTest::Any),
))),
None,
vec![],
),
None,
vec![0],
None,
None,
String::from("child::*"),
))
.template(Template::new(
Pattern::try_from("child::text()")?,
Transform::ContextItem,
None,
vec![0],
None,
None,
String::from("child::text()"),
))
.template_all(templates)
.output_definition(od)
.build();
keys.iter()
.for_each(|(name, m, u)| newctxt.declare_key(name.to_string(), m.clone(), u.clone()));
stylenode
.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLTEMPLATE))
.filter(|c| !c.get_attribute(&ATTRNAME).to_string().is_empty())
.try_for_each(|c| {
let name = c.get_attribute(&ATTRNAME);
let mut params: Vec<(QName, Option<Transform<N>>)> = Vec::new();
c.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLPARAM))
.try_for_each(|c| {
let p_name = c.get_attribute(&ATTRNAME);
if p_name.to_string().is_empty() {
Err(Error::new(
ErrorKind::StaticAbsent,
"name attribute is missing",
))
} else {
let sel = c.get_attribute(&ATTRSELECT);
if sel.to_string().is_empty() {
let mut body = vec![];
c.child_iter().try_for_each(|d| {
body.push(to_transform(d, &attr_sets)?);
Ok(())
})?;
params.push((
QName::from_local_name(
NcName::try_from(p_name.to_string().as_str()).map_err(
|_| Error::new(ErrorKind::ParseError, "not a QName"),
)?,
),
Some(Transform::SequenceItems(body)),
));
Ok(())
} else {
params.push((
QName::from_local_name(
NcName::try_from(p_name.to_string().as_str()).map_err(
|_| Error::new(ErrorKind::ParseError, "not a QName"),
)?,
),
Some(parse::<N>(&sel.to_string(), Some(c.clone()), None)?),
));
Ok(())
}
}
})?;
let mut body = vec![];
c.child_iter()
.filter(|c| !(c.is_element() && c.name().is_some_and(|cn| cn == *XSLPARAM)))
.try_for_each(|d| {
body.push(to_transform(d, &attr_sets)?);
Ok::<(), Error>(())
})?;
newctxt.callable_push(
QName::from_local_name(
NcName::try_from(name.to_string().as_str())
.map_err(|_| Error::new(ErrorKind::ParseError, "not a QName"))?,
),
Callable::new(
Transform::SequenceItems(body),
FormalParameters::Named(params),
),
);
Ok(())
})?;
stylenode
.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLFUNCTION))
.try_for_each(|c| {
let name = c.get_attribute(&ATTRNAME);
let eqname = c.to_qname(name.to_string())?;
if eqname.namespace_uri().is_none() {
return Err(Error::new_with_code(
ErrorKind::StaticAbsent,
"function name must have a namespace",
Some(QName::from_local_name(
NcName::try_from("XTSE0740").unwrap(),
)),
));
}
let mut params: Vec<QName> = Vec::new();
c.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLPARAM))
.try_for_each(|c| {
let p_name = c.get_attribute(&ATTRNAME);
if p_name.to_string().is_empty() {
Err(Error::new(
ErrorKind::StaticAbsent,
"name attribute is missing",
))
} else {
params.push(QName::from_local_name(
NcName::try_from(p_name.to_string().as_str()).map_err(|_| {
Error::new(ErrorKind::ParseError, "not a valid QName")
})?,
));
Ok(())
}
})?;
let mut body = vec![];
c.child_iter()
.filter(|c| !(c.is_element() && c.name().is_some_and(|cn| cn == *XSLPARAM)))
.try_for_each(|d| {
body.push(to_transform(d, &attr_sets)?);
Ok::<(), Error>(())
})?;
newctxt.callable_push(
eqname,
Callable::new(
Transform::SequenceItems(body),
FormalParameters::Positional(params),
),
);
Ok(())
})?;
stylenode
.child_iter()
.filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLVARIABLE))
.try_for_each(|c| {
let name = c.get_attribute(&ATTRNAME).to_string();
if name.is_empty() {
return Err(Error::new(
ErrorKind::StaticAbsent,
"variable must have a name",
));
}
let sel = c.get_attribute(&ATTRSELECT).to_string();
if sel.is_empty() {
newctxt.pre_var_push(
name,
Transform::SequenceItems(c.child_iter().try_fold(vec![], |mut body, e| {
body.push(to_transform(e, &attr_sets)?);
Ok(body)
})?),
);
Ok(())
} else {
newctxt.pre_var_push(name, parse::<N>(&sel.to_string(), Some(c.clone()), None)?);
Ok(())
}
})?;
Ok(newctxt)
}
fn to_transform<N: Node>(
n: N,
attr_sets: &HashMap<QName, Vec<Transform<N>>>,
) -> Result<Transform<N>, Error> {
match n.node_type() {
NodeType::Text => Ok(Transform::Literal(Item::Value(Rc::new(Value::from(
n.to_string(),
))))),
NodeType::Element => {
let qn = n.name().unwrap();
if qn == *XSLTEXT {
let doe = n.get_attribute(&ATTRDOE);
if !doe.to_string().is_empty() {
match &doe.to_string()[..] {
"yes" => Ok(Transform::Literal(Item::Value(Rc::new(Value::from(
n.to_string(),
))))),
"no" => {
let text = n
.to_string()
.replace('&', "&")
.replace('>', ">")
.replace('<', "<")
.replace('\'', "'")
.replace('\"', """);
Ok(Transform::Literal(Item::Value(Rc::new(Value::from(text)))))
}
_ => Err(Error::new(
ErrorKind::TypeError,
"disable-output-escaping only accepts values yes or no.".to_string(),
)),
}
} else {
let text = n
.to_string()
.replace('&', "&")
.replace('>', ">")
.replace('<', "<")
.replace('\'', "'")
.replace('\"', """);
Ok(Transform::Literal(Item::Value(Rc::new(Value::from(text)))))
}
} else if qn == *XSLVALUEOF {
let sel = n.get_attribute(&ATTRSELECT);
let doe = n.get_attribute(&ATTRDOE);
if !doe.to_string().is_empty() {
match &doe.to_string()[..] {
"yes" => Ok(Transform::LiteralText(
Box::new(parse::<N>(&sel.to_string(), Some(n.clone()), None)?),
OutputSpec::NoEscape,
)),
"no" => Ok(Transform::LiteralText(
Box::new(parse::<N>(&sel.to_string(), Some(n.clone()), None)?),
OutputSpec::Normal,
)),
_ => Err(Error::new(
ErrorKind::TypeError,
"disable-output-escaping only accepts values yes or no.".to_string(),
)),
}
} else {
Ok(Transform::LiteralText(
Box::new(parse::<N>(&sel.to_string(), Some(n.clone()), None)?),
OutputSpec::Normal,
))
}
} else if qn == *XSLAPPLYTEMPLATES {
let sel = n.get_attribute(&ATTRSELECT);
let m = n.get_attribute_node(&ATTRMODE);
let mut qm = None;
if let Some(s) = m {
qm = Some(n.to_qname(s.value().to_string())?)
}
let sort_keys = get_sort_keys(&n)?;
if !sel.to_string().is_empty() {
Ok(Transform::ApplyTemplates(
Box::new(parse::<N>(&sel.to_string(), Some(n.clone()), None)?),
qm,
sort_keys,
)) } else {
Ok(Transform::ApplyTemplates(
Box::new(Transform::Step(NodeMatch::new(
Axis::Child,
NodeTest::Kind(KindTest::Any),
))),
qm,
sort_keys,
)) }
} else if qn == *XSLAPPLYIMPORTS {
Ok(Transform::ApplyImports)
} else if qn == *XSLSEQUENCE {
let s = n.get_attribute(&ATTRSELECT);
if !s.to_string().is_empty() {
Ok(parse::<N>(&s.to_string(), Some(n.clone()), None)?)
} else {
Result::Err(Error::new(
ErrorKind::TypeError,
"missing select attribute".to_string(),
))
}
} else if qn == *XSLIF {
let t = n.get_attribute(&ATTRTEST);
if !t.to_string().is_empty() {
Ok(Transform::Switch(
vec![(
parse::<N>(&t.to_string(), Some(n.clone()), None)?,
Transform::SequenceItems(n.child_iter().try_fold(
vec![],
|mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
},
)?),
)],
Box::new(Transform::Empty),
))
} else {
Result::Err(Error::new(
ErrorKind::TypeError,
"missing test attribute".to_string(),
))
}
} else if qn == *XSLCHOOSE {
let mut clauses: Vec<(Transform<N>, Transform<N>)> = Vec::new();
let mut otherwise: Option<Transform<N>> = None;
let mut status: Option<Error> = None;
n.child_iter().try_for_each(|m| {
match m.node_type() {
NodeType::Element => {
let mn = m.name().unwrap();
if mn == *XSLWHEN {
if otherwise.is_none() {
let t = m.get_attribute(&ATTRTEST);
if !t.to_string().is_empty() {
clauses.push((
parse::<N>(&t.to_string(), Some(n.clone()), None)?,
Transform::SequenceItems(m.child_iter().try_fold(
vec![],
|mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
},
)?),
));
} else {
status.replace(Error::new(
ErrorKind::TypeError,
"missing test attribute".to_string(),
));
}
} else {
status.replace(Error::new(
ErrorKind::TypeError,
"invalid content in choose element: when follows otherwise"
.to_string(),
));
}
} else if mn == *XSLOTHERWISE {
if !clauses.is_empty() {
otherwise = Some(Transform::SequenceItems(
m.child_iter().try_fold(vec![], |mut o, e| {
o.push(to_transform(e, attr_sets)?);
Ok(o)
})?,
));
} else {
status.replace(Error::new(
ErrorKind::TypeError,
"invalid content in choose element: no when elements"
.to_string(),
));
}
} else {
status.replace(Error::new(
ErrorKind::TypeError,
"invalid element content in choose element".to_string(),
));
}
}
NodeType::Text => {
if !n.to_string().trim().is_empty() {
status.replace(Error::new(
ErrorKind::TypeError,
"invalid text content in choose element".to_string(),
));
}
}
NodeType::Comment | NodeType::ProcessingInstruction => {}
_ => {
status.replace(Error::new(
ErrorKind::TypeError,
"invalid content in choose element".to_string(),
));
}
}
Ok::<(), Error>(())
})?;
match status {
Some(e) => Result::Err(e),
None => Ok(Transform::Switch(
clauses,
otherwise.map_or(Box::new(Transform::Empty), Box::new),
)),
}
} else if qn == *XSLFOREACH {
let s = n.get_attribute(&ATTRSELECT);
if !s.to_string().is_empty() {
Ok(Transform::ForEach(
None,
Box::new(parse::<N>(&s.to_string(), Some(n.clone()), None)?),
Box::new(Transform::SequenceItems(n.child_iter().try_fold(
vec![],
|mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
},
)?)),
get_sort_keys(&n)?,
))
} else {
Result::Err(Error::new(
ErrorKind::TypeError,
"missing select attribute".to_string(),
))
}
} else if qn == *XSLFOREACHGROUP {
let ord = get_sort_keys(&n)?;
let s = n.get_attribute(&ATTRSELECT);
if !s.to_string().is_empty() {
match (
n.get_attribute(&ATTRGROUPBY).to_string().as_str(),
n.get_attribute(&ATTRGROUPADJACENT).to_string().as_str(),
n.get_attribute(&ATTRGROUPSTARTINGWITH).to_string().as_str(),
n.get_attribute(&ATTRGROUPENDINGWITH).to_string().as_str(),
) {
(by, "", "", "") => Ok(Transform::ForEach(
Some(Grouping::By(vec![parse::<N>(by, Some(n.clone()), None)?])),
Box::new(parse::<N>(&s.to_string(), Some(n.clone()), None)?),
Box::new(Transform::SequenceItems(n.child_iter().try_fold(
vec![],
|mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
},
)?)),
ord,
)),
("", adj, "", "") => Ok(Transform::ForEach(
Some(Grouping::Adjacent(vec![parse::<N>(
adj,
Some(n.clone()),
None,
)?])),
Box::new(parse::<N>(&s.to_string(), Some(n.clone()), None)?),
Box::new(Transform::SequenceItems(n.child_iter().try_fold(
vec![],
|mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
},
)?)),
ord,
)),
("", "", start, "") => Ok(Transform::ForEach(
Some(Grouping::StartingWith(Box::new(Pattern::try_from(start)?))),
Box::new(parse::<N>(&s.to_string(), Some(n.clone()), None)?),
Box::new(Transform::SequenceItems(n.child_iter().try_fold(
vec![],
|mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
},
)?)),
ord,
)),
_ => Result::Err(Error::new(
ErrorKind::NotImplemented,
"invalid grouping attribute(s) specified".to_string(),
)),
}
} else {
Result::Err(Error::new(
ErrorKind::TypeError,
"missing select attribute".to_string(),
))
}
} else if qn == *XSLCOPY {
let mut content: Vec<Transform<N>> =
n.child_iter().try_fold(vec![], |mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
})?;
let use_atts = n.get_attribute(&XSLATTRUSEATTRIBUTESETS);
let mut attrs = vec![];
use_atts.to_string().split_whitespace().try_for_each(|a| {
let eqa = n.to_qname(a)?;
attr_sets
.get(&eqa)
.iter()
.cloned()
.for_each(|a| attrs.append(&mut a.clone()));
Ok(())
})?;
Ok(Transform::Copy(
Box::new(Transform::ContextItem), Box::new(if content.is_empty() && attrs.is_empty() {
Transform::Empty
} else {
attrs.append(&mut content);
Transform::SequenceItems(attrs)
}),
))
} else if qn == *XSLCOPYOF {
let s = n.get_attribute(&ATTRSELECT);
if !s.to_string().is_empty() {
Ok(Transform::DeepCopy(Box::new(parse::<N>(
&s.to_string(),
Some(n.clone()),
None,
)?)))
} else {
Ok(Transform::DeepCopy(Box::new(Transform::ContextItem)))
}
} else if qn == *XSLCALLTEMPLATE {
let name = n.get_attribute(&ATTRNAME);
if !name.to_string().is_empty() {
let mut ap = vec![];
n.child_iter()
.filter(|c| c.is_element() && c.name().unwrap() == *XSLWITHPARAM)
.try_for_each(|c| {
let wp_name = c.get_attribute(&ATTRNAME);
if !wp_name.to_string().is_empty() {
let sel = c.get_attribute(&ATTRSELECT);
if sel.to_string().is_empty() {
let mut body = vec![];
c.child_iter().try_for_each(|d| {
body.push(to_transform(d, attr_sets)?);
Ok(())
})?;
ap.push((
QName::from_local_name(
NcName::try_from(wp_name.to_string().as_str())
.map_err(|_| {
Error::new(ErrorKind::ParseError, "not a QName")
})?,
),
Transform::SequenceItems(body),
));
Ok(())
} else {
ap.push((
QName::from_local_name(
NcName::try_from(wp_name.to_string().as_str())
.map_err(|_| {
Error::new(ErrorKind::ParseError, "not a QName")
})?,
),
parse::<N>(&sel.to_string(), Some(n.clone()), None)?,
));
Ok(())
}
} else {
Err(Error::new(
ErrorKind::StaticAbsent,
"missing name attribute",
))
}
})?;
Ok(Transform::Invoke(
QName::from_local_name(
NcName::try_from(name.to_string().as_str())
.map_err(|_| Error::new(ErrorKind::ParseError, "not a NcName"))?,
),
ActualParameters::Named(ap),
in_scope_namespaces(Some(n)),
))
} else {
Err(Error::new(
ErrorKind::StaticAbsent,
"name attribute missing",
))
}
} else if qn == *XSLELEMENT {
let m = n.get_attribute(&ATTRNAME);
if m.to_string().is_empty() {
return Err(Error::new(ErrorKind::TypeError, "missing name attribute"));
}
let mut content = n.child_iter().try_fold(vec![], |mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
})?;
let use_atts = n.get_attribute(&XSLATTRUSEATTRIBUTESETS);
let mut attrs = vec![];
use_atts.to_string().split_whitespace().try_for_each(|a| {
let eqa = n.to_qname(a)?;
attr_sets
.get(&eqa)
.iter()
.cloned()
.for_each(|a| attrs.append(&mut a.clone()));
Ok(())
})?;
Ok(Transform::Element(
Box::new(parse_avt(m.to_string().as_str(), Some(n.clone()))?),
Box::new(if content.is_empty() && attrs.is_empty() {
Transform::Empty
} else {
attrs.append(&mut content);
Transform::SequenceItems(attrs)
}),
))
} else if qn == *XSLATTRIBUTE {
let m = n.get_attribute(&ATTRNAME);
if !m.to_string().is_empty() {
Ok(Transform::LiteralAttribute(
QName::from_local_name(
NcName::try_from(m.to_string().as_str())
.map_err(|_| Error::new(ErrorKind::ParseError, "not a NcName"))?,
),
Box::new(Transform::SequenceItems(n.child_iter().try_fold(
vec![],
|mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
},
)?)),
))
} else {
Err(Error::new(ErrorKind::TypeError, "missing name attribute"))
}
} else if qn == *XSLCOMMENT {
Ok(Transform::LiteralComment(Box::new(
Transform::SequenceItems(n.child_iter().try_fold(vec![], |mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
})?),
)))
} else if qn == *XSLPROCESSINGINSTRUCTION {
let m = n.get_attribute(&ATTRNAME);
if m.to_string().is_empty() {
return Result::Err(Error::new(ErrorKind::TypeError, "missing name attribute"));
}
Ok(Transform::LiteralProcessingInstruction(
Box::new(parse_avt(m.to_string().as_str(), Some(n.clone()))?),
Box::new(Transform::SequenceItems(n.child_iter().try_fold(
vec![],
|mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
},
)?)),
))
} else if qn == *XSLMESSAGE {
let t = n.get_attribute(&ATTRTERMINATE);
Ok(Transform::Message(
Box::new(Transform::SequenceItems(n.child_iter().try_fold(
vec![],
|mut body, e| {
body.push(to_transform(e, attr_sets)?);
Ok(body)
},
)?)),
None,
Box::new(Transform::Empty),
Box::new(if t.to_string().is_empty() {
Transform::False
} else {
Transform::Literal(Item::Value(Rc::new(Value::from(t.to_string()))))
}),
))
} else if qn == *XSLNUMBER {
let value = n.get_attribute(&ATTRVALUE);
let sel = n.get_attribute(&ATTRSELECT);
let level = n.get_attribute(&ATTRLEVEL);
if level.to_string() != "" && level.to_string() != "single" {
return Err(Error::new(
ErrorKind::NotImplemented,
"only single level numbering is supported",
));
}
let count = n.get_attribute(&ATTRCOUNT);
let from = n.get_attribute(&ATTRFROM);
let format = n.get_attribute(&ATTRFORMAT);
if value.to_string().is_empty() {
Ok(Transform::FormatInteger(
Box::new(Transform::GenerateIntegers(
Box::new(Transform::Empty), Box::new(if sel.to_string().is_empty() {
Transform::ContextItem
} else {
parse::<N>(&sel.to_string(), Some(n.clone()), None)?
}), Box::new(Numbering::new(
Level::Single, if count.to_string().is_empty() {
None
} else {
Some(Pattern::try_from(count.to_string())?)
},
if from.to_string().is_empty() {
None
} else {
Some(Pattern::try_from(from.to_string())?)
},
)),
)),
Box::new(Transform::Literal(Item::Value(
if format.to_string().is_empty() {
Rc::new(Value::from("1"))
} else {
format
},
))),
))
} else {
Ok(Transform::FormatInteger(
Box::new(parse::<N>(&value.to_string(), Some(n.clone()), None)?),
Box::new(Transform::Literal(Item::Value(
if format.to_string().is_empty() {
Rc::new(Value::from("1"))
} else {
format
},
))),
))
}
} else if qn == *XSLDECIMALFORMAT {
Ok(Transform::NotImplemented(String::from(
"unsupported XSL element \"decimal-format\"",
)))
} else if qn.namespace_uri() == *XSLTNS {
Ok(Transform::NotImplemented(format!(
"unsupported XSL element \"{}\"",
qn.local_name().to_string()
)))
} else {
let u = qn.namespace_uri();
let a = qn.local_name();
let mut prefix = None;
if let Some(nsuri) = u.as_ref() {
if let Some(p) = n
.namespace_iter()
.find(|nsd| nsd.as_namespace_uri().unwrap() == nsuri)
{
if let Some(pp) = p.as_namespace_prefix()? {
prefix = Some(Box::new(Transform::Literal(Item::Value(Rc::new(
Value::from(pp.to_string()),
)))));
}
}
}
let use_atts = n.get_attribute(&XSLATTRUSEATTRIBUTESETS);
let mut attrs = vec![];
use_atts.to_string().split_whitespace().try_for_each(|a| {
let eqa = n.to_qname(a)?; attr_sets
.get(&eqa)
.iter()
.cloned()
.for_each(|a| attrs.append(&mut a.clone()));
Ok(())
})?;
let mut content = vec![];
if u.is_some() {
content.push(Transform::NamespaceDeclaration(
prefix,
Box::new(Transform::Literal(Item::Value(Rc::new(Value::from(
u.clone().unwrap(),
))))),
Box::new(Transform::Literal(Item::Value(Rc::new(Value::from(true))))),
));
}
n.attribute_iter()
.filter(|e| e.name().unwrap().namespace_uri() != *XSLTNS)
.try_for_each(|e| {
content.push(to_transform(e, attr_sets)?);
Ok::<(), Error>(())
})?;
n.child_iter().try_for_each(|e| {
content.push(to_transform(e, attr_sets)?);
Ok::<(), Error>(())
})?;
Ok(Transform::LiteralElement(
QName::new_from_parts(a, u),
Box::new(if content.is_empty() && attrs.is_empty() {
Transform::Empty
} else {
attrs.append(&mut content);
Transform::SequenceItems(attrs)
}),
))
}
}
NodeType::Attribute => {
let x = parse_avt(n.to_string().as_str(), Some(n.clone()))?;
Ok(Transform::LiteralAttribute(
n.name().unwrap(),
Box::new(x),
))
}
_ => {
Ok(Transform::NotImplemented(
"other template content".to_string(),
))
}
}
}
fn get_sort_keys<N: Node>(n: &N) -> Result<Vec<(Order, Transform<N>)>, Error> {
let mut result = vec![];
let mut nit = n.child_iter();
loop {
match nit.next() {
None => break,
Some(c) => match c.node_type() {
NodeType::Element => {
if c.name().is_some_and(|d| d == *XSLSORT) {
let ordval = c.get_attribute(&ATTRORDER);
let ord = match ordval.to_string().as_str() {
"descending" => Order::Descending,
_ => Order::Ascending,
};
let sortsel = c.get_attribute(&ATTRSELECT);
result.push((
ord,
parse::<N>(&sortsel.to_string(), Some(n.clone()), None)?,
));
} else {
break;
}
}
NodeType::Text => {
if c.value()
.to_string()
.as_str()
.find(|d: char| !d.is_whitespace())
.is_some()
{
break;
}
}
NodeType::Comment | NodeType::ProcessingInstruction => {}
_ => break,
},
}
}
if nit.any(|c| c.node_type() == NodeType::Element && c.name().is_some_and(|d| d == *XSLSORT)) {
Err(Error::new(ErrorKind::TypeError, "sort elements in body"))
} else {
Ok(result)
}
}
pub fn strip_whitespace<N: Node>(
t: N,
cpi: bool, strip: &Vec<NodeTest>,
preserve: &Vec<NodeTest>,
) -> Result<(), Error> {
t.child_iter().try_for_each(|n| {
strip_whitespace_node(n, cpi, strip, preserve, true)?;
Ok(())
})?;
Ok(())
}
pub fn strip_source_document<N: Node>(src: N, style: N) -> Result<(), Error> {
let mut ss: Vec<NodeTest> = vec![];
let mut ps: Vec<NodeTest> = vec![];
style.child_iter().try_for_each(|n| {
n.child_iter().try_for_each(|m| {
let nm = m.name();
if nm.as_ref().is_some_and(|nms| *nms == *XSLSTRIPSPACE) {
let v = m.get_attribute(&ATTRELEMENTS);
if !v.to_string().is_empty() {
v.to_string().split_whitespace().try_for_each(|t| {
ss.push(NodeTest::try_from(t)?);
Ok::<(), Error>(())
})?
} else {
return Result::Err(Error::new(
ErrorKind::Unknown,
String::from("missing elements attribute"),
));
}
} else if nm.as_ref().is_some_and(|nms| *nms == *XSLPRESERVESPACE) {
let v = m.get_attribute(&ATTRELEMENTS);
if !v.to_string().is_empty() {
v.to_string().split_whitespace().try_for_each(|t| {
ps.push(NodeTest::try_from(t)?);
Ok::<(), Error>(())
})?
} else {
return Result::Err(Error::new(
ErrorKind::Unknown,
String::from("missing elements attribute"),
));
}
}
Ok::<(), Error>(())
})?;
Ok::<(), Error>(())
})?;
strip_whitespace(src, false, &ss, &ps)
}
fn strip_whitespace_node<N: Node>(
mut n: N,
cpi: bool, strip: &Vec<NodeTest>,
preserve: &Vec<NodeTest>,
keep: bool,
) -> Result<(), Error> {
match n.node_type() {
NodeType::Comment | NodeType::ProcessingInstruction => {
if cpi {
n.pop()?;
}
}
NodeType::Element => {
let mut ss = -1.0;
let mut ps = -1.0;
strip.iter().for_each(|t| match t {
NodeTest::Kind(KindTest::Any) | NodeTest::Kind(KindTest::Element) => ss = -0.5,
NodeTest::Name(nt) => match nt {
NameTest::Wildcard(
WildcardOrNamespaceUri::Wildcard,
WildcardOrName::Name(_),
) => ss = -0.25,
NameTest::Wildcard(
WildcardOrNamespaceUri::NamespaceUri(_),
WildcardOrName::Wildcard,
) => ss = -0.25,
NameTest::Wildcard(_, _) => ss = -0.5,
NameTest::Name(qn) => {
if *qn == n.name().unwrap() {
ss = 0.5
}
}
},
_ => {}
});
preserve.iter().for_each(|t| match t {
NodeTest::Kind(KindTest::Any) | NodeTest::Kind(KindTest::Element) => ps = -0.5,
NodeTest::Name(nt) => match nt {
NameTest::Wildcard(
WildcardOrNamespaceUri::Wildcard,
WildcardOrName::Name(_),
) => ss = -0.25,
NameTest::Wildcard(
WildcardOrNamespaceUri::NamespaceUri(_),
WildcardOrName::Wildcard,
) => ss = -0.25,
NameTest::Wildcard(_, _) => ss = -0.5,
NameTest::Name(qn) => {
if *qn == n.name().unwrap() {
ss = 0.5
}
}
},
_ => {}
});
n.child_iter().try_for_each(|m| {
strip_whitespace_node(
m,
cpi,
strip,
preserve,
if ss > -1.0 {
ps >= ss
} else if ps > -1.0 {
true
} else {
keep
},
)
})?
}
NodeType::Text => {
if n.to_string().trim().is_empty() && !keep {
n.pop()?;
}
}
_ => {}
}
Ok(())
}