Skip to content

Commit 8a996ae

Browse files
committed
Fix #24: serialization corner cases.
1 parent 4b2d1c3 commit 8a996ae

File tree

2 files changed

+80
-57
lines changed

2 files changed

+80
-57
lines changed

serializer.rs

Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ impl ast::ComponentValue {
2020
css.push_char('@');
2121
serialize_identifier(value.as_slice(), css);
2222
},
23-
Hash(ref value) | IDHash(ref value) => {
23+
Hash(ref value) => {
24+
css.push_char('#');
25+
for c in value.iter() {
26+
serialize_char(c, css, /* is_identifier_start = */ false);
27+
}
28+
},
29+
IDHash(ref value) => {
2430
css.push_char('#');
2531
serialize_identifier(value.as_slice(), css);
2632
}
@@ -39,7 +45,16 @@ impl ast::ComponentValue {
3945
},
4046
Dimension(ref value, ref unit) => {
4147
css.push_str(value.representation);
42-
serialize_identifier(unit.as_slice(), css);
48+
// Disambiguate with scientific notation.
49+
let unit = unit.as_slice();
50+
if unit == "e" || unit == "E" || unit.starts_with("e-") || unit.starts_with("E-") {
51+
css.push_str("\\65 ");
52+
for c in unit.slice_from(1).iter() {
53+
serialize_char(c, css, /* is_identifier_start = */ false);
54+
}
55+
} else {
56+
serialize_identifier(unit, css)
57+
}
4358
},
4459

4560
UnicodeRange(start, end) => {
@@ -104,32 +119,34 @@ pub fn serialize_identifier(value: &str, css: &mut ~str) {
104119
Some(c) => { css.push_char('-'); c },
105120
}
106121
};
107-
serialize_char(c, css, /* is_start = */ true);
122+
serialize_char(c, css, /* is_identifier_start = */ true);
108123
for c in iter {
109-
serialize_char(c, css, /* is_start = */ false);
124+
serialize_char(c, css, /* is_identifier_start = */ false);
110125
}
126+
}
111127

