mirror of
https://github.com/typst/typst
synced 2025-06-28 00:03:17 +08:00
add support for loading data from yaml files (#447)
This commit is contained in:
parent
9e69a7b161
commit
387bcc3879
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1580,6 +1580,7 @@ dependencies = [
|
|||||||
"roxmltree",
|
"roxmltree",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"syntect",
|
"syntect",
|
||||||
"ttf-parser 0.18.1",
|
"ttf-parser 0.18.1",
|
||||||
|
1
assets/files/bad.yaml
Normal file
1
assets/files/bad.yaml
Normal file
@ -0,0 +1 @@
|
|||||||
|
this_will_break: [)
|
11
assets/files/scifi-authors.yaml
Normal file
11
assets/files/scifi-authors.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"Arthur C. Clarke":
|
||||||
|
- title: Against the Fall of Night
|
||||||
|
published: "1978"
|
||||||
|
- title: The songs of distant earth
|
||||||
|
published: "1986"
|
||||||
|
|
||||||
|
"Isaac Asimov":
|
||||||
|
- title: Quasar, Quasar, Burning Bright
|
||||||
|
published: "1977"
|
||||||
|
- title: Far as Human Eye Could See
|
||||||
|
published: 1987
|
8
assets/files/yamltypes.yaml
Normal file
8
assets/files/yamltypes.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
null_key: [null, ~]
|
||||||
|
"string": text
|
||||||
|
integer: 5
|
||||||
|
float: 1.12
|
||||||
|
mapping: { '1': "one", '2': "two"}
|
||||||
|
seq: [1, 2, 3, 4]
|
||||||
|
bool: false
|
||||||
|
true: bool
|
@ -23,6 +23,7 @@ once_cell = "1"
|
|||||||
roxmltree = "0.14"
|
roxmltree = "0.14"
|
||||||
rustybuzz = "0.5"
|
rustybuzz = "0.5"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
serde_yaml = "0.8"
|
||||||
smallvec = "1.10"
|
smallvec = "1.10"
|
||||||
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
||||||
ttf-parser = "0.18.1"
|
ttf-parser = "0.18.1"
|
||||||
|
@ -207,6 +207,96 @@ fn format_json_error(error: serde_json::Error) -> String {
|
|||||||
format!("failed to parse json file: syntax error in line {}", error.line())
|
format!("failed to parse json file: syntax error in line {}", error.line())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read structured data from a YAML file.
|
||||||
|
///
|
||||||
|
/// The file must contain a valid YAML object or array. YAML mappings will be
|
||||||
|
/// converted into Typst dictionaries, and YAML sequences will be converted into
|
||||||
|
/// Typst arrays. Strings and booleans will be converted into the Typst
|
||||||
|
/// equivalents, null-values (`null`, `~` or empty ``) will be converted into
|
||||||
|
/// `{none}`, and numbers will be converted to floats or integers depending on
|
||||||
|
/// whether they are whole numbers.
|
||||||
|
///
|
||||||
|
/// Note that mapping keys that are not a string cause the entry to be
|
||||||
|
/// discarded.
|
||||||
|
///
|
||||||
|
/// Custom YAML tags are ignored, though the loaded value will still be
|
||||||
|
/// present.
|
||||||
|
///
|
||||||
|
/// The function returns a dictionary or value or an array, depending on
|
||||||
|
/// the YAML file.
|
||||||
|
///
|
||||||
|
/// The YAML files in the example contain objects with authors as keys,
|
||||||
|
/// each with a sequence of their own submapping with the keys
|
||||||
|
/// "title" and "published"
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```example
|
||||||
|
/// #let bookshelf(contents) = {
|
||||||
|
/// for author, works in contents {
|
||||||
|
/// author
|
||||||
|
/// for work in works [
|
||||||
|
/// - #work.title (#work.published)
|
||||||
|
/// ]
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #bookshelf(yaml("scifi-authors.yaml"))
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Display: YAML
|
||||||
|
/// Category: data-loading
|
||||||
|
/// Returns: array or value or dictionary
|
||||||
|
#[func]
|
||||||
|
pub fn yaml(
|
||||||
|
/// Path to a YAML file.
|
||||||
|
path: Spanned<EcoString>,
|
||||||
|
) -> Value {
|
||||||
|
let Spanned { v: path, span } = path;
|
||||||
|
let path = vm.locate(&path).at(span)?;
|
||||||
|
let data = vm.world().file(&path).at(span)?;
|
||||||
|
let value: serde_yaml::Value =
|
||||||
|
serde_yaml::from_slice(&data).map_err(format_yaml_error).at(span)?;
|
||||||
|
convert_yaml(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a YAML value to a Typst value.
|
||||||
|
fn convert_yaml(value: serde_yaml::Value) -> Value {
|
||||||
|
match value {
|
||||||
|
serde_yaml::Value::Null => Value::None,
|
||||||
|
serde_yaml::Value::Bool(v) => Value::Bool(v),
|
||||||
|
serde_yaml::Value::Number(v) => match v.as_i64() {
|
||||||
|
Some(int) => Value::Int(int),
|
||||||
|
None => Value::Float(v.as_f64().unwrap_or(f64::NAN)),
|
||||||
|
},
|
||||||
|
serde_yaml::Value::String(v) => Value::Str(v.into()),
|
||||||
|
serde_yaml::Value::Sequence(v) => {
|
||||||
|
Value::Array(v.into_iter().map(convert_yaml).collect())
|
||||||
|
}
|
||||||
|
serde_yaml::Value::Mapping(v) => Value::Dict(
|
||||||
|
v.into_iter()
|
||||||
|
.map(|(key, value)| (convert_yaml_key(key), convert_yaml(value)))
|
||||||
|
.filter_map(|(key, value)| key.map(|key|(key, value)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an arbitary YAML mapping key into a Typst Dict Key.
|
||||||
|
/// Currently it only does so for strings, everything else
|
||||||
|
/// returns None
|
||||||
|
fn convert_yaml_key(key: serde_yaml::Value) -> Option<Str> {
|
||||||
|
match key {
|
||||||
|
serde_yaml::Value::String(v) => Some(Str::from(v)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format the user-facing YAML error message.
|
||||||
|
#[track_caller]
|
||||||
|
fn format_yaml_error(error: serde_yaml::Error) -> String {
|
||||||
|
format!("failed to parse yaml file: {}", error.to_string().trim())
|
||||||
|
}
|
||||||
|
|
||||||
/// Read structured data from an XML file.
|
/// Read structured data from an XML file.
|
||||||
///
|
///
|
||||||
/// The XML file is parsed into an array of dictionaries and strings. XML nodes
|
/// The XML file is parsed into an array of dictionaries and strings. XML nodes
|
||||||
|
@ -124,6 +124,7 @@ fn global(math: Module, calc: Module) -> Module {
|
|||||||
global.define("read", compute::read);
|
global.define("read", compute::read);
|
||||||
global.define("csv", compute::csv);
|
global.define("csv", compute::csv);
|
||||||
global.define("json", compute::json);
|
global.define("json", compute::json);
|
||||||
|
global.define("yaml", compute::yaml);
|
||||||
global.define("xml", compute::xml);
|
global.define("xml", compute::xml);
|
||||||
|
|
||||||
// Calc.
|
// Calc.
|
||||||
|
@ -41,6 +41,24 @@
|
|||||||
// Error: 7-18 failed to parse json file: syntax error in line 3
|
// Error: 7-18 failed to parse json file: syntax error in line 3
|
||||||
#json("/bad.json")
|
#json("/bad.json")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test reading YAML data
|
||||||
|
#let data = yaml("/yamltypes.yaml")
|
||||||
|
#test(data.len(), 7)
|
||||||
|
#test(data.null_key, (none, none))
|
||||||
|
#test(data.string, "text")
|
||||||
|
#test(data.integer, 5)
|
||||||
|
#test(data.float, 1.12)
|
||||||
|
#test(data.mapping, ("1": "one", "2": "two"))
|
||||||
|
#test(data.seq, (1,2,3,4))
|
||||||
|
#test(data.bool, false)
|
||||||
|
#test(data.keys().contains("true"), false)
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 7-18 failed to parse yaml file: while parsing a flow sequence, expected ',' or ']' at line 2 column 1
|
||||||
|
#yaml("/bad.yaml")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test reading XML data.
|
// Test reading XML data.
|
||||||
#let data = xml("/data.xml")
|
#let data = xml("/data.xml")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user