use serde::{Deserialize, Serialize};
use super::dimensions::Dimensions;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct DataItem {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
#[doc(hidden)]
pub name: Option<String>,
#[serde(rename = "@ItemType", skip_serializing_if = "Option::is_none")]
#[doc(hidden)]
pub item_type: Option<ItemType>,
#[serde(rename = "@Dimensions", skip_serializing_if = "Option::is_none")]
#[doc(hidden)]
pub dimensions: Option<Dimensions>,
#[serde(rename = "@NumberType", skip_serializing_if = "Option::is_none")]
#[doc(hidden)]
pub number_type: Option<NumberType>,
#[serde(rename = "@Format", skip_serializing_if = "Option::is_none")]
#[doc(hidden)]
pub format: Option<Format>,
#[serde(rename = "@Precision", skip_serializing_if = "Option::is_none")]
pub precision: Option<u8>,
#[serde(rename = "@Endian", skip_serializing_if = "Option::is_none")]
#[doc(hidden)]
pub endian: Option<Endian>,
#[serde(flatten)]
#[doc(hidden)]
pub data: DataContent,
#[serde(rename = "@Reference", skip_serializing_if = "Option::is_none")]
#[doc(hidden)]
pub reference: Option<String>,
}
impl Default for DataItem {
fn default() -> Self {
Self {
name: None,
item_type: None,
dimensions: Some(Dimensions(vec![1])),
number_type: Some(NumberType::default()),
format: Some(Format::default()),
precision: Some(4),
endian: None,
data: String::new().into(),
reference: None,
}
}
}
impl<'de> Deserialize<'de> for DataItem {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Raw {
#[serde(rename = "@Name", default)]
name: Option<String>,
#[serde(rename = "@ItemType", default)]
item_type: Option<ItemType>,
#[serde(rename = "@Dimensions", default)]
dimensions: Option<Dimensions>,
#[serde(rename = "@NumberType", default)]
number_type: Option<NumberType>,
#[serde(rename = "@Format", default)]
format: Option<Format>,
#[serde(rename = "@Precision", default)]
precision: Option<u8>,
#[serde(rename = "@Endian", default)]
endian: Option<Endian>,
#[serde(rename = "@Reference", default)]
reference: Option<String>,
#[serde(rename = "$text", default)]
text: Option<String>,
#[serde(rename = "xi:include", default)]
include: Option<XInclude>,
#[serde(rename = "DataItem", default)]
items: Vec<DataItem>,
}
let raw = Raw::deserialize(deserializer)?;
let data = if !raw.items.is_empty() {
DataContent::Items(raw.items)
} else if let Some(include) = raw.include {
DataContent::Include(include)
} else {
DataContent::Raw(raw.text.unwrap_or_default())
};
Ok(Self {
name: raw.name,
item_type: raw.item_type,
dimensions: raw.dimensions,
number_type: raw.number_type,
format: raw.format,
precision: raw.precision,
endian: raw.endian,
data,
reference: raw.reference,
})
}
}
impl DataItem {
pub fn new_reference(source: &Self, source_path: &str) -> Self {
Self {
name: None,
item_type: None,
dimensions: None,
number_type: None,
format: None,
precision: None,
endian: None,
data: format!(
"{}[@Name=\"{}\"]",
source_path,
source.name.clone().unwrap_or("MISSING".to_string())
)
.into(),
reference: Some("XML".to_string()),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename = "xi:include")]
pub struct XInclude {
#[serde(rename = "@href")]
#[doc(hidden)]
file_path: String,
#[serde(rename = "@parse", skip_serializing_if = "Option::is_none")]
#[doc(hidden)]
parse: Option<String>,
}
impl XInclude {
pub fn new(file_path: impl ToString, include_as_text: bool) -> Self {
Self {
file_path: file_path.to_string(),
parse: include_as_text.then(|| "text".to_string()), }
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum DataContent {
#[serde(rename = "$value")]
Raw(String),
#[serde(rename = "xi:include")]
Include(XInclude),
#[serde(rename = "DataItem")]
Items(Vec<DataItem>),
}
impl From<String> for DataContent {
fn from(data: String) -> Self {
Self::Raw(data)
}
}
impl From<&str> for DataContent {
fn from(data: &str) -> Self {
Self::Raw(data.to_string())
}
}
impl From<Vec<DataItem>> for DataContent {
fn from(items: Vec<DataItem>) -> Self {
Self::Items(items)
}
}
impl From<XInclude> for DataContent {
fn from(include: XInclude) -> Self {
Self::Include(include)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum NumberType {
#[default]
#[doc(hidden)]
Float,
#[doc(hidden)]
Int,
#[doc(hidden)]
UInt,
#[doc(hidden)]
Char,
#[doc(hidden)]
UChar,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum ItemType {
#[doc(hidden)]
HyperSlab,
#[doc(hidden)]
Coordinates,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum Format {
#[default]
#[doc(hidden)]
XML,
#[doc(hidden)]
HDF,
#[doc(hidden)]
Binary,
}
impl Format {
pub(crate) fn endian(self) -> Option<Endian> {
matches!(self, Self::Binary).then_some(Endian::Little)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum Endian {
#[doc(hidden)]
Native,
#[doc(hidden)]
Big,
#[default]
#[doc(hidden)]
Little,
}
#[cfg(test)]
mod tests {
use quick_xml::se::to_string;
use super::*;
#[derive(Deserialize, Serialize)]
struct XmlRoot {
#[serde(rename = "DataItem")]
data_item: DataItem,
}
#[test]
fn data_item_default() {
let default_item = DataItem::default();
assert!(default_item.name.is_none());
assert_eq!(default_item.dimensions, Some(Dimensions(vec![1])));
assert_eq!(default_item.number_type, Some(NumberType::Float));
assert_eq!(default_item.format, Some(Format::XML));
assert_eq!(default_item.precision, Some(4));
assert!(default_item.endian.is_none());
assert_eq!(default_item.data, String::new().into());
assert!(default_item.reference.is_none());
}
#[test]
fn number_type_default() {
assert_eq!(NumberType::default(), NumberType::Float);
}
#[test]
fn format_default() {
assert_eq!(Format::default(), Format::XML);
}
#[test]
fn format_endian() {
assert_eq!(Format::XML.endian(), None);
assert_eq!(Format::HDF.endian(), None);
assert_eq!(Format::Binary.endian(), Some(Endian::Little));
}
#[test]
fn data_item_custom() {
let custom_item = DataItem {
name: Some("custom_data_item".to_string()),
item_type: None,
dimensions: Some(Dimensions(vec![2, 3])),
number_type: Some(NumberType::Int),
format: Some(Format::HDF),
precision: Some(8),
endian: None,
data: "custom_data".to_string().into(),
reference: None,
};
assert_eq!(custom_item.name, Some("custom_data_item".to_string()));
assert_eq!(custom_item.dimensions, Some(Dimensions(vec![2, 3])));
assert_eq!(custom_item.number_type, Some(NumberType::Int));
assert_eq!(custom_item.format, Some(Format::HDF));
assert_eq!(custom_item.precision, Some(8));
assert_eq!(custom_item.data, "custom_data".into());
assert!(custom_item.reference.is_none());
}
#[test]
fn data_item_reference() {
let source_data_item = DataItem {
name: Some("source_data_item".to_string()),
..Default::default()
};
let ref_item = DataItem::new_reference(&source_data_item, "/Xdmf/Domain/DataItem");
assert!(ref_item.name.is_none());
assert!(ref_item.dimensions.is_none());
assert!(ref_item.number_type.is_none());
assert!(ref_item.format.is_none());
assert!(ref_item.precision.is_none());
assert!(ref_item.endian.is_none());
assert_eq!(
ref_item.data,
"/Xdmf/Domain/DataItem[@Name=\"source_data_item\"]".into()
);
assert_eq!(ref_item.reference, Some("XML".to_string()));
}
#[test]
fn data_item_serialize() {
let data_item = DataItem {
name: Some("custom_data_item".to_string()),
item_type: None,
dimensions: Some(Dimensions(vec![2, 3])),
number_type: Some(NumberType::Int),
format: Some(Format::HDF),
precision: Some(8),
endian: None,
data: "custom_data".to_string().into(),
reference: None,
};
pretty_assertions::assert_eq!(
to_string(&XmlRoot { data_item }).unwrap(),
"<XmlRoot>\
<DataItem Name=\"custom_data_item\" Dimensions=\"2 3\" NumberType=\"Int\" Format=\"HDF\" Precision=\"8\">custom_data</DataItem>\
</XmlRoot>"
);
}
#[test]
fn data_item_reference_serialize() {
let source_data_item = DataItem {
name: Some("source_data_item".to_string()),
..Default::default()
};
let ref_item = DataItem::new_reference(&source_data_item, "/Xdmf/Domain/DataItem");
pretty_assertions::assert_eq!(
to_string(&XmlRoot {
data_item: ref_item
})
.unwrap(),
"<XmlRoot>\
<DataItem Reference=\"XML\">/Xdmf/Domain/DataItem[@Name=\"source_data_item\"]</DataItem>\
</XmlRoot>"
);
}
#[test]
fn data_item_include_serialize() {
let custom_item = DataItem {
name: Some("custom_data_item".to_string()),
item_type: None,
dimensions: Some(Dimensions(vec![2, 3])),
number_type: Some(NumberType::Int),
format: Some(Format::HDF),
precision: Some(8),
endian: None,
data: XInclude::new("coords.txt".to_string(), true).into(),
reference: None,
};
assert_eq!(custom_item.name, Some("custom_data_item".to_string()));
assert_eq!(custom_item.dimensions, Some(Dimensions(vec![2, 3])));
assert_eq!(custom_item.number_type, Some(NumberType::Int));
assert_eq!(custom_item.format, Some(Format::HDF));
assert_eq!(custom_item.precision, Some(8));
assert_eq!(
custom_item.data,
XInclude::new("coords.txt".to_string(), true).into()
);
assert!(custom_item.reference.is_none());
pretty_assertions::assert_eq!(
to_string(&XmlRoot {
data_item: custom_item
})
.unwrap(),
"<XmlRoot>\
<DataItem Name=\"custom_data_item\" Dimensions=\"2 3\" NumberType=\"Int\" Format=\"HDF\" Precision=\"8\">\
<xi:include href=\"coords.txt\" parse=\"text\"/>\
</DataItem>\
</XmlRoot>"
);
}
#[test]
fn data_item_selection_serialize() {
let source = DataItem {
name: None,
item_type: None,
dimensions: Some(Dimensions(vec![9, 3])),
number_type: Some(NumberType::Float),
format: Some(Format::HDF),
precision: Some(8),
endian: None,
data: "mesh.h5:/data/t_0/0".to_string().into(),
reference: None,
};
let selector = DataItem {
name: None,
item_type: None,
dimensions: Some(Dimensions(vec![3, 2])),
number_type: Some(NumberType::Int),
format: Some(Format::XML),
precision: Some(4),
endian: None,
data: "2 0 1 1 4 3".to_string().into(),
reference: None,
};
let selection = DataItem {
name: None,
item_type: Some(ItemType::HyperSlab),
dimensions: Some(Dimensions(vec![4, 3])),
number_type: Some(NumberType::Float),
format: None,
precision: Some(8),
endian: None,
data: vec![selector, source].into(),
reference: None,
};
pretty_assertions::assert_eq!(
to_string(&XmlRoot {
data_item: selection
})
.unwrap(),
"<XmlRoot>\
<DataItem ItemType=\"HyperSlab\" Dimensions=\"4 3\" NumberType=\"Float\" Precision=\"8\">\
<DataItem Dimensions=\"3 2\" NumberType=\"Int\" Format=\"XML\" Precision=\"4\">2 0 1 1 4 3</DataItem>\
<DataItem Dimensions=\"9 3\" NumberType=\"Float\" Format=\"HDF\" Precision=\"8\">mesh.h5:/data/t_0/0</DataItem>\
</DataItem>\
</XmlRoot>"
);
}
#[test]
fn data_item_deserialize() {
let data_item = DataItem {
name: Some("custom_data_item".to_string()),
item_type: None,
dimensions: Some(Dimensions(vec![2, 3])),
number_type: Some(NumberType::Int),
format: Some(Format::HDF),
precision: Some(8),
endian: None,
data: "custom_data".to_string().into(),
reference: None,
};
let xml = to_string(&XmlRoot {
data_item: data_item.clone(),
})
.unwrap();
let round_tripped: XmlRoot = quick_xml::de::from_str(&xml).unwrap();
pretty_assertions::assert_eq!(round_tripped.data_item, data_item);
}
#[test]
fn data_item_reference_deserialize() {
let source_data_item = DataItem {
name: Some("source_data_item".to_string()),
..Default::default()
};
let ref_item = DataItem::new_reference(&source_data_item, "/Xdmf/Domain/DataItem");
let xml = to_string(&XmlRoot {
data_item: ref_item.clone(),
})
.unwrap();
let round_tripped: XmlRoot = quick_xml::de::from_str(&xml).unwrap();
pretty_assertions::assert_eq!(round_tripped.data_item, ref_item);
}
#[test]
fn data_item_selection_deserialize() {
let source = DataItem {
name: None,
item_type: None,
dimensions: Some(Dimensions(vec![9, 3])),
number_type: Some(NumberType::Float),
format: Some(Format::HDF),
precision: Some(8),
endian: None,
data: "mesh.h5:/data/t_0/0".to_string().into(),
reference: None,
};
let selector = DataItem {
name: None,
item_type: None,
dimensions: Some(Dimensions(vec![3, 2])),
number_type: Some(NumberType::Int),
format: Some(Format::XML),
precision: Some(4),
endian: None,
data: "2 0 1 1 4 3".to_string().into(),
reference: None,
};
let selection = DataItem {
name: None,
item_type: Some(ItemType::HyperSlab),
dimensions: Some(Dimensions(vec![4, 3])),
number_type: Some(NumberType::Float),
format: None,
precision: Some(8),
endian: None,
data: vec![selector, source].into(),
reference: None,
};
let xml = to_string(&XmlRoot {
data_item: selection.clone(),
})
.unwrap();
let round_tripped: XmlRoot = quick_xml::de::from_str(&xml).unwrap();
pretty_assertions::assert_eq!(round_tripped.data_item, selection);
}
#[test]
fn xinclude_serialize() {
pretty_assertions::assert_eq!(
to_string(&XInclude::new("coords.txt".to_string(), false)).unwrap(),
"<xi:include href=\"coords.txt\"/>"
);
pretty_assertions::assert_eq!(
to_string(&XInclude::new("coords.txt".to_string(), true)).unwrap(),
"<xi:include href=\"coords.txt\" parse=\"text\"/>"
);
}
}