200 lines
7.5 KiB
Rust
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)))
|
|
}
|
|
} |