use crate::error::Result;
use crate::formats::common::{TypeId, get_type_name, type_name_to_id};
use crate::formats::lsj::{self, LsjAttribute, LsjDocument, LsjHeader, LsjNode, LsjSave};
use crate::formats::lsx::{LsxAttribute as LsxAttrType, LsxDocument, LsxNode};
use std::collections::HashMap;
use std::path::Path;
pub fn convert_lsx_to_lsj<P: AsRef<Path>>(source: P, dest: P) -> Result<()> {
convert_lsx_to_lsj_with_progress(source, dest, &|_| {})
}
pub fn convert_lsx_to_lsj_with_progress<P: AsRef<Path>>(
source: P,
dest: P,
progress: crate::converter::ConvertProgressCallback,
) -> Result<()> {
use crate::converter::{ConvertPhase, ConvertProgress};
tracing::info!(
"Converting LSX→LSJ: {:?} → {:?}",
source.as_ref(),
dest.as_ref()
);
progress(&ConvertProgress::with_file(
ConvertPhase::ReadingSource,
1,
3,
"Reading LSX file...",
));
let lsx_doc = crate::formats::lsx::read_lsx(&source)?;
let region_count = lsx_doc.regions.len();
progress(&ConvertProgress::with_file(
ConvertPhase::Converting,
2,
3,
format!("Converting {region_count} regions to JSON..."),
));
let lsj_doc = to_lsj(&lsx_doc)?;
progress(&ConvertProgress::with_file(
ConvertPhase::WritingOutput,
3,
3,
"Writing LSJ file...",
));
lsj::write_lsj(&lsj_doc, dest)?;
tracing::info!("Conversion complete");
Ok(())
}
pub fn to_lsj(lsx: &LsxDocument) -> Result<LsjDocument> {
let mut regions = HashMap::new();
for region in &lsx.regions {
if let Some(root_node) = region.nodes.first() {
let lsj_region = convert_node_to_lsj(root_node)?;
regions.insert(region.id.clone(), lsj_region);
}
}
Ok(LsjDocument {
save: LsjSave {
header: LsjHeader {
version: format!("{}.{}.{}.{}", lsx.major, lsx.minor, lsx.revision, lsx.build),
},
regions,
},
})
}
fn convert_node_to_lsj(lsx_node: &LsxNode) -> Result<LsjNode> {
let mut lsj_node = LsjNode::new();
for attr in &lsx_node.attributes {
let lsj_attr = convert_attribute(attr)?;
lsj_node.attributes.insert(attr.id.clone(), lsj_attr);
}
for child in &lsx_node.children {
convert_child_node(&mut lsj_node, child)?;
}
Ok(lsj_node)
}
fn convert_child_node(parent: &mut LsjNode, child: &LsxNode) -> Result<()> {
let mut child_node = LsjNode::new();
for attr in &child.attributes {
let lsj_attr = convert_attribute(attr)?;
child_node.attributes.insert(attr.id.clone(), lsj_attr);
}
for grandchild in &child.children {
convert_child_node(&mut child_node, grandchild)?;
}
parent
.children
.entry(child.id.clone())
.or_default()
.push(child_node);
Ok(())
}
fn convert_attribute(attr: &LsxAttrType) -> Result<LsjAttribute> {
let type_id = type_name_to_id(&attr.type_name);
let type_name = get_type_name(type_id);
if type_id == 28 {
return Ok(LsjAttribute::TranslatedString {
type_name: type_name.to_string(),
value: if attr.value.is_empty() {
None
} else {
Some(attr.value.clone())
},
handle: attr.handle.clone().unwrap_or_default(),
version: attr.version,
});
}
if type_id == 33 {
return Ok(LsjAttribute::TranslatedFSString {
type_name: type_name.to_string(),
value: if attr.value.is_empty() {
None
} else {
Some(attr.value.clone())
},
handle: attr.handle.clone().unwrap_or_default(),
arguments: Vec::new(),
});
}
let json_value = convert_value_to_json(type_id, &attr.value)?;
Ok(LsjAttribute::Simple {
type_name: type_name.to_string(),
value: json_value,
})
}
fn convert_value_to_json(type_id: TypeId, value_str: &str) -> Result<serde_json::Value> {
use serde_json::Value;
Ok(match type_id {
1 => Value::Number(value_str.parse::<u8>().unwrap_or(0).into()), 27 => Value::Number(value_str.parse::<i8>().unwrap_or(0).into()), 2 => Value::Number(value_str.parse::<i16>().unwrap_or(0).into()),
3 => Value::Number(value_str.parse::<u16>().unwrap_or(0).into()),
4 => Value::Number(value_str.parse::<i32>().unwrap_or(0).into()),
5 => Value::Number(value_str.parse::<u32>().unwrap_or(0).into()),
24 => Value::Number(value_str.parse::<u64>().unwrap_or(0).into()),
26 | 32 => Value::Number(value_str.parse::<i64>().unwrap_or(0).into()),
6 => {
let f = value_str.parse::<f32>().unwrap_or(0.0);
Value::Number(
serde_json::Number::from_f64(f64::from(f)).unwrap_or(serde_json::Number::from(0)),
)
}
7 => {
let f = value_str.parse::<f64>().unwrap_or(0.0);
Value::Number(serde_json::Number::from_f64(f).unwrap_or(serde_json::Number::from(0)))
}
19 => Value::Bool(value_str == "True" || value_str == "true" || value_str == "1"),
8..=18 => Value::String(value_str.to_string()),
_ => Value::String(value_str.to_string()),
})
}