jabyengine/src/Tools/psxcdgen_ex/src/config_reader/xml.rs

200 lines
7.5 KiB
Rust

use std::path::PathBuf;
use tool_helper::{format_if_error, path_with_env_from, string_with_env_from};
use crate::config_reader::Directory;
use super::{CommonProperties, Configuration, Error, File, FileKind, LZ4State};
mod attribute_names {
pub const NAME: &'static str = "name";
pub const HIDDEN: &'static str = "hidden";
pub const PADDED_SIZE: &'static str = "padded_size";
pub const LBA_SOURCE: &'static str = "lba_source";
pub const LZ4_STATE: &'static str = "lz4";
}
pub fn parse(xml: String) -> Result<Configuration, Error> {
let mut config = Configuration::new();
let parser = format_if_error!(roxmltree::Document::parse(xml.as_str()), "Parsing XML file failed with: {error_text}")?;
let children = parser.root().children();
for node in children {
if node.is_element() && node.tag_name().name() == "ISO_Project" {
parse_iso_project(node, &mut config)?;
}
}
Ok(config)
}
fn parse_iso_project(iso_project: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
for node in iso_project.children() {
if node.is_element() {
match node.tag_name().name() {
"Description" => parse_description(node, config),
"Track" => parse_track(node, config),
"CD_Audio" => parse_cd_audio(node, config),
_ => Ok(())
}?;
}
}
Ok(())
}
fn parse_description(description: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
const LICENSE_STR:&str = "License";
for node in description.descendants() {
if node.is_element() {
match node.tag_name().name() {
"Publisher" => config.publisher = Some(String::from(node.text().unwrap_or_default())),
LICENSE_STR => config.license_path = Some(path_from_node(&node, LICENSE_STR)?),
_ => ()
}
}
}
Ok(())
}
fn parse_track(track: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
fn parse_regular_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
let common = read_common_properties(&file, is_hidden, None)?;
let path = path_from_node(&file, &common.name)?;
Ok(File{common, path, kind: FileKind::Regular})
}
fn parse_main_file(file: roxmltree::Node) -> Result<File, Error> {
if let Some(lba_path) = file.attribute(attribute_names::LBA_SOURCE) {
let common = read_common_properties(&file, false, Some(LZ4State::None))?;
let path = path_from_node(&file, &common.name)?;
Ok(File{common, path, kind: FileKind::Main(PathBuf::from(lba_path))})
}
else {
parse_regular_file(file, false)
}
}
fn parse_overlay_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
// v They will be compressed automatically
let common = read_common_properties(&file, is_hidden, Some(LZ4State::AlreadyCompressed))?;
let path = path_from_node(&file, &common.name)?;
Ok(File{common, path, kind: FileKind::Overlay(PathBuf::from(file.attribute(attribute_names::LBA_SOURCE).unwrap_or_default()))})
}
fn parse_xa_audio(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
// v Never compress XA-Audio
let common = read_common_properties(&file, is_hidden, Some(LZ4State::None))?;
let channel = {
let mut channel = Vec::new();
for node in file.children() {
if node.is_element() && node.tag_name().name() == "Channel" {
channel.push(path_from_node(&node, "Channel")?);
}
}
channel
};
Ok(File{common, path: PathBuf::new(), kind: FileKind::XA_Audio(channel)})
}
fn parse_file_system(cur_node: roxmltree::Node, root: &mut Directory, mut is_hidden: bool) -> Result<(), Error> {
for node in cur_node.children() {
if node.is_element() {
match node.tag_name().name() {
"File" => root.add_file(parse_regular_file(node, is_hidden)?),
"Main" => root.add_file(parse_main_file(node)?),
"Overlay" => root.add_file(parse_overlay_file(node, is_hidden)?),
"XA-Audio" => root.add_file(parse_xa_audio(node, is_hidden)?),
"Directory" => {
is_hidden |= parse_boolean_attribute(&node, attribute_names::HIDDEN)?;
let mut new_dir = Directory::new(node.attribute(attribute_names::NAME).unwrap_or_default(), is_hidden);
parse_file_system(node, &mut new_dir, is_hidden)?;
root.add_dir(new_dir);
},
_ => (),
}
}
}
Ok(())
}
parse_file_system(track, &mut config.root, false)
}
fn parse_cd_audio(cdda: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
config.cd_audio_files.push(path_from_node(&cdda, "CD Audio file")?);
Ok(())
}
fn read_common_properties(xml: &roxmltree::Node, is_hidden: bool, force_lz4_state: Option<LZ4State>) -> Result<CommonProperties, Error> {
let lz4_state = {
if let Some(forced_lz4_state) = force_lz4_state {
forced_lz4_state
}
else {
if let Some(state_str) = xml.attribute(attribute_names::LZ4_STATE) {
match state_str.to_lowercase().as_str() {
"yes" => LZ4State::Compress,
"already" => LZ4State::AlreadyCompressed,
_ => LZ4State::None,
}
}
else {
LZ4State::None
}
}
};
Ok(CommonProperties{
name: string_with_env_from(xml.attribute(attribute_names::NAME).unwrap_or_default()),
is_hidden: is_hidden | parse_boolean_attribute(&xml, attribute_names::HIDDEN)?,
lz4_state,
padded_size: read_padded_size(&xml)?
})
}
fn read_padded_size(xml: &roxmltree::Node) -> Result<Option<usize>, Error> {
if let Some(padded_attr) = xml.attribute(attribute_names::PADDED_SIZE) {
let padded_size = format_if_error!(padded_attr.parse::<usize>(), "Failed reading \"{}\" as padded size: {error_text}", padded_attr)?;
Ok(Some(padded_size))
}
else {
Ok(None)
}
}
fn parse_boolean_attribute(xml: &roxmltree::Node, attribute_name: &str) -> Result<bool, Error> {
if let Some(bool_str) = xml.attribute(attribute_name) {
match bool_str {
"true" | "1" => Ok(true),
"false" | "0" => Ok(false),
_ => {
return Err(Error::from_text(format!("Attribute \"{}\" expects either true,false or 1,0 as valid attributes - found {}", attribute_name, bool_str)));
}
}
}
else {
Ok(false)
}
}
fn path_from_node(node: &roxmltree::Node, name: &str) -> Result<PathBuf, Error> {
if let Some(path) = node.text() {
Ok(path_with_env_from(path))
}
else {
Err(Error::from_text(format!("No path specified for {}", name)))
}
}