ecitygml-io 0.0.2-alpha.5

IO operations for processing CityGML data.
Documentation
use crate::Error;
use crate::gml::codec::generics::GmlStringAttribute;
use crate::gml::codec::generics::double_attribute::GmlDoubleAttribute;
use crate::gml::codec::generics::generic_attribute_set::GmlGenericAttributeSet;
use crate::gml::codec::generics::int_attribute::GmlIntAttribute;
use crate::gml::codec::generics::measure_attribute::GmlMeasureAttribute;
use ecitygml_core::model::generics::{
    DoubleAttribute, GenericAttributeKind, GenericAttributeSet, IntAttribute, MeasureAttribute,
    StringAttribute,
};
use quick_xml::{de, se};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum GmlGenericAttributeKind {
    #[serde(rename(serialize = "gen:StringAttribute", deserialize = "StringAttribute"))]
    StringAttribute(GmlStringAttribute),
    #[serde(rename(serialize = "gen:IntAttribute", deserialize = "IntAttribute"))]
    IntAttribute(GmlIntAttribute),
    #[serde(rename(serialize = "gen:DoubleAttribute", deserialize = "DoubleAttribute"))]
    DoubleAttribute(GmlDoubleAttribute),
    #[serde(rename(serialize = "gen:MeasureAttribute", deserialize = "MeasureAttribute"))]
    MeasureAttribute(GmlMeasureAttribute),
    #[serde(rename(
        serialize = "gen:GenericAttributeSet",
        deserialize = "GenericAttributeSet"
    ))]
    GenericAttributeSet(GmlGenericAttributeSet),
}

impl TryFrom<GmlGenericAttributeKind> for GenericAttributeKind {
    type Error = Error;

    fn try_from(value: GmlGenericAttributeKind) -> Result<Self, Self::Error> {
        match value {
            GmlGenericAttributeKind::StringAttribute(x) => {
                let core_attr = StringAttribute::try_from(x)?;
                Ok(GenericAttributeKind::StringAttribute(core_attr))
            }
            GmlGenericAttributeKind::IntAttribute(x) => {
                let core_attr = IntAttribute::try_from(x)?;
                Ok(GenericAttributeKind::IntAttribute(core_attr))
            }
            GmlGenericAttributeKind::DoubleAttribute(x) => {
                let core_attr = DoubleAttribute::try_from(x)?;
                Ok(GenericAttributeKind::DoubleAttribute(core_attr))
            }
            GmlGenericAttributeKind::MeasureAttribute(x) => {
                let core_attr = MeasureAttribute::try_from(x)?;
                Ok(GenericAttributeKind::MeasureAttribute(core_attr))
            }
            GmlGenericAttributeKind::GenericAttributeSet(x) => {
                let core_attr = GenericAttributeSet::try_from(x)?;
                Ok(GenericAttributeKind::GenericAttributeSet(core_attr))
            }
        }
    }
}

impl From<&GenericAttributeKind> for GmlGenericAttributeKind {
    fn from(attr: &GenericAttributeKind) -> Self {
        match attr {
            GenericAttributeKind::StringAttribute(x) => {
                GmlGenericAttributeKind::StringAttribute(GmlStringAttribute::from(x))
            }
            GenericAttributeKind::IntAttribute(x) => {
                GmlGenericAttributeKind::IntAttribute(GmlIntAttribute::from(x))
            }
            GenericAttributeKind::DoubleAttribute(x) => {
                GmlGenericAttributeKind::DoubleAttribute(GmlDoubleAttribute::from(x))
            }
            GenericAttributeKind::MeasureAttribute(x) => {
                GmlGenericAttributeKind::MeasureAttribute(GmlMeasureAttribute::from(x))
            }
            GenericAttributeKind::GenericAttributeSet(x) => {
                GmlGenericAttributeKind::GenericAttributeSet(GmlGenericAttributeSet::from(x))
            }
        }
    }
}

pub fn deserialize_generic_attribute(xml_document: &[u8]) -> Result<GenericAttributeKind, Error> {
    let parsed_attribute: GmlGenericAttributeKind = de::from_reader(xml_document)?;
    let attribute = GenericAttributeKind::try_from(parsed_attribute)?;

    Ok(attribute)
}

pub fn serialize_generic_attribute(attr: &GenericAttributeKind) -> Result<String, Error> {
    let gml = GmlGenericAttributeKind::from(attr);
    Ok(se::to_string(&gml)?)
}

#[cfg(test)]
mod tests {
    use super::*;
    use egml::model::basic_types::Measure;
    // --- Serialization tests ---

