rrm_xml 0.0.1-alpha.0

XML parser for Rimworld mods. Part of rrm
Documentation
extern crate xml;

use std::fs::File;
use std::io::{BufReader, Read};
use xml::reader::{EventReader, XmlEvent};

#[derive(Debug)]
pub struct Element {
    pub value: String,
    pub name: String,
}

pub trait XMLFile {
    fn values_of(&self, keys: &[&str]) -> Vec<Element>;
}

impl Clone for Element {
    fn clone(&self) -> Self {
        Element {
            value: self.value.clone(),
            name: self.name.clone(),
        }
    }
}

impl XMLFile for File {
    fn values_of(&self, keys: &[&str]) -> Vec<Element> {
        let mut r = vec![];
        let mut record = Element {
            value: "".to_string(),
            name: "".to_string(),
        };

        let mut file = BufReader::new(self);
        let mut contents: Vec<u8> = Vec::new();

        file.read_to_end(&mut contents).unwrap();
        fix_common_issues(&mut contents);

        let contents = String::from_utf8(contents).unwrap();

        let parser = EventReader::from_str(&contents);
        let mut depth = 0;
        let mut dep = false;
        for e in parser {
            match e {
                Ok(XmlEvent::StartElement { name, .. }) => {
                    depth += 1;
                    record.name = name.to_string();
                    if record.name.as_str() == "modDependencies" { dep = true };
                }
                Ok(XmlEvent::Characters(value)) => {
                    if keys.contains(&&*record.name) && ([0, 1, 2].contains(&depth)) {
                        record.value = value;
                        r.push(record.clone());
                    } else if dep && record.name == "steamWorkshopUrl" {
                        let value = value
                            .to_string();

                        let value: Vec<&str> = value
                            .split("")
                            .into_iter()
                            .filter(
                                |c| ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
                                    .contains(c)
                            )
                            .collect();

                        record.value = value.join("");
                        r.push(record.clone());
                    }
                }
                Ok(XmlEvent::EndElement { .. }) => {
                    if record.name.as_str() == "modDependencies" { dep = false };
                    depth -= 1;
                }
                Err(e) => {
                    println!("Error: {}", e);
                }
                _ => {}
            }
        }

        r
    }
}

#[test]
fn test() {
    use crate::*;

    let file = File::open("/Applications/RimWorld.app/Mods/Achtung/About/Manifest.xml").unwrap();
    let r = file.values_of(&["version", "identifier"]);
    println!("{:?}", r);

    let file = File::open("/Applications/RimWorld.app/Mods/Achtung/About/About.xml").unwrap();
    let r = file.values_of(&["version", "identifier"]);
    println!("{:?}", r);

    let file =
        File::open("/Applications/RimWorld.app/Mods/Area_unlocker/About/Manifest.xml").unwrap();
    let r = file.values_of(&["version", "identifier"]);

    println!("{:?}", r);
}

fn fix_common_issues(contents: &mut Vec<u8>) {
    rm_bom(contents);
}

fn rm_bom(contents: &mut Vec<u8>) {
    let pat: [u8; 3] = [0xEF, 0xBB, 0xBF];

    let err = contents.windows(pat.len()).position(|w| w.eq(&pat));

    if let Some(i) = err {
        for _ in 0..3 {
            contents.remove(i);
        }
    }
}