Skip to content

Commit

Permalink
refactor+feat: splitting functions and structures to increase readabi…
Browse files Browse the repository at this point in the history
…lity + creating testes for every file
  • Loading branch information
nephi-dev committed Nov 24, 2023
1 parent 8af1e89 commit 285f6cb
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 193 deletions.
24 changes: 24 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#[macro_export]
macro_rules! f_str {
($e:expr) => {
String::from($e)
};
}
#[macro_export]
macro_rules! f_utf {
($e:expr) => {
String::from_utf8($e.to_vec()).unwrap()
};
}

#[cfg(test)]
mod tests {
#[test]
fn test_f_str() {
assert_eq!(f_str!("test"), String::from("test"));
}
#[test]
fn test_f_utf() {
assert_eq!(f_utf!(b"test"), String::from("test"));
}
}
93 changes: 93 additions & 0 deletions src/entities.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use pyo3::prelude::*;
use std::collections::HashMap;

#[derive(Clone)]
#[pyclass]
pub struct Node {
#[pyo3(get)]
pub name: String,
#[pyo3(get)]
pub attrs: HashMap<String, String>,
#[pyo3(get)]
pub children: Vec<Node>,
#[pyo3(get)]
pub text: Option<String>,
}

#[pymethods]
impl Node {
#[new]
pub fn new(
name: String,
attrs: Option<HashMap<String, String>>,
children: Option<Vec<Node>>,
text: Option<String>,
) -> PyResult<Self> {
let _attrs = attrs.unwrap_or_default();
let _children = children.unwrap_or_default();
Ok(Node {
name,
attrs: _attrs,
children: _children,
text,
})
}
fn __to_string(&self, spacing: Option<u8>) -> String {
let _spacing = spacing.unwrap_or(0);
let spaces = " ".repeat(_spacing as usize);
let mut s = String::new();
s.push_str(&format!("{}Name: {}", spaces, self.name));
if !self.attrs.is_empty() {
s.push_str(&format!("\n{}Attributes:", spaces));
for (k, v) in &self.attrs {
s.push_str(&format!("\n{}{}: {}", spaces, k, v));
}
}
if let Some(text) = &self.text {
s.push_str(&format!("\n{}Text: {}", spaces, text));
}
if !self.children.is_empty() {
s.push_str(&format!("\n{}Children:", spaces));
for child in &self.children {
s.push_str(&format!(
"\n{}{}\n",
spaces,
child.__to_string(Some(_spacing + 2))
));
}
}
s
}

fn __str__(&self) -> String {
self.__to_string(None)
}

fn __repr__(&self) -> String {
format!("Node({})", self.name)
}
}

#[cfg(test)]
mod tests {
use crate::entities::Node;
use crate::f_str;
use std::collections::HashMap;
#[test]
fn test_node() {
let mut attrs = HashMap::new();
attrs.insert(f_str!("test"), f_str!("test"));
let node = Node::new(
f_str!("test"),
Some(attrs),
Some(Vec::new()),
Some(f_str!("test")),
)
.unwrap();
assert_eq!(node.name, String::from("test"));
assert_eq!(node.attrs.len(), 1);
assert_eq!(node.attrs.get("test").unwrap(), "test");
assert_eq!(node.children.len(), 0);
assert_eq!(node.text.unwrap(), "test");
}
}
202 changes: 9 additions & 193 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,199 +1,15 @@
use pyo3::prelude::*;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use quick_xml::{Reader, Writer};
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::io::{Cursor, Write};

macro_rules! f_str {
($e:expr) => {
String::from($e)
};
}
macro_rules! f_utf {
($e:expr) => {
String::from_utf8($e.to_vec()).unwrap()
};
}

#[derive(Clone)]
#[pyclass]
struct Node {
#[pyo3(get)]
name: String,
#[pyo3(get)]
attrs: HashMap<String, String>,
#[pyo3(get)]
children: Vec<Node>,
#[pyo3(get)]
text: Option<String>,
}

#[pymethods]
impl Node {
#[new]
fn new(
name: String,
attrs: Option<HashMap<String, String>>,
children: Option<Vec<Node>>,
text: Option<String>,
) -> PyResult<Self> {
let _attrs = attrs.unwrap_or(HashMap::new());
let _children = children.unwrap_or(Vec::new());
Ok(Node {
name,
attrs: _attrs,
children: _children,
text,
})
}
fn __to_string(&self, spacing: Option<u8>) -> String {
let _spacing = spacing.unwrap_or(0);
let spaces = " ".repeat(_spacing as usize);
let mut s = String::new();
s.push_str(&format!("{}Name: {}", spaces, self.name));
if !self.attrs.is_empty() {
s.push_str(&format!("\n{}Attributes:", spaces));
for (k, v) in &self.attrs {
s.push_str(&format!("\n{}{}: {}", spaces, k, v));
}
}
if let Some(text) = &self.text {
s.push_str(&format!("\n{}Text: {}", spaces, text));
}
if !self.children.is_empty() {
s.push_str(&format!("\n{}Children:", spaces));
for child in &self.children {
s.push_str(&format!(
"\n{}{}\n",
spaces,
child.__to_string(Some(_spacing + 2))
));
}
}
s
}

fn __str__(&self) -> String {
self.__to_string(None)
}

fn __repr__(&self) -> String {
format!("Node({})", self.name)
}
}

