e57 0.11.12

A pure Rust library for reading and writing E57 files with point clouds and related image data.
Documentation
use crate::error::Converter;
use crate::{DateTime, Error, Result, Transform};
use roxmltree::Node;
use std::fmt::Display;
use std::str::FromStr;

pub fn opt_string(parent_node: &Node, tag_name: &str) -> Result<Option<String>> {
    if let Some(tag) = parent_node.children().find(|n| n.has_tag_name(tag_name)) {
        let expected_type = "String";
        if let Some(found_type) = tag.attribute("type") {
            if found_type != expected_type {
                Error::invalid(format!(
                    "Found XML tag '{tag_name}' with type '{found_type}' instead of '{expected_type}'"
                ))?
            }
        } else {
            Error::invalid(format!("XML tag '{tag_name}' has no 'type' attribute"))?
        }
        let text = tag.text().unwrap_or("");
        Ok(Some(text.to_string()))
    } else {
        Ok(None)
    }
}

pub fn req_string(parent_node: &Node, tag_name: &str) -> Result<String> {
    let str = opt_string(parent_node, tag_name)?;
    str.invalid_err(format!("XML tag '{tag_name}' was not found"))
}

fn opt_num<T: FromStr + Sync + Send>(
    parent_node: &Node,
    tag_name: &str,
    expected_type: &str,
) -> Result<Option<T>> {
    if let Some(tag) = parent_node.children().find(|n| n.has_tag_name(tag_name)) {
        if let Some(found_type) = tag.attribute("type") {
            if found_type != expected_type {
                Error::invalid(format!(
                    "Found XML tag '{tag_name}' with type '{found_type}' instead of '{expected_type}'"
                ))?
            }
        } else {
            Error::invalid(format!("XML tag '{tag_name}' has no 'type' attribute"))?
        }
        let text = tag.text().unwrap_or("0");
        if let Ok(parsed) = text.parse::<T>() {
            Ok(Some(parsed))
        } else {
            Error::invalid(format!(
                "Cannot parse value '{text}' of XML tag '{tag_name}' as '{expected_type}'"
            ))?
        }
    } else {
        Ok(None)
    }
}

pub fn opt_f64(parent_node: &Node, tag_name: &str) -> Result<Option<f64>> {
    opt_num(parent_node, tag_name, "Float")
}

pub fn req_f64(parent_node: &Node, tag_name: &str) -> Result<f64> {
    let double = opt_num(parent_node, tag_name, "Float")?;
    double.invalid_err(format!("XML tag '{tag_name}' was not found"))
}

pub fn opt_int<T: FromStr + Sync + Send>(parent_node: &Node, tag_name: &str) -> Result<Option<T>> {
    opt_num(parent_node, tag_name, "Integer")
}

pub fn req_int<T: FromStr + Send + Sync>(parent_node: &Node, tag_name: &str) -> Result<T> {
    let integer = opt_num(parent_node, tag_name, "Integer")?;
    integer.invalid_err(format!("XML tag '{tag_name}' was not found"))
}

pub fn opt_date_time(parent_node: &Node, tag_name: &str) -> Result<Option<DateTime>> {
    if let Some(tag) = parent_node.children().find(|n| n.has_tag_name(tag_name)) {
        let expected_type = "Structure";
        if let Some(found_type) = tag.attribute("type") {
            if found_type != expected_type {
                Error::invalid(format!(
                    "Found XML tag '{tag_name}' with type '{found_type}' instead of '{expected_type}'"
                ))?
            }
        } else {
            Error::invalid(format!("XML tag '{tag_name}' has no 'type' attribute"))?
        }
        DateTime::from_node(&tag)
    } else {
        Ok(None)
    }
}

pub fn opt_transform(parent_node: &Node, tag_name: &str) -> Result<Option<Transform>> {
    let node = parent_node.children().find(|n| n.has_tag_name(tag_name));
    if let Some(node) = node {
        Ok(Some(Transform::from_node(&node)?))
    } else {
        Ok(None)
    }
}

pub fn gen_string<T: Display>(tag_name: &str, value: &T) -> String {
    format!("<{tag_name} type=\"String\"><![CDATA[{value}]]></{tag_name}>\n")
}

pub fn gen_float<T: Display>(tag_name: &str, value: T) -> String {
    format!("<{tag_name} type=\"Float\">{value}</{tag_name}>\n")
}

pub fn gen_int<T: Display>(tag_name: &str, value: T) -> String {
    format!("<{tag_name} type=\"Integer\">{value}</{tag_name}>\n")
}