summaryrefslogtreecommitdiff
path: root/server/common/src/grit.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/common/src/grit.rs')
-rw-r--r--server/common/src/grit.rs1086
1 files changed, 1086 insertions, 0 deletions
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<IfOutput>,
+}
+
+#[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<Output> },
+}
+
+#[derive(Debug, PartialEq)]
+pub struct Translations {
+ pub file: Vec<IfFile>,
+}
+
+#[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<File> },
+}
+
+#[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<IfMessagePart>,
+}
+
+#[derive(Debug, PartialEq)]
+pub enum IfMessagePart {
+ Message(Message),
+
+ Part(PartRef),
+
+ If {
+ expr: String,
+
+ message: Vec<MessagePart>,
+ },
+}
+
+#[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<String>,
+ pub meaning: Option<String>,
+
+ pub content: Vec<TextPlaceholder>,
+}
+
+#[derive(Debug, PartialEq)]
+pub enum TextPlaceholder {
+ Text(String),
+ Placeholder {
+ name: String,
+ content: String,
+ example: Option<String>,
+ },
+}
+
+#[derive(Debug, PartialEq)]
+pub enum IfMessage {
+ Message(Message),
+
+ If { expr: String, message: Vec<Message> },
+}
+
+#[derive(Debug, PartialEq)]
+pub struct GritPart {
+ pub messages: Vec<IfMessage>,
+}
+
+fn get_opt_attribute<'a>(attributes: &'a Vec<OwnedAttribute>, 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<OwnedAttribute>, name: &str) -> anyhow::Result<&'a str> {
+ get_opt_attribute(attributes, name).ok_or(Error::msg(format!("Expected attribute {name}")))
+}
+
+fn parse_grit_element<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<Grit> {
+ let current_release = get_attribute(attributes, "current_release")?.parse::<u32>()?;
+ let latest_public_release =
+ get_attribute(attributes, "latest_public_release")?.parse::<u32>()?;
+
+ let mut outputs: Option<Outputs> = None;
+ let mut translations: Option<Translations> = None;
+ let mut release: Option<Release> = 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<R: Read>(
+ _attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<Outputs> {
+ let mut output = Vec::<IfOutput>::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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<Output> {
+ 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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<IfOutput> {
+ let expr = get_attribute(attributes, "expr")?;
+ let mut output = Vec::<Output>::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<R: Read>(
+ _attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<Translations> {
+ let mut file = Vec::<IfFile>::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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<File> {
+ 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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<IfFile> {
+ let expr = get_attribute(attributes, "expr")?;
+ let mut file = Vec::<File>::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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<Release> {
+ let allow_pseudo = get_attribute(attributes, "allow_pseudo")?.parse::<bool>()?;
+ let seq = get_attribute(attributes, "seq")?.parse::<u32>()?;
+
+ let mut messages: Option<Messages> = 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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<Messages> {
+ let fallback_to_english = get_attribute(attributes, "fallback_to_english")?.parse::<bool>()?;
+
+ let mut messages = Vec::<IfMessagePart>::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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<Message> {
+ 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::<TextPlaceholder>::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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<IfMessagePart> {
+ let expr = get_attribute(attributes, "expr")?;
+
+ let mut message = Vec::<MessagePart>::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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<IfMessage> {
+ let expr = get_attribute(attributes, "expr")?;
+
+ let mut message = Vec::<Message>::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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<PartRef> {
+ 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<R: Read>(
+ attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<TextPlaceholder> {
+ let name = get_attribute(attributes, "name")?;
+
+ let mut content = String::new();
+ let mut example: Option<String> = 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<R: Read>(
+ _attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<String> {
+ 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<R: Read>(
+ _attributes: &Vec<OwnedAttribute>,
+ reader: &mut EventReader<R>,
+) -> anyhow::Result<GritPart> {
+ let mut messages = Vec::<IfMessage>::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<Path>) -> anyhow::Result<Grit> {
+ 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<Grit> = 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<Path>) -> anyhow::Result<GritPart> {
+ 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<GritPart> = 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()
+}