miniexcel 0.1.0

Experimental Rust XLSX reader and writer for MiniExcel
Documentation
use calamine::{Data, DataType, Range};
use std::sync::Arc;

use crate::CellValue;

pub(crate) struct SelectedRow {
    pub(crate) excel_row: usize,
    pub(crate) start_column: usize,
    pub(crate) values: Vec<Data>,
    pub(crate) cells: Vec<SelectedCellMetadata>,
}

pub(crate) struct SelectedCellMetadata {
    pub(crate) excel_column: usize,
    pub(crate) formula: Option<String>,
    pub(crate) style_id: u32,
    pub(crate) number_format: Option<Arc<str>>,
}

pub(crate) fn header_names(values: &[Data]) -> Vec<Option<String>> {
    values
        .iter()
        .map(|value| {
            if value.is_empty() || value.to_string().trim().is_empty() {
                None
            } else {
                Some(value.to_string())
            }
        })
        .collect()
}

pub(crate) fn column_names(start_column: usize, width: usize) -> Vec<Option<String>> {
    (start_column..start_column + width).map(|column| Some(column_name(column))).collect()
}

fn column_name(mut column: usize) -> String {
    let mut letters = Vec::with_capacity(3);
    column += 1;
    while column > 0 {
        column -= 1;
        letters.push(char::from(b'A' + (column % 26) as u8));
        column /= 26;
    }
    letters.iter().rev().collect()
}

pub(crate) fn trim_header_row(values: &mut [Data]) {
    for value in values {
        if let Data::String(header) = value {
            *header = header.trim().to_owned();
        }
    }
}

pub(crate) fn row_to_range(headers: Option<&[Data]>, values: &[Data]) -> Range<Data> {
    let height = usize::from(headers.is_some()) + 1;
    let width = values.len();
    if width == 0 {
        return Range::empty();
    }

    let mut range = Range::new((0, 0), ((height - 1) as u32, (width - 1) as u32));
    if let Some(headers) = headers {
        for (column_index, value) in headers.iter().enumerate() {
            range.set_value((0, column_index as u32), value.clone());
        }
    }
    let row_index = u32::from(headers.is_some());
    for (column_index, value) in values.iter().enumerate() {
        range.set_value((row_index, column_index as u32), value.clone());
    }
    range
}

pub(crate) fn to_cell_value(value: &Data) -> CellValue {
    match value {
        Data::Empty => CellValue::Empty,
        Data::Bool(value) => CellValue::Bool(*value),
        Data::Int(value) => CellValue::Int(*value),
        Data::Float(value)
            if value.is_finite()
                && value.fract() == 0.0
                && *value >= i64::MIN as f64
                && *value < -(i64::MIN as f64) =>
        {
            CellValue::Int(*value as i64)
        }
        Data::Float(value) => CellValue::Float(*value),
        Data::String(value) => CellValue::String(value.clone()),
        Data::DateTime(value) if value.is_duration() => {
            value.as_duration().map_or(CellValue::Float(value.as_f64()), CellValue::Duration)
        }
        Data::DateTime(value) => {
            value.as_datetime().map_or(CellValue::Float(value.as_f64()), CellValue::DateTime)
        }
        Data::DateTimeIso(iso) if iso.contains('T') || iso.contains(' ') => {
            value.as_datetime().map_or_else(|| CellValue::String(iso.clone()), CellValue::DateTime)
        }
        Data::DateTimeIso(iso) if iso.contains(':') => {
            value.as_time().map_or_else(|| CellValue::String(iso.clone()), CellValue::Time)
        }
        Data::DateTimeIso(iso) => {
            value.as_date().map_or_else(|| CellValue::String(iso.clone()), CellValue::Date)
        }
        Data::DurationIso(iso) => {
            value.as_duration().map_or_else(|| CellValue::String(iso.clone()), CellValue::Duration)
        }
        Data::Error(error) => CellValue::Error(error.to_string()),
    }
}