diff options
Diffstat (limited to 'support/texlab/src/citation/field/text.rs')
-rw-r--r-- | support/texlab/src/citation/field/text.rs | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/support/texlab/src/citation/field/text.rs b/support/texlab/src/citation/field/text.rs new file mode 100644 index 0000000000..87119bd47c --- /dev/null +++ b/support/texlab/src/citation/field/text.rs @@ -0,0 +1,263 @@ +use std::str::FromStr; + +use rowan::{ast::AstNode, NodeOrToken}; +use rustc_hash::FxHashSet; +use strum::EnumString; + +use crate::syntax::bibtex::{ + Accent, Command, CurlyGroup, Field, HasAccentName, HasCommandName, HasName, HasValue, HasWord, + Join, Literal, QuoteGroup, Root, SyntaxKind::*, SyntaxToken, Value, +}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, EnumString)] +#[strum(ascii_case_insensitive)] +pub enum TextField { + Abstract, + Addendum, + BookSubtitle, + BookTitle, + BookTitleAddon, + Chapter, + Doi, + EditorType, + EditorTypeA, + EditorTypeB, + EditorTypeC, + Eid, + Eprint, + EprintClass, + EprintType, + EventTitle, + EventTitleAddon, + Holder, + HowPublished, + Isbn, + Issn, + Issue, + IssueSubtitle, + IssueTitle, + IssueTitleAddon, + Journal, + JournalSubtitle, + JournalTitle, + JournalTitleAddon, + Language, + Location, + MainTitle, + MainSubtitle, + MainTitleAddon, + Note, + OrigLanguage, + Publisher, + Pubstate, + Series, + Subtitle, + Title, + TitleAddon, + Type, + Unknown, + Url, + Venue, + Version, +} + +impl TextField { + pub fn parse(input: &str) -> Option<Self> { + Self::from_str(input).ok() + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Default)] +pub struct TextFieldData { + pub text: String, +} + +impl TextFieldData { + pub fn parse(field: &Field) -> Option<Self> { + let mut builder = TextFieldDataBuilder::default(); + builder.visit_value(&field.value()?)?; + Some(builder.data) + } +} + +#[derive(Default)] +struct TextFieldDataBuilder { + data: TextFieldData, + string_stack: FxHashSet<String>, +} + +impl TextFieldDataBuilder { + fn visit_value(&mut self, value: &Value) -> Option<()> { + match value { + Value::Literal(lit) => { + self.visit_literal(lit)?; + } + Value::CurlyGroup(group) => { + self.visit_curly_group(group)?; + } + Value::QuoteGroup(group) => { + self.visit_quote_group(group)?; + } + Value::Join(join) => { + self.visit_join(join)?; + } + Value::Accent(accent) => { + let _ = self.visit_accent(accent); + } + Value::Command(command) => { + let _ = self.visit_command(command); + } + }; + + Some(()) + } + + fn visit_literal(&mut self, lit: &Literal) -> Option<()> { + if lit + .name_token() + .and_then(|name| self.visit_string_reference(&name)) + .is_none() + { + lit.syntax() + .text() + .for_each_chunk(|text| self.data.text.push_str(text)); + } + + Some(()) + } + + fn visit_string_reference(&mut self, name: &SyntaxToken) -> Option<()> { + let root = Root::cast(name.ancestors().last()?)?; + let name = name.text(); + + let value = root + .strings() + .filter(|string| { + string + .name_token() + .map_or(false, |token| token.text() == name) + }) + .find_map(|string| string.value())?; + + if !self.string_stack.insert(name.to_string()) { + return None; + } + + let _ = self.visit_value(&value); + self.string_stack.remove(name); + Some(()) + } + + fn visit_curly_group(&mut self, group: &CurlyGroup) -> Option<()> { + for child in group.syntax().children_with_tokens() { + match child { + NodeOrToken::Node(node) => { + self.visit_value(&Value::cast(node)?)?; + } + NodeOrToken::Token(token) => { + match token.kind() { + L_CURLY | R_CURLY => (), + WHITESPACE | NBSP => self.data.text.push(' '), + _ => self.data.text.push_str(token.text()), + }; + } + }; + } + + Some(()) + } + + fn visit_quote_group(&mut self, group: &QuoteGroup) -> Option<()> { + for child in group.syntax().children_with_tokens() { + match child { + NodeOrToken::Node(node) => { + self.visit_value(&Value::cast(node)?)?; + } + NodeOrToken::Token(token) => { + match token.kind() { + QUOTE => (), + WHITESPACE | NBSP => self.data.text.push(' '), + _ => self.data.text.push_str(token.text()), + }; + } + }; + } + + Some(()) + } + + fn visit_join(&mut self, join: &Join) -> Option<()> { + if let Some(left) = join.left_value() { + self.visit_value(&left)?; + } + + if let Some(right) = join.right_value() { + self.visit_value(&right)?; + } + + Some(()) + } + + fn visit_accent(&mut self, accent: &Accent) -> Option<()> { + let name = accent.accent_name_token()?; + let word = accent.word_token()?; + + let mut chars = word.text().chars(); + let a = chars.next()?; + + if chars.next().is_some() { + self.data.text.push_str(word.text()); + } else { + let b = match name.text() { + r#"\`"# => '\u{0300}', + r#"\'"# => '\u{0301}', + r#"\^"# => '\u{0302}', + r#"\""# => '\u{0308}', + r#"\H"# => '\u{030B}', + r#"\~"# => '\u{0303}', + + r#"\c"# => '\u{0327}', + r#"\k"# => '\u{0328}', + r#"\="# => '\u{0304}', + r#"\b"# => '\u{0331}', + r#"\."# => '\u{0307}', + r#"\d"# => '\u{0323}', + r#"\r"# => '\u{030A}', + r#"\u"# => '\u{0306}', + r#"\v"# => '\u{030C}', + _ => '\u{0000}', + }; + + match unicode_normalization::char::compose(a, b) { + Some(c) => self.data.text.push(c), + None => self.data.text.push_str(word.text()), + }; + } + + Some(()) + } + + fn visit_command(&mut self, command: &Command) -> Option<()> { + let name = command.command_name_token()?; + let replacement = match name.text() { + r#"\l"# => "\u{0142}", + r#"\o"# => "\u{00F8}", + r#"\i"# => "\u{0131}", + r#"\&"# => "&", + r#"\$"# => "$", + r#"\{"# => "{", + r#"\}"# => "}", + r#"\%"# => "%", + r#"\#"# => "#", + r#"\_"# => "_", + r#"\ "# | r#"\,"# | r#"\;"# => " ", + r#"\hyphen"# => "-", + r#"\TeX"# => "TeX", + r#"\LaTeX"# => "LaTeX", + text => text, + }; + + self.data.text.push_str(replacement); + Some(()) + } +} |