feat: xml

- add new feature: xml parser

Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
Pakin 2026-04-17 11:05:17 +07:00
parent 6219459e3e
commit 21984bdfba
7 changed files with 409 additions and 6 deletions

225
README.md
View file

@ -26,6 +26,7 @@ let recipe_dir = cfg.get("RECIPE_DIR").unwrap();
---
### Get recipe from specific country (latest)
```rust
...
let latest_versions = grep_latest_versions(recipe_dir).unwrap();
@ -69,7 +70,6 @@ import::generate_recipe_sheet_table("mys", 626);
### Notes
Simple Snippet Patterns
```rust
@ -87,4 +87,225 @@ let pure_mat_id = if curr_rpl_mat_id > 300000 {
} else {
curr_rpl_mat_id
};
```
```
---
### XML Parser (Experimental)
This will parse xml file and create node structure type `Vec<Node>` where `Node` is from `crate::xml::node`
Xml-related functions
- `parse_xml_to_tree`: this will parse raw xml string into node structure.
- `print_tree`: printing node structure to xml
- `generate_nodes_from_xml`: generate node index map from list of `(catalog_name, catalog_path)`
Node functions
- `find_by_child_value`: search expected value from node (`parent_node`) which this value should be in node named `child_name`.
- `get_child`: get child node from current node's children
Shortcut Macro
- `get_path`: macro for accessing the node inner child by provided key `key1.key2.key3...`
Example of parsing xml layout v3 and generate into `new-layout-v2` format
```rust
use libtbr::xml::*;
// ...
let taobin_dir = cfg
.get("TAOBIN_REPO")
.expect("Taobin directory path not provided");
let v3_dir = format!("{taobin_dir}/inter/ltu/xml/multi/v3");
// pre-defined paths configuration in format (catalog_name, catalog_path)
let v3_catalogs = vec![
(
"recommend",
format!("{ltu_v3_dir}/event/event_v3/active_promotions.lxml"),
),
(
"coffee",
format!("{ltu_v3_dir}/page_catalog_group_coffee.lxml"),
),
("milk", format!("{ltu_v3_dir}/page_catalog_group_milk.lxml")),
("tea", format!("{ltu_v3_dir}/page_catalog_group_tea.lxml")),
(
"health",
format!("{ltu_v3_dir}/page_catalog_group_health.lxml"),
),
(
"other",
format!("{ltu_v3_dir}/page_catalog_group_other.lxml"),
),
];
// input must be `Vec<(&str, String)>`
let mut v3_catalog_nodes: IndexMap<String, Vec<Node>> =
generate_nodes_from_xml(v3_catalogs)?;
for (_, (catalog_name, catalog_nodes)) in v3_catalog_nodes.iter().enumerate() {
if catalog_nodes.len() == 1
&& let Some(root_node) = catalog_nodes.first()
{
// get_path is a macro for accessing the node inner child
//
// usage: get_path!(root_node, key1.key2.key3...);
//
let current_menus_result: Option<&Node> = get_path!(root_node, ScrollableCatalog.Menus);
if let Some(current_menus) = current_menus_result {
println!(
"Name={},file=page_catalog_group_{}.skt",
catalog_name, catalog_name
);
let ccm = current_menus.clone();
for menu_block in ccm.children.clone() {
let mut name_row = String::from("\tname\t");
let mut desc_row = String::from("\tdesc\t");
let mut img_row = String::from("\timg\t");
let tag_filter_option = get_path!(menu_block, TagFilter);
let idle_image_tag = match get_path!(menu_block, IdleImage) {
Some(img_path) => {
let img_path = img_path.clone().value.unwrap_or("".to_string());
let img_path_spl: Vec<String> = img_path
.trim()
.replace("\"", "")
.split("/")
.map(|x| x.to_string())
.collect();
img_path_spl
.last()
.unwrap()
.replace("[amp]", "&")
.to_string()
}
None => "".to_string(),
};
img_row.push_str(format!("{idle_image_tag}\t-\t-\t-\t\t\t||||||||||||||||||||||||||\t||||||||||||||||||||||||||\t||||||||||||||||||||||||||\t\t\t\t\t\t\t\t-\t-\t-\t-\t-\t").as_str());
let hot_state_val = match get_path!(menu_block, HotState) {
Some(state) => state
.clone()
.value
.and_then(|x| {
if x.to_string().contains("Disable2") {
return Some("-".to_string());
} else {
return Some(x.replace("$", "").replace(".Button", ""));
}
})
.unwrap()
.trim()
.to_string(),
None => "-".to_string(),
};
let ice_state_val = match get_path!(menu_block, IceState) {
Some(state) => state
.clone()
.value
.and_then(|x| {
if x.to_string().contains("Disable2") {
return Some("-".to_string());
} else {
return Some(x.replace("$", "").replace(".Button", ""));
}
})
.unwrap()
.trim()
.to_string(),
None => "-".to_string(),
};
let blend_state_val = match get_path!(menu_block, BlendState) {
Some(state) => state
.clone()
.value
.and_then(|x| {
if x.to_string().contains("Disable2") {
return Some("-".to_string());
} else {
return Some(x.replace("$", "").replace(".Button", ""));
}
})
.unwrap()
.trim()
.to_string(),
None => "-".to_string(),
};
let names = match get_path!(menu_block, Name.LanguageGroup) {
Some(names) => names.clone(),
None => Node::default(),
};
// Description
let descs = match get_path!(menu_block, Description.LanguageGroup) {
Some(descs) => descs.clone(),
None => Node::default(),
};
for name in names.children.clone() {
if let Some(value) = name.value {
name_row.push_str(format!("{value}\t").replace("[amp]", "&").as_str());
} else {
name_row.push_str(format!("\t").as_str());
}
}
name_row.push_str(
format!(
"{},-\t{},-\t{},-\t\t\t\t\t\t\t\t-\t-\t-\t-\t{}",
hot_state_val,
ice_state_val,
blend_state_val,
tag_filter_option
.clone()
.unwrap_or(&Node::default())
.value
.clone()
.unwrap_or("-".to_string())
.trim()
.replace("\"", "")
)
.as_str(),
);
for desc in descs.children.clone() {
if let Some(value) = desc.value {
desc_row.push_str(format!("{value}\t").replace("[amp]", "&").as_str());
} else {
desc_row.push_str(format!("\t").as_str());
}
}
desc_row.push_str(
format!(
"||||||||||||||||||||||||||\t||||||||||||||||||||||||||\t||||||||||||||||||||||||||\t\t\t\t\t\t\t\t-\t-\t-\t-\t-\t"
)
.as_str(),
);
//||||||||||||||||||||||||||
println!("{name_row}");
println!("{desc_row}");
println!("{img_row}");
println!("");
}
// name Americano อเมริกาโน Amerikano Americano 59-01-01-0003,59-21-01-0003 59-01-02-0001,59-21-02-0001 -,- - - Signature - CoffeeNoMilk,Recommend
// desc Espresso, Water กาแฟ และน้ำ Espresas, vanduo Espresso, Apă |||||||||||||||||||||||||| |||||||||||||||||||||||||| |||||||||||||||||||||||||| - - - - -
// img bn_hot_americano.png - bn_hot_america_no.png bn_hot_america_no.png posi1 |||||||||||||||||||||||||| |||||||||||||||||||||||||| - - - - -
}
}
}
```