use quick_xml::events::{BytesEnd, BytesStart};
use crate::error::Result;
use crate::reflect::{EnergymlType, Roles, TypeInfo, TypeKind};
use crate::value::{AsFieldValue, FieldValue, FromOwnedValue, OwnedValue, Reflect, downcast_owned};
use crate::xml::{XmlDeserialize, XmlEvent, XmlReader, XmlSerialize, XmlWriter};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct AnyXml {
pub attributes: Vec<(String, String)>,
pub content: String,
pub namespaces: Vec<(String, String)>,
}
impl AnyXml {
pub fn is_empty(&self) -> bool {
self.attributes.is_empty() && self.content.trim().is_empty()
}
pub fn namespace(&self, prefix: &str) -> Option<&str> {
self.namespaces
.iter()
.find(|(p, _)| p == prefix)
.map(|(_, uri)| uri.as_str())
}
pub fn attribute(&self, name: &str) -> Option<&str> {
self.attributes
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.as_str())
}
}
impl XmlSerialize for AnyXml {
fn xml_attributes(&self, tag: &mut BytesStart<'_>) -> Result<()> {
for (name, value) in &self.attributes {
tag.push_attribute((name.as_str(), value.as_str()));
}
for (prefix, uri) in &self.namespaces {
let declaration = format!("xmlns:{prefix}");
if !self.attributes.iter().any(|(name, _)| name == &declaration) {
tag.push_attribute((declaration.as_str(), uri.as_str()));
}
}
Ok(())
}
fn xml_children(&self, w: &mut XmlWriter<'_>) -> Result<()> {
w.write_raw(&self.content)
}
}
pub fn capture_into(
into: &mut AnyXml,
r: &mut XmlReader<'_>,
start: &BytesStart<'_>,
ns: Option<&str>,
) -> Result<()> {
remember_prefix(into, start.name().as_ref(), ns.map(str::to_string));
write_start(&mut into.content, start)?;
let mut depth = 1usize;
loop {
match r.next_event()? {
XmlEvent::Start(e, uri) => {
depth += 1;
remember_prefix(into, e.name().as_ref(), uri);
write_start(&mut into.content, &e)?;
}
XmlEvent::End(e) => {
depth -= 1;
write_end(&mut into.content, &e);
if depth == 0 {
break;
}
}
XmlEvent::Text(t) => into.content.push_str(&escape_text(&t)),
XmlEvent::Eof => return Err(crate::error::Error::Eof),
}
}
Ok(())
}
fn remember_prefix(into: &mut AnyXml, name: &[u8], uri: Option<String>) {
let Some(uri) = uri else { return };
let name = String::from_utf8_lossy(name);
if let Some((prefix, _)) = name.split_once(':')
&& !into.namespaces.iter().any(|(p, _)| p == prefix)
{
into.namespaces.push((prefix.to_string(), uri));
}
}
impl XmlDeserialize for AnyXml {
fn xml_read(r: &mut XmlReader<'_>, start: &BytesStart<'_>) -> Result<Self> {
let mut attributes = Vec::new();
let mut namespaces: Vec<(String, String)> = Vec::new();
for attribute in start.attributes() {
let attribute = attribute.map_err(|e| crate::error::Error::Xml(e.to_string()))?;
let name = String::from_utf8_lossy(attribute.key.as_ref()).into_owned();
let value = attribute
.normalized_value(quick_xml::XmlVersion::default())?
.into_owned();
if let Some(prefix) = name.strip_prefix("xmlns:") {
if !namespaces.iter().any(|(p, _)| p == prefix) {
namespaces.push((prefix.to_string(), value));
}
continue;
}
if name == "xmlns" {
continue;
}
attributes.push((name, value));
}
let mut content = String::new();
let mut depth = 1usize;
loop {
match r.next_event()? {
XmlEvent::Start(e, uri) => {
depth += 1;
if let Some(uri) = uri {
let name = String::from_utf8_lossy(e.name().as_ref()).into_owned();
if let Some((prefix, _)) = name.split_once(':')
&& !namespaces.iter().any(|(p, _)| p == prefix)
{
namespaces.push((prefix.to_string(), uri));
}
}
write_start(&mut content, &e)?;
}
XmlEvent::End(e) => {
depth -= 1;
if depth == 0 {
break;
}
write_end(&mut content, &e);
}
XmlEvent::Text(t) => content.push_str(&escape_text(&t)),
XmlEvent::Eof => return Err(crate::error::Error::Eof),
}
}
namespaces.retain(|(prefix, _)| {
content.contains(&format!("<{prefix}:"))
|| content.contains(&format!(" {prefix}:"))
|| attributes.iter().any(|(name, value)| {
name.starts_with(&format!("{prefix}:")) || value.starts_with(&format!("{prefix}:"))
})
});
Ok(AnyXml {
attributes,
content,
namespaces,
})
}
}
fn write_start(out: &mut String, e: &BytesStart<'_>) -> Result<()> {
out.push('<');
out.push_str(&String::from_utf8_lossy(e.name().as_ref()));
for attribute in e.attributes() {
let attribute = attribute.map_err(|err| crate::error::Error::Xml(err.to_string()))?;
out.push(' ');
out.push_str(&String::from_utf8_lossy(attribute.key.as_ref()));
out.push_str("=\"");
out.push_str(&escape_attr(
&attribute.normalized_value(quick_xml::XmlVersion::default())?,
));
out.push('"');
}
out.push('>');
Ok(())
}
fn write_end(out: &mut String, e: &BytesEnd<'_>) {
out.push_str("</");
out.push_str(&String::from_utf8_lossy(e.name().as_ref()));
out.push('>');
}
fn escape_text(text: &str) -> String {
text.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
}
fn escape_attr(value: &str) -> String {
escape_text(value).replace('"', """)
}
impl EnergymlType for AnyXml {
fn type_info() -> &'static TypeInfo {
static INFO: TypeInfo = TypeInfo {
name: "AnyXml",
package: "eml",
version: "",
schema_version: "",
xml_name: "AnyXml",
object_type: "AnyXml",
type_key: "AnyXml",
xml_ns: crate::ns::EML,
content_type: None,
qualified_type: None,
root_element: None,
doc: "An XML subtree kept verbatim: the generator does not model this vocabulary.",
kind: TypeKind::Struct,
is_abstract: false,
is_top_level: false,
is_nestable: false,
is_object_ref: false,
roles: Roles::EMPTY,
base: None,
fields: &[],
variants: &[],
subtypes: &[],
is_open: false,
other_pattern: None,
make: Some(|| Box::new(AnyXml::default()) as Box<dyn Reflect>),
};
&INFO
}
}
impl Reflect for AnyXml {
fn type_info(&self) -> &'static TypeInfo {
<Self as EnergymlType>::type_info()
}
fn field(&self, _name: &str) -> Option<FieldValue<'_>> {
None
}
fn set_field(&mut self, name: &str, _value: OwnedValue) -> Result<()> {
Err(crate::error::Error::UnknownField {
ty: "AnyXml",
field: name.to_string(),
})
}
fn field_object_mut(&mut self, _name: &str, _index: Option<usize>) -> Option<&mut dyn Reflect> {
None
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
}
impl AsFieldValue for AnyXml {
fn as_field_value(&self) -> FieldValue<'_> {
FieldValue::Object(self)
}
}
impl FromOwnedValue for AnyXml {
fn from_owned_value(value: OwnedValue) -> Result<Self> {
downcast_owned::<Self>(value, "AnyXml")
}
}
impl crate::validate::Validate for AnyXml {
fn validate_into(&self, _ctx: &mut crate::validate::ValidationCtx<'_>) {
}
}
impl AnyXml {
pub fn declared_type(&self, registry: &crate::registry::Registry) -> Option<&'static TypeInfo> {
let declared = self
.attributes
.iter()
.find(|(name, _)| name == "xsi:type" || name.ends_with(":type") || name == "type")
.map(|(_, value)| value.as_str())?;
let (prefix, local) = declared.split_once(':')?;
let uri = self.namespace(prefix)?;
let package = crate::ns::well_known_namespaces()
.iter()
.find(|known| known.uri == uri)
.map(|known| known.prefix)?;
let mut candidates: Vec<&'static TypeInfo> = registry
.types()
.filter(|info| info.xml_name == local && info.package == package && !info.version.is_empty())
.collect();
candidates.sort_by_key(|info| {
(
crate::naming::is_prerelease(info.version),
std::cmp::Reverse(version_rank(info.version)),
)
});
candidates.into_iter().next()
}
pub fn typed(
&self,
registry: &crate::registry::Registry,
) -> Option<crate::error::Result<(Box<dyn crate::registry::DynObject>, crate::registry::Departures)>>
{
let info = self.declared_type(registry)?;
let declarations: String = self
.namespaces
.iter()
.map(|(prefix, uri)| format!(" xmlns:{prefix}=\"{uri}\""))
.collect();
let namespace = crate::ns::Namespace::well_known(info.package)?;
let opening = format!("{}:{}", namespace.prefix, info.xml_name);
let extra = if self.namespace(namespace.prefix).is_some() {
String::new()
} else {
format!(" xmlns:{}=\"{}\"", namespace.prefix, namespace.uri)
};
let xml = format!("<{opening}{declarations}{extra}>{}</{opening}>", self.content);
Some(registry.read_xml_as_named(info, xml.as_bytes(), crate::registry::ReadMode::Lenient))
}
pub fn rebuilt(&self, object: &dyn crate::registry::DynObject) -> crate::error::Result<AnyXml> {
let info = object.as_reflect().type_info();
let namespace = crate::ns::Namespace::well_known(info.package).ok_or_else(|| {
crate::error::Error::UnknownType(format!("no namespace for package `{}`", info.package))
})?;
let written = crate::xml::to_xml_string(
object.as_xml(),
namespace,
info.xml_name,
&[namespace, crate::ns::EML, crate::ns::XSI],
crate::xml::XmlWriteOptions {
declaration: false,
..Default::default()
},
)?;
Ok(AnyXml {
content: inner_of(&written),
..self.clone()
})
}
}
impl AnyXml {
pub fn of(object: &dyn crate::registry::DynObject) -> crate::error::Result<AnyXml> {
let info = object.as_reflect().type_info();
let namespace = crate::ns::Namespace::well_known(info.package).ok_or_else(|| {
crate::error::Error::UnknownType(format!("no namespace for package `{}`", info.package))
})?;
let blank = AnyXml {
attributes: vec![("xsi:type".to_string(), namespace.qualify(info.xml_name))],
content: String::new(),
namespaces: [namespace, crate::ns::EML, crate::ns::XSI]
.iter()
.map(|n| (n.prefix.to_string(), n.uri.to_string()))
.collect(),
};
blank.rebuilt(object)
}
}
fn inner_of(xml: &str) -> String {
let Some(open) = xml.find('>') else {
return String::new();
};
if xml[..open].ends_with('/') {
return String::new();
}
let Some(close) = xml.rfind("</") else {
return String::new();
};
if close <= open {
return String::new();
}
xml[open + 1..close].to_string()
}
fn version_rank(version: &str) -> Vec<u32> {
version
.split('.')
.map(|part| part.parse::<u32>().unwrap_or(0))
.collect()
}