XML reading
This commit is contained in:
parent
fffb55f79a
commit
f6adc45638
37
src/diag.rs
37
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}"),
|
||||
}
|
||||
}
|
||||
|
29
src/image.rs
29
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),
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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::*;
|
||||
|
@ -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)
|
||||
}
|
||||
|
3
tests/res/bad.xml
Normal file
3
tests/res/bad.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<data>
|
||||
<hello name="hi">1
|
||||
</data>
|
7
tests/res/data.xml
Normal file
7
tests/res/data.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<data>
|
||||
<hello name="hi">1</hello>
|
||||
<data>
|
||||
<hello>World</hello>
|
||||
<hello>World</hello>
|
||||
</data>
|
||||
</data>
|
@ -27,3 +27,32 @@
|
||||
---
|
||||
// Error: 7-22 failed to parse json file: syntax error in line 3
|
||||
#json("/res/bad.json")
|
||||
|
||||
---
|
||||
// Test reading XML data.
|
||||
#let data = xml("/res/data.xml")
|
||||
#test(data, ((
|
||||
tag: "data",
|
||||
attrs: (:),
|
||||
children: (
|
||||
"\n ",
|
||||
(tag: "hello", attrs: (name: "hi"), children: ("1",)),
|
||||
"\n ",
|
||||
(
|
||||
tag: "data",
|
||||
attrs: (:),
|
||||
children: (
|
||||
"\n ",
|
||||
(tag: "hello", attrs: (:), children: ("World",)),
|
||||
"\n ",
|
||||
(tag: "hello", attrs: (:), children: ("World",)),
|
||||
"\n ",
|
||||
),
|
||||
),
|
||||
"\n",
|
||||
),
|
||||
),))
|
||||
|
||||
---
|
||||
// Error: 6-20 failed to parse xml file: found closing tag 'data' instead of 'hello' in line 3
|
||||
#xml("/res/bad.xml")
|
||||
|
Loading…
x
Reference in New Issue
Block a user