Skip to content

Instantly share code, notes, and snippets.

@tinaun
Created July 18, 2019 04:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tinaun/05547a38324ee300ecd857de8f89c03e to your computer and use it in GitHub Desktop.
Save tinaun/05547a38324ee300ecd857de8f89c03e to your computer and use it in GitHub Desktop.
//! deserialization from horrible xml
//!
//!
mod helpers;
use quick_xml::Reader;
use quick_xml::events::{Event, BytesStart};
use error::{Error, Result};
use std::path::{PathBuf, Path};
use std::io::{BufReader};
use std::collections::HashMap;
use std::fs::File;
use openbound::{Assets, Asset, AssetType, Openbound};
pub use self::helpers::*;
type Classes = HashMap<String, Vec<Event<'static>>>;
struct FileVisitor {
name: String,
level_root: PathBuf,
deps: Vec<PathBuf>,
classes: Classes,
reader: Reader<BufReader<File>>,
depth: u8,
jump_back: bool, // FIXME: refactor to remove this
expand: bool,
class: Option<String>,
ev_idx: usize,
}
impl FileVisitor {
fn new<T: AsRef<Path>, U: AsRef<Path>>(root: T, path: U) -> Result<Self> {
Self::with_classes(root, path, HashMap::new())
}
fn with_classes<T: AsRef<Path>, U:AsRef<Path>>(
root: T,
path: U,
classes: Classes)
-> Result<Self>
{
let reader = Reader::from_file(root.as_ref().join(path.as_ref()))?;
let name = path.as_ref().file_name()
.and_then(|f| f.to_str())
.unwrap_or("").to_string();
println!("parsing {}", name);
Ok(FileVisitor {
name,
deps: vec![],
classes,
level_root: root.as_ref().to_path_buf(),
reader,
depth: 0,
jump_back: false,
expand: true,
class: None,
ev_idx: 0,
})
}
// panics if event is not Start or Empty
fn expand_class<'a>(&mut self, e: &Event<'a>) -> Result<Option<Event<'static>>> {
if self.expand {
let buf = e.unwrap();
if let Ok(class_name) = buf.get_attr_owned("class", &self.reader) {
println!("expanding class: {:?}", class_name);
if let Some(class) = self.classes.get(&class_name) {
self.class = Some(class_name);
self.ev_idx = 0;
let start = class[0].is_start();
let ev = class[0].unwrap();
if ev.name() != buf.name() {
return Err(Error::Level(
format!("mismatched classes\nexpected: {:?} found: {:?}",
ev.name(), buf.name())
));
} else {
let other = buf.attributes().map(|a| a.unwrap());
let ev = ev.clone().with_attributes(other).into_owned();
if start {
return Ok(Some(Event::Start(ev)));
} else {
return Ok(Some(Event::Empty(ev)));
}
}
}
}
}
Ok(None)
}
fn read_tag_at_depth<'a>(&mut self, temp_buf: &'a mut Vec<u8>, depth: u8) -> Result<Option<Event<'static>>> {
loop {
let next = if self.class.is_none() {
self.reader.read_event(temp_buf).map(|e| e.into_owned())?
} else {
self.ev_idx += 1; // if in a class, get events from the class
let c = self.class.as_ref().unwrap();
let c = self.classes.get(c).unwrap();
if self.ev_idx == c.len() {
self.class = None;
continue;
}
c[self.ev_idx].clone()
};
if self.jump_back {
self.depth -= 1;
self.jump_back = false;
}
match next {
e @ Event::Start(_) => {
self.depth += 1;
match &e {
&Event::Start(ref buf) => {
//println!("open: {:?} {} {}", String::from_utf8_lossy(&buf), depth, self.depth);
if buf.name() != b"sburb" && self.depth == 1 {
return Err(Error::Level(format!("Root tag of {} must be <sburb>", self.name)));
} else if self.depth == 1 {
if let Ok(attr) = buf.get_attr("levelPath") {
let value = attr.unescaped_value()?;
let value = self.reader.decode(value.as_ref());
self.level_root.push(value.as_ref());
println!("new root: {}", self.level_root.display());
}
} else if buf.name() == b"dependencies" {
self.parse_deps(temp_buf)?;
continue;
} else if buf.name() == b"classes" {
self.expand = false;
}
},
_ => {},
}
if let Some(e) = self.expand_class(&e)? {
return Ok(Some(e));
} else {
return Ok(Some(e));
}
},
Event::Empty(buf) => {
self.depth += 1;
self.jump_back = true;
let e = Event::Empty(buf);
let e = self.expand_class(&e)?.unwrap_or(e);
if let &Event::Start(_) = &e {
self.jump_back = false;
};
return Ok(Some(e));
},
e @ Event::Text(_) => {
return Ok(Some(e));
}
Event::End(buf) => {
//println!("close: {:?} {} {}", String::from_utf8_lossy(&buf), depth, self.depth);
if buf.name() == b"classes" {
self.expand = true;
}
self.depth -= 1;
if self.depth < depth {
return Ok(None);
}
},
Event::Eof if self.deps.len() == 0 => return Ok(None),
Event::Eof => {
let new_dep = self.deps.pop().unwrap();
let name = new_dep.file_name()
.and_then(|f| f.to_str())
.unwrap_or("").to_string();
println!("parsing {}", name);
let reader = Reader::from_file(&new_dep)?;
temp_buf.clear();
self.reader = reader;
},
_ => {},
}
}
}
fn read_tag<'a>(&mut self, temp_buf: &'a mut Vec<u8>) -> Result<Option<Event<'a>>> {
self.read_tag_at_depth(temp_buf, 0)
}
/// calls f on each element named 'name' and is a a direct child of
/// the current element
fn parse_children<F>(&mut self, temp_buf: &mut Vec<u8>, mut f: F) -> Result<()>
where F: FnMut(BytesStart, &Reader<BufReader<File>>) -> Result<()>
{
let layer = self.depth + 1;
while let Some(ev) = self.read_tag_at_depth(temp_buf, layer)? {
match ev {
Event::Start(buf) | Event::Empty(buf) => {
println!("{:?} {} {}", String::from_utf8_lossy(&buf), layer, self.depth);
f(buf, &self.reader)?;
},
_ => {},
}
}
Ok(())
}
fn parse_deps(&mut self, temp_buf: &mut Vec<u8>) -> Result<()> {
let start = self.depth;
while self.depth >= start {
if let Some(Event::Start(ref buf)) = self.read_tag(temp_buf)? {
if buf.name() == b"dependency" {
if let Some(Event::Text(ref buf)) = self.read_tag(temp_buf)? {
let buf = buf.unescaped()?;
let buf = self.reader.decode(buf.as_ref());
self.deps.push(self.level_root.join(buf.as_ref()));
println!("{:?}", self.deps);
}
}
}
}
temp_buf.clear();
Ok(())
}
fn parse_assets(&mut self, temp_buf: &mut Vec<u8>, assets: &mut Assets) -> Result<()> {
let start = self.depth;
while self.depth >= start {
if let Some(Event::Start(ref buf)) = self.read_tag(temp_buf)? {
if buf.name() == b"asset" {
let asset_name = buf.get_attr_owned("name", &self.reader)?;
let ty = match buf.get_attr("type")?.unescaped_value()?.as_ref() {
b"graphic" => { Some(AssetType::Graphic) },
b"audio" => { Some(AssetType::Audio) },
b"font" => { Some(AssetType::Font) },
b"path" => { Some(AssetType::Path) },
_ => { None },
};
let src = self.reader.read_text(buf.name(), &mut vec![])?;
self.depth -= 1;
//println!("self.depth {} {}", self.depth, src);
ty.map(|ty| {
assets.map.insert(asset_name, Asset {
src, ty,
loaded: None,
});
});
}
}
}
temp_buf.clear();
Ok(())
}
fn parse_classes(&mut self, classes: &mut Classes) -> Result<()> {
let start = self.depth;
let mut read_buf = vec![];
let mut events = vec![];
let mut current_class = None;
while self.depth >= start {
match self.reader.read_event(&mut read_buf)? {
Event::Empty(buf) => {
if self.depth == start {
let class_name = buf.get_attr_owned("class", &self.reader)?;
println!("class: {}", class_name);
events.push(Event::Empty(buf).into_owned());
let mut old_events = vec![];
::std::mem::swap(&mut events, &mut old_events);
classes.insert(class_name, old_events);
} else {
events.push(Event::Empty(buf).into_owned());
}
},
Event::Start(buf) => {
if self.depth == start {
let class_name = buf.get_attr_owned("class", &self.reader)?;
println!("class: {}", class_name);
current_class = Some(class_name);
}
events.push(Event::Start(buf).into_owned());
self.depth += 1;
},
Event::End(buf) => {
self.depth -= 1;
if self.depth == start {
let mut old_events = vec![];
::std::mem::swap(&mut events, &mut old_events);
current_class.take().map(|c| {
classes.insert(c , old_events);
});
} else {
events.push(Event::End(buf).into_owned());
}
},
e => {
if self.depth > start {
events.push(e.into_owned());
}
}
}
read_buf.clear();
}
Ok(())
}
}
pub fn from_root(dir: PathBuf, root: Option<PathBuf>) -> Result<Openbound> {
let root = root.unwrap_or(PathBuf::from("levels/openbound/openbound.xml"));
let mut reader = FileVisitor::new(&dir, &root)?;
let mut temp_buf = vec![];
// we need to run through the file tree twice - once for collecting assets + classes, the next for
// everything else - who the fuck decided to use adhoc mixin classes here
let mut assets = None;
let mut classes = HashMap::new();
let mut name = "[unnamed]".to_string();
let mut start_character = None;
while let Some(ev) = reader.read_tag(&mut temp_buf)? {
match ev {
Event::Start(buf) => {
if buf.name() == b"sburb" && assets.is_none() {
let attr = buf.get_attr("resourcePath")?;
start_character = buf.get_attr_owned("char", &reader.reader).ok();
let value = attr.unescaped_value()?;
let value = reader.reader.decode(value.as_ref());
if let Ok(v) = buf.get_attr_owned("name", &reader.reader) {
name = v;
}
assets = Some(Assets::new(dir.join(value.as_ref())));
} else if buf.name() == b"assets" {
assets.as_mut().map(|a| reader.parse_assets(&mut temp_buf, a));
} else if buf.name() == b"classes" {
reader.parse_classes(&mut classes)?;
}
},
_ => {}
}
}
let mut reader = FileVisitor::with_classes(dir, root, classes)?;
let assets = assets.ok_or(Error::Level("invalid asset path!".to_string()))?;
let mut hud = HashMap::new();
let mut sprites = HashMap::new();
let mut map = String::new();
let mut paths = Vec::new();
let mut size = [0, 0];
temp_buf.clear();
while let Some(ev) = reader.read_tag(&mut temp_buf)? {
match ev {
Event::Start(buf) => {
if buf.name() == b"room" {
if let Ok(m) = buf.get_attr_owned("walkableMap", &reader.reader) {
map = m;
}
if let Ok(x) = buf.get_attr_int("width", &reader.reader) {
size[0] = x;
}
if let Ok(y) = buf.get_attr_int("height", &reader.reader) {
size[1] = y;
}
} else if (buf.name() == b"sprite" || buf.name() == b"character") && reader.expand {
let (name, mut sprite) = if buf.name() == b"sprite" {
parse_sprite(buf, &reader.reader)?
} else {
parse_character(buf, &reader.reader)?
};
println!("found sprite {}", name);
reader.parse_children(&mut temp_buf, |buf, rdr| {
match buf.name() {
b"animation" => {
if let Ok(_) = buf.get_attr("sliced") {
println!("skipping sliced animation...");
return Ok(());
}
let name = buf.get_attr_owned("name", rdr).ok();
let anim = Animation::parse(buf, rdr)?;
let name = name.unwrap_or(anim.sprite.clone());
println!("animation: {}", name);
sprite.animations.insert(name, anim);
}
b"action" => {
let name = buf.get_attr_owned("name", rdr).ok();
println!("action: {:?}", name);
sprite.actions.push(name.unwrap_or(String::new()));
}
_ => {},
}
Ok(())
})?;
sprites.insert(name, sprite);
} else if buf.name() == b"dialogsprites"{
reader.parse_children(&mut temp_buf, |buf, rdr| {
if buf.name() != b"animation" {
return Ok(());
}
if let Ok(_) = buf.get_attr("sliced") {
println!("skipping sliced animation...");
return Ok(());
}
let name = buf.get_attr_owned("name", rdr)?;
println!("dialogsprite: {}", name);
let anim = Animation::parse(buf, rdr)?;
hud.insert(name, anim);
Ok(())
})?;
} else if buf.name() == b"paths" {
reader.parse_children(&mut temp_buf, |buf, rdr| {
if buf.name() != b"motionpath" {
return Ok(());
}
let path = helpers::parse_motion_path(buf, rdr, &assets)?;
paths.push(path);
Ok(())
})?;
}
},
_ => {}
}
}
Ok(Openbound {
name,
assets,
sprites,
hud,
start_character,
map,
size,
paths,
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment