use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::Write;
use std::path::Path;
use std::{fmt, ops};
mod split_unquoted;
use split_unquoted::SplitUnquoted;
pub mod error;
pub use error::Error;
pub use error::ParseError;
#[derive(Debug)]
pub struct Node {
pub tag: String,
pub attributes: HashMap<String, String>,
nodes: HashMap<String, Vec<Node>>,
pub content: String,
}
struct Payload<'a> {
prolog: &'a str,
node: Option<Node>,
remaining: &'a str,
}
fn validate_root(root: Result<Payload, Error>) -> Result<Node, Error> {
match root {
Ok(v) if v.prolog.len() != 0 => Err(Error::ContentOutsideRoot),
Ok(v) => Ok(v.node.unwrap_or(Node {
tag: String::new(),
content: String::new(),
nodes: HashMap::new(),
attributes: HashMap::new(),
})),
Err(e) => Err(e),
}
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Node, Error> {
validate_root(load_from_slice(&std::fs::read_to_string(path)?))
}
pub fn from_string(string: &str) -> Result<Node, Error> {
validate_root(load_from_slice(string))
}
pub fn new(tag: &str, content: String) -> Node {
Node {
attributes: HashMap::new(),
content,
tag: tag.to_owned(),
nodes: HashMap::new(),
}
}
pub fn new_filled(
tag: &str,
attributes: HashMap<String, String>,
content: String,
nodes: HashMap<String, Vec<Node>>,
) -> Node {
Node {
tag: tag.to_owned(),
attributes,
nodes,
content,
}
}
fn newlines_in_slice(string: &str) -> usize {
string.chars().filter(|c| *c == '\n').count()
}
fn load_from_slice(string: &str) -> Result<Payload, Error> {
let opening_del = match string.find("<") {
Some(v) => v,
None => {
return Ok(Payload {
prolog: "",
node: None,
remaining: string,
});
}
};
let closing_del = match string.find(">") {
Some(v) => v,
None => {
return Err(Error::ParseError(
ParseError::MissingClosingDelimiter,
newlines_in_slice(&string[..opening_del]),
))
}
};
let mut tag_parts =
SplitUnquoted::split(&string[opening_del + 1..closing_del], |c| c.is_whitespace());
let tag_name = tag_parts.next().unwrap().trim();
let prolog = string[..opening_del].trim();
if &tag_name[0..1] == "?" {
return load_from_slice(&string[closing_del + 1..]);
}
let mut attributes = HashMap::new();
for part in tag_parts {
if part == "/" {
break;
}
let equal_sign = match part.find("=") {
Some(v) => v,
None => {
return Err(Error::ParseError(
ParseError::MissingAttributeValue(part.to_owned()),
newlines_in_slice(&string[..closing_del]),
))
}
};
let (k, v) = part.split_at(equal_sign);
let v = if &v[1..2] == "\"" && (&v[v.len() - 1..] == "\"" || v.ends_with("\"/")) {
&v[2..v.len() - 1]
} else {
return Err(Error::ParseError(
ParseError::MissingQuotes(part.to_owned()),
newlines_in_slice(&string[..closing_del]),
));
};
attributes.insert(k.to_owned(), v.to_owned());
}
if string[opening_del + 1..closing_del].ends_with("/") {
return Ok(Payload {
prolog,
node: Some(Node {
tag: tag_name.to_owned(),
nodes: HashMap::new(),
attributes,
content: String::new(),
}),
remaining: &string[closing_del + 1..],
});
}
let closing_tag = match string.find(&format!("</{}>", tag_name)) {
Some(v) => v,
None => {
return Err(Error::ParseError(
ParseError::MissingClosingTag(tag_name.to_owned()),
newlines_in_slice(&string[..closing_del]),
))
}
};
let mut content = String::with_capacity(512);
let mut nodes = HashMap::new();
let mut buf = &string[closing_del + 1..closing_tag];
let mut offset = closing_del;
while buf.len() != 0 {
let payload = load_from_slice(buf).map_err(|e| match e {
Error::ParseError(e, ln) => {
Error::ParseError(e, ln + newlines_in_slice(&string[..offset]))
}
e => e,
})?;
if let Some(node) = payload.node {
let v = nodes
.entry(node.tag.clone())
.or_insert(Vec::with_capacity(1));
v.push(node);
}
if payload.remaining.as_ptr() == buf.as_ptr() {
break;
}
content.push_str(&payload.prolog);
offset += buf.len() - payload.remaining.len();
buf = payload.remaining;
}
content.push_str(buf);
let remaining = &string[closing_tag + tag_name.len() + 3..];
Ok(Payload {
prolog,
node: Some(Node {
tag: tag_name.to_owned(),
attributes,
nodes,
content: content.trim().into(),
}),
remaining,
})
}
impl Node {
pub fn get_nodes(&self, tag: &str) -> Option<&Vec<Node>> {
self.nodes.get(tag)
}
pub fn try_get_nodes(&self, tag: &str) -> Result<&Vec<Node>, Error> {
match self.nodes.get(tag) {
Some(v) => Ok(v),
None => Err(Error::TagNotFound(self.tag.to_owned(), tag.to_owned())),
}
}
pub fn add_attribute(&mut self, key: &str, val: &str) -> Option<String> {
self.attributes.insert(key.to_owned(), val.to_owned())
}
pub fn get_attribute(&self, key: &str) -> Option<&String> {
self.attributes.get(key)
}
pub fn try_get_attribute(&self, key: &str) -> Result<&String, Error> {
match self.attributes.get(key) {
Some(v) => Ok(v),
None => Err(Error::AttributeNotFound(
self.tag.to_owned(),
key.to_owned(),
)),
}
}
pub fn add_node(&mut self, node: Node) {
let v = self
.nodes
.entry(node.tag.clone())
.or_insert(Vec::with_capacity(1));
v.push(node);
}
pub fn add_new_node(&mut self, tag: &str, content: String) {
self.add_node(new(tag, content));
}
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
let mut file = File::create(path)?;
file.write_all(self.to_string().as_bytes())?;
Ok(())
}
pub fn save_to_file_pretty<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
let mut file = File::create(path)?;
file.write_all(self.to_string_pretty().as_bytes())?;
Ok(())
}
pub fn to_string_pretty(&self) -> String {
fn internal(node: &Node, depth: usize) -> String {
if node.tag == "" {
return "".to_owned();
}
match node.nodes.len() + node.content.len() {
0 => format!(
"{indent}<{}{}/>\n",
node.tag,
node.attributes
.iter()
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
.collect::<String>(),
indent = " ".repeat(depth * 4)
),
_ => format!(
"{indent}<{tag}{attr}>{beg}{nodes}{content}{end}</{tag}>\n",
tag = node.tag,
attr = node
.attributes
.iter()
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
.collect::<String>(),
nodes = node
.nodes
.iter()
.flat_map(|(_, nodes)| nodes.iter())
.map(|node| internal(node, depth + 1))
.collect::<String>(),
beg = match node.nodes.len() {
0 => "",
_ => "\n",
},
end = match node.nodes.len() {
0 => "".to_owned(),
_ => " ".repeat(depth * 4),
},
content = node.content,
indent = " ".repeat(depth * 4),
),
}
}
internal(&self, 0)
}
}
impl std::fmt::Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
if self.tag == "" {
return write!(f, "");
}
match self.nodes.len() + self.content.len() {
0 => write!(
f,
"<{}{}/>",
self.tag,
self.attributes
.iter()
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
.collect::<String>(),
),
_ => write!(
f,
"<{tag}{attr}>{nodes}{content}</{tag}>",
tag = self.tag,
attr = self
.attributes
.iter()
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
.collect::<String>(),
nodes = self
.nodes
.iter()
.flat_map(|(_, nodes)| nodes.iter())
.map(|node| node.to_string())
.collect::<String>(),
content = self.content,
),
}
}
}
impl ops::Index<&str> for Node {
type Output = [Node];
fn index(&self, tag: &str) -> &Self::Output {
match self.nodes.get(tag) {
Some(v) => &v[..],
None => &[],
}
}
}