use raves_metadata_types::xmp::{
XmpElement, XmpPrimitive, XmpValue, XmpValueStructField,
parse_types::{XmpKind as Kind, XmpKindStructField as Field},
};
use xmltree::Element;
use crate::xmp::{
RDF_NAMESPACE,
error::XmpParsingError,
heuristics::XmpElementHeuristicsExt as _,
value::{XmpElementExt, prims::parse_primitive},
};
pub fn value_struct(
element: &Element,
maybe_schema: Option<&'static Kind>,
) -> Result<XmpElement, XmpParsingError> {
let get_field_info = |field_ns: &Option<&str>, field_name: &str| -> Option<&Field> {
let Some(Kind::Struct(schema_fields)) = maybe_schema else {
log::trace!("No field schema given. Setting `schema` to `None`.");
return None;
};
log::trace!("A schema was given! Checking field info...");
schema_fields.iter().find(|field| {
field.ident.name() == field_name
&& if let Some(string_ns) = field_ns {
log::trace!("Field namespace found! Will return based on namespace eq...");
field.ident.ns() == Some(string_ns)
} else {
field.ident.ns().is_none()
}
})
};
let attr_fields = element.attributes.iter().flat_map(|(keys, value)| {
let (ns, name) = (&keys.namespace_ref(), &keys.local_name);
if (name == "parseType" || name == "about")
&& ns.is_some_and(|inner_ns: &str| inner_ns == RDF_NAMESPACE)
{
log::trace!("Won't make a field from `rdf:parseType` attribute.");
return None;
}
log::trace!("Getting field info on struct field: `{name}`");
if let Some(field_info) = get_field_info(ns, name) {
let Kind::Simple(prim) = field_info.ty else {
log::error!(
"Found attribute on struct, but schema asked for non-primitive type: {:#?}",
field_info.ty
);
return None;
};
log::trace!(
"Struct field `{name}` was in the schema! \
Constructing accordingly..."
);
return Some(XmpValueStructField::Value {
ident: field_info.ident.name().into(),
namespace: field_info.ident.ns().map(|ns| ns.into()),
value: parse_primitive(value.into(), prim)
.inspect_err(|e| {
log::error!(
"On attribute field `{name}`, failed to parse primitive. err: {e}"
)
})
.ok()?,
});
}
log::trace!(
"Struct field `{name}` was not in the schema! \
Constructing text value..."
);
Some(XmpValueStructField::Value {
ident: name.into(),
namespace: (*ns).map(|n| n.into()),
value: XmpValue::Simple(XmpPrimitive::Text(value.into())),
})
});
let Some(fields_container) = (match element.attributes.iter().any(|(attr_keys, attr_value)| {
attr_keys.local_name == "parseType"
&& attr_keys
.namespace_ref()
.is_some_and(|inner_ns| inner_ns == RDF_NAMESPACE)
&& attr_value == "Resource"
}) {
true => Some(element),
false => element
.get_child("Description")
.filter(|desc_elem| desc_elem.is_rdf_description()),
}) else {
log::trace!(
"We aren't a container, and don't have a container! \
Stopping struct `{struct_name}` here...",
struct_name = &element.name
);
return element.to_xmp_element(XmpValue::Struct(attr_fields.collect()));
};
let inner_fields = fields_container
.children
.iter()
.flat_map(|c| c.as_element())
.flat_map(|c| {
log::trace!(
"Parsing inner field `{inner_field_name}` on struct `{struct_name}`...",
inner_field_name = &c.name,
struct_name = &element.name
);
value_struct_field(
c,
get_field_info(
&match &c.namespace {
Some(ns) => Some(ns.as_str()),
None => None,
},
&c.name,
),
)
});
element.to_xmp_element(XmpValue::Struct(attr_fields.chain(inner_fields).collect()))
}
pub fn value_struct_field(
element: &Element,
maybe_field_kind: Option<&'static Field>,
) -> Option<XmpValueStructField> {
let (ident, namespace, element) = match maybe_field_kind {
Some(field_kind) => (
field_kind.ident.name().into(),
field_kind.ident.ns().map(|s| s.into()),
element
.value_with_schema(field_kind.ty)
.inspect_err(|e| log::error!("Field with known schema failed to parse! err: {e}"))
.ok()?,
),
None => (
element.name.clone(),
element.namespace.clone(),
match element.value_generic() {
Ok(s) => s,
Err(e) => {
log::trace!("Parsing value generically failed! err: {e}");
return None;
}
},
),
};
Some(match element.value {
XmpValue::Simple(_) => XmpValueStructField::Value {
ident,
namespace,
value: element.value,
},
_ => XmpValueStructField::Element {
ident,
namespace,
element,
},
})
}
#[cfg(test)]
mod tests {
use crate::xmp::Xmp;
#[test]
fn several_struct_types() {
_ = env_logger::builder()
.filter_level(log::LevelFilter::max())
.format_file(true)
.format_line_number(true)
.try_init();
let xmp: Xmp = Xmp::new(
r#"<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:ns="ns:myName/">
<!-- struct 1: regular syntax -->
<ns:Struct1>
<rdf:Description>
<ns:Field1>value1</ns:Field1>
<ns:Field2>value2</ns:Field2>
</rdf:Description>
</ns:Struct1>
<!-- struct 2: condensed (no inner rdf:Description tag) -->
<ns:Struct2 rdf:parseType="Resource">
<ns:Field1>value1</ns:Field1>
<ns:Field2>value2</ns:Field2>
</ns:Struct2>
<!--- struct 3: fields as desc attributes (shorthand) -->
<ns:Struct3>
<rdf:Description ns:Field1="value1" ns:Field2="value2"/>
</ns:Struct3>
<!--- struct 4: fields as self atttributes (shorthand) -->
<ns:Struct4 ns:Field1="value1" ns:Field2="value2"/>
<!--- struct 5 -->
<ns:Struct5>
<rdf:Description ns:Field1="value1">
<ns:Field2>value2</ns:Field2>
</rdf:Description>
</ns:Struct5>
</rdf:Description>
</rdf:RDF>"#,
)
.unwrap();
assert_eq!(xmp.document().values_ref().len(), 5);
}
}