1use super::shadow_parts::ShadowParts;
10use crate::color::{parsing::parse_color_keyword, AbsoluteColor};
11use crate::derives::*;
12use crate::properties::PropertyDeclarationBlock;
13use crate::shared_lock::Locked;
14use crate::str::str_join;
15use crate::str::{read_exponent, read_fraction, HTML_SPACE_CHARACTERS};
16use crate::str::{read_numbers, split_commas, split_html_space_chars};
17use crate::values::specified::color::Color;
18use crate::values::specified::LengthPercentage;
19use crate::values::AtomString;
20use crate::{Atom, LocalName, Namespace, Prefix};
21use app_units::Au;
22use euclid::num::Zero;
23use num_traits::ToPrimitive;
24use selectors::attr::AttrSelectorOperation;
25use servo_arc::Arc;
26use std::str::FromStr;
27
28const UNSIGNED_LONG_MAX: u32 = 2147483647;
30
31#[derive(Clone, Copy, Debug, PartialEq)]
32#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
33pub enum LengthOrPercentageOrAuto {
34 Auto,
35 Percentage(f32),
36 Length(Au),
37}
38
39#[derive(Clone, Debug)]
40#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
41pub enum AttrValue {
42 String(String),
43 TokenList(String, Vec<Atom>),
44 UInt(String, u32),
45 Int(String, i32),
46 Double(String, f64),
47 Atom(Atom),
48 LengthPercentage(String, Option<LengthPercentage>),
49 Color(String, Option<AbsoluteColor>),
50 Dimension(String, LengthOrPercentageOrAuto),
51
52 ResolvedUrl(
57 String,
58 #[ignore_malloc_size_of = "Arc"] Option<Arc<url::Url>>,
59 ),
60
61 Declaration(
74 String,
75 #[ignore_malloc_size_of = "Arc"] Arc<Locked<PropertyDeclarationBlock>>,
76 ),
77
78 ShadowParts(String, ShadowParts),
80}
81
82fn do_parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i64, ()> {
86 let mut input = input
87 .skip_while(|c| HTML_SPACE_CHARACTERS.iter().any(|s| s == c))
88 .peekable();
89
90 let sign = match input.peek() {
91 None => return Err(()),
92 Some(&'-') => {
93 input.next();
94 -1
95 },
96 Some(&'+') => {
97 input.next();
98 1
99 },
100 Some(_) => 1,
101 };
102
103 let (value, _) = read_numbers(input);
104
105 value.and_then(|value| value.checked_mul(sign)).ok_or(())
106}
107
108pub fn parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i32, ()> {
111 do_parse_integer(input).and_then(|result| result.to_i32().ok_or(()))
112}
113
114pub fn parse_unsigned_integer<T: Iterator<Item = char>>(input: T) -> Result<u32, ()> {
117 do_parse_integer(input).and_then(|result| result.to_u32().ok_or(()))
118}
119
120pub fn parse_double(string: &str) -> Result<f64, ()> {
123 let trimmed = string.trim_matches(HTML_SPACE_CHARACTERS);
124 let mut input = trimmed.chars().peekable();
125
126 let (value, divisor, chars_skipped) = match input.peek() {
127 None => return Err(()),
128 Some(&'-') => {
129 input.next();
130 (-1f64, -1f64, 1)
131 },
132 Some(&'+') => {
133 input.next();
134 (1f64, 1f64, 1)
135 },
136 _ => (1f64, 1f64, 0),
137 };
138
139 let (value, value_digits) = if let Some(&'.') = input.peek() {
140 (0f64, 0)
141 } else {
142 let (read_val, read_digits) = read_numbers(input);
143 (
144 value * read_val.and_then(|result| result.to_f64()).unwrap_or(1f64),
145 read_digits,
146 )
147 };
148
149 let input = trimmed
150 .chars()
151 .skip(value_digits + chars_skipped)
152 .peekable();
153
154 let (mut value, fraction_digits) = read_fraction(input, divisor, value);
155
156 let input = trimmed
157 .chars()
158 .skip(value_digits + chars_skipped + fraction_digits)
159 .peekable();
160
161 if let Some(exp) = read_exponent(input) {
162 value *= 10f64.powi(exp)
163 };
164
165 Ok(value)
166}
167
168impl AttrValue {
169 pub fn from_serialized_tokenlist(tokens: String) -> AttrValue {
170 let atoms =
171 split_html_space_chars(&tokens)
172 .map(Atom::from)
173 .fold(vec![], |mut acc, atom| {
174 if !acc.contains(&atom) {
175 acc.push(atom)
176 }
177 acc
178 });
179 AttrValue::TokenList(tokens, atoms)
180 }
181
182 pub fn from_comma_separated_tokenlist(tokens: String) -> AttrValue {
183 let atoms = split_commas(&tokens)
184 .map(Atom::from)
185 .fold(vec![], |mut acc, atom| {
186 if !acc.contains(&atom) {
187 acc.push(atom)
188 }
189 acc
190 });
191 AttrValue::TokenList(tokens, atoms)
192 }
193
194 pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue {
195 let tokens = String::from(str_join(&atoms, "\x20"));
197 AttrValue::TokenList(tokens, atoms)
198 }
199
200 pub fn from_u32(string: String, default: u32) -> AttrValue {
202 let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
203 let result = if result > UNSIGNED_LONG_MAX {
204 default
205 } else {
206 result
207 };
208 AttrValue::UInt(string, result)
209 }
210
211 pub fn from_i32(string: String, default: i32) -> AttrValue {
212 let result = parse_integer(string.chars()).unwrap_or(default);
213 AttrValue::Int(string, result)
214 }
215
216 pub fn from_double(string: String, default: f64) -> AttrValue {
218 let result = parse_double(&string).unwrap_or(default);
219
220 if result.is_normal() {
221 AttrValue::Double(string, result)
222 } else {
223 AttrValue::Double(string, default)
224 }
225 }
226
227 pub fn from_limited_i32(string: String, default: i32) -> AttrValue {
229 let result = parse_integer(string.chars()).unwrap_or(default);
230
231 if result < 0 {
232 AttrValue::Int(string, default)
233 } else {
234 AttrValue::Int(string, result)
235 }
236 }
237
238 pub fn from_limited_u32(string: String, default: u32) -> AttrValue {
240 let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
241 let result = if result == 0 || result > UNSIGNED_LONG_MAX {
242 default
243 } else {
244 result
245 };
246 AttrValue::UInt(string, result)
247 }
248
249 pub fn from_atomic(string: String) -> AttrValue {
250 let value = Atom::from(string);
251 AttrValue::Atom(value)
252 }
253
254 pub fn from_resolved_url(base: &Arc<::url::Url>, url: String) -> AttrValue {
255 let joined = base.join(&url).ok().map(Arc::new);
256 AttrValue::ResolvedUrl(url, joined)
257 }
258
259 pub fn from_legacy_color(string: String) -> AttrValue {
260 let parsed = parse_legacy_color(&string).ok();
261 AttrValue::Color(string, parsed)
262 }
263
264 pub fn from_dimension(string: String) -> AttrValue {
265 let parsed = parse_length(&string);
266 AttrValue::Dimension(string, parsed)
267 }
268
269 pub fn from_nonzero_dimension(string: String) -> AttrValue {
270 let parsed = parse_nonzero_length(&string);
271 AttrValue::Dimension(string, parsed)
272 }
273
274 pub fn from_shadow_parts(string: String) -> AttrValue {
275 let shadow_parts = ShadowParts::parse(&string);
276 AttrValue::ShadowParts(string, shadow_parts)
277 }
278
279 pub fn as_tokens(&self) -> &[Atom] {
285 match *self {
286 AttrValue::TokenList(_, ref tokens) => tokens,
287 _ => panic!("Tokens not found"),
288 }
289 }
290
291 pub fn as_atom(&self) -> &Atom {
297 match *self {
298 AttrValue::Atom(ref value) => value,
299 _ => panic!("Atom not found"),
300 }
301 }
302
303 pub fn as_length_percentage(&self) -> Option<&LengthPercentage> {
309 match *self {
310 AttrValue::LengthPercentage(_, ref length_percentage) => length_percentage.as_ref(),
311 _ => panic!("LengthPercentage not found"),
312 }
313 }
314
315 pub fn as_color(&self) -> Option<&AbsoluteColor> {
321 match *self {
322 AttrValue::Color(_, ref color) => color.as_ref(),
323 _ => panic!("Color not found"),
324 }
325 }
326
327 pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto {
333 match *self {
334 AttrValue::Dimension(_, ref l) => l,
335 _ => panic!("Dimension not found"),
336 }
337 }
338
339 pub fn as_resolved_url(&self) -> Option<&Arc<::url::Url>> {
345 match *self {
346 AttrValue::ResolvedUrl(_, ref url) => url.as_ref(),
347 _ => panic!("Url not found"),
348 }
349 }
350
351 pub fn as_int(&self) -> i32 {
359 if let AttrValue::Int(_, value) = *self {
360 value
361 } else {
362 panic!("Int not found");
363 }
364 }
365
366 pub fn as_uint(&self) -> u32 {
374 if let AttrValue::UInt(_, value) = *self {
375 value
376 } else {
377 panic!("Uint not found");
378 }
379 }
380
381 pub fn as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto {
391 if let AttrValue::UInt(_, value) = *self {
392 LengthOrPercentageOrAuto::Length(Au::from_px(value as i32))
393 } else {
394 panic!("Uint not found");
395 }
396 }
397
398 pub fn as_shadow_parts(&self) -> &ShadowParts {
407 if let AttrValue::ShadowParts(_, value) = &self {
408 value
409 } else {
410 panic!("Not a shadowpart attribute");
411 }
412 }
413
414 pub fn eval_selector(&self, selector: &AttrSelectorOperation<&AtomString>) -> bool {
415 selector.eval_str(self)
419 }
420}
421
422impl ::std::ops::Deref for AttrValue {
423 type Target = str;
424
425 fn deref(&self) -> &str {
426 match *self {
427 AttrValue::String(ref value)
428 | AttrValue::TokenList(ref value, _)
429 | AttrValue::UInt(ref value, _)
430 | AttrValue::Double(ref value, _)
431 | AttrValue::LengthPercentage(ref value, _)
432 | AttrValue::Color(ref value, _)
433 | AttrValue::Int(ref value, _)
434 | AttrValue::ResolvedUrl(ref value, _)
435 | AttrValue::Declaration(ref value, _)
436 | AttrValue::ShadowParts(ref value, _)
437 | AttrValue::Dimension(ref value, _) => &value,
438 AttrValue::Atom(ref value) => &value,
439 }
440 }
441}
442
443impl PartialEq<Atom> for AttrValue {
444 fn eq(&self, other: &Atom) -> bool {
445 match *self {
446 AttrValue::Atom(ref value) => value == other,
447 _ => other == &**self,
448 }
449 }
450}
451
452pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto {
454 match parse_length(value) {
455 LengthOrPercentageOrAuto::Length(x) if x == Au::zero() => LengthOrPercentageOrAuto::Auto,
456 LengthOrPercentageOrAuto::Percentage(x) if x == 0. => LengthOrPercentageOrAuto::Auto,
457 x => x,
458 }
459}
460
461pub fn parse_legacy_color(mut input: &str) -> Result<AbsoluteColor, ()> {
465 if input.is_empty() {
467 return Err(());
468 }
469
470 input = input.trim_matches(HTML_SPACE_CHARACTERS);
472
473 if input.eq_ignore_ascii_case("transparent") {
475 return Err(());
476 }
477
478 if let Ok(Color::Absolute(ref absolute)) = parse_color_keyword(input) {
480 return Ok(absolute.color);
481 }
482
483 if input.len() == 4 {
485 if let (b'#', Ok(r), Ok(g), Ok(b)) = (
486 input.as_bytes()[0],
487 hex(input.as_bytes()[1] as char),
488 hex(input.as_bytes()[2] as char),
489 hex(input.as_bytes()[3] as char),
490 ) {
491 return Ok(AbsoluteColor::srgb_legacy(r * 17, g * 17, b * 17, 1.0));
492 }
493 }
494
495 let mut new_input = String::new();
497 for ch in input.chars() {
498 if ch as u32 > 0xffff {
499 new_input.push_str("00")
500 } else {
501 new_input.push(ch)
502 }
503 }
504 let mut input = &*new_input;
505
506 for (char_count, (index, _)) in input.char_indices().enumerate() {
508 if char_count == 128 {
509 input = &input[..index];
510 break;
511 }
512 }
513
514 if input.as_bytes()[0] == b'#' {
516 input = &input[1..]
517 }
518
519 let mut new_input = Vec::new();
521 for ch in input.chars() {
522 if hex(ch).is_ok() {
523 new_input.push(ch as u8)
524 } else {
525 new_input.push(b'0')
526 }
527 }
528 let mut input = new_input;
529
530 while input.is_empty() || (input.len() % 3) != 0 {
532 input.push(b'0')
533 }
534
535 let mut length = input.len() / 3;
537 let (mut red, mut green, mut blue) = (
538 &input[..length],
539 &input[length..length * 2],
540 &input[length * 2..],
541 );
542
543 if length > 8 {
545 red = &red[length - 8..];
546 green = &green[length - 8..];
547 blue = &blue[length - 8..];
548 length = 8
549 }
550
551 while length > 2 && red[0] == b'0' && green[0] == b'0' && blue[0] == b'0' {
553 red = &red[1..];
554 green = &green[1..];
555 blue = &blue[1..];
556 length -= 1
557 }
558
559 return Ok(AbsoluteColor::srgb_legacy(
561 hex_string(red).unwrap(),
562 hex_string(green).unwrap(),
563 hex_string(blue).unwrap(),
564 1.0,
565 ));
566
567 fn hex(ch: char) -> Result<u8, ()> {
568 match ch {
569 '0'..='9' => Ok((ch as u8) - b'0'),
570 'a'..='f' => Ok((ch as u8) - b'a' + 10),
571 'A'..='F' => Ok((ch as u8) - b'A' + 10),
572 _ => Err(()),
573 }
574 }
575
576 fn hex_string(string: &[u8]) -> Result<u8, ()> {
577 match string.len() {
578 0 => Err(()),
579 1 => hex(string[0] as char),
580 _ => {
581 let upper = hex(string[0] as char)?;
582 let lower = hex(string[1] as char)?;
583 Ok((upper << 4) | lower)
584 },
585 }
586 }
587}
588
589pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto {
594 value = value.trim_start_matches(HTML_SPACE_CHARACTERS);
598
599 match value.chars().nth(0) {
601 Some('0'..='9') => {},
602 _ => return LengthOrPercentageOrAuto::Auto,
603 }
604
605 let mut end_index = value.len();
613 let (mut found_full_stop, mut found_percent) = (false, false);
614 for (i, ch) in value.chars().enumerate() {
615 match ch {
616 '0'..='9' => continue,
617 '%' => {
618 found_percent = true;
619 end_index = i;
620 break;
621 },
622 '.' if !found_full_stop => {
623 found_full_stop = true;
624 continue;
625 },
626 _ => {
627 end_index = i;
628 break;
629 },
630 }
631 }
632 value = &value[..end_index];
633
634 if found_percent {
635 let result: Result<f32, _> = FromStr::from_str(value);
636 match result {
637 Ok(number) => return LengthOrPercentageOrAuto::Percentage((number as f32) / 100.0),
638 Err(_) => return LengthOrPercentageOrAuto::Auto,
639 }
640 }
641
642 match FromStr::from_str(value) {
643 Ok(number) => LengthOrPercentageOrAuto::Length(Au::from_f64_px(number)),
644 Err(_) => LengthOrPercentageOrAuto::Auto,
645 }
646}
647
648#[derive(Clone, Debug)]
650#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
651pub struct AttrIdentifier {
652 pub local_name: LocalName,
653 pub name: LocalName,
654 pub namespace: Namespace,
655 pub prefix: Option<Prefix>,
656}