added xml, properties and ini
All checks were successful
Version Check / check-version (pull_request) Successful in 3s
All checks were successful
Version Check / check-version (pull_request) Successful in 3s
This commit is contained in:
@@ -22,6 +22,8 @@ impl HierarchicalHandler {
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
||||
FormatType::Toml => toml::from_str(&content)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
||||
FormatType::Xml => xml_to_json(&content)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(value)
|
||||
@@ -47,12 +49,124 @@ impl HierarchicalHandler {
|
||||
));
|
||||
}
|
||||
}
|
||||
FormatType::Xml => json_to_xml(value),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
fs::write(path, content)
|
||||
}
|
||||
}
|
||||
|
||||
fn xml_to_json(content: &str) -> io::Result<Value> {
|
||||
use quick_xml::reader::Reader;
|
||||
use quick_xml::events::Event;
|
||||
|
||||
let mut reader = Reader::from_str(content);
|
||||
reader.config_mut().trim_text(true);
|
||||
let mut buf = Vec::new();
|
||||
|
||||
fn parse_recursive(reader: &mut Reader<&[u8]>) -> io::Result<Value> {
|
||||
let mut map = Map::new();
|
||||
let mut text = String::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()).to_string();
|
||||
let val = parse_recursive(reader)?;
|
||||
|
||||
if let Some(existing) = map.get_mut(&name) {
|
||||
if let Some(arr) = existing.as_array_mut() {
|
||||
arr.push(val);
|
||||
} else {
|
||||
let old = existing.take();
|
||||
*existing = Value::Array(vec![old, val]);
|
||||
}
|
||||
} else {
|
||||
map.insert(name, val);
|
||||
}
|
||||
}
|
||||
Ok(Event::End(_)) => break,
|
||||
Ok(Event::Text(e)) => {
|
||||
text.push_str(&String::from_utf8_lossy(e.as_ref()));
|
||||
}
|
||||
Ok(Event::Eof) => break,
|
||||
_ => {}
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
if map.is_empty() {
|
||||
if text.is_empty() {
|
||||
Ok(Value::Null)
|
||||
} else {
|
||||
Ok(Value::String(text))
|
||||
}
|
||||
} else {
|
||||
if !text.is_empty() {
|
||||
map.insert("$text".to_string(), Value::String(text));
|
||||
}
|
||||
Ok(Value::Object(map))
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the first start tag
|
||||
loop {
|
||||
match reader.read_event_into(&mut buf) {
|
||||
Ok(Event::Start(e)) => {
|
||||
let name = String::from_utf8_lossy(e.name().as_ref()).to_string();
|
||||
let val = parse_recursive(&mut reader)?;
|
||||
let mut root = Map::new();
|
||||
root.insert(name, val);
|
||||
return Ok(Value::Object(root));
|
||||
}
|
||||
Ok(Event::Eof) => break,
|
||||
_ => {}
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
Ok(Value::Object(Map::new()))
|
||||
}
|
||||
|
||||
fn json_to_xml(value: &Value) -> String {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
let mut s = String::new();
|
||||
for (k, v) in map {
|
||||
if k == "$text" {
|
||||
s.push_str(&v.as_str().unwrap_or(""));
|
||||
} else if let Some(arr) = v.as_array() {
|
||||
for item in arr {
|
||||
s.push_str(&format!("<{}>", k));
|
||||
s.push_str(&json_to_xml(item));
|
||||
s.push_str(&format!("</{}>", k));
|
||||
}
|
||||
} else {
|
||||
s.push_str(&format!("<{}>", k));
|
||||
s.push_str(&json_to_xml(v));
|
||||
s.push_str(&format!("</{}>", k));
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
let mut s = String::new();
|
||||
for v in arr {
|
||||
s.push_str(&json_to_xml(v));
|
||||
}
|
||||
s
|
||||
}
|
||||
Value::String(v) => v.clone(),
|
||||
Value::Number(v) => v.to_string(),
|
||||
Value::Bool(v) => v.to_string(),
|
||||
Value::Null => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// remove unused get_xml_root_name
|
||||
// fn get_xml_root_name(content: &str) -> Option<String> { ... }
|
||||
|
||||
fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut Vec<ConfigItem>) {
|
||||
let path = if prefix.is_empty() {
|
||||
key_name.to_string()
|
||||
@@ -322,7 +436,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_flatten_unflatten() {
|
||||
fn test_json_flatten_unflatten() {
|
||||
let mut vars = Vec::new();
|
||||
let json = serde_json::json!({
|
||||
"services": {
|
||||
@@ -350,4 +464,129 @@ mod tests {
|
||||
assert!(unflattened_json.contains("\"8080:80\""));
|
||||
assert!(unflattened_json.contains("true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_preservation() {
|
||||
let mut vars = Vec::new();
|
||||
// A JSON with various tricky types
|
||||
let json = serde_json::json!({
|
||||
"port_num": 8080,
|
||||
"port_str": "8080",
|
||||
"is_enabled": true,
|
||||
"is_enabled_str": "true",
|
||||
"float_num": 3.14,
|
||||
"float_str": "3.14"
|
||||
});
|
||||
|
||||
flatten(&json, "", 0, "", &mut vars);
|
||||
|
||||
let mut root = Value::Object(Map::new());
|
||||
for var in vars {
|
||||
if !var.is_group {
|
||||
insert_into_value(&mut root, &var.path, var.value.as_deref().unwrap_or(""), var.value_type);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that types are exactly preserved after re-assembling
|
||||
let unflattened = root.as_object().unwrap();
|
||||
|
||||
assert!(unflattened["port_num"].is_number(), "port_num should be a number");
|
||||
assert_eq!(unflattened["port_num"].as_i64(), Some(8080));
|
||||
|
||||
assert!(unflattened["port_str"].is_string(), "port_str should be a string");
|
||||
assert_eq!(unflattened["port_str"].as_str(), Some("8080"));
|
||||
|
||||
assert!(unflattened["is_enabled"].is_boolean(), "is_enabled should be a boolean");
|
||||
assert_eq!(unflattened["is_enabled"].as_bool(), Some(true));
|
||||
|
||||
assert!(unflattened["is_enabled_str"].is_string(), "is_enabled_str should be a string");
|
||||
assert_eq!(unflattened["is_enabled_str"].as_str(), Some("true"));
|
||||
|
||||
assert!(unflattened["float_num"].is_number(), "float_num should be a number");
|
||||
assert_eq!(unflattened["float_num"].as_f64(), Some(3.14));
|
||||
|
||||
assert!(unflattened["float_str"].is_string(), "float_str should be a string");
|
||||
assert_eq!(unflattened["float_str"].as_str(), Some("3.14"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_yaml_flatten_unflatten() {
|
||||
let yaml_str = "
|
||||
server:
|
||||
port: 8080
|
||||
port_str: \"8080\"
|
||||
enabled: true
|
||||
";
|
||||
let yaml_val: Value = serde_yaml::from_str(yaml_str).unwrap();
|
||||
let mut vars = Vec::new();
|
||||
flatten(&yaml_val, "", 0, "", &mut vars);
|
||||
|
||||
let mut root = Value::Object(Map::new());
|
||||
for var in vars {
|
||||
if !var.is_group {
|
||||
insert_into_value(&mut root, &var.path, var.value.as_deref().unwrap_or(""), var.value_type);
|
||||
}
|
||||
}
|
||||
|
||||
let unflattened_yaml = serde_yaml::to_string(&root).unwrap();
|
||||
assert!(unflattened_yaml.contains("port: 8080"));
|
||||
// Serde YAML might output '8080' or "8080"
|
||||
assert!(unflattened_yaml.contains("port_str: '8080'") || unflattened_yaml.contains("port_str: \"8080\""));
|
||||
assert!(unflattened_yaml.contains("enabled: true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toml_flatten_unflatten() {
|
||||
let toml_str = "
|
||||
[server]
|
||||
port = 8080
|
||||
port_str = \"8080\"
|
||||
enabled = true
|
||||
";
|
||||
// parse to toml Value, then convert to serde_json Value to reuse the same flatten path
|
||||
let toml_val: toml::Value = toml::from_str(toml_str).unwrap();
|
||||
let json_val: Value = serde_json::to_value(toml_val).unwrap();
|
||||
|
||||
let mut vars = Vec::new();
|
||||
flatten(&json_val, "", 0, "", &mut vars);
|
||||
|
||||
let mut root = Value::Object(Map::new());
|
||||
for var in vars {
|
||||
if !var.is_group {
|
||||
insert_into_value(&mut root, &var.path, var.value.as_deref().unwrap_or(""), var.value_type);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to TOML
|
||||
let toml_root: toml::Value = serde_json::from_value(root).unwrap();
|
||||
let unflattened_toml = toml::to_string(&toml_root).unwrap();
|
||||
|
||||
assert!(unflattened_toml.contains("port = 8080"));
|
||||
assert!(unflattened_toml.contains("port_str = \"8080\""));
|
||||
assert!(unflattened_toml.contains("enabled = true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xml_flatten_unflatten() {
|
||||
let xml_str = "<config><server><port>8080</port><enabled>true</enabled></server></config>";
|
||||
|
||||
let json_val = xml_to_json(xml_str).unwrap();
|
||||
|
||||
let mut vars = Vec::new();
|
||||
flatten(&json_val, "", 0, "", &mut vars);
|
||||
|
||||
let mut root = Value::Object(Map::new());
|
||||
for var in vars {
|
||||
if !var.is_group {
|
||||
insert_into_value(&mut root, &var.path, var.value.as_deref().unwrap_or(""), var.value_type);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Reconstructed root: {:?}", root);
|
||||
let unflattened_xml = json_to_xml(&root);
|
||||
|
||||
assert!(unflattened_xml.contains("<port>8080</port>"));
|
||||
assert!(unflattened_xml.contains("<enabled>true</enabled>"));
|
||||
assert!(unflattened_xml.contains("<config>") && unflattened_xml.contains("</config>"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user