[librsvg: 1/8] Add support for CSS :lang() pseudo-class
- From: Marge Bot <marge-bot src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 1/8] Add support for CSS :lang() pseudo-class
- Date: Fri, 15 Oct 2021 21:01:05 +0000 (UTC)
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]