use super::protocol::*;
use std::{
io::{BufRead, BufReader, Read},
str::FromStr,
};
use quick_xml::{
events::{attributes::Attributes, Event},
Reader,
};
pub fn parse<S: Read>(stream: S) -> Protocol {
let mut reader = Reader::from_reader(BufReader::new(stream));
let reader_config = reader.config_mut();
reader_config.trim_text(true);
reader_config.expand_empty_elements = true;
parse_protocol(reader)
}
fn decode_utf8_or_panic(txt: Vec<u8>) -> String {
match String::from_utf8(txt) {
Ok(txt) => txt,
Err(e) => panic!("Invalid UTF8: '{}'", String::from_utf8_lossy(&e.into_bytes())),
}
}
fn parse_or_panic<T: FromStr>(txt: &[u8]) -> T {
match std::str::from_utf8(txt).ok().and_then(|val| val.parse().ok()) {
Some(version) => version,
None => panic!(
"Invalid value '{}' for parsing type '{}'",
String::from_utf8_lossy(txt),
std::any::type_name::<T>()
),
}
}
fn init_protocol<R: BufRead>(reader: &mut Reader<R>) -> Protocol {
for _ in 0..3 {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Decl(_) | Event::DocType(_) | Event::Comment(_)) => {
continue;
}
Ok(Event::Start(bytes)) => {
assert!(bytes.name().into_inner() == b"protocol", "Missing protocol toplevel tag");
if let Some(attr) = bytes
.attributes()
.filter_map(|res| res.ok())
.find(|attr| attr.key.into_inner() == b"name")
{
return Protocol::new(decode_utf8_or_panic(attr.value.into_owned()));
} else {
panic!("Protocol must have a name");
}
}
_ => panic!("Ill-formed protocol file"),
}
}
panic!("Ill-formed protocol file");
}
fn parse_protocol<R: BufRead>(mut reader: Reader<R>) -> Protocol {
let mut protocol = init_protocol(&mut reader);
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => {
match bytes.name().into_inner() {
b"copyright" => {
let mut copyright = String::new();
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Text(text)) => {
if let Ok(text) = text.decode() {
copyright.push_str(&text);
}
}
Ok(Event::CData(cdata)) => {
if let Ok(cdata) = String::from_utf8(cdata.into_inner().into())
{
copyright.push_str(&cdata);
}
}
Ok(Event::GeneralRef(byte_ref)) => {
if let Ok(Some(c)) = byte_ref.resolve_char_ref() {
copyright.push(c);
} else if let Ok(content) = byte_ref.xml10_content() {
if let Some(s) =
quick_xml::escape::resolve_xml_entity(&content)
{
copyright.push_str(s);
}
}
}
Ok(Event::End(bytes)) => {
assert!(
bytes.name().into_inner() == "copyright".as_bytes(),
"Ill-formed protocol file"
);
break;
}
e => {
panic!("Ill-formed protocol file: {e:?}");
}
}
}
protocol.copyright = Some(copyright)
}
b"interface" => {
protocol.interfaces.push(parse_interface(&mut reader, bytes.attributes()));
}
b"description" => {
protocol.description =
Some(parse_description(&mut reader, bytes.attributes()));
}
name => panic!(
"Ill-formed protocol file: unexpected token `{}` in protocol {}",
String::from_utf8_lossy(name),
protocol.name
),
}
}
Ok(Event::End(bytes)) => {
let name = bytes.name().into_inner();
assert!(
name == b"protocol",
"Unexpected closing token `{}`",
String::from_utf8_lossy(name)
);
break;
}
Ok(Event::Comment(_)) => {}
e => panic!("Ill-formed protocol file: unexpected token {e:?}"),
}
}
protocol
}
fn parse_interface<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Interface {
let mut interface = Interface::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => interface.name = decode_utf8_or_panic(attr.value.into_owned()),
b"version" => interface.version = parse_or_panic(&attr.value),
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
interface.description = Some(parse_description(reader, bytes.attributes()))
}
b"request" => interface.requests.push(parse_request(reader, bytes.attributes())),
b"event" => interface.events.push(parse_event(reader, bytes.attributes())),
b"enum" => interface.enums.push(parse_enum(reader, bytes.attributes())),
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"interface" => break,
_ => {}
}
}
interface
}
fn parse_description<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> (String, String) {
let mut summary = String::new();
for attr in attrs.filter_map(|res| res.ok()) {
if attr.key.into_inner() == b"summary" {
summary = String::from_utf8_lossy(&attr.value)
.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
}
}
let mut description = String::new();
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Text(bytes)) => {
if !description.is_empty() {
description.push_str("\n\n");
}
description.push_str(&bytes.decode().unwrap_or_default())
}
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"description" => break,
Ok(Event::Comment(_)) => {}
e => panic!("Ill-formed protocol file: {e:?}"),
}
}
(summary, description)
}
fn parse_request<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Message {
let mut request = Message::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => request.name = decode_utf8_or_panic(attr.value.into_owned()),
b"type" => request.typ = Some(parse_type(&attr.value)),
b"since" => request.since = parse_or_panic(&attr.value),
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
request.description = Some(parse_description(reader, bytes.attributes()))
}
b"arg" => request.args.push(parse_arg(reader, bytes.attributes())),
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"request" => break,
_ => {}
}
}
request
}
fn parse_enum<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Enum {
let mut enu = Enum::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => enu.name = decode_utf8_or_panic(attr.value.into_owned()),
b"since" => enu.since = parse_or_panic(&attr.value),
b"bitfield" => {
if &attr.value[..] == b"true" {
enu.bitfield = true
}
}
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
enu.description = Some(parse_description(reader, bytes.attributes()))
}
b"entry" => enu.entries.push(parse_entry(reader, bytes.attributes())),
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"enum" => break,
_ => {}
}
}
enu
}
fn parse_event<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Message {
let mut event = Message::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => event.name = decode_utf8_or_panic(attr.value.into_owned()),
b"type" => event.typ = Some(parse_type(&attr.value)),
b"since" => event.since = parse_or_panic(&attr.value),
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
event.description = Some(parse_description(reader, bytes.attributes()))
}
b"arg" => event.args.push(parse_arg(reader, bytes.attributes())),
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"event" => break,
_ => {}
}
}
event
}
fn parse_arg<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Arg {
let mut arg = Arg::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => arg.name = decode_utf8_or_panic(attr.value.into_owned()),
b"type" => arg.typ = parse_type(&attr.value),
b"summary" => {
arg.summary = Some(
String::from_utf8_lossy(&attr.value)
.split_whitespace()
.collect::<Vec<_>>()
.join(" "),
)
}
b"interface" => arg.interface = Some(parse_or_panic(&attr.value)),
b"allow-null" => {
if &*attr.value == b"true" {
arg.allow_null = true
}
}
b"enum" => arg.enum_ = Some(decode_utf8_or_panic(attr.value.into_owned())),
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
arg.description = Some(parse_description(reader, bytes.attributes()))
}
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"arg" => break,
_ => {}
}
}
arg
}
fn parse_type(txt: &[u8]) -> Type {
match txt {
b"int" => Type::Int,
b"uint" => Type::Uint,
b"fixed" => Type::Fixed,
b"string" => Type::String,
b"object" => Type::Object,
b"new_id" => Type::NewId,
b"array" => Type::Array,
b"fd" => Type::Fd,
b"destructor" => Type::Destructor,
e => panic!("Unexpected type: {}", String::from_utf8_lossy(e)),
}
}
fn parse_entry<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Entry {
let mut entry = Entry::new();
for attr in attrs.filter_map(|res| res.ok()) {
match attr.key.into_inner() {
b"name" => entry.name = decode_utf8_or_panic(attr.value.into_owned()),
b"value" => {
entry.value = if attr.value.starts_with(b"0x") {
if let Some(val) = std::str::from_utf8(&attr.value[2..])
.ok()
.and_then(|s| u32::from_str_radix(s, 16).ok())
{
val
} else {
panic!("Invalid number: {}", String::from_utf8_lossy(&attr.value))
}
} else {
parse_or_panic(&attr.value)
};
}
b"since" => entry.since = parse_or_panic(&attr.value),
b"summary" => {
entry.summary = Some(
String::from_utf8_lossy(&attr.value)
.split_whitespace()
.collect::<Vec<_>>()
.join(" "),
)
}
_ => {}
}
}
loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
b"description" => {
entry.description = Some(parse_description(reader, bytes.attributes()))
}
name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
},
Ok(Event::End(bytes)) if bytes.name().into_inner() == b"entry" => break,
_ => {}
}
}
entry
}
#[cfg(test)]
mod tests {
#[test]
fn xml_parse() {
let protocol_file =
std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap();
let _ = crate::parse::parse(protocol_file);
}
#[test]
fn headerless_xml_parse() {
let protocol_file =
std::fs::File::open("./tests/scanner_assets/test-headerless-protocol.xml").unwrap();
let _ = crate::parse::parse(protocol_file);
}
}