[librsvg: 1/8] Add support for CSS :lang() pseudo-class




commit f784fc0cd72f026505b28366b6d9ad01a1c0f059
Author: Michael Howell <michael notriddle com>
Date:   Wed Oct 13 10:19:40 2021 -0700

    Add support for CSS :lang() pseudo-class
    
    Fixes #649
    
    Part-of: <https://gitlab.gnome.org/GNOME/librsvg/-/merge_requests/606>

 src/css.rs                                   |  32 +++++++++++++++++++-
 src/document.rs                              |  16 +++++++++-
 src/element.rs                               |  42 +++++++++++++++++++++++++++
 tests/fixtures/reftests/xml-lang-css-ref.png | Bin 0 -> 126 bytes
 tests/fixtures/reftests/xml-lang-css.svg     |  22 ++++++++++++++
 5 files changed, 110 insertions(+), 2 deletions(-)
---
diff --git a/src/css.rs b/src/css.rs
index 1508060b..3c9f1b97 100644
--- a/src/css.rs
+++ b/src/css.rs
@@ -79,6 +79,7 @@ use cssparser::{
     RuleListParser, SourceLocation, ToCss, _cssparser_internal_to_lowercase,
 };
 use data_url::mime::Mime;
+use language_tags::LanguageTag;
 use markup5ever::{namespace_url, ns, LocalName, Namespace, Prefix, QualName};
 use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
 use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, QuirksMode};
@@ -86,6 +87,7 @@ use selectors::{OpaqueElement, SelectorImpl, SelectorList};
 use std::cmp::Ordering;
 use std::fmt;
 use std::str;
+use std::str::FromStr;
 
 use crate::error::*;
 use crate::io::{self, BinaryData};
@@ -215,6 +217,27 @@ impl<'i> selectors::Parser<'i> for RuleParser {
             )),
         }
     }
+    fn parse_non_ts_functional_pseudo_class(
+        &self,
+        name: CowRcStr<'i>,
+        arguments: &mut Parser<'i, '_>,
+    ) -> Result<NonTSPseudoClass, cssparser::ParseError<'i, Self::Error>> {
+        match &*name {
+            "lang" => {
+                let language_tag = {
+                    let language_tag = arguments.expect_ident_or_string()?.clone();
+                    LanguageTag::from_str(&language_tag).map_err(|_| {
+                        
arguments.new_custom_error(selectors::parser::SelectorParseErrorKind::UnsupportedPseudoClassOrElement(language_tag))
+                    })?
+                };
+                arguments.expect_exhausted()?;
+                Ok(NonTSPseudoClass::Lang(language_tag))
+            }
+            _ => Err(arguments.new_custom_error(
+                selectors::parser::SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
+            )),
+        }
+    }
 }
 
 // `cssparser::RuleListParser` is a struct which requires that we
