use raves_metadata_types::xmp::{
XmpValue,
parse_types::{XmpKind as Kind, XmpKindStructField as Field, XmpPrimitiveKind as Prim},
};
use xmltree::Element;
use crate::xmp::{
error::{XmpElementResult, XmpParsingError},
value::{XmpElementExt, structs::value_struct_field},
};
pub fn value_union(
element: &Element,
always: &'static [Field],
discriminant: &'static Field,
optional: &'static [(&'static str, &'static [Field])],
) -> XmpElementResult {
let Field {
ident: _,
ty: Kind::Simple(Prim::Text),
} = discriminant
else {
log::error!(
"Unable to parse union. Discriminant was a non-text type: \
{discriminant:#?}"
);
return Err(XmpParsingError::UnionDiscriminantWasntText {
element_name: element.name.clone(),
discriminant_kind: discriminant,
});
};
let known_pairs: Vec<(&Field, _)> = {
let always_pairs = always
.iter()
.chain([discriminant])
.map(|f| (f, &f.ident))
.map(|(f, f_ident)| (f, (f_ident.ns(), f_ident.name())));
let optional_pairs = optional.iter().flat_map(|(_, slic)| {
slic.iter()
.map(|f| (f, &f.ident))
.map(|(f, f_ident)| (f, (f_ident.ns(), f_ident.name())))
});
always_pairs.chain(optional_pairs).collect()
};
let mut expected_fields: Vec<_> = Vec::with_capacity(always.len());
let mut unexpected_fields: Vec<_> = Vec::with_capacity(element.children.len() - always.len());
for c in element.children.iter().flat_map(|c| c.as_element()) {
let (c_ns, c_name) = (&c.namespace, &c.name);
let mut known_field = false;
for (field, (known_ns, known_name)) in &known_pairs {
let ns_mismatch = match c_ns {
Some(c_ns) => Some(c_ns.as_str()) != known_ns.map(|k: &str| k),
None => known_ns.is_some(),
};
if ns_mismatch {
log::trace!(
"Namespaces don't match - not a known field. found: `{c_ns:?}`, want: `{known_ns:?}`."
);
continue;
}
if c_name != *known_name {
log::trace!(
"Names don't match - not a known field. \
found: {c_name} \
want: {known_name}"
);
continue;
}
known_field = true;
if let Some(parsed_field) = value_struct_field(c, Some(field)) {
expected_fields.push(parsed_field);
}
}
if !known_field && let Some(parsed_c) = value_struct_field(c, None) {
unexpected_fields.push(parsed_c);
}
}
let Some(found_discriminant) = expected_fields.iter().find(|f| {
let names_match = f.ident() == discriminant.ident.name();
log::trace!("finding discrim... `names_match: bool = {names_match}`");
if !names_match {
log::trace!(
"name for this field was: `{}`. expected: `{}`",
f.ident(),
discriminant.ident.name()
);
}
let namespaces_match = match f.namespace() {
Some(s) => Some(s.as_str()) == discriminant.ident.ns(),
None => discriminant.ident.ns().is_none(),
};
log::trace!("finding discrim... `namespaces_match: bool = {namespaces_match}`");
if !namespaces_match {
log::trace!(
"namespace for this field was: `{:?}`. expected: `{:?}`",
f.namespace(),
discriminant.ident.ns(),
);
}
names_match && namespaces_match
}) else {
log::error!(
"Parsed union, but couldn't find discriminant!
- discriminant: {discriminant:#?}
- expected_fields: {expected_fields:#?}
- unexpected_fields: {unexpected_fields:#?}"
);
return Err(XmpParsingError::UnionNoDiscriminant {
element_name: element.name.to_string(),
});
};
element.to_xmp_element(XmpValue::Union {
discriminant: Box::new(found_discriminant.clone()),
expected_fields,
unexpected_fields,
})
}
#[cfg(test)]
mod tests {
use raves_metadata_types::{
xmp::parse_types::XmpKind,
xmp::{XmpElement, XmpPrimitive, XmpValue, XmpValueStructField},
};
use xmltree::Element;
use crate::xmp::value::unions::value_union;
#[test]
fn union_colorant() {
_ = env_logger::builder()
.filter_level(log::LevelFilter::max())
.format_file(true)
.format_line_number(true)
.try_init();
let xml = r#"<rdf:li rdf:parseType="Resource" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/">
<xmpG:swatchName>black</xmpG:swatchName>
<xmpG:mode>CMYK</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:cyan>100</xmpG:cyan>
<xmpG:magenta>100</xmpG:magenta>
<xmpG:yellow>100</xmpG:yellow>
<xmpG:black>100</xmpG:black>
</rdf:li>"#;
let element = Element::parse(xml.as_bytes()).expect("xmltree should parse");
let colorant = &raves_metadata_types::xmp::types::COLORANT;
let XmpKind::Union {
always,
discriminant,
optional,
} = colorant
else {
panic!("`Colorant` is no longer a union I guess..?");
};
let parsed_union =
value_union(&element, always, discriminant, optional).expect("should parse out union");
assert_eq!(
parsed_union,
XmpElement {
namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".into(),
prefix: "rdf".into(),
name: "li".into(),
value: XmpValue::Union {
discriminant: Box::new(XmpValueStructField::Value {
ident: "mode".into(),
namespace: Some("http://ns.adobe.com/xap/1.0/g/".into()),
value: XmpValue::Simple(XmpPrimitive::Text("CMYK".into()))
}),
expected_fields: vec![
XmpValueStructField::Value {
ident: "swatchName".into(),
namespace: Some("http://ns.adobe.com/xap/1.0/g/".into()),
value: XmpValue::Simple(XmpPrimitive::Text("black".into()))
},
XmpValueStructField::Value {
ident: "mode".into(),
namespace: Some("http://ns.adobe.com/xap/1.0/g/".into()),
value: XmpValue::Simple(XmpPrimitive::Text("CMYK".into()))
},
XmpValueStructField::Value {
ident: "type".into(),
namespace: Some("http://ns.adobe.com/xap/1.0/g/".into()),
value: XmpValue::Simple(XmpPrimitive::Text("PROCESS".into()))
},
XmpValueStructField::Value {
ident: "cyan".into(),
namespace: Some("http://ns.adobe.com/xap/1.0/g/".into()),
value: XmpValue::Simple(XmpPrimitive::Real(100.0))
},
XmpValueStructField::Value {
ident: "magenta".into(),
namespace: Some("http://ns.adobe.com/xap/1.0/g/".into()),
value: XmpValue::Simple(XmpPrimitive::Real(100.0))
},
XmpValueStructField::Value {
ident: "yellow".into(),
namespace: Some("http://ns.adobe.com/xap/1.0/g/".into()),
value: XmpValue::Simple(XmpPrimitive::Real(100.0))
},
XmpValueStructField::Value {
ident: "black".into(),
namespace: Some("http://ns.adobe.com/xap/1.0/g/".into()),
value: XmpValue::Simple(XmpPrimitive::Real(100.0))
},
],
unexpected_fields: vec![]
}
}
);
}
}