Basic implementation of XML parsing, doesn't quite work yet
This commit is contained in:
parent
1f6fe840fc
commit
d1c991ba92
6 changed files with 201 additions and 15 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -28,6 +28,7 @@ calloop = "0.6"
|
|||
crossbeam-channel = "0.4"
|
||||
num = "0.3"
|
||||
stoppable_thread = "0.2"
|
||||
roxmltree = "0.13"
|
||||
|
||||
#thiserror = "1.0"
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
19
src/config/xml_ext.rs
Normal 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()))
|
||||
}
|
||||
}
|
27
src/value.rs
27
src/value.rs
|
@ -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)),
|
||||
|
|
Loading…
Add table
Reference in a new issue