fn read_node(root_tag: String, reader: &mut Reader<&[u8]>) -> Node {
let mut buf = Vec::new();
let mut root = Node {
name: root_tag.clone(),
attrs: HashMap::new(),
children: Vec::new(),
text: None,
};
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => match e.name().as_ref() {
_e if _e == root_tag.as_bytes() => {
for attr in e.attributes() {
let attr = attr.unwrap();
let attr_name = f_utf!(attr.key.as_ref());
let attr_value = f_utf!(attr.value);
root.attrs.insert(attr_name, attr_value);
}
}
_ => {
let tag_name = f_utf!(e.name().as_ref());
let child = read_node(tag_name, reader);
root.children.push(child);
}
},
Ok(Event::Text(e)) => {
root.text = Some(f_str!(e.unescape().unwrap()));
}
Ok(Event::End(e)) if e.name().as_ref() == root_tag.as_bytes() => {
break;
}
Ok(Event::Eof) => break,
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
_ => (),
}
buf.clear();
}
root
}

fn write_node(writer: &mut Writer<Cursor<Vec<u8>>>, node: Node) {
let mut start = BytesStart::new(&node.name);
for (k, v) in node.attrs {
start.push_attribute((k.as_str(), v.as_str()));
}
writer.write_event(Event::Start(start)).unwrap();
if let Some(text) = node.text {
writer
.write_event(Event::Text(BytesText::new(&text)))
.unwrap();
}
for child in node.children {
write_node(writer, child);
}
writer
.write_event(Event::End(BytesEnd::new(node.name)))
.unwrap();
}

fn write_node_to_string(node: Node, indent: usize, default_xml_def: bool) -> String {
let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', indent);
write_node(&mut writer, node);
let result = writer.into_inner().into_inner();
let mut return_string = String::new();
if default_xml_def {
return_string.push_str("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
}
return_string.push_str(&f_utf!(result));
return_string
}

#[pyfunction]
fn read_file(file_path: String, root_tag: String) -> Node {
let mut file = File::open(file_path).unwrap();
let mut file_str = String::new();
file.read_to_string(&mut file_str).unwrap();
let mut reader = Reader::from_str(file_str.as_str());
reader.trim_text(true);
read_node(f_str!(root_tag), &mut reader)
}

#[pyfunction]
fn read_string(xml_string: String, root_tag: String) -> Node {
let mut reader = Reader::from_str(xml_string.as_str());
reader.trim_text(true);
read_node(f_str!(root_tag), &mut reader)
}

#[pyfunction]
fn write_file(node: Node, file_path: String, indent: Option<usize>, default_xml_def: Option<bool>) {
let _indent = indent.unwrap_or(4);
let _default_xml_def = default_xml_def.unwrap_or(true);
let mut file = File::create(file_path).unwrap();
let xml_string = write_node_to_string(node, _indent, _default_xml_def);
file.write_all(xml_string.as_bytes()).unwrap();
}

#[pyfunction]
fn write_string(node: Node, indent: Option<usize>, default_xml_def: Option<bool>) -> String {
let _indent = indent.unwrap_or(4);
let _default_xml_def = default_xml_def.unwrap_or(true);
write_node_to_string(node, _indent, _default_xml_def)
}
mod common;
mod entities;
mod read;
mod write;

#[pymodule]
fn rxml(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Node>()?;
m.add_function(wrap_pyfunction!(read_file, m)?)?;
m.add_function(wrap_pyfunction!(read_string, m)?)?;
m.add_function(wrap_pyfunction!(write_file, m)?)?;
m.add_function(wrap_pyfunction!(write_string, m)?)?;
m.add_class::<entities::Node>()?;
m.add_function(wrap_pyfunction!(read::read_file, m)?)?;
m.add_function(wrap_pyfunction!(read::read_string, m)?)?;
m.add_function(wrap_pyfunction!(write::write_file, m)?)?;
m.add_function(wrap_pyfunction!(write::write_string, m)?)?;
Ok(())
}
Loading

0 comments on commit 285f6cb

Please sign in to comment.