From 158a9f64daac699d094dc7550c81be9059aefa0a Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Thu, 5 Jun 2025 01:50:52 +0200 Subject: Add grit module to common Parses grit files. I tried using serde-xml-rs but it can't handle text mixed with elements so xml-rs and event stream it is. --- server/common/src/grit.rs | 1086 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1086 insertions(+) create mode 100644 server/common/src/grit.rs (limited to 'server/common/src/grit.rs') diff --git a/server/common/src/grit.rs b/server/common/src/grit.rs new file mode 100644 index 0000000..273da8a --- /dev/null +++ b/server/common/src/grit.rs @@ -0,0 +1,1086 @@ +#![allow(dead_code)] + +use anyhow::Error; +use std::fs; +use std::io::{BufReader, Read}; +use std::path::Path; +use tokio::task::spawn_blocking; +use xml::attribute::OwnedAttribute; +use xml::reader::{EventReader, ParserConfig, XmlEvent}; + +#[derive(Debug, PartialEq)] +pub struct Grit { + pub current_release: u32, + pub latest_public_release: u32, + + pub outputs: Outputs, + pub translations: Translations, + pub release: Release, +} + +#[derive(Debug, PartialEq)] +pub struct Outputs { + pub output: Vec, +} + +#[derive(Debug, PartialEq)] +pub struct Output { + pub filename: String, + pub output_type: String, + pub lang: String, +} + +#[derive(Debug, PartialEq)] +pub enum IfOutput { + Output(Output), + + If { expr: String, output: Vec }, +} + +#[derive(Debug, PartialEq)] +pub struct Translations { + pub file: Vec, +} + +#[derive(Debug, PartialEq)] +pub struct File { + pub path: String, + pub lang: String, +} + +#[derive(Debug, PartialEq)] +pub enum IfFile { + File(File), + + If { expr: String, file: Vec }, +} + +#[derive(Debug, PartialEq)] +pub struct Release { + pub allow_pseudo: bool, + pub seq: u32, + + pub messages: Messages, +} + +#[derive(Debug, PartialEq)] +pub struct Messages { + pub fallback_to_english: bool, + + pub messages: Vec, +} + +#[derive(Debug, PartialEq)] +pub enum IfMessagePart { + Message(Message), + + Part(PartRef), + + If { + expr: String, + + message: Vec, + }, +} + +#[derive(Debug, PartialEq)] +pub enum MessagePart { + Message(Message), + + Part(PartRef), +} + +#[derive(Debug, PartialEq)] +pub struct PartRef { + pub file: String, +} + +#[derive(Debug, PartialEq)] +pub struct Message { + pub desc: String, + pub name: String, + pub internal_comment: Option, + pub meaning: Option, + + pub content: Vec, +} + +#[derive(Debug, PartialEq)] +pub enum TextPlaceholder { + Text(String), + Placeholder { + name: String, + content: String, + example: Option, + }, +} + +#[derive(Debug, PartialEq)] +pub enum IfMessage { + Message(Message), + + If { expr: String, message: Vec }, +} + +#[derive(Debug, PartialEq)] +pub struct GritPart { + pub messages: Vec, +} + +fn get_opt_attribute<'a>(attributes: &'a Vec, name: &str) -> Option<&'a str> { + for attribute in attributes { + if attribute.name.local_name == name { + return Some(attribute.value.as_str()); + } + } + None +} + +fn get_attribute<'a>(attributes: &'a Vec, name: &str) -> anyhow::Result<&'a str> { + get_opt_attribute(attributes, name).ok_or(Error::msg(format!("Expected attribute {name}"))) +} + +fn parse_grit_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let current_release = get_attribute(attributes, "current_release")?.parse::()?; + let latest_public_release = + get_attribute(attributes, "latest_public_release")?.parse::()?; + + let mut outputs: Option = None; + let mut translations: Option = None; + let mut release: Option = None; + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "outputs" => { + outputs = Some(parse_outputs_element(&attributes, reader)?); + } + "translations" => { + translations = Some(parse_translations_element(&attributes, reader)?); + } + "release" => { + release = Some(parse_release_element(&attributes, reader)?); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in grit", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "grit"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(Grit { + current_release, + latest_public_release, + outputs: outputs.ok_or(Error::msg("Expected outputs in grit"))?, + translations: translations.ok_or(Error::msg("Expected outputs in grit"))?, + release: release.ok_or(Error::msg("Expected outputs in grit"))?, + }) +} + +fn parse_outputs_element( + _attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let mut output = Vec::::new(); + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "if" => { + output.push(parse_if_output_element(&attributes, reader)?); + } + "output" => { + output.push(IfOutput::Output(parse_output_element(&attributes, reader)?)); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in outputs", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "outputs"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(Outputs { output }) +} + +fn parse_output_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let filename = get_attribute(attributes, "filename")?; + let output_type = get_attribute(attributes, "type")?; + let lang = get_attribute(attributes, "lang")?; + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes: _, + namespace: _, + } => { + return Err(Error::msg(format!( + "Unexpected {0} in output", + name.local_name + ))); + } + XmlEvent::EndElement { name } => { + assert!(name.local_name == "output"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(Output { + filename: filename.to_string(), + output_type: output_type.to_string(), + lang: lang.to_string(), + }) +} + +fn parse_if_output_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let expr = get_attribute(attributes, "expr")?; + let mut output = Vec::::new(); + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "output" => { + output.push(parse_output_element(&attributes, reader)?); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in outputs>if", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "if"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(IfOutput::If { + expr: expr.to_string(), + output, + }) +} + +fn parse_translations_element( + _attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let mut file = Vec::::new(); + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "if" => { + file.push(parse_if_file_element(&attributes, reader)?); + } + "file" => { + file.push(IfFile::File(parse_file_element(&attributes, reader)?)); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in translations", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "translations"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(Translations { file }) +} + +fn parse_file_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let path = get_attribute(attributes, "path")?; + let lang = get_attribute(attributes, "lang")?; + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes: _, + namespace: _, + } => { + return Err(Error::msg(format!( + "Unexpected {0} in file", + name.local_name + ))); + } + XmlEvent::EndElement { name } => { + assert!(name.local_name == "file"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(File { + path: path.to_string(), + lang: lang.to_string(), + }) +} + +fn parse_if_file_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let expr = get_attribute(attributes, "expr")?; + let mut file = Vec::::new(); + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "file" => { + file.push(parse_file_element(&attributes, reader)?); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in outputs>if", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "if"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(IfFile::If { + expr: expr.to_string(), + file, + }) +} + +fn parse_release_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let allow_pseudo = get_attribute(attributes, "allow_pseudo")?.parse::()?; + let seq = get_attribute(attributes, "seq")?.parse::()?; + + let mut messages: Option = None; + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "messages" => { + messages = Some(parse_messages_element(&attributes, reader)?); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in release", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "release"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(Release { + allow_pseudo, + seq, + messages: messages.ok_or(Error::msg("No messages in release"))?, + }) +} + +fn parse_messages_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let fallback_to_english = get_attribute(attributes, "fallback_to_english")?.parse::()?; + + let mut messages = Vec::::new(); + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "message" => { + messages.push(IfMessagePart::Message(parse_message_element( + &attributes, + reader, + )?)); + } + "part" => { + messages.push(IfMessagePart::Part(parse_part_element( + &attributes, + reader, + )?)); + } + "if" => { + messages.push(parse_if_message_part_element(&attributes, reader)?); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in messages", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "messages"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(Messages { + fallback_to_english, + messages, + }) +} + +fn parse_message_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let desc = get_attribute(attributes, "desc")?; + let name = get_attribute(attributes, "name")?; + let internal_comment = get_opt_attribute(attributes, "internal_comment").map(|s| s.to_string()); + let meaning = get_opt_attribute(attributes, "meaning").map(|s| s.to_string()); + + let mut content = Vec::::new(); + let mut first = true; + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "ph" => { + first = false; + content.push(parse_placeholder_element(&attributes, reader)?); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in file", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "message"); + break; + } + XmlEvent::Characters(data) => content.push(TextPlaceholder::Text(if first { + first = false; + data.trim_start().to_string() + } else { + data + })), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + if !first { + match content.last_mut().unwrap() { + TextPlaceholder::Text(data) => { + data.truncate(data.trim_end().len()); + if data.is_empty() { + content.pop(); + } + } + TextPlaceholder::Placeholder { + name: _, + content: _, + example: _, + } => {} + } + } + + Ok(Message { + desc: desc.to_string(), + name: name.to_string(), + internal_comment, + meaning, + content, + }) +} + +fn parse_if_message_part_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let expr = get_attribute(attributes, "expr")?; + + let mut message = Vec::::new(); + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "message" => { + message.push(MessagePart::Message(parse_message_element( + &attributes, + reader, + )?)); + } + "part" => { + message.push(MessagePart::Part(parse_part_element(&attributes, reader)?)); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in messages>if", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "if"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(IfMessagePart::If { + expr: expr.to_string(), + message, + }) +} + +fn parse_if_message_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let expr = get_attribute(attributes, "expr")?; + + let mut message = Vec::::new(); + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "message" => { + message.push(parse_message_element(&attributes, reader)?); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in grit-part>if", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "if"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(IfMessage::If { + expr: expr.to_string(), + message, + }) +} + +fn parse_part_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let file = get_attribute(attributes, "file")?; + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes: _, + namespace: _, + } => { + return Err(Error::msg(format!( + "Unexpected {0} in part", + name.local_name + ))); + } + XmlEvent::EndElement { name } => { + assert!(name.local_name == "part"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(PartRef { + file: file.to_string(), + }) +} + +fn parse_placeholder_element( + attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let name = get_attribute(attributes, "name")?; + + let mut content = String::new(); + let mut example: Option = None; + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "ex" => { + if example.is_some() { + return Err(Error::msg("Multiple examples in placeholder")); + } + example = Some(parse_placeholder_example_element(&attributes, reader)?); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in file", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "ph"); + break; + } + XmlEvent::Characters(data) => { + if example.is_some() { + return Err(Error::msg("Text after example in placeholder")); + } + content.push_str(data.as_str()); + } + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(TextPlaceholder::Placeholder { + name: name.to_string(), + content, + example, + }) +} + +fn parse_placeholder_example_element( + _attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let mut content = String::new(); + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes: _, + namespace: _, + } => { + return Err(Error::msg(format!("Unexpected {0} in ex", name.local_name))); + } + XmlEvent::EndElement { name } => { + assert!(name.local_name == "ex"); + break; + } + XmlEvent::Characters(data) => { + content.push_str(data.as_str()); + } + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(content) +} + +fn parse_grit_part_element( + _attributes: &Vec, + reader: &mut EventReader, +) -> anyhow::Result { + let mut messages = Vec::::new(); + + loop { + let event = reader.next()?; + match event { + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => match name.local_name.as_str() { + "message" => { + messages.push(IfMessage::Message(parse_message_element( + &attributes, + reader, + )?)); + } + "if" => { + messages.push(parse_if_message_element(&attributes, reader)?); + } + _ => { + return Err(Error::msg(format!( + "Unexpected {0} in grit-part", + name.local_name + ))); + } + }, + XmlEvent::EndElement { name } => { + assert!(name.local_name == "grit-part"); + break; + } + XmlEvent::Characters(_) => (), + + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::EndDocument => panic!("Unexpected EOD"), + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + + Ok(GritPart { messages }) +} + +pub async fn parse_grit(path: impl AsRef) -> anyhow::Result { + let path = path.as_ref().to_path_buf(); + spawn_blocking(move || { + let file = fs::File::open(path)?; + let reader = BufReader::new(file); + let mut ereader = ParserConfig::new() + .ignore_comments(true) + .whitespace_to_characters(true) + .cdata_to_characters(true) + .create_reader(reader); + let mut ret: Option = None; + loop { + let event = ereader.next()?; + match event { + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => { + if name.local_name == "grit" { + ret = Some(parse_grit_element(&attributes, &mut ereader)?); + } else { + return Err(Error::msg("Document root != grit")); + } + } + XmlEvent::EndDocument => break, + XmlEvent::EndElement { name: _ } => panic!("Unexpected EoE"), + XmlEvent::Characters(_) => (), + + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + Ok(ret.unwrap()) + }) + .await + .unwrap() +} + +pub async fn parse_grit_part(path: impl AsRef) -> anyhow::Result { + let path = path.as_ref().to_path_buf(); + spawn_blocking(move || { + let file = fs::File::open(path)?; + let reader = BufReader::new(file); + let mut ereader = ParserConfig::new() + .ignore_comments(true) + .whitespace_to_characters(true) + .cdata_to_characters(true) + .create_reader(reader); + let mut ret: Option = None; + loop { + let event = ereader.next()?; + match event { + XmlEvent::StartDocument { + version: _, + encoding: _, + standalone: _, + } => (), + XmlEvent::StartElement { + name, + attributes, + namespace: _, + } => { + if name.local_name == "grit-part" { + ret = Some(parse_grit_part_element(&attributes, &mut ereader)?); + } else { + return Err(Error::msg("Document root != grit-part")); + } + } + XmlEvent::EndDocument => break, + XmlEvent::EndElement { name: _ } => panic!("Unexpected EoE"), + XmlEvent::Characters(_) => (), + + XmlEvent::ProcessingInstruction { name: _, data: _ } => (), + XmlEvent::CData(_) => (), + XmlEvent::Comment(_) => (), + XmlEvent::Whitespace(_) => (), + } + } + Ok(ret.unwrap()) + }) + .await + .unwrap() +} -- cgit v1.2.3-70-g09d2