use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EnclosureType {
DoubleQuote,
SingleQuote,
Parenthesis,
SquareBracket,
CurlyBrace,
JapaneseQuote,
JapaneseDoubleQuote,
JapaneseAngleBracket,
JapaneseDoubleAngleBracket,
JapaneseLenticularBracket,
JapaneseTortoiseShellBracket,
FrenchQuote,
GermanQuote,
Custom(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnclosureChar {
pub enclosure_type: EnclosureType,
pub is_opening: bool,
pub is_symmetric: bool,
}
pub trait EnclosureRules: Send + Sync {
fn get_enclosure_char(&self, ch: char) -> Option<EnclosureChar>;
fn is_matching_pair(&self, open: char, close: char) -> bool;
fn can_contain_sentences(&self, enclosure_type: EnclosureType) -> bool {
matches!(
enclosure_type,
EnclosureType::Parenthesis
| EnclosureType::SquareBracket
| EnclosureType::CurlyBrace
| EnclosureType::JapaneseQuote
| EnclosureType::JapaneseDoubleQuote
| EnclosureType::JapaneseAngleBracket
| EnclosureType::JapaneseDoubleAngleBracket
| EnclosureType::JapaneseLenticularBracket
| EnclosureType::JapaneseTortoiseShellBracket
)
}
}
#[derive(Debug, Clone, Default)]
pub struct StandardEnclosureRules {
char_map: HashMap<char, EnclosureChar>,
pair_map: HashMap<char, char>,
}
impl StandardEnclosureRules {
pub fn new() -> Self {
let mut char_map = HashMap::new();
let mut pair_map = HashMap::new();
char_map.insert(
'"',
EnclosureChar {
enclosure_type: EnclosureType::DoubleQuote,
is_opening: true,
is_symmetric: false,
},
);
char_map.insert(
'"',
EnclosureChar {
enclosure_type: EnclosureType::DoubleQuote,
is_opening: false,
is_symmetric: false,
},
);
pair_map.insert('"', '"');
char_map.insert(
'\'',
EnclosureChar {
enclosure_type: EnclosureType::SingleQuote,
is_opening: true,
is_symmetric: false,
},
);
char_map.insert(
'\'',
EnclosureChar {
enclosure_type: EnclosureType::SingleQuote,
is_opening: false,
is_symmetric: false,
},
);
pair_map.insert('\'', '\'');
char_map.insert(
'"',
EnclosureChar {
enclosure_type: EnclosureType::DoubleQuote,
is_opening: true,
is_symmetric: false,
},
);
char_map.insert(
'"',
EnclosureChar {
enclosure_type: EnclosureType::DoubleQuote,
is_opening: false,
is_symmetric: false,
},
);
pair_map.insert('"', '"');
char_map.insert(
'\u{2018}', EnclosureChar {
enclosure_type: EnclosureType::SingleQuote,
is_opening: true,
is_symmetric: false,
},
);
char_map.insert(
'\u{2019}', EnclosureChar {
enclosure_type: EnclosureType::SingleQuote,
is_opening: false,
is_symmetric: false,
},
);
pair_map.insert('\u{2018}', '\u{2019}');
char_map.insert(
'(',
EnclosureChar {
enclosure_type: EnclosureType::Parenthesis,
is_opening: true,
is_symmetric: false,
},
);
char_map.insert(
')',
EnclosureChar {
enclosure_type: EnclosureType::Parenthesis,
is_opening: false,
is_symmetric: false,
},
);
pair_map.insert('(', ')');
char_map.insert(
'[',
EnclosureChar {
enclosure_type: EnclosureType::SquareBracket,
is_opening: true,
is_symmetric: false,
},
);
char_map.insert(
']',
EnclosureChar {
enclosure_type: EnclosureType::SquareBracket,
is_opening: false,
is_symmetric: false,
},
);
pair_map.insert('[', ']');
char_map.insert(
'{',
EnclosureChar {
enclosure_type: EnclosureType::CurlyBrace,
is_opening: true,
is_symmetric: false,
},
);
char_map.insert(
'}',
EnclosureChar {
enclosure_type: EnclosureType::CurlyBrace,
is_opening: false,
is_symmetric: false,
},
);
pair_map.insert('{', '}');
Self { char_map, pair_map }
}
pub fn with_extended_quotes(mut self) -> Self {
self.char_map.insert(
'«',
EnclosureChar {
enclosure_type: EnclosureType::FrenchQuote,
is_opening: true,
is_symmetric: false,
},
);
self.char_map.insert(
'»',
EnclosureChar {
enclosure_type: EnclosureType::FrenchQuote,
is_opening: false,
is_symmetric: false,
},
);
self.pair_map.insert('«', '»');
self.char_map.insert(
'„',
EnclosureChar {
enclosure_type: EnclosureType::GermanQuote,
is_opening: true,
is_symmetric: false,
},
);
self.char_map.insert(
'"',
EnclosureChar {
enclosure_type: EnclosureType::GermanQuote,
is_opening: false,
is_symmetric: false,
},
);
self.pair_map.insert('„', '"');
self
}
}
impl EnclosureRules for StandardEnclosureRules {
fn get_enclosure_char(&self, ch: char) -> Option<EnclosureChar> {
match ch {
'"' | '\'' => Some(EnclosureChar {
enclosure_type: if ch == '"' {
EnclosureType::DoubleQuote
} else {
EnclosureType::SingleQuote
},
is_opening: true, is_symmetric: true, }),
_ => self.char_map.get(&ch).copied(),
}
}
fn is_matching_pair(&self, open: char, close: char) -> bool {
self.pair_map.get(&open).is_some_and(|&c| c == close)
}
}
#[derive(Debug, Clone, Default)]
pub struct EnclosureStack {
stack: Vec<(EnclosureType, char)>,
}
impl EnclosureStack {
pub fn new() -> Self {
Self::default()
}
pub fn depth(&self) -> usize {
self.stack.len()
}
pub fn push(&mut self, enclosure_type: EnclosureType, open_char: char) {
self.stack.push((enclosure_type, open_char));
}
pub fn close(&mut self, enclosure_type: EnclosureType) -> bool {
if let Some((last_type, _)) = self.stack.last() {
if *last_type == enclosure_type {
self.stack.pop();
return true;
}
}
false
}
pub fn current_enclosure(&self) -> Option<EnclosureType> {
self.stack.last().map(|(t, _)| *t)
}
pub fn is_enclosed(&self) -> bool {
!self.stack.is_empty()
}
pub fn clear(&mut self) {
self.stack.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_standard_enclosure_rules() {
let rules = StandardEnclosureRules::new();
assert!(rules.get_enclosure_char('(').is_some());
assert!(rules.get_enclosure_char(')').is_some());
assert!(rules.get_enclosure_char('[').is_some());
assert!(rules.get_enclosure_char('a').is_none());
assert!(rules.is_matching_pair('(', ')'));
assert!(rules.is_matching_pair('[', ']'));
assert!(rules.is_matching_pair('{', '}'));
assert!(!rules.is_matching_pair('(', ']'));
}
#[test]
fn test_enclosure_stack() {
let mut stack = EnclosureStack::new();
assert_eq!(stack.depth(), 0);
assert!(!stack.is_enclosed());
stack.push(EnclosureType::Parenthesis, '(');
assert_eq!(stack.depth(), 1);
assert!(stack.is_enclosed());
stack.push(EnclosureType::DoubleQuote, '"');
assert_eq!(stack.depth(), 2);
assert!(stack.close(EnclosureType::DoubleQuote));
assert_eq!(stack.depth(), 1);
assert!(stack.close(EnclosureType::Parenthesis));
assert_eq!(stack.depth(), 0);
assert!(!stack.close(EnclosureType::SquareBracket));
}
#[test]
fn test_extended_quotes() {
let rules = StandardEnclosureRules::new().with_extended_quotes();
assert!(rules.get_enclosure_char('«').is_some());
assert!(rules.get_enclosure_char('»').is_some());
assert!(rules.is_matching_pair('«', '»'));
}
}