use crate::color::{AbsoluteColor, parsing::parse_color_keyword};
use crate::properties::PropertyDeclarationBlock;
use crate::shared_lock::Locked;
use crate::str::str_join;
use crate::str::{read_exponent, read_fraction, HTML_SPACE_CHARACTERS};
use crate::str::{read_numbers, split_commas, split_html_space_chars};
use crate::values::specified::Length;
use crate::values::specified::color::Color;
use crate::values::AtomString;
use crate::{Atom, LocalName, Namespace, Prefix};
use app_units::Au;
use euclid::num::Zero;
use num_traits::ToPrimitive;
use selectors::attr::AttrSelectorOperation;
use servo_arc::Arc;
use std::str::FromStr;
const UNSIGNED_LONG_MAX: u32 = 2147483647;
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub enum LengthOrPercentageOrAuto {
Auto,
Percentage(f32),
Length(Au),
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub enum AttrValue {
String(String),
TokenList(String, Vec<Atom>),
UInt(String, u32),
Int(String, i32),
Double(String, f64),
Atom(Atom),
Length(String, Option<Length>),
Color(String, Option<AbsoluteColor>),
Dimension(String, LengthOrPercentageOrAuto),
ResolvedUrl(
String,
#[ignore_malloc_size_of = "Arc"] Option<Arc<url::Url>>
),
Declaration(
String,
#[ignore_malloc_size_of = "Arc"] Arc<Locked<PropertyDeclarationBlock>>,
),
}
fn do_parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i64, ()> {
let mut input = input
.skip_while(|c| HTML_SPACE_CHARACTERS.iter().any(|s| s == c))
.peekable();
let sign = match input.peek() {
None => return Err(()),
Some(&'-') => {
input.next();
-1
},
Some(&'+') => {
input.next();
1
},
Some(_) => 1,
};
let (value, _) = read_numbers(input);
value.and_then(|value| value.checked_mul(sign)).ok_or(())
}
pub fn parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i32, ()> {
do_parse_integer(input).and_then(|result| result.to_i32().ok_or(()))
}
pub fn parse_unsigned_integer<T: Iterator<Item = char>>(input: T) -> Result<u32, ()> {
do_parse_integer(input).and_then(|result| result.to_u32().ok_or(()))
}
pub fn parse_double(string: &str) -> Result<f64, ()> {
let trimmed = string.trim_matches(HTML_SPACE_CHARACTERS);
let mut input = trimmed.chars().peekable();
let (value, divisor, chars_skipped) = match input.peek() {
None => return Err(()),
Some(&'-') => {
input.next();
(-1f64, -1f64, 1)
},
Some(&'+') => {
input.next();
(1f64, 1f64, 1)
},
_ => (1f64, 1f64, 0),
};
let (value, value_digits) = if let Some(&'.') = input.peek() {
(0f64, 0)
} else {
let (read_val, read_digits) = read_numbers(input);
(
value * read_val.and_then(|result| result.to_f64()).unwrap_or(1f64),
read_digits,
)
};
let input = trimmed
.chars()
.skip(value_digits + chars_skipped)
.peekable();
let (mut value, fraction_digits) = read_fraction(input, divisor, value);
let input = trimmed
.chars()
.skip(value_digits + chars_skipped + fraction_digits)
.peekable();
if let Some(exp) = read_exponent(input) {
value *= 10f64.powi(exp)
};
Ok(value)
}
impl AttrValue {
pub fn from_serialized_tokenlist(tokens: String) -> AttrValue {
let atoms =
split_html_space_chars(&tokens)
.map(Atom::from)
.fold(vec![], |mut acc, atom| {
if !acc.contains(&atom) {
acc.push(atom)
}
acc
});
AttrValue::TokenList(tokens, atoms)
}
pub fn from_comma_separated_tokenlist(tokens: String) -> AttrValue {
let atoms = split_commas(&tokens)
.map(Atom::from)
.fold(vec![], |mut acc, atom| {
if !acc.contains(&atom) {
acc.push(atom)
}
acc
});
AttrValue::TokenList(tokens, atoms)
}
pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue {
let tokens = String::from(str_join(&atoms, "\x20"));
AttrValue::TokenList(tokens, atoms)
}
pub fn from_u32(string: String, default: u32) -> AttrValue {
let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
let result = if result > UNSIGNED_LONG_MAX {
default
} else {
result
};
AttrValue::UInt(string, result)
}
pub fn from_i32(string: String, default: i32) -> AttrValue {
let result = parse_integer(string.chars()).unwrap_or(default);
AttrValue::Int(string, result)
}
pub fn from_double(string: String, default: f64) -> AttrValue {
let result = parse_double(&string).unwrap_or(default);
if result.is_normal() {
AttrValue::Double(string, result)
} else {
AttrValue::Double(string, default)
}
}
pub fn from_limited_i32(string: String, default: i32) -> AttrValue {
let result = parse_integer(string.chars()).unwrap_or(default);
if result < 0 {
AttrValue::Int(string, default)
} else {
AttrValue::Int(string, result)
}
}
pub fn from_limited_u32(string: String, default: u32) -> AttrValue {
let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
let result = if result == 0 || result > UNSIGNED_LONG_MAX {
default
} else {
result
};
AttrValue::UInt(string, result)
}
pub fn from_atomic(string: String) -> AttrValue {
let value = Atom::from(string);
AttrValue::Atom(value)
}
pub fn from_resolved_url(base: &Arc<::url::Url>, url: String) -> AttrValue {
let joined = base.join(&url).ok().map(Arc::new);
AttrValue::ResolvedUrl(url, joined)
}
pub fn from_legacy_color(string: String) -> AttrValue {
let parsed = parse_legacy_color(&string).ok();
AttrValue::Color(string, parsed)
}
pub fn from_dimension(string: String) -> AttrValue {
let parsed = parse_length(&string);
AttrValue::Dimension(string, parsed)
}
pub fn from_nonzero_dimension(string: String) -> AttrValue {
let parsed = parse_nonzero_length(&string);
AttrValue::Dimension(string, parsed)
}
pub fn as_tokens(&self) -> &[Atom] {
match *self {
AttrValue::TokenList(_, ref tokens) => tokens,
_ => panic!("Tokens not found"),
}
}
pub fn as_atom(&self) -> &Atom {
match *self {
AttrValue::Atom(ref value) => value,
_ => panic!("Atom not found"),
}
}
pub fn as_color(&self) -> Option<&AbsoluteColor> {
match *self {
AttrValue::Color(_, ref color) => color.as_ref(),
_ => panic!("Color not found"),
}
}
pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto {
match *self {
AttrValue::Dimension(_, ref l) => l,
_ => panic!("Dimension not found"),
}
}
pub fn as_resolved_url(&self) -> Option<&Arc<::url::Url>> {
match *self {
AttrValue::ResolvedUrl(_, ref url) => url.as_ref(),
_ => panic!("Url not found"),
}
}
pub fn as_uint(&self) -> u32 {
if let AttrValue::UInt(_, value) = *self {
value
} else {
panic!("Uint not found");
}
}
pub fn as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto {
if let AttrValue::UInt(_, value) = *self {
LengthOrPercentageOrAuto::Length(Au::from_px(value as i32))
} else {
panic!("Uint not found");
}
}
pub fn eval_selector(&self, selector: &AttrSelectorOperation<&AtomString>) -> bool {
selector.eval_str(self)
}
}
impl ::std::ops::Deref for AttrValue {
type Target = str;
fn deref(&self) -> &str {
match *self {
AttrValue::String(ref value) |
AttrValue::TokenList(ref value, _) |
AttrValue::UInt(ref value, _) |
AttrValue::Double(ref value, _) |
AttrValue::Length(ref value, _) |
AttrValue::Color(ref value, _) |
AttrValue::Int(ref value, _) |
AttrValue::ResolvedUrl(ref value, _) |
AttrValue::Declaration(ref value, _) |
AttrValue::Dimension(ref value, _) => &value,
AttrValue::Atom(ref value) => &value,
}
}
}
impl PartialEq<Atom> for AttrValue {
fn eq(&self, other: &Atom) -> bool {
match *self {
AttrValue::Atom(ref value) => value == other,
_ => other == &**self,
}
}
}
pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto {
match parse_length(value) {
LengthOrPercentageOrAuto::Length(x) if x == Au::zero() => LengthOrPercentageOrAuto::Auto,
LengthOrPercentageOrAuto::Percentage(x) if x == 0. => LengthOrPercentageOrAuto::Auto,
x => x,
}
}
pub fn parse_legacy_color(mut input: &str) -> Result<AbsoluteColor, ()> {
if input.is_empty() {
return Err(());
}
input = input.trim_matches(HTML_SPACE_CHARACTERS);
if input.eq_ignore_ascii_case("transparent") {
return Err(());
}
if let Ok(Color::Absolute(ref absolute)) = parse_color_keyword(input) {
return Ok(absolute.color);
}
if input.len() == 4 {
if let (b'#', Ok(r), Ok(g), Ok(b)) = (
input.as_bytes()[0],
hex(input.as_bytes()[1] as char),
hex(input.as_bytes()[2] as char),
hex(input.as_bytes()[3] as char),
) {
return Ok(AbsoluteColor::srgb_legacy(r * 17, g * 17, b * 17, 1.0));
}
}
let mut new_input = String::new();
for ch in input.chars() {
if ch as u32 > 0xffff {
new_input.push_str("00")
} else {
new_input.push(ch)
}
}
let mut input = &*new_input;
for (char_count, (index, _)) in input.char_indices().enumerate() {
if char_count == 128 {
input = &input[..index];
break;
}
}
if input.as_bytes()[0] == b'#' {
input = &input[1..]
}
let mut new_input = Vec::new();
for ch in input.chars() {
if hex(ch).is_ok() {
new_input.push(ch as u8)
} else {
new_input.push(b'0')
}
}
let mut input = new_input;
while input.is_empty() || (input.len() % 3) != 0 {
input.push(b'0')
}
let mut length = input.len() / 3;
let (mut red, mut green, mut blue) = (
&input[..length],
&input[length..length * 2],
&input[length * 2..],
);
if length > 8 {
red = &red[length - 8..];
green = &green[length - 8..];
blue = &blue[length - 8..];
length = 8
}
while length > 2 && red[0] == b'0' && green[0] == b'0' && blue[0] == b'0' {
red = &red[1..];
green = &green[1..];
blue = &blue[1..];
length -= 1
}
return Ok(AbsoluteColor::srgb_legacy(
hex_string(red).unwrap(),
hex_string(green).unwrap(),
hex_string(blue).unwrap(),
1.0,
));
fn hex(ch: char) -> Result<u8, ()> {
match ch {
'0'..='9' => Ok((ch as u8) - b'0'),
'a'..='f' => Ok((ch as u8) - b'a' + 10),
'A'..='F' => Ok((ch as u8) - b'A' + 10),
_ => Err(()),
}
}
fn hex_string(string: &[u8]) -> Result<u8, ()> {
match string.len() {
0 => Err(()),
1 => hex(string[0] as char),
_ => {
let upper = hex(string[0] as char)?;
let lower = hex(string[1] as char)?;
Ok((upper << 4) | lower)
},
}
}
}
pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto {
value = value.trim_start_matches(HTML_SPACE_CHARACTERS);
match value.chars().nth(0) {
Some('0'..='9') => {},
_ => return LengthOrPercentageOrAuto::Auto,
}
let mut end_index = value.len();
let (mut found_full_stop, mut found_percent) = (false, false);
for (i, ch) in value.chars().enumerate() {
match ch {
'0'..='9' => continue,
'%' => {
found_percent = true;
end_index = i;
break;
},
'.' if !found_full_stop => {
found_full_stop = true;
continue;
},
_ => {
end_index = i;
break;
},
}
}
value = &value[..end_index];
if found_percent {
let result: Result<f32, _> = FromStr::from_str(value);
match result {
Ok(number) => return LengthOrPercentageOrAuto::Percentage((number as f32) / 100.0),
Err(_) => return LengthOrPercentageOrAuto::Auto,
}
}
match FromStr::from_str(value) {
Ok(number) => LengthOrPercentageOrAuto::Length(Au::from_f64_px(number)),
Err(_) => LengthOrPercentageOrAuto::Auto,
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub struct AttrIdentifier {
pub local_name: LocalName,
pub name: LocalName,
pub namespace: Namespace,
pub prefix: Option<Prefix>,
}