    #[test]
    fn test_serialize_string_attribute() {
        let attr = GenericAttributeKind::StringAttribute(StringAttribute {
            name: "material".to_string(),
            value: "concrete".to_string(),
        });

        let xml = serialize_generic_attribute(&attr).unwrap();

        assert!(
            xml.contains("<gen:StringAttribute>"),
            "missing root element: {xml}"
        );
        assert!(
            xml.contains("<gen:name>material</gen:name>"),
            "missing name: {xml}"
        );
        assert!(
            xml.contains("<gen:value>concrete</gen:value>"),
            "missing value: {xml}"
        );
    }

    #[test]
    fn test_serialize_int_attribute() {
        let attr = GenericAttributeKind::IntAttribute(IntAttribute {
            name: "storeys".to_string(),
            value: 4,
        });

        let xml = serialize_generic_attribute(&attr).unwrap();

        assert!(
            xml.contains("<gen:IntAttribute>"),
            "missing root element: {xml}"
        );
        assert!(
            xml.contains("<gen:name>storeys</gen:name>"),
            "missing name: {xml}"
        );
        assert!(
            xml.contains("<gen:value>4</gen:value>"),
            "missing value: {xml}"
        );
    }

    #[test]
    fn test_serialize_double_attribute() {
        let attr = GenericAttributeKind::DoubleAttribute(DoubleAttribute {
            name: "height".to_string(),
            value: 12.5,
        });

        let xml = serialize_generic_attribute(&attr).unwrap();

        assert!(
            xml.contains("<gen:DoubleAttribute>"),
            "missing root element: {xml}"
        );
        assert!(
            xml.contains("<gen:name>height</gen:name>"),
            "missing name: {xml}"
        );
        assert!(
            xml.contains("<gen:value>12.5</gen:value>"),
            "missing value: {xml}"
        );
    }

    #[test]
    fn test_serialize_measure_attribute() {
        let attr = GenericAttributeKind::MeasureAttribute(MeasureAttribute {
            name: "GrossFloorArea".to_string(),
            value: Measure {
                uom: "m2".to_string(),
                value: 250.0,
            },
        });

        let xml = serialize_generic_attribute(&attr).unwrap();

        assert!(
            xml.contains("<gen:MeasureAttribute>"),
            "missing root element: {xml}"
        );
        assert!(
            xml.contains("<gen:name>GrossFloorArea</gen:name>"),
            "missing name: {xml}"
        );
        assert!(xml.contains("uom=\"m2\""), "missing uom attribute: {xml}");
        assert!(xml.contains("250"), "missing value: {xml}");
    }

    #[test]
    fn test_serialize_generic_attribute_set() {
        let string_attribute: GenericAttributeKind =
            StringAttribute::new("GrossFloorArea".to_string(), "250".to_string()).into();
        let int_attribute: GenericAttributeKind = IntAttribute::new("floors".to_string(), 7).into();
        let generic_attribute_set: GenericAttributeKind = GenericAttributeSet::new(
            "generic_attribute_name".to_string(),
            vec![string_attribute, int_attribute],
        )
        .into();

        let xml = serialize_generic_attribute(&generic_attribute_set).unwrap();

        assert!(
            xml.contains("<gen:GenericAttributeSet>"),
            "missing root element: {xml}"
        );
        assert!(
            xml.contains("<gen:name>generic_attribute_name</gen:name>"),
            "missing name: {xml}"
        );
        assert!(
            xml.contains("<gen:StringAttribute>"),
            "missing element: {xml}"
        );
        assert!(xml.contains("250"), "missing value: {xml}");
    }

    #[test]
    fn test_serialize_string_attribute_round_trip() {
        let original = GenericAttributeKind::StringAttribute(StringAttribute {
            name: "construction_method".to_string(),
            value: "prefab".to_string(),
        });

        let xml = serialize_generic_attribute(&original).unwrap();
        let recovered = deserialize_generic_attribute(xml.as_bytes()).unwrap();

        assert_eq!(original, recovered);
    }

    #[test]
    fn test_serialize_int_attribute_round_trip() {
        let original = GenericAttributeKind::IntAttribute(IntAttribute {
            name: "floors".to_string(),
            value: 7,
        });

        let xml = serialize_generic_attribute(&original).unwrap();
        let recovered = deserialize_generic_attribute(xml.as_bytes()).unwrap();

        assert_eq!(original, recovered);
    }

    #[test]
    fn test_serialize_double_attribute_round_trip() {
        let original = GenericAttributeKind::DoubleAttribute(DoubleAttribute {
            name: "area".to_string(),
            value: 99.9,
        });

        let xml = serialize_generic_attribute(&original).unwrap();
        let recovered = deserialize_generic_attribute(xml.as_bytes()).unwrap();

        assert_eq!(original, recovered);
    }