@@ -316,6 +339,7 @@ impl<'i> AtRuleParser<'i> for RuleParser {
 pub enum NonTSPseudoClass {
     Link,
     Visited,
+    Lang(LanguageTag),
 }
 
 impl ToCss for NonTSPseudoClass {
@@ -326,6 +350,7 @@ impl ToCss for NonTSPseudoClass {
         match self {
             NonTSPseudoClass::Link => write!(dest, "link"),
             NonTSPseudoClass::Visited => write!(dest, "visited"),
+            NonTSPseudoClass::Lang(lang) => write!(dest, "lang(\"{}\")", lang),
         }
     }
 }
@@ -517,9 +542,14 @@ impl selectors::Element for RsvgElement {
     where
         F: FnMut(&Self, ElementSelectorFlags),
     {
-        match *pc {
+        match pc {
             NonTSPseudoClass::Link => self.is_link(),
             NonTSPseudoClass::Visited => false,
+            NonTSPseudoClass::Lang(css_lang) => self
+                .0
+                .borrow_element()
+                .get_language()
+                .map_or(false, |e_lang| css_lang.matches(e_lang)),
         }
     }
 
diff --git a/src/document.rs b/src/document.rs
index 406a6d79..1db4e7c8 100644
--- a/src/document.rs
+++ b/src/document.rs
@@ -487,7 +487,7 @@ impl DocumentBuilder {
         attrs: Attributes,
         parent: Option<Node>,
     ) -> Node {
-        let node = Node::new(NodeData::new_element(name, attrs));
+        let mut node = Node::new(NodeData::new_element(name, attrs));
 
         if let Some(id) = node.borrow_element().get_id() {
             // This is so we don't overwrite an existing id
@@ -498,6 +498,20 @@ impl DocumentBuilder {
 
         if let Some(mut parent) = parent {
             parent.append(node.clone());
+
+            // If no language is specified on an element,
+            // it implicitly gets one from the parent,
+            // https://www.w3.org/International/questions/qa-when-xmllang#bytheway
+            //
+            // "It's important to remember that xml:lang has scope: lower-level elements inherit
+            // the language attribute. This can be used to identify the language for a lot of
+            // content (without having redundant language tags on every element)."
+            if let Some(parent_language) = parent.borrow_element().get_language() {
+                let mut child = node.borrow_element_mut();
+                if child.get_language().is_none() {
+                    child.set_language(parent_language.clone());
+                }
+            }
         } else if self.tree.is_none() {
             self.tree = Some(node.clone());
         } else {
diff --git a/src/element.rs b/src/element.rs
index 675756ea..782acf67 100644
--- a/src/element.rs
+++ b/src/element.rs
@@ -1,10 +1,12 @@
 //! SVG Elements.
 
+use language_tags::LanguageTag;
 use markup5ever::{expanded_name, local_name, namespace_url, ns, QualName};
 use once_cell::sync::Lazy;
 use std::collections::{HashMap, HashSet};
 use std::fmt;
 use std::ops::Deref;
+use std::str::FromStr;
 
 use crate::accept_language::UserLanguage;
 use crate::bbox::BoundingBox;
@@ -104,6 +106,7 @@ pub struct ElementInner<T: SetAttributes + Draw> {
     required_extensions: Option<RequiredExtensions>,
     required_features: Option<RequiredFeatures>,
     system_language: Option<SystemLanguage>,
+    language: Option<LanguageTag>,
     pub element_impl: T,
 }
 
@@ -128,11 +131,13 @@ impl<T: SetAttributes + Draw> ElementInner<T> {
             required_extensions: Default::default(),
             required_features: Default::default(),
             system_language: Default::default(),
+            language: Default::default(),
             element_impl,
         };
 
         let mut set_attributes = || -> Result<(), ElementError> {
             e.set_conditional_processing_attributes()?;
+            e.set_language_attribute()?;
             e.set_presentation_attributes()?;
             Ok(())
         };
@@ -160,6 +165,14 @@ impl<T: SetAttributes + Draw> ElementInner<T> {
         self.class.as_deref()
     }
 
+    fn get_language(&self) -> Option<&LanguageTag> {
+        self.language.as_ref()
+    }
+
+    fn set_language(&mut self, language_tag: LanguageTag) {
+        self.language = Some(language_tag);
+    }
+
     fn get_specified_values(&self) -> &SpecifiedValues {
         &self.specified_values
     }
@@ -189,6 +202,27 @@ impl<T: SetAttributes + Draw> ElementInner<T> {
                 .unwrap_or(true)
     }
 
+    fn set_language_attribute(&mut self) -> Result<(), ElementError> {
+        for (attr, value) in self.attributes.iter() {
+            match attr.expanded() {
+                expanded_name!(xml "lang") => {
+                    self.language = Some(
+                        LanguageTag::from_str(value)
+                            .map_err(|e| {
+                                ValueErrorKind::parse_error(&format!(
+                                    "invalid language tag: \"{}\"",
+                                    e
+                                ))
+                            })
+                            .attribute(attr)?,
+                    );
+                }
+                _ => {}
+            }
+        }
+        Ok(())
+    }
+
     fn set_conditional_processing_attributes(&mut self) -> Result<(), ElementError> {
         for (attr, value) in self.attributes.iter() {
             match attr.expanded() {
@@ -497,6 +531,14 @@ impl Element {
         call_inner!(self, get_class)
     }
 
+    pub fn get_language(&self) -> Option<&LanguageTag> {
+        call_inner!(self, get_language)
+    }
+
+    pub fn set_language(&mut self, language: LanguageTag) {
+        call_inner!(self, set_language, language)
+    }
+
     pub fn get_specified_values(&self) -> &SpecifiedValues {
         call_inner!(self, get_specified_values)
     }
diff --git a/tests/fixtures/reftests/xml-lang-css-ref.png b/tests/fixtures/reftests/xml-lang-css-ref.png
new file mode 100644
index 00000000..b200c44b
Binary files /dev/null and b/tests/fixtures/reftests/xml-lang-css-ref.png differ
diff --git a/tests/fixtures/reftests/xml-lang-css.svg b/tests/fixtures/reftests/xml-lang-css.svg
new file mode 100644
index 00000000..67b6c8ee
--- /dev/null
+++ b/tests/fixtures/reftests/xml-lang-css.svg
@@ -0,0 +1,22 @@
+<svg xmlns="http://www.w3.org/2000/svg"; xmlns:xlink="http://www.w3.org/1999/xlink"; width="10" height="50">
+  <style type="text/css">
+    /* Elements should appear in red-green-blue-yellow order.
+       The :lang(en) should match en-US. It will also match en-GB,
+       but that gets overridden later. */
+    :lang(en) { fill: red }
+    /* Languages should be inherited from their parent elements. */
+    :lang(es) { fill: green }
+    /* Child languages should override the parent ones. */
+    :lang(it) { fill: blue }
+    /* Language inheritence should walk more than one level of the DOM.
+       en-GB should match en-GB, but not en-US. */
+    :lang(en-GB) { fill: yellow }
+    /* a rect with no lang attribute should default to black */
+    svg { fill: black }
+  </style>
+  <rect xml:lang="en-US" x="0" y="0" width="10" height="10" />
+  <g xml:lang="es"><rect x="0" y="10" width="10" height="10" /></g>
+  <g xml:lang="es"><rect xml:lang="it" x="0" y="20" width="10" height="10" /></g>
+  <g xml:lang="en-GB"><g><rect x="0" y="30" width="10" height="10" /></g></g>
+  <rect x="0" y="40" width="10" height="10"/>
+</svg>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]