112-
#[inline]
113-
fn serialize_char(c: char, css: &mut ~str, is_start: bool) {
114-
match c {
115-
'0'..'9' if is_start => css.push_str(format!("\\\\3{} ", c)),
116-
'-' if is_start => css.push_str("\\-"),
117-
'0'..'9' | 'A'..'Z' | 'a'..'z' | '_' | '-' => css.push_char(c),
118-
_ if c > '\x7F' => css.push_char(c),
119-
'\n' => css.push_str("\\A "),
120-
'\r' => css.push_str("\\D "),
121-
'\x0C' => css.push_str("\\C "),
122-
_ => { css.push_char('\\'); css.push_char(c) },
123-
}
128+
129+
#[inline]
130+
fn serialize_char(c: char, css: &mut ~str, is_identifier_start: bool) {
131+
match c {
132+
'0'..'9' if is_identifier_start => css.push_str(format!("\\\\3{} ", c)),
133+
'-' if is_identifier_start => css.push_str("\\-"),
134+
'0'..'9' | 'A'..'Z' | 'a'..'z' | '_' | '-' => css.push_char(c),
135+
_ if c > '\x7F' => css.push_char(c),
136+
'\n' => css.push_str("\\A "),
137+
'\r' => css.push_str("\\D "),
138+
'\x0C' => css.push_str("\\C "),
139+
_ => { css.push_char('\\'); css.push_char(c) },
124140
}
125141
}
126142

143+
127144
pub fn serialize_string(value: &str, css: &mut ~str) {
128145
css.push_char('"');
129146
// TODO: avoid decoding/re-encoding UTF-8?
130147
for c in value.iter() {
131148
match c {
132-
'"' => css.push_str("\""),
149+
'"' => css.push_str("\\\""),
133150
'\\' => css.push_str("\\\\"),
134151
'\n' => css.push_str("\\A "),
135152
'\r' => css.push_str("\\D "),
@@ -163,44 +180,52 @@ impl<'self, I: Iterator<&'self ComponentValue>> ToCss for I {
163180
match $value { $($pattern)|+ => true, _ => false }
164181
);
165182
)
183+
// This does not borrow-check: for component_value in self {
166184
loop { match self.next() { None => break, Some(component_value) => {
167-
let (_a, _b) = (previous, component_value);
168-
// FIXME: this is incorrect.
169-
// See https://github.com/mozilla-servo/rust-cssparser/issues/24
170-
// if (
171-
// matches!(*a, Hash(*) | IDHash(*) | AtKeyword(*)) &&
172-
// matches!(*b, Number(*) | Percentage(*) | Ident(*) | Dimension(*) |
173-
// UnicodeRange(*) | URL(*) | Function(*))
174-
// ) || (
175-
// matches!(*a, Number(*) | Ident(*) | Dimension(*)) &&
176-
// matches!(*b, Number(*) | Ident(*) | Dimension(*))
177-
// ) || (
178-
// matches!(*a, Number(*) | Ident(*) | Dimension(*)) &&
179-
// matches!(*b, Percentage(*) | UnicodeRange(*) | URL(*) | Function(*))
180-
// ) || (
181-
// matches!(*a, Ident(*)) &&
182-
// matches!(*b, ParenthesisBlock(*))
183-
// ) || (
184-
// matches!(*a, Delim('#') | Delim('@')) &&
185-
// !matches!(*b, WhiteSpace)
186-
// ) || (
187-
// matches!(*a, Delim('-') | Delim('+') | Delim('.') | Delim('<') |
188-
// Delim('>') | Delim('!')) &&
189-
// !matches!(*b, WhiteSpace)
190-
// ) || (
191-
// !matches!(*a, WhiteSpace) &&
192-
// matches!(*b, Delim('-') | Delim('+') | Delim('.') | Delim('<') |
193-
// Delim('>') | Delim('!'))
194-
// ) || (
195-
// matches!(*a, Delim('/')) &&
196-
// matches!(*b, Delim('*'))
197-
// ) || (
198-
// matches!(*a, Delim('*')) &&
199-
// matches!(*b, Delim('/'))
200-
// ) {
201-
// css.push_str("/**/")
202-
// }
203-
component_value.to_css_push(css);
185+
let (a, b) = (previous, component_value);
186+
if (
187+
matches!(*a, Ident(*) | AtKeyword(*) | Hash(*) | IDHash(*) |
188+
Dimension(*) | Delim('#') | Delim('-') | Number(*)) &&
189+
matches!(*b, Ident(*) | Function(*) | URL(*) | BadURL(*) |
190+
Number(*) | Percentage(*) | Dimension(*) | UnicodeRange(*))
191+
) || (
192+
matches!(*a, Ident(*)) &&
193+
matches!(*b, ParenthesisBlock(*))
194+
) || (
195+
matches!(*a, Ident(*) | AtKeyword(*) | Hash(*) | IDHash(*) | Dimension(*)) &&
196+
matches!(*b, Delim('-') | CDC)
197+
) || (
198+
matches!(*a, Delim('#') | Delim('-') | Number(*) | Delim('@')) &&
199+
matches!(*b, Ident(*) | Function(*) | URL(*) | BadURL(*))
200+
) || (
201+
matches!(*a, Delim('@')) &&
202+
matches!(*b, Ident(*) | Function(*) | URL(*) | BadURL(*) |
203+
UnicodeRange(*) | Delim('-'))
204+
) || (
205+
matches!(*a, UnicodeRange(*) | Delim('.') | Delim('+')) &&
206+
matches!(*b, Number(*) | Percentage(*) | Dimension(*))
207+
) || (
208+
matches!(*a, UnicodeRange(*)) &&
209+
matches!(*b, Ident(*) | Function(*) | Delim('?'))
210+
) || (match (a, b) { (&Delim(a), &Delim(b)) => matches!((a, b),
211+
('#', '-') |
212+
('$', '=') |
213+
('*', '=') |
214+
('^', '=') |
215+
('~', '=') |
216+
('|', '=') |
217+
('|', '|') |
218+
('/', '*')
219+
), _ => false }) {
220+
css.push_str("/**/")
221+
}
222+
// Skip whitespace when '\n' was previously written at the previous iteration.
223+
if !matches!((previous, component_value), (&Delim('\\'), &WhiteSpace)) {
224+
component_value.to_css_push(css);
225+
}
226+
if component_value == &Delim('\\') {
227+
css.push_char('\n');
228+
}
204229
previous = component_value;
205230
}}}
206231
}

tests.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,7 @@ fn nth() {
176176
}
177177
178178
179-
// FIXME: serializer tests disabled for now
180-
// https://github.com/mozilla-servo/rust-cssparser/issues/24
181-
//#[test]
179+
#[test]
182180
fn serializer() {
183181
do run_json_tests(include_str!("css-parsing-tests/component_value_list.json")) |input| {
184182
let component_values = tokenize(input).map(|(c, _)| c).to_owned_vec();

0 commit comments

Comments
 (0)