use cssparser::{
self, AtRuleParser, BasicParseErrorKind, CowRcStr, DeclarationParser, Parser, ParserInput,
ParserState, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation,
StyleSheetParser, ToCss, match_ignore_ascii_case, parse_important,
};
use language_tags::LanguageTag;
use markup5ever::{self, Namespace, QualName, ns};
use precomputed_hash::PrecomputedHash;
use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
use selectors::bloom::BloomFilter;
use selectors::context::SelectorCaches;
use selectors::matching::{
ElementSelectorFlags, MatchingContext, MatchingForInvalidation, MatchingMode,
NeedsSelectorFlags, QuirksMode,
};
use selectors::parser::ParseRelative;
use selectors::{OpaqueElement, SelectorImpl, SelectorList};
use std::cmp::Ordering;
use std::fmt;
use std::str;
use std::str::FromStr;
use crate::element::Element;
use crate::error::*;
use crate::io;
use crate::node::{Node, NodeBorrow, NodeCascade};
use crate::properties::{ComputedValues, ParseAs, ParsedProperty, parse_value};
use crate::rsvg_log;
use crate::session::Session;
use crate::url_resolver::{AllowedUrl, UrlResolver};
pub struct Declaration {
pub prop_name: QualName,
pub property: ParsedProperty,
pub important: bool,
}
pub enum RuleBodyItem {
Decl(Declaration),
#[allow(dead_code)] Rule(Rule),
}
pub struct DeclParser;
impl<'i> DeclarationParser<'i> for DeclParser {
type Declaration = RuleBodyItem;
type Error = ValueErrorKind;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
_declaration_start: &ParserState,
) -> Result<RuleBodyItem, cssparser::ParseError<'i, Self::Error>> {
let prop_name = QualName::new(None, ns!(), markup5ever::LocalName::from(name.as_ref()));
let property = parse_value(&prop_name, input, ParseAs::Property)?;
let important = input.try_parse(parse_important).is_ok();
Ok(RuleBodyItem::Decl(Declaration {
prop_name,
property,
important,
}))
}
}
impl<'i> AtRuleParser<'i> for DeclParser {
type Prelude = ();
type AtRule = RuleBodyItem;
type Error = ValueErrorKind;
}
impl<'i> QualifiedRuleParser<'i> for DeclParser {
type Prelude = ();
type QualifiedRule = RuleBodyItem;
type Error = ValueErrorKind;
}
impl<'i> RuleBodyItemParser<'i, RuleBodyItem, ValueErrorKind> for DeclParser {
fn parse_declarations(&self) -> bool {
true
}
fn parse_qualified(&self) -> bool {
false
}
}
pub struct RuleParser {
session: Session,
}
#[allow(dead_code)] #[derive(Debug)]
pub enum ParseErrorKind<'i> {
Selector(selectors::parser::SelectorParseErrorKind<'i>),
}
impl<'i> From<selectors::parser::SelectorParseErrorKind<'i>> for ParseErrorKind<'i> {
fn from(e: selectors::parser::SelectorParseErrorKind<'_>) -> ParseErrorKind<'_> {
ParseErrorKind::Selector(e)
}
}
pub struct QualifiedRule {
selectors: SelectorList<Selector>,
declarations: Vec<Declaration>,
}
pub enum AtRulePrelude {
Import(String),
}
pub enum AtRule {
Import(String),
}
pub enum Rule {
AtRule(AtRule),
QualifiedRule(QualifiedRule),
}
impl<'i> selectors::Parser<'i> for RuleParser {
type Impl = Selector;
type Error = ParseErrorKind<'i>;
fn default_namespace(&self) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> {
Some(ns!(svg))
}
fn namespace_for_prefix(
&self,
_prefix: &<Self::Impl as SelectorImpl>::NamespacePrefix,
) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> {
None
}
fn parse_non_ts_pseudo_class(
&self,
location: SourceLocation,
name: CowRcStr<'i>,
) -> Result<NonTSPseudoClass, cssparser::ParseError<'i, Self::Error>> {
match &*name {
"link" => Ok(NonTSPseudoClass::Link),
"visited" => Ok(NonTSPseudoClass::Visited),
_ => Err(location.new_custom_error(
selectors::parser::SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
)),
}
}
fn parse_non_ts_functional_pseudo_class(
&self,
name: CowRcStr<'i>,
arguments: &mut Parser<'i, '_>,
_after_part: bool,
) -> Result<NonTSPseudoClass, cssparser::ParseError<'i, Self::Error>> {
match &*name {
"lang" => {
let tags = arguments.parse_comma_separated(|arg| {
let language_tag = arg.expect_ident_or_string()?.clone();
LanguageTag::from_str(&language_tag).map_err(|_| {
arg.new_custom_error(selectors::parser::SelectorParseErrorKind::UnsupportedPseudoClassOrElement(language_tag))
})
})?;
arguments.expect_exhausted()?;
Ok(NonTSPseudoClass::Lang(tags))
}
_ => Err(arguments.new_custom_error(
selectors::parser::SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
)),
}
}
}
impl<'i> QualifiedRuleParser<'i> for RuleParser {
type Prelude = SelectorList<Selector>;
type QualifiedRule = Rule;
type Error = ValueErrorKind;
fn parse_prelude<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, cssparser::ParseError<'i, Self::Error>> {
SelectorList::parse(self, input, ParseRelative::No).map_err(|e| ParseError {
kind: cssparser::ParseErrorKind::Custom(ValueErrorKind::parse_error(
"Could not parse selector",
)),
location: e.location,
})
}
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
_start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<Self::QualifiedRule, cssparser::ParseError<'i, Self::Error>> {
let declarations = RuleBodyParser::<_, _, Self::Error>::new(input, &mut DeclParser)
.filter_map(|r| match r {
Ok(RuleBodyItem::Decl(decl)) => Some(decl),
Ok(RuleBodyItem::Rule(_)) => None,
Err(e) => {
rsvg_log!(self.session, "Invalid declaration; ignoring: {:?}", e);
None
}
})
.collect();
Ok(Rule::QualifiedRule(QualifiedRule {
selectors: prelude,
declarations,
}))
}
}
impl<'i> AtRuleParser<'i> for RuleParser {
type Prelude = AtRulePrelude;
type AtRule = Rule;
type Error = ValueErrorKind;
#[allow(clippy::type_complexity)]
fn parse_prelude<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, cssparser::ParseError<'i, Self::Error>> {
match_ignore_ascii_case! {
&name,
"import" => {
let url = input.expect_url_or_string()?.as_ref().to_owned();
Ok(AtRulePrelude::Import(url))
},
_ => Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name))),
}
}
fn rule_without_block(
&mut self,
prelude: Self::Prelude,
_start: &ParserState,
) -> Result<Self::AtRule, ()> {
let AtRulePrelude::Import(url) = prelude;
Ok(Rule::AtRule(AtRule::Import(url)))
}
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum NonTSPseudoClass {
Link,
Visited,
Lang(Vec<LanguageTag>),
}
impl ToCss for NonTSPseudoClass {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
match self {
NonTSPseudoClass::Link => write!(dest, "link"),
NonTSPseudoClass::Visited => write!(dest, "visited"),
NonTSPseudoClass::Lang(lang) => write!(
dest,
"lang(\"{}\")",
lang.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("\",\"")
),
}
}
}
impl selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
type Impl = Selector;
fn is_active_or_hover(&self) -> bool {
false
}
fn is_user_action_state(&self) -> bool {
false
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PseudoElement;
impl ToCss for PseudoElement {
fn to_css<W>(&self, _dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
Ok(())
}
}
impl selectors::parser::PseudoElement for PseudoElement {
type Impl = Selector;
}
#[derive(Debug, Clone)]
pub struct Selector;
#[derive(Clone, PartialEq, Eq)]
pub struct AttributeValue(String);
impl From<&str> for AttributeValue {
fn from(s: &str) -> AttributeValue {
AttributeValue(s.to_owned())
}
}
impl ToCss for AttributeValue {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
use std::fmt::Write;
write!(cssparser::CssStringWriter::new(dest), "{}", &self.0)
}
}
impl AsRef<str> for AttributeValue {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Identifier(markup5ever::LocalName);
impl From<&str> for Identifier {
fn from(s: &str) -> Identifier {
Identifier(markup5ever::LocalName::from(s))
}
}
impl ToCss for Identifier {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
cssparser::serialize_identifier(&self.0, dest)
}
}
impl PrecomputedHash for Identifier {
fn precomputed_hash(&self) -> u32 {
self.0.precomputed_hash()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct LocalName(markup5ever::LocalName);
impl From<&str> for LocalName {
fn from(s: &str) -> LocalName {
LocalName(markup5ever::LocalName::from(s))
}
}
impl ToCss for LocalName {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
cssparser::serialize_identifier(&self.0, dest)
}
}
impl PrecomputedHash for LocalName {
fn precomputed_hash(&self) -> u32 {
self.0.precomputed_hash()
}
}
#[derive(Clone, Default, PartialEq, Eq)]
pub struct NamespacePrefix(markup5ever::Prefix);
impl From<&str> for NamespacePrefix {
fn from(s: &str) -> NamespacePrefix {
NamespacePrefix(markup5ever::Prefix::from(s))
}
}
impl ToCss for NamespacePrefix {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
cssparser::serialize_identifier(&self.0, dest)
}
}
impl SelectorImpl for Selector {
type ExtraMatchingData<'a> = ();
type AttrValue = AttributeValue;
type Identifier = Identifier;
type LocalName = LocalName;
type NamespaceUrl = Namespace;
type NamespacePrefix = NamespacePrefix;
type BorrowedNamespaceUrl = Namespace;
type BorrowedLocalName = LocalName;
type NonTSPseudoClass = NonTSPseudoClass;
type PseudoElement = PseudoElement;
}
#[derive(Clone, PartialEq)]
pub struct RsvgElement(Node);
impl From<Node> for RsvgElement {
fn from(n: Node) -> RsvgElement {
RsvgElement(n)
}
}
impl fmt::Debug for RsvgElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.borrow())
}
}
impl selectors::Element for RsvgElement {
type Impl = Selector;
fn opaque(&self) -> OpaqueElement {
let element: &Element = &self.0.borrow_element();
OpaqueElement::new::<Element>(element)
}
fn parent_element(&self) -> Option<Self> {
self.0.parent().map(|n| n.into())
}
fn parent_node_is_shadow_root(&self) -> bool {
false
}
fn containing_shadow_host(&self) -> Option<Self> {
None
}
fn is_pseudo_element(&self) -> bool {
false
}
fn prev_sibling_element(&self) -> Option<Self> {
let mut sibling = self.0.previous_sibling();
while let Some(ref sib) = sibling {
if sib.is_element() {
return sibling.map(|n| n.into());
}
sibling = sib.previous_sibling();
}
None
}
fn next_sibling_element(&self) -> Option<Self> {
let mut sibling = self.0.next_sibling();
while let Some(ref sib) = sibling {
if sib.is_element() {
return sibling.map(|n| n.into());
}
sibling = sib.next_sibling();
}
None
}
fn is_html_element_in_html_document(&self) -> bool {
false
}
fn has_local_name(&self, local_name: &LocalName) -> bool {
self.0.borrow_element().element_name().local == local_name.0
}
fn has_namespace(&self, ns: &Namespace) -> bool {
self.0.borrow_element().element_name().ns == *ns
}
fn is_same_type(&self, other: &Self) -> bool {
self.0.borrow_element().element_name() == other.0.borrow_element().element_name()
}
fn attr_matches(
&self,
ns: &NamespaceConstraint<&Namespace>,
local_name: &LocalName,
operation: &AttrSelectorOperation<&AttributeValue>,
) -> bool {
self.0
.borrow_element()
.get_attributes()
.iter()
.find(|(attr, _)| {
match *ns {
NamespaceConstraint::Any => local_name.0 == attr.local,
NamespaceConstraint::Specific(ns) => {
QualName::new(None, ns.clone(), local_name.0.clone()) == *attr
}
}
})
.map(|(_, value)| {
operation.eval_str(value)
})
.unwrap_or(false)
}
fn match_non_ts_pseudo_class(
&self,
pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
_context: &mut MatchingContext<'_, Self::Impl>,
) -> bool
where {
match pc {
NonTSPseudoClass::Link => self.is_link(),
NonTSPseudoClass::Visited => false,
NonTSPseudoClass::Lang(css_lang) => self
.0
.borrow_element()
.get_computed_values()
.xml_lang()
.0
.as_ref()
.is_some_and(|e_lang| {
css_lang
.iter()
.any(|l| l.is_language_range() && l.matches(e_lang))
}),
}
}
fn match_pseudo_element(
&self,
_pe: &<Self::Impl as SelectorImpl>::PseudoElement,
_context: &mut MatchingContext<'_, Self::Impl>,
) -> bool {
false
}
fn is_link(&self) -> bool {
self.0.is_element()
&& match *self.0.borrow_element_data() {
crate::element::ElementData::Link(ref link) => link.link.is_some(),
_ => false,
}
}
fn is_html_slot_element(&self) -> bool {
false
}
fn has_id(&self, id: &Identifier, case_sensitivity: CaseSensitivity) -> bool {
self.0
.borrow_element()
.get_id()
.map(|self_id| case_sensitivity.eq(self_id.as_bytes(), id.0.as_bytes()))
.unwrap_or(false)
}
fn has_class(&self, name: &Identifier, case_sensitivity: CaseSensitivity) -> bool {
self.0
.borrow_element()
.get_class()
.map(|classes| {
classes
.split_whitespace()
.any(|class| case_sensitivity.eq(class.as_bytes(), name.0.as_bytes()))
})
.unwrap_or(false)
}
fn has_custom_state(&self, _name: &<Self::Impl as SelectorImpl>::Identifier) -> bool {
false
}
fn imported_part(&self, _name: &Identifier) -> Option<Identifier> {
None
}
fn is_part(&self, _name: &Identifier) -> bool {
false
}
fn is_empty(&self) -> bool {
self.0
.children()
.all(|child| child.is_chars() && child.borrow_chars().is_empty())
}
fn is_root(&self) -> bool {
self.0.parent().is_none()
}
fn add_element_unique_hashes(&self, _filter: &mut BloomFilter) -> bool {
false
}
fn first_element_child(&self) -> Option<Self> {
self.0
.children()
.find(|child| child.is_element())
.map(|n| n.into())
}
fn apply_selector_flags(&self, _: ElementSelectorFlags) {
todo!()
}
}
#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub enum Origin {
UserAgent,
User,
Author,
}
pub struct Stylesheet {
origin: Origin,
qualified_rules: Vec<QualifiedRule>,
}
struct Match<'a> {
specificity: u32,
origin: Origin,
declaration: &'a Declaration,
}
impl<'a> Ord for Match<'a> {
fn cmp(&self, other: &Self) -> Ordering {
match self.origin.cmp(&other.origin) {
Ordering::Equal => self.specificity.cmp(&other.specificity),
o => o,
}
}
}
impl<'a> PartialOrd for Match<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> PartialEq for Match<'a> {
fn eq(&self, other: &Self) -> bool {
self.origin == other.origin && self.specificity == other.specificity
}
}
impl<'a> Eq for Match<'a> {}
impl Stylesheet {
fn empty(origin: Origin) -> Stylesheet {
Stylesheet {
origin,
qualified_rules: Vec::new(),
}
}
pub fn from_data(
buf: &str,
url_resolver: &UrlResolver,
origin: Origin,
session: Session,
) -> Result<Self, LoadingError> {
let mut stylesheet = Stylesheet::empty(origin);
stylesheet.add_rules_from_string(buf, url_resolver, session)?;
Ok(stylesheet)
}
pub fn from_href(
aurl: &AllowedUrl,
origin: Origin,
session: Session,
) -> Result<Self, LoadingError> {
let mut stylesheet = Stylesheet::empty(origin);
stylesheet.load(aurl, session)?;
Ok(stylesheet)
}
fn add_rules_from_string(
&mut self,
buf: &str,
url_resolver: &UrlResolver,
session: Session,
) -> Result<(), LoadingError> {
let mut input = ParserInput::new(buf);
let mut parser = Parser::new(&mut input);
let mut rule_parser = RuleParser {
session: session.clone(),
};
StyleSheetParser::new(&mut parser, &mut rule_parser)
.filter_map(|r| match r {
Ok(rule) => Some(rule),
Err(e) => {
rsvg_log!(session, "Invalid rule; ignoring: {:?}", e);
None
}
})
.for_each(|rule| match rule {
Rule::AtRule(AtRule::Import(url)) => match url_resolver.resolve_href(&url) {
Ok(aurl) => {
let _ = self.load(&aurl, session.clone());
}
Err(e) => {
rsvg_log!(session, "Not loading stylesheet from \"{}\": {}", url, e);
}
},
Rule::QualifiedRule(qr) => self.qualified_rules.push(qr),
});
Ok(())
}
fn load(&mut self, aurl: &AllowedUrl, session: Session) -> Result<(), LoadingError> {
io::acquire_data(aurl, None)
.map_err(LoadingError::from)
.and_then(|data| {
String::from_utf8(data.data).map_err(|_| {
rsvg_log!(
session,
"\"{}\" does not contain valid UTF-8 CSS data; ignoring",
aurl
);
LoadingError::BadCss
})
})
.and_then(|utf8| {
let url = (**aurl).clone();
self.add_rules_from_string(&utf8, &UrlResolver::new(Some(url)), session)
})
}
fn get_matches<'a>(
&'a self,
node: &Node,
match_ctx: &mut MatchingContext<'_, Selector>,
acc: &mut Vec<Match<'a>>,
) {
for rule in &self.qualified_rules {
for selector in rule.selectors.slice() {
let matches = selectors::matching::matches_selector(
selector,
0,
None,
&RsvgElement(node.clone()),
match_ctx,
);
if matches {
for decl in rule.declarations.iter() {
acc.push(Match {
declaration: decl,
specificity: selector.specificity(),
origin: self.origin,
});
}
}
}
}
}
}
pub fn cascade(
root: &mut Node,
ua_stylesheets: &[Stylesheet],
author_stylesheets: &[Stylesheet],
user_stylesheets: &[Stylesheet],
session: &Session,
) {
for mut node in root.descendants().filter(|n| n.is_element()) {
let mut matches = Vec::new();
let parent = node.parent().clone();
node.borrow_element_mut().inherit_xml_lang(parent);
let mut caches = SelectorCaches::default();
let mut match_ctx = MatchingContext::new(
MatchingMode::Normal,
None,
&mut caches,
QuirksMode::NoQuirks,
NeedsSelectorFlags::No,
MatchingForInvalidation::No,
);
for s in ua_stylesheets
.iter()
.chain(author_stylesheets)
.chain(user_stylesheets)
{
s.get_matches(&node, &mut match_ctx, &mut matches);
}
matches.as_mut_slice().sort();
let mut element = node.borrow_element_mut();
for m in matches {
element.apply_style_declaration(m.declaration, m.origin);
}
element.set_style_attribute(session);
}
let values = ComputedValues::default();
root.cascade(&values);
}
#[cfg(test)]
mod tests {
use super::*;
use selectors::Element;
use crate::document::Document;
use crate::is_element_of_type;
#[test]
fn xml_lang() {
let document = Document::load_from_bytes(
br#"<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xml:lang="zh">
<text id="a" x="10" y="10" width="30" height="30"></text>
<text id="b" x="10" y="20" width="30" height="30" xml:lang="en"></text>
</svg>
"#,
);
let a = document.lookup_internal_node("a").unwrap();
assert_eq!(
a.borrow_element()
.get_computed_values()
.xml_lang()
.0
.unwrap()
.as_str(),
"zh"
);
let b = document.lookup_internal_node("b").unwrap();
assert_eq!(
b.borrow_element()
.get_computed_values()
.xml_lang()
.0
.unwrap()
.as_str(),
"en"
);
}
#[test]
fn impl_element() {
let document = Document::load_from_bytes(
br#"<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="a">
<rect id="b" x="10" y="10" width="30" height="30"/>
<circle id="c" cx="10" cy="10" r="10"/>
<rect id="d" class="foo bar"/>
</svg>
"#,
);
let a = document.lookup_internal_node("a").unwrap();
let b = document.lookup_internal_node("b").unwrap();
let c = document.lookup_internal_node("c").unwrap();
let d = document.lookup_internal_node("d").unwrap();
assert!(is_element_of_type!(a, Svg));
assert!(is_element_of_type!(b, Rect));
assert!(is_element_of_type!(c, Circle));
assert!(is_element_of_type!(d, Rect));
let a = RsvgElement(a);
let b = RsvgElement(b);
let c = RsvgElement(c);
let d = RsvgElement(d);
assert_eq!(a.parent_element(), None);
assert_eq!(b.parent_element(), Some(a.clone()));
assert_eq!(c.parent_element(), Some(a.clone()));
assert_eq!(d.parent_element(), Some(a.clone()));
assert_eq!(b.next_sibling_element(), Some(c.clone()));
assert_eq!(c.next_sibling_element(), Some(d.clone()));
assert_eq!(d.next_sibling_element(), None);
assert_eq!(b.prev_sibling_element(), None);
assert_eq!(c.prev_sibling_element(), Some(b.clone()));
assert_eq!(d.prev_sibling_element(), Some(c.clone()));
assert!(a.has_local_name(&LocalName::from("svg")));
assert!(a.has_namespace(&ns!(svg)));
assert!(!a.is_same_type(&b));
assert!(b.is_same_type(&d));
assert!(a.has_id(
&Identifier::from("a"),
CaseSensitivity::AsciiCaseInsensitive
));
assert!(!b.has_id(
&Identifier::from("foo"),
CaseSensitivity::AsciiCaseInsensitive
));
assert!(d.has_class(
&Identifier::from("foo"),
CaseSensitivity::AsciiCaseInsensitive
));
assert!(d.has_class(
&Identifier::from("bar"),
CaseSensitivity::AsciiCaseInsensitive
));
assert!(!a.has_class(
&Identifier::from("foo"),
CaseSensitivity::AsciiCaseInsensitive
));
assert!(d.is_empty());
assert!(!a.is_empty());
}
}