extern crate custom_error;
use crate::ParserError::*;
use custom_error::custom_error;
use std::{fs::File, io::Read};
#[derive(Clone, PartialEq, Debug)]
pub struct Mod {
pub id: i64,
pub name: String,
pub from_steam: bool,
pub link: String,
}
#[derive(PartialEq, Debug)]
pub struct Preset {
pub name: String,
pub mods: Vec<Mod>,
}
custom_error! {pub ParserError
StringConvFail = "Failed to read file to string",
DocParseErr = "Failed to parse document with error",
DocReadErr = "Failed to read file from path",
TagFindErr = "Unable to find necessary tags. Please check your preset is correctly exported"
}
impl Preset {
pub fn from_file(file: File) -> Result<Self, ParserError> {
parse(file)
}
pub fn from_fs(path: String) -> Result<Self, ParserError> {
let file: File = match File::open(path) {
Ok(f) => f,
Err(_) => return Err(DocReadErr),
};
parse(file)
}
pub fn as_mods(&self) -> Vec<Mod> {
let mut vec = vec![];
for _mod in &self.mods {
vec.push(_mod.clone())
}
vec
}
pub fn as_ids(&self) -> Vec<i64> {
let mut vec = vec![];
for _mod in &self.mods {
vec.push(_mod.clone().id)
}
vec
}
pub fn as_names(&self) -> Vec<String> {
let mut vec = vec![];
for _mod in &self.mods {
vec.push(_mod.clone().name)
}
vec
}
pub fn as_links(&self) -> Vec<String> {
let mut vec = vec![];
for _mod in &self.mods {
vec.push(_mod.clone().link)
}
vec
}
}
fn parse(mut file: File) -> Result<Preset, ParserError> {
let mut contents: String = "".to_string();
match file.read_to_string(&mut contents) {
Ok(_) => {}
Err(_) => return Err(StringConvFail),
};
match roxmltree::Document::parse(&contents) {
Ok(doc) => {
let mut preset: Preset = Preset {
name: "".to_string(),
mods: vec![],
};
let html_node = match doc.root().children().find(|n| n.has_tag_name("html")) {
Some(n) => n,
None => return Err(TagFindErr),
};
for node in html_node.children().filter(|n| n.is_element()) {
if node.has_tag_name("head") {
let tag = match node.children().find(|n| {
n.has_tag_name("meta") && n.attribute("name") == Some("arma:PresetName")
}) {
Some(n) => n,
None => return Err(TagFindErr),
};
preset.name = tag.attribute("content").unwrap().parse().unwrap();
} else if node.has_tag_name("body") {
let im1 = match node
.children()
.find(|n| n.has_tag_name("div") && n.attribute("class") == Some("mod-list"))
{
Some(n) => n,
None => return Err(TagFindErr),
};
let im2 = match im1.children().find(|n| n.has_tag_name("table")) {
Some(n) => n,
None => return Err(TagFindErr),
};
let im3 = im2.children().filter(|n| {
n.has_tag_name("tr") && n.attribute("data-type") == Some("ModContainer")
});
for mod_cont in im3 {
let mut temp_mod = Mod {
id: 0,
name: "".to_string(),
from_steam: false,
link: "".to_string(),
};
for item in mod_cont.children().filter(|n| n.is_element()) {
if item.has_attribute("data-type") {
temp_mod.name = match item.children().find(|n| n.is_text()) {
Some(n) => n,
None => return Err(TagFindErr),
}
.text()
.unwrap()
.parse()
.unwrap();
} else {
if item
.children()
.find(|n| n.attribute("class") == Some("from-steam"))
.is_some()
{
temp_mod.from_steam = true;
} else if item
.children()
.find(|n| n.attribute("class") == Some("from-local"))
.is_some()
{
temp_mod.from_steam = false;
} else {
if item.children().find(|n| n.has_tag_name("a")).is_some() {
temp_mod.link =
match item.children().find(|n| n.has_tag_name("a")) {
Some(n) => n,
None => return Err(TagFindErr),
}
.attribute("href")
.unwrap()
.parse()
.unwrap();
temp_mod.id = temp_mod.link.replace("http://steamcommunity.com/sharedfiles/filedetails/?id=", "").parse().unwrap()
} else {
temp_mod.link = match item
.children()
.find(|n| n.has_tag_name("span"))
{
Some(n) => n,
None => return Err(TagFindErr),
}
.attribute("data-meta")
.unwrap()
.parse()
.unwrap();
}
}
}
}
preset.mods.push(temp_mod);
}
}
}
Ok(preset)
}
Err(_) => return Err(DocParseErr),
}
}
#[cfg(test)]
mod tests {
use crate::{parse, Mod, Preset};
use std::fs::File;
#[test]
fn parse_file() {
let preset = Preset {
name: "Parser Test".parse().unwrap(),
mods: vec![
Mod {
id: 0,
name: "Ryan\'s ACE Canteen".parse().unwrap(),
from_steam: false,
link: "local:Ryan\'s ACE Canteen|@Ryan\'s ACE Canteen|"
.parse()
.unwrap(),
},
Mod {
id: 450814997,
name: "CBA_A3".parse().unwrap(),
from_steam: true,
link: "http://steamcommunity.com/sharedfiles/filedetails/?id=450814997"
.parse()
.unwrap(),
},
Mod {
id: 463939057,
name: "ace".parse().unwrap(),
from_steam: true,
link: "http://steamcommunity.com/sharedfiles/filedetails/?id=463939057"
.parse()
.unwrap(),
},
],
};
assert_eq!(
parse(File::open("tests\\samples\\Arma 3 Preset Parser Test.html").unwrap()).unwrap(),
preset
);
}
#[test]
fn parse_from_fs() {
let preset: Preset = Preset {
name: "Parser Test".parse().unwrap(),
mods: vec![
Mod {
id: 0,
name: "Ryan\'s ACE Canteen".parse().unwrap(),
from_steam: false,
link: "local:Ryan\'s ACE Canteen|@Ryan\'s ACE Canteen|"
.parse()
.unwrap(),
},
Mod {
id: 450814997,
name: "CBA_A3".parse().unwrap(),
from_steam: true,
link: "http://steamcommunity.com/sharedfiles/filedetails/?id=450814997"
.parse()
.unwrap(),
},
Mod {
id: 463939057,
name: "ace".parse().unwrap(),
from_steam: true,
link: "http://steamcommunity.com/sharedfiles/filedetails/?id=463939057"
.parse()
.unwrap(),
},
],
};
assert_eq!(
Preset::from_fs(
"tests\\samples\\Arma 3 Preset Parser Test.html"
.parse()
.unwrap()
)
.unwrap(),
preset
);
}
#[test]
fn parse_from_file() {
let preset: Preset = Preset {
name: "Parser Test".parse().unwrap(),
mods: vec![
Mod {
id: 0,
name: "Ryan\'s ACE Canteen".parse().unwrap(),
from_steam: false,
link: "local:Ryan\'s ACE Canteen|@Ryan\'s ACE Canteen|"
.parse()
.unwrap(),
},
Mod {
id: 450814997,
name: "CBA_A3".parse().unwrap(),
from_steam: true,
link: "http://steamcommunity.com/sharedfiles/filedetails/?id=450814997"
.parse()
.unwrap(),
},
Mod {
id: 463939057,
name: "ace".parse().unwrap(),
from_steam: true,
link: "http://steamcommunity.com/sharedfiles/filedetails/?id=463939057"
.parse()
.unwrap(),
},
],
};
assert_eq!(
Preset::from_file(
File::open("tests\\samples\\Arma 3 Preset Parser Test.html").unwrap()
)
.unwrap(),
preset
);
}
}