1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use crate::errors::LocaleError;
use crate::parser::ParserError;

use unic_langid_impl::LanguageIdentifier;

use std::collections::BTreeMap;
use std::iter::Peekable;

use tinystr::{TinyStr4, TinyStr8};

#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct TransformExtensionList {
    tlang: Option<LanguageIdentifier>,
    tfields: BTreeMap<TinyStr4, Vec<TinyStr8>>,
}

fn parse_tkey(key: &str) -> Result<TinyStr4, ParserError> {
    if key.len() != 2
        || !key.as_bytes()[0].is_ascii_alphabetic()
        || !key.as_bytes()[1].is_ascii_digit()
    {
        return Err(ParserError::InvalidSubtag);
    }
    let tkey: TinyStr4 = key.parse().map_err(|_| ParserError::InvalidSubtag)?;
    Ok(tkey.to_ascii_lowercase())
}

fn parse_tvalue(t: &str) -> Result<TinyStr8, ParserError> {
    let s: TinyStr8 = t.parse().map_err(|_| ParserError::InvalidSubtag)?;
    if t.len() < 3 || t.len() > 8 || !s.is_ascii_alphanumeric() {
        return Err(ParserError::InvalidSubtag);
    }

    Ok(s.to_ascii_lowercase())
}

fn is_language_subtag(t: &str) -> bool {
    let slen = t.len();
    (slen >= 2 && slen <= 8 || slen == 4) && !t.contains(|c: char| !c.is_ascii_alphabetic())
}

impl TransformExtensionList {
    pub fn is_empty(&self) -> bool {
        self.tlang.is_none() && self.tfields.is_empty()
    }

    pub fn set_tlang(&mut self, tlang: LanguageIdentifier) -> Result<(), LocaleError> {
        self.tlang = Some(tlang);
        Ok(())
    }

    pub fn set_tfield(&mut self, tkey: &str, tvalue: Vec<&str>) -> Result<(), LocaleError> {
        let tkey = parse_tkey(tkey)?;
        let mut t = Vec::with_capacity(tvalue.len());
        for val in tvalue {
            t.push(parse_tvalue(val)?);
        }

        self.tfields.insert(tkey, t);
        Ok(())
    }

    pub fn try_from_iter<'a>(
        mut iter: &mut Peekable<impl Iterator<Item = &'a str>>,
    ) -> Result<Self, ParserError> {
        let mut text = Self::default();

        let mut st_peek = iter.peek();

        let mut current_tkey = None;
        let mut current_tvalue = vec![];

        while let Some(subtag) = st_peek {
            let slen = subtag.len();

            if slen == 2
                && subtag.as_bytes()[0].is_ascii_alphabetic()
                && subtag.as_bytes()[1].is_ascii_digit()
            {
                let tkey: TinyStr4 = subtag.parse().map_err(|_| ParserError::InvalidSubtag)?;
                current_tkey = Some(tkey);
                iter.next();
            } else if current_tkey.is_some() {
                current_tvalue.push(parse_tvalue(subtag)?);
            } else if is_language_subtag(subtag) {
                text.tlang = Some(
                    LanguageIdentifier::try_from_iter(&mut iter, true)
                        .map_err(|_| ParserError::InvalidLanguage)?,
                );
            } else {
                break;
            }
            st_peek = iter.peek();
        }

        Ok(text)
    }
}

impl std::fmt::Display for TransformExtensionList {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        if self.is_empty() {
            return Ok(());
        }

        f.write_str("-t")?;

        if let Some(tlang) = &self.tlang {
            write!(f, "-{}", tlang)?;
        }

        for (k, t) in &self.tfields {
            write!(f, "-{}", k)?;
            for v in t {
                write!(f, "-{}", v)?;
            }
        }
        Ok(())
    }
}