    #[test]
    fn test_serialize_measure_attribute_round_trip() {
        let original = GenericAttributeKind::MeasureAttribute(MeasureAttribute {
            name: "volume".to_string(),
            value: Measure {
                uom: "m3".to_string(),
                value: 1500.0,
            },
        });

        let xml = serialize_generic_attribute(&original).unwrap();
        let recovered = deserialize_generic_attribute(xml.as_bytes()).unwrap();

        assert_eq!(original, recovered);
    }

    // --- Deserialization tests ---

    #[test]
    fn test_deserialize_string_attribute_via_generic_kind() {
        let xml_document = b"<gen:StringAttribute>
    <gen:name>attribute_name</gen:name>
    <gen:value>1100</gen:value>
  </gen:StringAttribute>";

        let generic_attribute = deserialize_generic_attribute(xml_document).expect("should work");

        assert_eq!(generic_attribute.name(), "attribute_name");
        assert_eq!(
            generic_attribute.as_string().expect("must be string").value,
            "1100"
        );
    }

    #[test]
    fn test_deserialize_string_attribute() {
        let xml_document = b"<gen:StringAttribute>
          <gen:name>attribute_name</gen:name>
          <gen:value>1100</gen:value>
        </gen:StringAttribute>";

        let generic_attribute = deserialize_generic_attribute(xml_document).expect("should work");

        match generic_attribute {
            GenericAttributeKind::StringAttribute(x) => {
                assert_eq!(x.name, "attribute_name");
                assert_eq!(x.value, "1100");
            }
            other => panic!("Expected Double attribute, got {:?}", other),
        }
    }

    #[test]
    fn test_deserialize_int_attribute() {
        let xml_document = b"<gen:IntAttribute>
          <gen:name>attribute_name</gen:name>
          <gen:value>1100</gen:value>
        </gen:IntAttribute>";

        let generic_attribute = deserialize_generic_attribute(xml_document).expect("should work");

        match generic_attribute {
            GenericAttributeKind::IntAttribute(x) => {
                assert_eq!(x.name, "attribute_name");
                assert_eq!(x.value, 1100);
            }
            other => panic!("Expected Double attribute, got {:?}", other),
        }
    }

    #[test]
    fn test_deserialize_double_attribute() {
        let xml_document = b"<gen:DoubleAttribute>
          <gen:name>attribute_name</gen:name>
          <gen:value>42.2</gen:value>
        </gen:DoubleAttribute>";

        let generic_attribute = deserialize_generic_attribute(xml_document).expect("should work");

        match generic_attribute {
            GenericAttributeKind::DoubleAttribute(x) => {
                assert_eq!(x.name, "attribute_name");
                assert_eq!(x.value, 42.2);
            }
            other => panic!("Expected Double attribute, got {:?}", other),
        }
    }

    #[test]
    fn test_deserialize_generic_attribute_set() {
        let xml_document = "<gen:GenericAttributeSet>
          <gen:name>generic_attribute_name</gen:name>
          <gen:genericAttribute>
            <gen:StringAttribute>
              <gen:name>MyStringAttributeName</gen:name>
              <gen:value>abc</gen:value>
            </gen:StringAttribute>
          </gen:genericAttribute>
          <gen:genericAttribute>
            <gen:IntAttribute>
              <gen:name>MyIntAttributeName</gen:name>
              <gen:value>-123</gen:value>
            </gen:IntAttribute>
          </gen:genericAttribute>
      </gen:GenericAttributeSet>";

        let generic_attribute_kind =
            deserialize_generic_attribute(xml_document.as_ref()).expect("should work");

        let GenericAttributeKind::GenericAttributeSet(generic_attribute_set) =
            generic_attribute_kind
        else {
            panic!("Expected GenericAttributeSet");
        };
        assert_eq!(generic_attribute_set.name, "generic_attribute_name");
        assert_eq!(generic_attribute_set.generic_attributes.len(), 2);

        let first_generic_attribute = &generic_attribute_set.generic_attributes[0];
        let GenericAttributeKind::StringAttribute(string_attribute) = first_generic_attribute
        else {
            panic!("Expected StringAttribute");
        };
        assert_eq!(string_attribute.name, "MyStringAttributeName");
        assert_eq!(string_attribute.value, "abc");

        let second_generic_attribute = &generic_attribute_set.generic_attributes[1];
        let GenericAttributeKind::IntAttribute(int_attribute) = second_generic_attribute else {
            panic!("Expected IntAttribute");
        };
        assert_eq!(int_attribute.name, "MyIntAttributeName");
        assert_eq!(int_attribute.value, -123);
    }
}