hpx_browser/css_parser/
token.rs1use std::borrow::Cow;
2
3use crate::css_parser::source::SourceLocation;
4
5#[derive(Debug, Clone, PartialEq)]
7pub struct Token<'a> {
8 pub kind: TokenKind<'a>,
9 pub loc: SourceLocation,
10}
11
12#[derive(Debug, Clone, PartialEq)]
14pub enum TokenKind<'a> {
15 Ident(&'a str),
16 Function(&'a str),
17 AtKeyword(&'a str),
18 Hash {
19 value: &'a str,
20 is_id: bool,
21 },
22 String(&'a str),
23 BadString,
24 Url(&'a str),
25 BadUrl,
26 Number {
27 value: f64,
28 int_value: Option<i64>,
29 has_sign: bool,
30 },
31 Percentage {
32 value: f64,
33 int_value: Option<i64>,
34 },
35 Dimension {
36 value: f64,
37 int_value: Option<i64>,
38 unit: &'a str,
39 },
40 Whitespace,
41 Delim(char),
42 Colon,
43 Semicolon,
44 Comma,
45 OpenSquare,
46 CloseSquare,
47 OpenParen,
48 CloseParen,
49 OpenCurly,
50 CloseCurly,
51 Cdo,
52 Cdc,
53 Eof,
54}
55
56impl<'a> TokenKind<'a> {
57 pub fn is_whitespace(&self) -> bool {
58 matches!(self, TokenKind::Whitespace)
59 }
60
61 pub fn is_ident(&self) -> bool {
62 matches!(self, TokenKind::Ident(_))
63 }
64}
65
66pub fn resolve_escapes(raw: &str) -> Cow<'_, str> {
68 if !raw.contains('\\') {
69 return Cow::Borrowed(raw);
70 }
71
72 let mut result = String::with_capacity(raw.len());
73 let mut chars = raw.chars();
74
75 while let Some(ch) = chars.next() {
76 if ch == '\\' {
77 match chars.next() {
78 None => {
79 result.push(ch);
80 }
81 Some('\n') => {}
82 Some(next) if next.is_ascii_hexdigit() => {
83 let mut hex = String::with_capacity(6);
84 hex.push(next);
85 for _ in 0..5 {
86 match chars.clone().next() {
87 Some(h) if h.is_ascii_hexdigit() => {
88 hex.push(h);
89 chars.next();
90 }
91 _ => break,
92 }
93 }
94 if let Some(&ws) = chars.as_str().as_bytes().first() {
95 if ws == b' ' || ws == b'\t' || ws == b'\n' {
96 chars.next();
97 }
98 }
99 if let Ok(code) = u32::from_str_radix(&hex, 16) {
100 if let Some(c) = char::from_u32(code) {
101 if c == '\0' {
102 result.push('\u{FFFD}');
103 } else {
104 result.push(c);
105 }
106 } else {
107 result.push('\u{FFFD}');
108 }
109 } else {
110 result.push('\u{FFFD}');
111 }
112 }
113 Some(next) => {
114 result.push(next);
115 }
116 }
117 } else {
118 result.push(ch);
119 }
120 }
121
122 Cow::Owned(result)
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn resolve_no_escapes() {
131 let result = resolve_escapes("hello");
132 assert!(matches!(result, Cow::Borrowed(_)));
133 assert_eq!(result, "hello");
134 }
135
136 #[test]
137 fn resolve_hex_escape() {
138 assert_eq!(resolve_escapes("\\26 "), "&");
139 assert_eq!(resolve_escapes("\\000026 "), "&");
140 }
141
142 #[test]
143 fn resolve_simple_escape() {
144 assert_eq!(resolve_escapes("\\("), "(");
145 assert_eq!(resolve_escapes("hello\\.world"), "hello.world");
146 }
147
148 #[test]
149 fn resolve_null_escape() {
150 assert_eq!(resolve_escapes("\\0 "), "\u{FFFD}");
151 }
152
153 #[test]
154 fn resolve_newline_continuation() {
155 assert_eq!(resolve_escapes("hel\\\nlo"), "hello");
156 }
157}