Basic implementation of XML parsing, doesn't quite work yet

This commit is contained in:
elkowar 2020-10-03 00:13:56 +02:00
parent 1f6fe840fc
commit d1c991ba92
6 changed files with 201 additions and 15 deletions

16
Cargo.lock generated
View file

@ -338,6 +338,7 @@ dependencies = [
"num",
"pretty_assertions",
"regex",
"roxmltree",
"serde",
"stoppable_thread",
"structopt",
@ -1440,6 +1441,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "roxmltree"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17dfc6c39f846bfc7d2ec442ad12055d79608d501380789b965d22f9354451f2"
dependencies = [
"xmlparser",
]
[[package]]
name = "rustc-demangle"
version = "0.1.16"
@ -1789,3 +1799,9 @@ dependencies = [
"winapi 0.2.8",
"winapi-build",
]
[[package]]
name = "xmlparser"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"

View file

@ -28,6 +28,7 @@ calloop = "0.6"
crossbeam-channel = "0.4"
num = "0.3"
stoppable_thread = "0.2"
roxmltree = "0.13"
#thiserror = "1.0"

View file

@ -14,6 +14,35 @@ pub struct WidgetDefinition {
}
impl WidgetDefinition {
pub fn from_xml(xml: roxmltree::Node) -> Result<Self> {
if !xml.is_element() {
bail!("Tried to parse element of type {:?} as Widget definition", xml.node_type());
} else if xml.tag_name().name().to_lowercase() != "def" {
bail!(
"Illegal element: only <def> may be used in definition block, but found '{}'",
xml.tag_name().name()
);
} else if xml.children().count() != 1 {
bail!(
"Widget definition '{}' needs to contain exactly one element",
xml.tag_name().name()
);
}
Ok(WidgetDefinition {
name: xml.try_attribute("name")?.to_owned(),
size: if let Some(node) = xml.children().find(|child| child.tag_name().name() == "size") {
Some((node.try_attribute("x")?.parse()?, node.try_attribute("y")?.parse()?))
} else {
None
},
// we can unwrap here, because we previously verified that there is exactly one child
structure: WidgetUse::from_xml(xml.first_child().unwrap())?,
})
}
pub fn parse_hocon(name: String, hocon: &Hocon) -> Result<Self> {
let definition = hocon.as_hash()?;
let structure = definition
@ -42,6 +71,40 @@ pub struct WidgetUse {
pub attrs: HashMap<String, AttrValue>,
}
impl WidgetUse {
pub fn from_xml(xml: roxmltree::Node) -> Result<Self> {
match xml.node_type() {
roxmltree::NodeType::Text => Ok(WidgetUse::simple_text(AttrValue::parse_string(
xml.text()
.context("couldn't get text from node")?
.trim_matches('\n')
.trim()
.to_owned(),
))),
roxmltree::NodeType::Element => {
let widget_name = xml.tag_name();
let attrs = xml
.attributes()
.iter()
.map(|attr| (attr.name().to_owned(), AttrValue::parse_string(attr.value().to_owned())))
.collect::<HashMap<_, _>>();
let children = xml
.children()
.filter(|child| !child.is_comment())
.filter(|child| !(child.is_text() && child.text().unwrap().trim().trim_matches('\n').is_empty()))
.map(|child| WidgetUse::from_xml(child))
.collect::<Result<_>>()?;
Ok(WidgetUse {
name: widget_name.name().to_string(),
attrs,
children,
})
}
_ => Err(anyhow!("Tried to parse node of type {:?} as widget use", xml.node_type())),
}
}
}
impl WidgetUse {
pub fn new(name: String, children: Vec<WidgetUse>) -> Self {
WidgetUse {
@ -174,4 +237,29 @@ mod test {
expected
);
}
#[test]
fn test_parse_widget_use_xml() {
let input = r#"
<widget_name attr1="hi" attr2="12">
<child_widget/>
foo
</widget_name>
"#;
let document = roxmltree::Document::parse(input).unwrap();
let xml = document.root_element().clone();
let expected = WidgetUse {
name: "widget_name".to_owned(),
attrs: hashmap! {
"attr1".to_owned() => AttrValue::Concrete(PrimitiveValue::String("hi".to_owned())),
"attr2".to_owned() => AttrValue::Concrete(PrimitiveValue::Number(12f64)),
},
children: vec![
WidgetUse::new("child_widget".to_owned(), Vec::new()),
WidgetUse::simple_text(AttrValue::Concrete(PrimitiveValue::String("foo".to_owned()))),
],
};
assert_eq!(expected, WidgetUse::from_xml(xml).unwrap());
}
}

View file

@ -5,9 +5,11 @@ use hocon::*;
use hocon_ext::HoconExt;
use std::collections::HashMap;
use std::convert::TryFrom;
use xml_ext::*;
pub mod element;
pub mod hocon_ext;
pub mod xml_ext;
#[allow(unused)]
macro_rules! try_type {
@ -31,7 +33,49 @@ pub struct EwwConfig {
impl EwwConfig {
pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
EwwConfig::from_hocon(&parse_hocon(&content)?)
let document = roxmltree::Document::parse(&content)?;
EwwConfig::from_xml(document.root_element())
}
pub fn from_xml(xml: roxmltree::Node) -> Result<Self> {
let definitions = xml
.find_child_with_tag("definitions")?
.children()
.map(|child| {
let def = WidgetDefinition::from_xml(child)?;
Ok((def.name.clone(), def))
})
.collect::<Result<HashMap<_, _>>>()
.context("error parsing widget definitions")?;
let windows = xml
.find_child_with_tag("windows")?
.children()
.map(|child| Ok((child.try_attribute("name")?.to_owned(), EwwWindowDefinition::from_xml(child)?)))
.collect::<Result<HashMap<_, _>>>()
.context("error parsing window definitions")?;
let default_vars = xml
.find_child_with_tag("variables")
.map(|variables_node| {
variables_node
.children()
.map(|child| {
Some((
child.tag_name().name().to_owned(),
PrimitiveValue::parse_string(child.text()?.trim_matches('\n').trim()),
))
})
.collect::<Option<HashMap<_, _>>>()
})
.unwrap_or_default()
.context("error parsing default variable value")?;
Ok(EwwConfig {
widgets: definitions,
windows,
default_vars,
})
}
pub fn from_hocon(hocon: &Hocon) -> Result<Self> {
@ -87,7 +131,24 @@ pub struct EwwWindowDefinition {
}
impl EwwWindowDefinition {
pub fn from_hocon(hocon: &Hocon) -> Result<EwwWindowDefinition> {
pub fn from_xml(xml: roxmltree::Node) -> Result<Self> {
if xml.tag_name().name() != "window" {
bail!(
"Only <window> tags are valid window definitions, but found {}",
xml.tag_name().name()
);
}
let size_node = xml.find_child_with_tag("size")?;
let size = (size_node.try_attribute("x")?.parse()?, size_node.try_attribute("y")?.parse()?);
let pos_node = xml.find_child_with_tag("pos")?;
let position = (pos_node.try_attribute("x")?.parse()?, pos_node.try_attribute("y")?.parse()?);
let widget = WidgetUse::from_xml(xml.find_child_with_tag("widget")?)?;
Ok(EwwWindowDefinition { position, size, widget })
}
pub fn from_hocon(hocon: &Hocon) -> Result<Self> {
let data = hocon.as_hash().context("window config has to be a map structure")?;
let position: Option<_> = try {
(

19
src/config/xml_ext.rs Normal file
View file

@ -0,0 +1,19 @@
use anyhow::*;
use extend::ext;
#[ext(pub)]
impl<'a, 'b> roxmltree::Node<'a, 'b> {
fn find_child_with_tag(&self, tag_name: &str) -> Result<Self>
where
Self: Sized,
{
self.children()
.find(|child| child.tag_name().name() == tag_name)
.with_context(|| anyhow!("node {} contained no child of type {}", self.tag_name().name(), tag_name,))
}
fn try_attribute(&self, key: &str) -> Result<&str> {
self.attribute(key)
.with_context(|| anyhow!("attribute '{}' missing from '{}'", key, self.tag_name().name()))
}
}

View file

@ -12,20 +12,12 @@ pub enum PrimitiveValue {
Boolean(bool),
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PollingCommandValue {
command: String,
interval: std::time::Duration,
}
impl std::str::FromStr for PrimitiveValue {
type Err = anyhow::Error;
/// parses the value, trying to turn it into a number and a boolean first, before deciding that it is a string.
fn from_str(s: &str) -> Result<PrimitiveValue> {
Ok(s.parse()
.map(PrimitiveValue::Number)
.or_else(|_| s.parse().map(PrimitiveValue::Boolean))
.unwrap_or_else(|_| PrimitiveValue::String(remove_surrounding(s, '\'').to_string())))
Ok(PrimitiveValue::parse_string(s))
}
}
@ -61,6 +53,13 @@ impl From<&str> for PrimitiveValue {
}
impl PrimitiveValue {
/// parses the value, trying to turn it into a number and a boolean first, before deciding that it is a string.
pub fn parse_string(s: &str) -> Self {
s.parse()
.map(PrimitiveValue::Number)
.or_else(|_| s.parse().map(PrimitiveValue::Boolean))
.unwrap_or_else(|_| PrimitiveValue::String(remove_surrounding(s, '\'').to_string()))
}
pub fn as_string(&self) -> Result<String> {
match self {
PrimitiveValue::String(x) => Ok(x.clone()),
@ -137,11 +136,13 @@ impl AttrValue {
try_match!(AttrValue::VarRef(x) = self).map_err(|e| anyhow!("{:?} is not a VarRef", e))
}
pub fn from_string(s: String) -> Self {
/// parses the value, trying to turn it into VarRef,
/// a number and a boolean first, before deciding that it is a string.
pub fn parse_string(s: String) -> Self {
if s.starts_with("$$") {
AttrValue::VarRef(s.trim_start_matches("$$").to_string())
} else {
AttrValue::Concrete(PrimitiveValue::String(s.clone()))
AttrValue::Concrete(PrimitiveValue::parse_string(&s))
}
}
}
@ -155,7 +156,7 @@ impl std::convert::TryFrom<&Hocon> for AttrValue {
type Error = anyhow::Error;
fn try_from(value: &Hocon) -> Result<Self> {
Ok(match value {
Hocon::String(s) => AttrValue::from_string(s.clone()),
Hocon::String(s) => AttrValue::parse_string(s.clone()),
Hocon::Integer(n) => AttrValue::Concrete(PrimitiveValue::Number(*n as f64)),
Hocon::Real(n) => AttrValue::Concrete(PrimitiveValue::Number(*n as f64)),
Hocon::Boolean(b) => AttrValue::Concrete(PrimitiveValue::Boolean(*b)),