use crate::core::Environment;
use crate::error::{Result, TemplateError};
use crate::template::{ModelKind, NodeHashModel, TModel, TemplateNodeModel};
use roxmltree::{Node, NodeId};
use std::rc::Rc;
use super::ns_prefixes::NsPrefixes;
use super::tree::XmlTree;
#[derive(Clone)]
pub struct XmlNode {
tree: Rc<XmlTree>,
node_id: NodeId,
attr: Option<String>,
doctype: bool,
}
impl XmlNode {
pub fn parse(s: &str) -> Result<XmlNode> {
let tree = XmlTree::parse(s)?;
Ok(XmlNode {
node_id: tree.doc.root().id(),
tree,
attr: None,
doctype: false,
})
}
fn node(&self) -> Node<'_, 'static> {
self.tree
.doc
.get_node(self.node_id)
.expect("XmlNode 的 node_id 必然有效")
}
fn is_attr(&self) -> bool {
self.attr.is_some()
}
fn attributes(&self) -> Vec<roxmltree::Attribute<'_, 'static>> {
let n = self.node();
if !n.is_element() {
return Vec::new();
}
n.attributes()
.filter(|a| !a.name().starts_with("xmlns"))
.collect()
}
fn child_nodes(&self) -> Vec<XmlNode> {
let mut out = Vec::new();
let is_root = self.node().node_type() == roxmltree::NodeType::Root;
let mut doctype_injected = false;
for c in self.node().children() {
if matches!(
c.node_type(),
roxmltree::NodeType::Comment | roxmltree::NodeType::PI
) {
continue;
}
if is_root && !doctype_injected && c.is_element() && self.tree.doctype.is_some() {
out.push(XmlNode {
tree: self.tree.clone(),
node_id: self.node_id, attr: None,
doctype: true,
});
doctype_injected = true;
}
out.push(XmlNode {
tree: self.tree.clone(),
node_id: c.id(),
attr: None,
doctype: false,
});
}
if is_root && !doctype_injected && self.tree.doctype.is_some() {
out.push(XmlNode {
tree: self.tree.clone(),
node_id: self.node_id,
attr: None,
doctype: true,
});
}
out
}
fn next_sibling_node(&self) -> Option<XmlNode> {
if self.is_attr() {
return None;
}
let mut seen_self = false;
for c in self.node().parent()?.children() {
if c.id() == self.node_id {
seen_self = true;
continue;
}
if seen_self
&& !matches!(
c.node_type(),
roxmltree::NodeType::Comment | roxmltree::NodeType::PI
)
{
return Some(XmlNode {
tree: self.tree.clone(),
node_id: c.id(),
attr: None,
doctype: false,
});
}
}
None
}
fn previous_sibling_node(&self) -> Option<XmlNode> {
if self.is_attr() {
return None;
}
let mut prev = None;
for c in self.node().parent()?.children() {
if c.id() == self.node_id {
return prev;
}
if !matches!(
c.node_type(),
roxmltree::NodeType::Comment | roxmltree::NodeType::PI
) {
prev = Some(XmlNode {
tree: self.tree.clone(),
node_id: c.id(),
attr: None,
doctype: false,
});
}
}
None
}
fn child_elements(&self) -> Vec<XmlNode> {
self.child_nodes()
.into_iter()
.filter(|c| c.node().is_element())
.collect()
}
fn descendant_elements(&self) -> Vec<XmlNode> {
let mut out = Vec::new();
for d in self.node().descendants() {
if d.is_element() {
out.push(XmlNode {
tree: self.tree.clone(),
node_id: d.id(),
attr: None,
doctype: false,
});
}
}
out
}
fn node_name(&self) -> Option<String> {
if self.doctype {
let name = self.tree.doctype.as_ref()?.name.clone();
return Some(format!("@document_type${name}"));
}
if let Some(an) = &self.attr {
return Some(local_part(an));
}
let n = self.node();
match n.node_type() {
roxmltree::NodeType::Element => {
Some(n.tag_name().name().to_string())
}
roxmltree::NodeType::Root => Some("@document".to_string()),
roxmltree::NodeType::Text => Some("@text".to_string()),
roxmltree::NodeType::Comment => Some("@comment".to_string()),
roxmltree::NodeType::PI => {
Some(format!("@pi${}", n.pi().map(|p| p.target).unwrap_or("")))
}
}
}
fn node_type(&self) -> String {
if self.doctype {
return "document_type".to_string();
}
if self.is_attr() {
return "attribute".to_string();
}
match self.node().node_type() {
roxmltree::NodeType::Element => "element".to_string(),
roxmltree::NodeType::Root => "document".to_string(),
roxmltree::NodeType::Text => "text".to_string(),
roxmltree::NodeType::Comment => "comment".to_string(),
roxmltree::NodeType::PI => "pi".to_string(),
}
}
fn node_namespace(&self) -> Option<String> {
if self.is_attr() {
let n = self.node();
if n.is_element() {
for a in n.attributes() {
if local_part(a.name()) == self.attr.as_deref().unwrap_or("") {
let ns = a.namespace();
return match ns {
Some(ns) if !ns.is_empty() => Some(ns.to_string()),
_ => None,
};
}
}
}
return None;
}
match self.node().node_type() {
roxmltree::NodeType::Element => {
Some(self.node().tag_name().namespace().unwrap_or("").to_string())
}
_ => None,
}
}
fn text_content(&self) -> String {
if self.is_attr() {
return self.attribute_value().unwrap_or_default();
}
let n = self.node();
match n.node_type() {
roxmltree::NodeType::Text => n.text().unwrap_or("").to_string(),
roxmltree::NodeType::Element => {
let mut out = String::new();
for c in n.children() {
if c.is_text() {
out.push_str(c.text().unwrap_or(""));
} else if c.is_element() {
let sub = XmlNode {
tree: self.tree.clone(),
node_id: c.id(),
attr: None,
doctype: false,
};
out.push_str(&sub.text_content());
}
}
out
}
roxmltree::NodeType::Root => {
let mut out = String::new();
for c in n.children() {
if c.is_element() {
let sub = XmlNode {
tree: self.tree.clone(),
node_id: c.id(),
attr: None,
doctype: false,
};
out.push_str(&sub.text_content());
}
}
out
}
_ => String::new(),
}
}
fn scalar_value(&self) -> Result<String> {
if self.doctype {
return Ok(self
.tree
.doctype
.as_ref()
.map(|d| d.raw.clone())
.unwrap_or_default());
}
if let Some(v) = self.attribute_value() {
return Ok(v);
}
let n = self.node();
match n.node_type() {
roxmltree::NodeType::Element => {
for c in n.children() {
if c.is_element() {
return Err(TemplateError::misc(format!(
"Only elements with no child elements can be processed as text.\nThis element with name \"{}\" has a child element named: {}",
self.node_name().unwrap_or_default(),
c.tag_name().name()
)));
}
}
Ok(self.text_content())
}
roxmltree::NodeType::Text | roxmltree::NodeType::Comment => {
Ok(n.text().unwrap_or("").to_string())
}
roxmltree::NodeType::PI => Ok(n
.pi()
.map(|p| p.value.unwrap_or(""))
.unwrap_or("")
.to_string()),
_ => Err(TemplateError::type_mismatch("string", "node")),
}
}
fn attribute_value(&self) -> Option<String> {
let an = self.attr.as_deref()?;
let n = self.node();
if !n.is_element() {
return None;
}
for a in n.attributes() {
if a.name() == an || attr_qualified_name(n, a) == an {
return Some(a.value().to_string());
}
}
None
}
fn parent_node(&self) -> Option<XmlNode> {
if self.attr.is_some() {
return Some(XmlNode {
tree: self.tree.clone(),
node_id: self.node_id,
attr: None,
doctype: false,
});
}
let n = self.node();
if n.node_type() == roxmltree::NodeType::Root {
return None;
}
n.parent().map(|p| XmlNode {
tree: self.tree.clone(),
node_id: p.id(),
attr: None,
doctype: false,
})
}
fn document_node(&self) -> XmlNode {
XmlNode {
tree: self.tree.clone(),
node_id: self.tree.doc.root().id(),
attr: None,
doctype: false,
}
}
fn root_element(&self) -> Option<XmlNode> {
let n = self.node().document().root_element();
Some(XmlNode {
tree: self.tree.clone(),
node_id: n.id(),
attr: None,
doctype: false,
})
}
pub(crate) fn into_model(self) -> TModel {
let is_document = self.node_type() == "document" && !self.doctype;
let mut m = TModel::nothing();
m.node = Some(Rc::new(self.clone()) as Rc<dyn TemplateNodeModel>);
m.node_hash = Some(Rc::new(self.clone()) as Rc<dyn NodeHashModel>);
if !is_document {
m.scalar = Some(Rc::new(self) as Rc<dyn crate::template::TemplateScalarModel>);
}
m.type_name = "node";
m.kind = ModelKind::Node;
m
}
}
impl TemplateNodeModel for XmlNode {
fn parent(&self) -> Result<Option<TModel>> {
Ok(self.parent_node().map(|p| p.into_model()))
}
fn children(&self) -> Result<Vec<TModel>> {
if self.doctype {
return Err(TemplateError::misc(
"entering the child nodes of a DTD node is not currently supported",
));
}
if self.is_attr() {
return Ok(Vec::new());
}
Ok(self
.child_nodes()
.into_iter()
.map(|c| c.into_model())
.collect())
}
fn next_sibling(&self) -> Result<Option<TModel>> {
Ok(self.next_sibling_node().map(|n| n.into_model()))
}
fn previous_sibling(&self) -> Result<Option<TModel>> {
Ok(self.previous_sibling_node().map(|n| n.into_model()))
}
fn name(&self) -> Result<Option<String>> {
Ok(self.node_name())
}
fn node_type(&self) -> Result<String> {
Ok(self.node_type())
}
fn namespace(&self) -> Result<Option<String>> {
Ok(self.node_namespace())
}
}
impl crate::template::TemplateScalarModel for XmlNode {
fn as_string(&self) -> Result<String> {
self.scalar_value()
}
}
impl NodeHashModel for XmlNode {
fn get(&self, env: &mut Environment, key: &str) -> Result<Option<TModel>> {
Ok(self.hash_get(env, key)?.map(ensure_node_list))
}
}
struct NodeListModel {
nodes: Vec<TModel>,
}
impl NodeHashModel for NodeListModel {
fn get(&self, env: &mut Environment, key: &str) -> Result<Option<TModel>> {
let mut merged: Vec<TModel> = Vec::new();
for nm in &self.nodes {
if let Some(nh) = &nm.node_hash {
if let Some(res) = nh.get(env, key)? {
flatten_node_result(&mut merged, res);
}
}
}
Ok(Some(make_node_list(merged)))
}
}
fn flatten_node_result(out: &mut Vec<TModel>, m: TModel) {
if let Some(seq) = &m.sequence {
let n = seq.size().unwrap_or(0);
for i in 0..n {
if let Ok(item) = seq.get(i) {
out.push(item);
}
}
} else {
out.push(m);
}
}
fn make_node_list(nodes: Vec<TModel>) -> TModel {
let mut out = TModel::from_sequence(nodes.clone());
out.node_hash = Some(Rc::new(NodeListModel { nodes }) as Rc<dyn NodeHashModel>);
out
}
fn ensure_node_list(m: TModel) -> TModel {
if m.node_hash.is_none() {
if let Some(seq) = &m.sequence {
let n = seq.size().unwrap_or(0);
let mut nodes = Vec::with_capacity(n);
for i in 0..n {
if let Ok(item) = seq.get(i) {
nodes.push(item);
}
}
return make_node_list(nodes);
}
}
m
}
pub fn parse_xml(s: &str) -> Result<TModel> {
Ok(XmlNode::parse(s)?.into_model())
}
fn local_part(name: &str) -> String {
match name.rsplit_once(':') {
Some((_, l)) => l.to_string(),
None => name.to_string(),
}
}
fn attr_qualified_name(el: roxmltree::Node, attr: roxmltree::Attribute) -> String {
let local = attr.name();
let Some(ns) = attr.namespace() else {
return local.to_string();
};
for a in el.attributes() {
if let Some(prefix) = a.name().strip_prefix("xmlns:") {
if a.value() == ns {
return format!("{prefix}:{local}");
}
}
}
local.to_string()
}
fn split_qname(s: &str) -> Result<(Option<String>, String, bool)> {
if s == "*" {
return Ok((None, String::new(), true));
}
if s.is_empty() || s.starts_with('@') || s.starts_with('[') {
return Err(TemplateError::misc(format!(
"Unsupported XPath query: //{s}"
)));
}
match s.split_once(':') {
Some((p, l)) if !p.is_empty() && !l.is_empty() => {
Ok((Some(p.to_string()), l.to_string(), false))
}
Some(_) => Err(TemplateError::misc(format!(
"Unsupported XPath query: //{s}"
))),
None => Ok((None, s.to_string(), false)),
}
}
fn build_prefix_lookup(
n: &Node<'_, 'static>,
prefixes: &NsPrefixes,
has_default_ns: bool,
default_ns: Option<&str>,
lookup: &mut Vec<(Option<String>, String)>,
next_gen: &mut usize,
) {
let ns_uri = n.tag_name().namespace();
if let Some(uri) = ns_uri {
if !uri.is_empty() {
let prefix = match prefixes.get_prefix_for_namespace(uri) {
Some(p) => p.to_string(),
None => {
let existing = lookup
.iter()
.find(|(u, _)| u.as_deref() == Some(uri))
.map(|(_, p)| p.clone());
match existing {
Some(p) => p,
None => {
loop {
let mut m = *next_gen;
*next_gen += 1;
let mut p = String::new();
while m > 0 {
m -= 1;
p.insert(0, char::from(b'a' + (m % 26) as u8));
m /= 26;
}
if prefixes.get_namespace_for_prefix(&p).is_none() {
break p;
}
}
}
}
}
};
if !lookup.iter().any(|(u, _)| u.as_deref() == Some(uri)) {
lookup.push((Some(uri.to_string()), prefix));
}
}
} else if has_default_ns {
if let Some(dns) = default_ns {
if !lookup.iter().any(|(u, _)| u.as_deref() == Some(dns)) {
lookup.push((Some(dns.to_string()), String::new()));
}
}
}
for c in n.children() {
if c.is_element() {
build_prefix_lookup(&c, prefixes, has_default_ns, default_ns, lookup, next_gen);
}
}
}
#[path = "node_query.rs"]
mod node_query;
#[cfg(test)]
#[path = "node_tests.rs"]
mod node_tests;