mod attribute_marker_processor;
mod line_parser;
mod markup_parse_error;
mod parsed_markup;
pub use self::line_parser::{
Result, CHARACTER_ATTRIBUTE, CHARACTER_ATTRIBUTE_NAME_PROPERTY, TRIM_WHITESPACE_PROPERTY,
};
pub(crate) use self::{attribute_marker_processor::*, line_parser::*};
pub use self::{markup_parse_error::*, parsed_markup::*};
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn test_markup_parsing() {
let line = "A [b]B[/b]";
let markup = line_parser().parse_markup(line).unwrap();
assert_eq!("A B", markup.text);
assert_eq!(1, markup.attributes.len());
assert_eq!("b", markup.attributes[0].name);
assert_eq!(2, markup.attributes[0].position);
assert_eq!(1, markup.attributes[0].length);
}
#[test]
fn test_overlapping_attributes() {
let line = "[a][b][c]X[/b][/a]X[/c]";
let markup = line_parser().parse_markup(line).unwrap();
assert_eq!(3, markup.attributes.len());
assert_eq!("a", markup.attributes[0].name);
assert_eq!("b", markup.attributes[1].name);
assert_eq!("c", markup.attributes[2].name);
}
#[test]
fn test_text_extraction() {
let line = "A [b]B [c]C[/c][/b]";
let markup = line_parser().parse_markup(line).unwrap();
assert_eq!("B C", markup.text_for_attribute(&markup.attributes[0]));
assert_eq!("C", markup.text_for_attribute(&markup.attributes[1]));
}
#[test]
fn test_attribute_removal() {
let line = "[a][b]A [c][X]x[/b] [d]x[/X][/c] B[/d] [e]C[/e][/a]";
let original_markup = line_parser().parse_markup(line).unwrap();
assert_eq!("X", original_markup.attributes[3].name);
let trimmed_markup = original_markup.delete_range(&original_markup.attributes[3]);
assert_eq!("A x x B C", original_markup.text);
assert_eq!(6, original_markup.attributes.len());
assert_eq!("A B C", trimmed_markup.text);
assert_eq!(4, trimmed_markup.attributes.len());
assert_eq!("a", trimmed_markup.attributes[0].name);
assert_eq!(0, trimmed_markup.attributes[0].position);
assert_eq!(6, trimmed_markup.attributes[0].length);
assert_eq!("b", trimmed_markup.attributes[1].name);
assert_eq!(0, trimmed_markup.attributes[1].position);
assert_eq!(2, trimmed_markup.attributes[1].length);
assert_eq!("d", trimmed_markup.attributes[2].name);
assert_eq!(2, trimmed_markup.attributes[2].position);
assert_eq!(2, trimmed_markup.attributes[2].length);
assert_eq!("e", trimmed_markup.attributes[3].name);
assert_eq!(5, trimmed_markup.attributes[3].position);
assert_eq!(1, trimmed_markup.attributes[3].length);
}
#[test]
fn test_finding_attributes() {
let line = "A [b]B[/b] [b]C[/b]";
let markup = line_parser().parse_markup(line).unwrap();
let attribute = markup.get_attribute("b").unwrap();
assert_eq!(&attribute, &markup.attributes[0]);
assert_ne!(&attribute, &markup.attributes[1]);
assert!(markup.get_attribute("c").is_none());
}
#[test]
fn test_multibyte_character_parsing() {
for input in [
"á [á]S[/á]",
"á [a]á[/a]",
"á [a]S[/a]",
"S [á]S[/á]",
"S [a]á[/a]",
"S [a]S[/a]",
] {
let markup = line_parser().parse_markup(input).unwrap();
assert_eq!(1, markup.attributes.len());
assert_eq!(2, markup.attributes[0].position);
assert_eq!(1, markup.attributes[0].length);
}
}
#[test]
fn test_unexpected_close_marker_throws() {
for input in ["[a][/a][/b]", "[/b]", "[a][/][/b]"] {
let markup = line_parser().parse_markup(input);
assert!(markup.is_err());
}
}
#[test]
fn test_markup_shortcut_property_parsing() {
let line = "[a=1]s[/a]";
let markup = line_parser().parse_markup(line).unwrap();
assert_eq!(1, markup.attributes.len());
let attribute = &markup.attributes[0];
assert_eq!("a", attribute.name);
assert_eq!(0, attribute.position);
assert_eq!(1, attribute.length);
let value = attribute.properties.get("a").unwrap();
assert_eq!(&MarkupValue::Integer(1), value);
}
#[test]
fn test_markup_multiple_property_parsing() {
let line = "[a p1=1 p2=2]s[/a]";
let markup = line_parser().parse_markup(line).unwrap();
assert_eq!(1, markup.attributes.len());
let attribute = &markup.attributes[0];
assert_eq!("a", attribute.name);
assert_eq!(2, attribute.properties.len());
let p1 = attribute.properties.get("p1").unwrap();
assert_eq!(&MarkupValue::Integer(1), p1);
let p2 = attribute.properties.get("p2").unwrap();
assert_eq!(&MarkupValue::Integer(2), p2);
}
#[test]
fn test_markup_property_parsing() {
for (input, expected_value) in [
("[a p=\"string\"]s[/a]", MarkupValue::from("string")),
("[a p=\"str\\\"ing\"]s[/a]", "str\"ing".into()),
("[a p=string]s[/a]", "string".into()),
("[a p=42]s[/a]", 42.into()),
("[a p=13.37]s[/a]", 13.37.into()),
("[a p=true]s[/a]", true.into()),
("[a p=false]s[/a]", false.into()),
] {
let markup = line_parser().parse_markup(input).unwrap();
let attribute = &markup.attributes[0];
let property_value = attribute.properties.get("p").unwrap();
assert_eq!(&expected_value, property_value);
}
}
#[test]
fn test_multiple_attributes() {
for input in [
"A [b]B [c]C[/c][/b] D", "A [b]B [c]C[/b][/c] D", "A [b]B [c]C[/] D", ] {
let markup = line_parser().parse_markup(input).unwrap();
assert_eq!("A B C D", markup.text);
assert_eq!(2, markup.attributes.len());
assert_eq!("b", markup.attributes[0].name);
assert_eq!(2, markup.attributes[0].position);
assert_eq!(2, markup.attributes[0].source_position);
assert_eq!(3, markup.attributes[0].length);
assert_eq!("c", markup.attributes[1].name);
assert_eq!(4, markup.attributes[1].position);
assert_eq!(7, markup.attributes[1].source_position);
assert_eq!(1, markup.attributes[1].length);
}
}
#[test]
fn test_self_closing_attributes() {
let line = "A [a/] B";
let markup = line_parser().parse_markup(line).unwrap();
assert_eq!("A B", markup.text);
assert_eq!(1, markup.attributes.len());
assert_eq!("a", markup.attributes[0].name);
assert!(markup.attributes[0].properties.is_empty());
assert_eq!(2, markup.attributes[0].position);
assert_eq!(0, markup.attributes[0].length);
}
#[test]
fn test_attributes_may_trim_trailing_whitespace() {
for (input, expected_text) in [
("A [a/] B", "A B"),
("A [a trimwhitespace=true/] B", "A B"),
("A [a trimwhitespace=false/] B", "A B"),
("A [nomarkup/] B", "A B"),
("A [nomarkup trimwhitespace=false/] B", "A B"),
("A [nomarkup trimwhitespace=true/] B", "A B"),
] {
let markup = line_parser().parse_markup(input).unwrap();
assert_eq!(expected_text, markup.text);
}
}
#[test]
fn test_implicit_character_attribute_parsing() {
for input in [
"Mae: Wow!",
"[character name=\"Mae\"]Mae: [/character]Wow!",
] {
let markup = line_parser().parse_markup(input).unwrap();
assert_eq!("Mae: Wow!", markup.text);
assert_eq!(1, markup.attributes.len());
let attribute = &markup.attributes[0];
assert_eq!("character", attribute.name);
assert_eq!(0, attribute.position);
assert_eq!(5, attribute.length);
assert_eq!(1, attribute.properties.len());
assert_eq!(
&MarkupValue::String("Mae".to_owned()),
attribute.properties.get("name").unwrap()
);
}
}
#[test]
fn test_no_markup_mode_parsing() {
let line = "S [a]S[/a] [nomarkup][a]S;][/a][/nomarkup]";
let markup = line_parser().parse_markup(line).unwrap();
assert_eq!("S S [a]S;][/a]", markup.text);
assert_eq!(2, markup.attributes.len());
assert_eq!("a", markup.attributes[0].name);
assert_eq!(2, markup.attributes[0].position);
assert_eq!(1, markup.attributes[0].length);
assert_eq!("nomarkup", markup.attributes[1].name);
assert_eq!(4, markup.attributes[1].position);
assert_eq!(10, markup.attributes[1].length);
}
#[test]
fn test_markup_escaping() {
let line = r"[a]hello \[b\]hello\[/b\][/a]";
let markup = line_parser().parse_markup(line).unwrap();
assert_eq!("hello [b]hello[/b]", markup.text);
assert_eq!(1, markup.attributes.len());
assert_eq!("a", markup.attributes[0].name);
assert_eq!(0, markup.attributes[0].position);
assert_eq!(18, markup.attributes[0].length);
}
#[test]
fn test_numeric_properties() {
let line = "[select value=1 1=one 2=two 3=three /]";
let markup = line_parser().parse_markup(line).unwrap();
assert_eq!(1, markup.attributes.len());
let attribute = &markup.attributes[0];
assert_eq!("select", attribute.name);
assert_eq!(4, attribute.properties.len());
assert_eq!(
&MarkupValue::Integer(1),
attribute.properties.get("value").unwrap()
);
assert_eq!(
&MarkupValue::String("one".to_owned()),
attribute.properties.get("1").unwrap()
);
assert_eq!(
&MarkupValue::String("two".to_owned()),
attribute.properties.get("2").unwrap()
);
assert_eq!(
&MarkupValue::String("three".to_owned()),
attribute.properties.get("3").unwrap()
);
assert_eq!("one", markup.text);
}
#[test]
fn test_number_pluralisation() {
for (value, locale, expected) in [
(1, "en", "a single cat"),
(2, "en", "2 cats"),
(3, "en", "3 cats"),
(1, "en-AU", "a single cat"),
(2, "en-AU", "2 cats"),
(3, "en-AU", "3 cats"),
] {
let line = format!("[plural value={value} one=\"a single cat\" other=\"% cats\"/]",);
let mut line_parser = line_parser();
line_parser.set_language_code(Language::from(locale));
let markup = line_parser.parse_markup(&line).unwrap();
assert_eq!(expected, markup.text, "locale: {locale}");
}
}
fn line_parser() -> LineParser {
let dialogue_text_processor = Box::new(DialogueTextProcessor::new());
LineParser::new()
.register_marker_processor("select", dialogue_text_processor.clone())
.register_marker_processor("plural", dialogue_text_processor.clone())
.register_marker_processor("ordinal", dialogue_text_processor)
}
trait ParsedMarkupAsLine {
fn as_line(&self) -> Line;
fn get_attribute(&self, name: &str) -> Option<MarkupAttribute> {
self.as_line().attribute(name).cloned()
}
fn text_for_attribute(&self, attribute: &MarkupAttribute) -> String {
self.as_line().text_for_attribute(attribute).to_string()
}
fn delete_range(&self, attribute_to_delete: &MarkupAttribute) -> ParsedMarkup {
let line = self.as_line().delete_range(attribute_to_delete);
ParsedMarkup {
text: line.text,
attributes: line.attributes,
}
}
}
impl ParsedMarkupAsLine for ParsedMarkup {
fn as_line(&self) -> Line {
Line {
id: "test".into(),
text: self.text.clone(),
attributes: self.attributes.clone(),
}
}
}
}