summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/diag.rs37
-rw-r--r--src/image.rs29
-rw-r--r--src/library/mod.rs1
-rw-r--r--src/library/prelude.rs6
-rw-r--r--src/library/utility/data.rs45
5 files changed, 89 insertions, 29 deletions
diff --git a/src/diag.rs b/src/diag.rs
index 81bb7e51..0d016a21 100644
--- a/src/diag.rs
+++ b/src/diag.rs
@@ -3,6 +3,7 @@
use std::fmt::{self, Display, Formatter};
use std::io;
use std::path::{Path, PathBuf};
+use std::str::Utf8Error;
use std::string::FromUtf8Error;
use comemo::Tracked;
@@ -191,6 +192,12 @@ impl Display for FileError {
}
}
+impl From<Utf8Error> for FileError {
+ fn from(_: Utf8Error) -> Self {
+ Self::InvalidUtf8
+ }
+}
+
impl From<FromUtf8Error> for FileError {
fn from(_: FromUtf8Error) -> Self {
Self::InvalidUtf8
@@ -202,3 +209,33 @@ impl From<FileError> for String {
error.to_string()
}
}
+
+/// Format a user-facing error message for an XML-like file format.
+pub fn format_xml_like_error(format: &str, error: roxmltree::Error) -> String {
+ match error {
+ roxmltree::Error::UnexpectedCloseTag { expected, actual, pos } => {
+ format!(
+ "failed to parse {format}: found closing tag '{actual}' \
+ instead of '{expected}' in line {}",
+ pos.row
+ )
+ }
+ roxmltree::Error::UnknownEntityReference(entity, pos) => {
+ format!(
+ "failed to parse {format}: unknown entity '{entity}' in line {}",
+ pos.row
+ )
+ }
+ roxmltree::Error::DuplicatedAttribute(attr, pos) => {
+ format!(
+ "failed to parse {format}: duplicate attribute '{attr}' in line {}",
+ pos.row
+ )
+ }
+ roxmltree::Error::NoRootNode => {
+ format!("failed to parse {format}: missing root node")
+ }
+ roxmltree::Error::SizeLimit => "file is too large".into(),
+ _ => format!("failed to parse {format}"),
+ }
+}
diff --git a/src/image.rs b/src/image.rs
index 717b1a93..ae4d165f 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -2,7 +2,7 @@
use std::io;
-use crate::diag::StrResult;
+use crate::diag::{format_xml_like_error, StrResult};
use crate::util::Buffer;
/// A raster or vector image.
@@ -161,31 +161,6 @@ fn format_usvg_error(error: usvg::Error) -> String {
usvg::Error::InvalidSize => {
"failed to parse svg: width, height, or viewbox is invalid".into()
}
- usvg::Error::ParsingFailed(error) => match error {
- roxmltree::Error::UnexpectedCloseTag { expected, actual, pos } => {
- format!(
- "failed to parse svg: found closing tag '{actual}' \
- instead of '{expected}' in line {}",
- pos.row
- )
- }
- roxmltree::Error::UnknownEntityReference(entity, pos) => {
- format!(
- "failed to parse svg: unknown entity '{entity}' in line {}",
- pos.row
- )
- }
- roxmltree::Error::DuplicatedAttribute(attr, pos) => {
- format!(
- "failed to parse svg: duplicate attribute '{attr}' in line {}",
- pos.row
- )
- }
- roxmltree::Error::NoRootNode => {
- "failed to parse svg: missing root node".into()
- }
- roxmltree::Error::SizeLimit => "file is too large".into(),
- _ => "failed to parse svg".into(),
- },
+ usvg::Error::ParsingFailed(error) => format_xml_like_error("svg", error),
}
}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index d806f298..91e4671c 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -98,6 +98,7 @@ pub fn new() -> Scope {
std.def_fn("lorem", utility::lorem);
std.def_fn("csv", utility::csv);
std.def_fn("json", utility::json);
+ std.def_fn("xml", utility::xml);
// Predefined colors.
std.define("black", Color::BLACK);
diff --git a/src/library/prelude.rs b/src/library/prelude.rs
index 44d1af7f..03bef51e 100644
--- a/src/library/prelude.rs
+++ b/src/library/prelude.rs
@@ -9,10 +9,12 @@ pub use std::sync::Arc;
pub use comemo::Tracked;
pub use typst_macros::node;
-pub use crate::diag::{with_alternative, At, SourceError, SourceResult, StrResult};
+pub use crate::diag::{
+ with_alternative, At, FileError, FileResult, SourceError, SourceResult, StrResult,
+};
pub use crate::eval::{
Arg, Args, Array, Cast, Dict, Dynamic, Func, Node, RawAlign, RawLength, RawStroke,
- Scope, Smart, Value, Vm,
+ Scope, Smart, Str, Value, Vm,
};
pub use crate::frame::*;
pub use crate::geom::*;
diff --git a/src/library/utility/data.rs b/src/library/utility/data.rs
index e3efe6e7..0cff42c1 100644
--- a/src/library/utility/data.rs
+++ b/src/library/utility/data.rs
@@ -1,5 +1,6 @@
use std::fmt::Write;
+use crate::diag::format_xml_like_error;
use crate::library::prelude::*;
/// Read structured data from a CSV file.
@@ -84,3 +85,47 @@ fn format_json_error(error: serde_json::Error) -> String {
error.line()
)
}
+
+/// Read structured data from an XML file.
+pub fn xml(vm: &mut 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)
+}