feat: xml
- add new feature: xml parser Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
parent
6219459e3e
commit
21984bdfba
7 changed files with 409 additions and 6 deletions
|
|
@ -2,3 +2,4 @@
|
|||
pub mod models;
|
||||
pub mod previews;
|
||||
pub mod recipe_functions;
|
||||
pub mod xml;
|
||||
|
|
|
|||
|
|
@ -339,3 +339,12 @@ pub fn grep_latest_versions(dir_path: &str) -> Result<HashMap<String, usize>, st
|
|||
|
||||
Ok(vs)
|
||||
}
|
||||
|
||||
pub fn read_tsv_file(path: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut content = String::new();
|
||||
|
||||
file.read_to_string(&mut content)?;
|
||||
|
||||
Ok(content.lines().map(|x| x.to_string()).collect())
|
||||
}
|
||||
|
|
|
|||
1
src/xml/mod.rs
Normal file
1
src/xml/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod node;
|
||||
158
src/xml/node.rs
Normal file
158
src/xml/node.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
use indexmap::IndexMap;
|
||||
use quick_xml::events::Event;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Node {
|
||||
pub name: String,
|
||||
pub children: Vec<Node>,
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
pub fn parse_xml_to_tree(xml: &str) -> Vec<Node> {
|
||||
let mut reader = quick_xml::Reader::from_str(xml);
|
||||
|
||||
let mut stack: Vec<Node> = Vec::new();
|
||||
let mut roots: Vec<Node> = Vec::new();
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event_into(&mut buf) {
|
||||
Ok(Event::Start(e)) => {
|
||||
let name = String::from_utf8_lossy(e.name().as_ref()).into_owned();
|
||||
stack.push(Node {
|
||||
name,
|
||||
children: Vec::new(),
|
||||
value: None,
|
||||
});
|
||||
}
|
||||
Ok(Event::End(_)) => {
|
||||
if let Some(finished_node) = stack.pop() {
|
||||
if let Some(parent) = stack.last_mut() {
|
||||
parent.children.push(finished_node);
|
||||
} else {
|
||||
roots.push(finished_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Event::Text(e)) => {
|
||||
if let Some(current_node) = stack.last_mut() {
|
||||
let curr_text = String::from_utf8(e.clone().into_inner().to_vec()).unwrap();
|
||||
// println!("detect text: {curr_text} -- {e:?}");
|
||||
current_node.value = Some(curr_text);
|
||||
}
|
||||
}
|
||||
Ok(Event::Eof) => break,
|
||||
Err(e) => {
|
||||
println!("error: {e:?}");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
roots
|
||||
}
|
||||
|
||||
pub fn print_tree(nodes: &[Node], depth: usize) {
|
||||
for node in nodes {
|
||||
let indent = " ".repeat(depth);
|
||||
match (&node.value, node.children.is_empty()) {
|
||||
(Some(val), true) => {
|
||||
println!("{}<{}>{}</{}>", indent, node.name, val, node.name);
|
||||
}
|
||||
(_, false) => {
|
||||
println!("{}<{}>", indent, node.name);
|
||||
print_tree(&node.children, depth + 1);
|
||||
println!("{}</{}>", indent, node.name);
|
||||
}
|
||||
(None, true) => {
|
||||
println!("{}<{}></{}>", indent, node.name, node.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn find_by_child_value(
|
||||
&self,
|
||||
parent_name: &str,
|
||||
child_name: &str,
|
||||
target_value: &str,
|
||||
) -> Vec<Node> {
|
||||
let mut matches = Vec::new();
|
||||
|
||||
if self.name == parent_name {
|
||||
if self.children.iter().any(|c| {
|
||||
c.name == child_name && c.clone().value.is_some_and(|x| x.contains(target_value))
|
||||
}) {
|
||||
matches.push(self.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for child in &self.children {
|
||||
matches.extend(child.find_by_child_value(parent_name, child_name, target_value));
|
||||
}
|
||||
matches
|
||||
}
|
||||
|
||||
pub fn get_child(&self, name: &str) -> Option<&Node> {
|
||||
self.children.iter().find(|c| c.name == name)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_nodes_from_xml(
|
||||
catalog_map: Vec<(&str, String)>,
|
||||
) -> Result<IndexMap<String, Vec<Node>>, Box<dyn std::error::Error>> {
|
||||
let mut result = IndexMap::new();
|
||||
|
||||
for catalog_m in catalog_map {
|
||||
let catalog_name = catalog_m.0;
|
||||
let catalog_path = catalog_m.1.clone();
|
||||
|
||||
let mut file = File::open(catalog_path)?;
|
||||
let mut content = String::new();
|
||||
|
||||
file.read_to_string(&mut content)?;
|
||||
|
||||
// clean comment
|
||||
let mut new_file = String::new();
|
||||
for line in content.lines() {
|
||||
if line.contains(";") && !line.contains(";include") {
|
||||
continue;
|
||||
} else {
|
||||
new_file.push_str(format!("{line}\n").replace("&", "[amp]").as_str());
|
||||
}
|
||||
}
|
||||
|
||||
let node = parse_xml_to_tree(&new_file);
|
||||
result.insert(catalog_name.to_string(), node);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// get node from path of node vector
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// let current_menus_result: Option<&Node> = get_path!(root_node, ScrollableCatalog.Menus);
|
||||
///
|
||||
/// // Possible results
|
||||
///
|
||||
/// //Some(Node { name: "Menus", children: [Node { name: "Menu", children: [Node { name: "State",....
|
||||
///
|
||||
/// //None
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! get_path {
|
||||
($node:expr, $last:ident) => {
|
||||
$node.get_child(stringify!($last))
|
||||
};
|
||||
// recursive case
|
||||
($node:expr, $next:ident . $($rest:ident).+) => {
|
||||
$node
|
||||
.get_child(stringify!($next))
|
||||
.and_then(|child| get_path!(child, $($rest).+))
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue