summaryrefslogtreecommitdiff
path: root/library/src/compute/data.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-29 13:37:25 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-29 14:18:13 +0100
commit0efe669278a5e1c3f2985eba2f3360e91159c54a (patch)
tree502712857c48f0decb5e698257c0a96d358a436e /library/src/compute/data.rs
parent836692e73cff0356e409a9ba5b4887b86809d4ca (diff)
Reorganize library and tests
Diffstat (limited to 'library/src/compute/data.rs')
-rw-r--r--library/src/compute/data.rs129
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)
+}