[librsvg: 3/7] (#256): Fix matching of the systemLanguage attribute with the user's locale
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 3/7] (#256): Fix matching of the systemLanguage attribute with the user's locale
- Date: Fri, 5 Oct 2018 16:09:15 +0000 (UTC)
commit ac5b5b1dbe0f80c1e4aee6983b9af1d5b3a0612b
Author: Federico Mena Quintero <federico gnome org>
Date: Thu Oct 4 09:51:31 2018 -0500
(#256): Fix matching of the systemLanguage attribute with the user's locale
The SVG spec says that the systemLanguage attribute is a
comma-separated list of BCP47 language tags, but librsvg was assuming
that it was a list of X/Open locale names (e.g. what one would set in
the LC_MESSAGES or LANG environment variables).
Now, we convert the output of g_get_language_names() to BCP47 language
tags using the locale_config crate.
Then, we use the language_tags crate to actually match those tags
against the values from the systemLanguage attribute.
https://gitlab.gnome.org/GNOME/librsvg/issues/256
rsvg_internals/src/cond.rs | 170 +++++++++++++++++++++++++++++++++------------
rsvg_internals/src/lib.rs | 2 +
rsvg_internals/src/node.rs | 10 +--
3 files changed, 135 insertions(+), 47 deletions(-)
---
diff --git a/rsvg_internals/src/cond.rs b/rsvg_internals/src/cond.rs
index b5fd9dad..63f77dce 100644
--- a/rsvg_internals/src/cond.rs
+++ b/rsvg_internals/src/cond.rs
@@ -1,9 +1,16 @@
-use error::*;
-use std::marker::PhantomData;
-
#[allow(unused_imports, deprecated)]
use std::ascii::AsciiExt;
+use std::str::FromStr;
+
+use glib;
+use itertools::{FoldWhile, Itertools};
+use language_tags::LanguageTag;
+use locale_config::{LanguageRange, Locale};
+
+use error::*;
+use parsers::ParseError;
+
// No extensions at the moment.
static IMPLEMENTED_EXTENSIONS: &[&str] = &[];
@@ -61,35 +68,103 @@ impl RequiredFeatures {
}
#[derive(Debug, PartialEq)]
-pub struct SystemLanguage<'a>(pub bool, pub PhantomData<&'a i8>);
-
-impl<'a> SystemLanguage<'a> {
- // Parse a systemLanguage attribute
- // http://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
- pub fn from_attribute(
- s: &str,
- system_languages: &[String],
- ) -> Result<SystemLanguage<'a>, ValueErrorKind> {
- Ok(SystemLanguage(
- s.split(',')
- .map(|s| s.trim())
- .filter(|s| !s.is_empty())
- .any(|l| {
- system_languages.iter().any(|sl| {
- if sl.eq_ignore_ascii_case(l) {
- return true;
- }
+pub struct SystemLanguage(pub bool);
- if let Some(offset) = l.find('-') {
- return sl.eq_ignore_ascii_case(&l[..offset]);
+impl SystemLanguage {
+ /// Parse a `systemLanguage` attribute and match it against a given `Locale`
+ ///
+ /// The [`systemLanguage`] conditional attribute is a
+ /// comma-separated list of [BCP47] Language Tags. This function
+ /// parses the attribute and matches the result against a given
+ /// `locale`. If there is a match, i.e. if the given locale
+ /// supports one of the languages listed in the `systemLanguage`
+ /// attribute, then the `SystemLanguage.0` will be `true`;
+ /// otherwise it will be `false`.
+ ///
+ /// Normally, calling code will pass `&Locale::current()` for the
+ /// `locale` attribute; this is the user's current locale.
+ ///
+ /// [`systemLanguage`]:
https://www.w3.org/TR/SVG/struct.html#ConditionalProcessingSystemLanguageAttribute
+ /// [BCP47]: http://www.ietf.org/rfc/bcp/bcp47.txt
+ pub fn from_attribute(s: &str, locale: &Locale) -> Result<SystemLanguage, ValueErrorKind> {
+ s.split(',')
+ .map(LanguageTag::from_str)
+ .fold_while(
+ // start with no match
+ Ok(SystemLanguage(false)),
+ // The accumulator is Result<SystemLanguage, ValueErrorKind>
+ |acc, tag_result| match tag_result {
+ Ok(language_tag) => {
+ let have_match = acc.unwrap().0;
+ if have_match {
+ FoldWhile::Continue(Ok(SystemLanguage(have_match)))
+ } else {
+ locale_accepts_language_tag(locale, &language_tag)
+ .map(|matches| FoldWhile::Continue(Ok(SystemLanguage(matches))))
+ .unwrap_or_else(|e| FoldWhile::Done(Err(e)))
}
+ }
- false
- })
- }),
- PhantomData,
- ))
+ Err(e) => FoldWhile::Done(Err(ValueErrorKind::Parse(ParseError::new(
+ &format!("invalid language tag: \"{}\"", e),
+ )))),
+ },
+ )
+ .into_inner()
+ }
+}
+
+/// Gets the user's preferred locale from the environment and
+/// translates it to a `Locale` with `LanguageRange` fallbacks.
+///
+/// The `Locale::current()` call only contemplates a single language,
+/// but glib is smarter, and `g_get_langauge_names()` can provide
+/// fallbacks, for example, when LC_MESSAGES="en_US.UTF-8:de" (USA
+/// English and German). This function converts the output of
+/// `g_get_language_names()` into a `Locale` with appropriate
+/// fallbacks.
+pub fn locale_from_environment() -> Result<Locale, String> {
+ let mut locale = Locale::invariant();
+
+ for name in glib::get_language_names() {
+ let range = LanguageRange::from_unix(&name).map_err(|e| format!("{}", e))?;
+ locale.add(&range);
+ }
+
+ Ok(locale)
+}
+
+fn locale_accepts_language_tag(
+ locale: &Locale,
+ language_tag: &LanguageTag,
+) -> Result<bool, ValueErrorKind> {
+ for locale_range in locale.tags_for("messages") {
+ if locale_range == LanguageRange::invariant() {
+ continue;
+ }
+
+ let str_locale_range = locale_range.as_ref();
+
+ let locale_tag = LanguageTag::from_str(str_locale_range).map_err(|e| {
+ ValueErrorKind::Parse(ParseError::new(&format!(
+ "invalid language tag \"{}\" in locale: {}",
+ str_locale_range, e
+ )))
+ })?;
+
+ if !locale_tag.is_language_range() {
+ return Err(ValueErrorKind::Value(format!(
+ "language tag \"{}\" is not a language range",
+ locale_tag
+ )));
+ }
+
+ if locale_tag.matches(language_tag) {
+ return Ok(true);
+ }
}
+
+ Ok(false)
}
#[cfg(test)]
@@ -135,41 +210,50 @@ mod tests {
#[test]
fn system_language() {
- let system_languages = vec![String::from("de"), String::from("en_US")];
+ let user_prefers = Locale::new("de,en-US").unwrap();
+
+ assert!(SystemLanguage::from_attribute("", &user_prefers).is_err());
+
+ assert!(SystemLanguage::from_attribute("12345", &user_prefers).is_err());
+
+ assert_eq!(
+ SystemLanguage::from_attribute("fr", &user_prefers),
+ Ok(SystemLanguage(false))
+ );
assert_eq!(
- SystemLanguage::from_attribute("", &system_languages),
- Ok(SystemLanguage(false, PhantomData))
+ SystemLanguage::from_attribute("en", &user_prefers),
+ Ok(SystemLanguage(false))
);
assert_eq!(
- SystemLanguage::from_attribute("fr", &system_languages),
- Ok(SystemLanguage(false, PhantomData))
+ SystemLanguage::from_attribute("de", &user_prefers),
+ Ok(SystemLanguage(true))
);
assert_eq!(
- SystemLanguage::from_attribute("de", &system_languages),
- Ok(SystemLanguage(true, PhantomData))
+ SystemLanguage::from_attribute("en-US", &user_prefers),
+ Ok(SystemLanguage(true))
);
assert_eq!(
- SystemLanguage::from_attribute("en_US", &system_languages),
- Ok(SystemLanguage(true, PhantomData))
+ SystemLanguage::from_attribute("en-GB", &user_prefers),
+ Ok(SystemLanguage(false))
);
assert_eq!(
- SystemLanguage::from_attribute("DE", &system_languages),
- Ok(SystemLanguage(true, PhantomData))
+ SystemLanguage::from_attribute("DE", &user_prefers),
+ Ok(SystemLanguage(true))
);
assert_eq!(
- SystemLanguage::from_attribute("de-LU", &system_languages),
- Ok(SystemLanguage(true, PhantomData))
+ SystemLanguage::from_attribute("de-LU", &user_prefers),
+ Ok(SystemLanguage(true))
);
assert_eq!(
- SystemLanguage::from_attribute("fr, de", &system_languages),
- Ok(SystemLanguage(true, PhantomData))
+ SystemLanguage::from_attribute("fr, de", &user_prefers),
+ Ok(SystemLanguage(true))
);
}
}
diff --git a/rsvg_internals/src/lib.rs b/rsvg_internals/src/lib.rs
index 2dc9d95b..cb26e9f8 100644
--- a/rsvg_internals/src/lib.rs
+++ b/rsvg_internals/src/lib.rs
@@ -10,7 +10,9 @@ extern crate gdk_pixbuf;
extern crate glib;
extern crate glib_sys;
extern crate itertools;
+extern crate language_tags;
extern crate libc;
+extern crate locale_config;
extern crate nalgebra;
extern crate num_traits;
extern crate owning_ref;
diff --git a/rsvg_internals/src/node.rs b/rsvg_internals/src/node.rs
index 0e394cad..0381dfa5 100644
--- a/rsvg_internals/src/node.rs
+++ b/rsvg_internals/src/node.rs
@@ -1,6 +1,5 @@
use cairo::{Matrix, MatrixTrait};
use downcast_rs::*;
-use glib;
use glib::translate::*;
use glib_sys;
use std::cell::{Cell, Ref, RefCell};
@@ -8,7 +7,7 @@ use std::ptr;
use std::rc::{Rc, Weak};
use attributes::Attribute;
-use cond::{RequiredExtensions, RequiredFeatures, SystemLanguage};
+use cond::{locale_from_environment, RequiredExtensions, RequiredFeatures, SystemLanguage};
use drawing_ctx::DrawingCtx;
use error::*;
use handle::{self, RsvgHandle};
@@ -390,8 +389,11 @@ impl Node {
}
Attribute::SystemLanguage if cond => {
- cond = SystemLanguage::from_attribute(value, &glib::get_language_names())
- .map(|SystemLanguage(res, _)| res)?;
+ cond = SystemLanguage::from_attribute(
+ value,
+ &(locale_from_environment().map_err(|e| ValueErrorKind::Value(e))?),
+ )
+ .map(|SystemLanguage(res)| res)?;
}
_ => {}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]