eww/src/config/xml_ext.rs
ay9thqi3tbqiwbegqsg a[soiaosshasdg ac8bc7d8b5
Fix a few clippy warnings (#59)
2020-11-14 17:57:50 +01:00

242 lines
7.4 KiB
Rust

use crate::util::StringExt;
use anyhow::*;
use itertools::Itertools;
use std::fmt;
#[macro_export]
macro_rules! with_text_pos_context {
($node:expr => $($code:tt)*) => {{
let result: Result<_> = try { $($code)* };
result.with_context(|| anyhow!("at: {}", $node.text_pos()))
}};
}
#[derive(Debug, Clone)]
pub enum XmlNode<'a, 'b> {
Element(XmlElement<'a, 'b>),
Text(XmlText<'a, 'b>),
Ignored(roxmltree::Node<'a, 'b>),
}
impl<'a, 'b> fmt::Display for XmlNode<'a, 'b> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
XmlNode::Text(text) => write!(f, "{}", text),
XmlNode::Element(elem) => write!(f, "{}", elem),
XmlNode::Ignored(node) => write!(f, "{:?}", node),
}
}
}
/// Get the part of a string that is selected by the start and end TextPos.
/// Will panic if the range is out of bounds in any way.
fn get_text_from_text_range(s: &str, (start_pos, end_pos): (roxmltree::TextPos, roxmltree::TextPos)) -> String {
let mut code_text = s
.lines()
.dropping(start_pos.row as usize - 1)
.take(end_pos.row as usize - (start_pos.row as usize - 1))
.collect_vec();
if let Some(first_line) = code_text.first_mut() {
*first_line = first_line.split_at(start_pos.col as usize - 1).1;
}
if let Some(last_line) = code_text.last_mut() {
*last_line = last_line.split_at(end_pos.col as usize - 1).0;
}
code_text.join("\n")
}
impl<'a, 'b> XmlNode<'a, 'b> {
pub fn get_sourcecode(&self) -> String {
let input_text = self.node().document().input_text();
let range = self.node().range();
let start_pos = self.node().document().text_pos_at(range.start);
let end_pos = self.node().document().text_pos_at(range.end);
get_text_from_text_range(input_text, (start_pos, end_pos))
}
pub fn as_text_or_sourcecode(&self) -> String {
self.as_text().map(|c| c.text()).unwrap_or_else(|_| self.get_sourcecode())
}
pub fn as_text(&self) -> Result<&XmlText<'a, 'b>> {
match self {
XmlNode::Text(text) => Ok(text),
_ => Err(anyhow!("'{}' is not a text node", self)),
}
}
pub fn as_element(&self) -> Result<&XmlElement<'a, 'b>> {
match self {
XmlNode::Element(element) => Ok(element),
_ => Err(anyhow!("'{}' is not an element node", self)),
}
}
pub fn text_range(&self) -> std::ops::Range<usize> {
self.node().range()
}
pub fn text_pos(&self) -> roxmltree::TextPos {
let document = self.node().document();
let range = self.node().range();
document.text_pos_at(range.start)
}
fn node(&self) -> roxmltree::Node<'a, 'b> {
match self {
XmlNode::Text(x) => x.0,
XmlNode::Element(x) => x.0,
XmlNode::Ignored(x) => *x,
}
}
}
#[derive(Debug, Clone)]
pub struct XmlText<'a, 'b>(roxmltree::Node<'a, 'b>);
impl<'a, 'b> fmt::Display for XmlText<'a, 'b> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Text(\"{}\")", self.text())
}
}
impl<'a, 'b> XmlText<'a, 'b> {
pub fn text(&self) -> String {
self.0.text().unwrap_or_default().trim_lines().trim_matches('\n').to_owned()
}
pub fn text_pos(&self) -> roxmltree::TextPos {
let document = self.0.document();
let range = self.0.range();
document.text_pos_at(range.start)
}
}
#[derive(Debug, Clone)]
pub struct XmlElement<'a, 'b>(roxmltree::Node<'a, 'b>);
impl<'a, 'b> fmt::Display for XmlElement<'a, 'b> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let children = self
.children()
.map(|child| format!("{}", child))
.map(|x| x.lines().map(|line| format!(" {}", line)).join("\n"))
.join("\n");
if children.is_empty() {
write!(f, "{}</{}>", self.as_tag_string(), self.tag_name())
} else {
write!(f, "{}\n{}\n</{}>", self.as_tag_string(), children, self.tag_name())
}
}
}
impl<'a, 'b> XmlElement<'a, 'b> {
pub fn as_tag_string(&self) -> String {
let attrs = self
.attributes()
.iter()
.map(|attr| format!("{}=\"{}\"", attr.name(), attr.value()))
.join(" ");
format!("<{} {}>", self.tag_name(), attrs)
}
pub fn tag_name(&self) -> &str {
self.0.tag_name().name()
}
pub fn child(&self, tagname: &str) -> Result<XmlElement> {
with_text_pos_context! { self =>
self.child_elements()
.find(|child| child.tag_name() == tagname)
.with_context(|| anyhow!("child element '{}' missing from {}", tagname, self.as_tag_string()))?
}
}
pub fn children(&self) -> impl Iterator<Item = XmlNode> {
self.0
.children()
.filter(|child| child.is_element() || (child.is_text() && !child.text().unwrap_or_default().is_blank()))
.map(XmlNode::from)
}
pub fn child_elements(&self) -> impl Iterator<Item = XmlElement> {
self.0.children().filter(|child| child.is_element()).map(XmlElement)
}
pub fn attributes(&self) -> &[roxmltree::Attribute] {
self.0.attributes()
}
pub fn attr(&self, key: &str) -> Result<&str> {
with_text_pos_context! { self =>
self.0
.attribute(key)
.with_context(|| anyhow!("'{}' missing attribute '{}'", self.as_tag_string(), key))?
}
}
pub fn optional_attr<O, F: FnOnce(&str) -> Result<O>>(&self, key: &str, parse: F) -> Result<Option<O>> {
match self.0.attribute(key) {
Some(value) => parse(value).map(Some),
None => Ok(None),
}
}
pub fn parse_optional_attr<E, O: std::str::FromStr<Err = E>>(&self, key: &str) -> Result<Option<O>, E> {
match self.0.attribute(key) {
Some(value) => value.parse::<O>().map(Some),
None => Ok(None),
}
}
pub fn only_child(&self) -> Result<XmlNode> {
with_text_pos_context! { self =>
let mut children_iter = self.children();
let only_child = children_iter
.next()
.with_context(|| anyhow!("'{}' had no children", self.as_tag_string()))?;
if children_iter.next().is_some() {
bail!("'{}' had more than one child", &self);
}
only_child
}
}
pub fn only_child_element(&self) -> Result<XmlElement> {
with_text_pos_context! { self =>
self.only_child()?.as_element()?.clone()
}
}
pub fn text_pos(&self) -> roxmltree::TextPos {
let document = self.0.document();
let range = self.0.range();
document.text_pos_at(range.start)
}
}
impl<'a, 'b> From<XmlElement<'a, 'b>> for XmlNode<'a, 'b> {
fn from(elem: XmlElement<'a, 'b>) -> Self {
XmlNode::Element(elem)
}
}
impl<'a, 'b> From<XmlText<'a, 'b>> for XmlNode<'a, 'b> {
fn from(elem: XmlText<'a, 'b>) -> Self {
XmlNode::Text(elem)
}
}
impl<'a, 'b> From<roxmltree::Node<'a, 'b>> for XmlNode<'a, 'b> {
fn from(node: roxmltree::Node<'a, 'b>) -> Self {
if node.is_text() {
XmlNode::Text(XmlText(node))
} else if node.is_element() | node.is_root() {
XmlNode::Element(XmlElement(node))
} else {
XmlNode::Ignored(node)
}
}
}