diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-29 13:37:25 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-29 14:18:13 +0100 |
| commit | 0efe669278a5e1c3f2985eba2f3360e91159c54a (patch) | |
| tree | 502712857c48f0decb5e698257c0a96d358a436e /library/src/compute/data.rs | |
| parent | 836692e73cff0356e409a9ba5b4887b86809d4ca (diff) | |
Reorganize library and tests
Diffstat (limited to 'library/src/compute/data.rs')
| -rw-r--r-- | library/src/compute/data.rs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs new file mode 100644 index 00000000..4f6e3b67 --- /dev/null +++ b/library/src/compute/data.rs @@ -0,0 +1,129 @@ +use std::fmt::Write; + +use typst::diag::{format_xml_like_error, FileError}; + +use crate::prelude::*; + +/// Read structured data from a CSV file. +pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> { + let Spanned { v: path, span } = + args.expect::<Spanned<EcoString>>("path to csv file")?; + + let path = vm.locate(&path).at(span)?; + let data = vm.world().file(&path).at(span)?; + + let mut builder = csv::ReaderBuilder::new(); + builder.has_headers(false); + + let mut reader = builder.from_reader(data.as_slice()); + let mut vec = vec![]; + + for result in reader.records() { + let row = result.map_err(format_csv_error).at(span)?; + let array = row.iter().map(|field| Value::Str(field.into())).collect(); + vec.push(Value::Array(array)) + } + + Ok(Value::Array(Array::from_vec(vec))) +} + +/// Format the user-facing CSV error message. +fn format_csv_error(error: csv::Error) -> String { + match error.kind() { + csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(), + csv::ErrorKind::UnequalLengths { pos, expected_len, len } => { + let mut msg = format!( + "failed to parse csv file: found {len} instead of {expected_len} fields" + ); + if let Some(pos) = pos { + write!(msg, " in line {}", pos.line()).unwrap(); + } + msg + } + _ => "failed to parse csv file".into(), + } +} + +/// Read structured data from a JSON file. +pub fn json(vm: &Vm, args: &mut Args) -> SourceResult<Value> { + let Spanned { v: path, span } = + args.expect::<Spanned<EcoString>>("path to json file")?; + + let path = vm.locate(&path).at(span)?; + let data = vm.world().file(&path).at(span)?; + let value: serde_json::Value = + serde_json::from_slice(&data).map_err(format_json_error).at(span)?; + + Ok(convert_json(value)) +} + +/// Convert a JSON value to a Typst value. +fn convert_json(value: serde_json::Value) -> Value { + match value { + serde_json::Value::Null => Value::None, + serde_json::Value::Bool(v) => Value::Bool(v), + serde_json::Value::Number(v) => match v.as_i64() { + Some(int) => Value::Int(int), + None => Value::Float(v.as_f64().unwrap_or(f64::NAN)), + }, + serde_json::Value::String(v) => Value::Str(v.into()), + serde_json::Value::Array(v) => { + Value::Array(v.into_iter().map(convert_json).collect()) + } + serde_json::Value::Object(v) => Value::Dict( + v.into_iter() + .map(|(key, value)| (key.into(), convert_json(value))) + .collect(), + ), + } +} + +/// Format the user-facing JSON error message. +fn format_json_error(error: serde_json::Error) -> String { + assert!(error.is_syntax() || error.is_eof()); + format!("failed to parse json file: syntax error in line {}", error.line()) +} + +/// Read structured data from an XML file. +pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult<Value> { + let Spanned { v: path, span } = + args.expect::<Spanned<EcoString>>("path to xml file")?; + + let path = vm.locate(&path).at(span)?; + let data = vm.world().file(&path).at(span)?; + let text = std::str::from_utf8(&data).map_err(FileError::from).at(span)?; + + let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?; + + Ok(convert_xml(document.root())) +} + +/// Convert an XML node to a Typst value. +fn convert_xml(node: roxmltree::Node) -> Value { + if node.is_text() { + return Value::Str(node.text().unwrap_or_default().into()); + } + + let children: Array = node.children().map(convert_xml).collect(); + if node.is_root() { + return Value::Array(children); + } + + let tag: Str = node.tag_name().name().into(); + let attrs: Dict = node + .attributes() + .iter() + .map(|attr| (attr.name().into(), attr.value().into())) + .collect(); + + Value::Dict(dict! { + "tag" => tag, + "attrs" => attrs, + "children" => children, + }) +} + +/// Format the user-facing XML error message. +fn format_xml_error(error: roxmltree::Error) -> String { + format_xml_like_error("xml file", error) +} |
