Skip to content

Commit 6670c35

Browse files
committed
Merge pull request servo#25 from SimonSapin/serializer
Add an incomplete CSS serializer.
2 parents 1531ef7 + ee74590 commit 6670c35

File tree

4 files changed

+220
-1
lines changed

4 files changed

+220
-1
lines changed

Makefile.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ cssparser-test: lib.rs $(RUST_SRC)
2222

2323
.PHONY: check
2424
check: cssparser-test
25-
./cssparser-test
25+
./cssparser-test $(TEST)
2626

2727
.PHONY: check-debug
2828
check-debug: cssparser-tests

lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ pub use tokenizer::*;
1313
pub use parser::*;
1414
pub use color::*;
1515
pub use nth::*;
16+
pub use serializer::{ToCss, serialize_identifier, serialize_string};
1617

1718
pub mod ast;
1819
pub mod tokenizer;
1920
pub mod parser;
2021
pub mod color;
2122
pub mod nth;
23+
pub mod serializer;
2224

2325
#[cfg(test)]
2426
mod tests;

serializer.rs

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
use ast;
6+
use ast::*;
7+
8+
9+
impl ast::ComponentValue {
10+
pub fn to_css(&mut self) -> ~str {
11+
let mut css = ~"";
12+
self.to_css_push(&mut css);
13+
css
14+
}
15+
16+
pub fn to_css_push(&self, css: &mut ~str) {
17+
match *self {
18+
Ident(ref value) => serialize_identifier(value.as_slice(), css),
19+
AtKeyword(ref value) => {
20+
css.push_char('@');
21+
serialize_identifier(value.as_slice(), css);
22+
},
23+
Hash(ref value) | IDHash(ref value) => {
24+
css.push_char('#');
25+
serialize_identifier(value.as_slice(), css);
26+
}
27+
String(ref value) => serialize_string(value.as_slice(), css),
28+
URL(ref value) => {
29+
css.push_str("url(");
30+
serialize_string(value.as_slice(), css);
31+
css.push_char(')');
32+
},
33+
Delim(value) => css.push_char(value),
34+
35+
Number(ref value) => css.push_str(value.representation),
36+
Percentage(ref value) => {
37+
css.push_str(value.representation);
38+
css.push_char('%');
39+
},
40+
Dimension(ref value, ref unit) => {
41+
css.push_str(value.representation);
42+
serialize_identifier(unit.as_slice(), css);
43+
},
44+
45+
UnicodeRange(start, end) => {
46+
css.push_str(format!("U+{:X}", start));
47+
if end != start {
48+
css.push_str(format!("-{:X}", end));
49+
}
50+
}
51+
52+
WhiteSpace => css.push_char(' '),
53+
Colon => css.push_char(':'),
54+
Semicolon => css.push_char(';'),
55+
Comma => css.push_char(','),
56+
IncludeMatch => css.push_str("~="),
57+
DashMatch => css.push_str("|="),
58+
PrefixMatch => css.push_str("^="),
59+
SuffixMatch => css.push_str("$="),
60+
SubstringMatch => css.push_str("*="),
61+
Column => css.push_str("||"),
62+
CDO => css.push_str("<!--"),
63+
CDC => css.push_str("-->"),
64+
65+
Function(ref name, ref arguments) => {
66+
serialize_identifier(name.as_slice(), css);
67+
css.push_char('(');
68+
arguments.iter().to_css_push(css);
69+
css.push_char(')');
70+
},
71+
ParenthesisBlock(ref content) => {
72+
css.push_char('(');
73+
content.iter().to_css_push(css);
74+
css.push_char(')');
75+
},
76+
SquareBracketBlock(ref content) => {
77+
css.push_char('[');
78+
content.iter().to_css_push(css);
79+
css.push_char(']');
80+
},
81+
CurlyBracketBlock(ref content) => {
82+
css.push_char('{');
83+
content.iter().map(|t| match *t { (ref c, _) => c }).to_css_push(css);
84+
css.push_char('}');
85+
},
86+
87+
BadURL => css.push_str("url(<bad url>)"),
88+
BadString => css.push_str("\"<bad string>\n"),
89+
CloseParenthesis => css.push_char(')'),
90+
CloseSquareBracket => css.push_char(']'),
91+
CloseCurlyBracket => css.push_char('}'),
92+
}
93+
}
94+
}
95+
96+
97+
pub fn serialize_identifier(value: &str, css: &mut ~str) {
98+
// TODO: avoid decoding/re-encoding UTF-8?
99+
let mut iter = value.iter();
100+
let mut c = iter.next().unwrap();
101+
if c == '-' {
102+
c = match iter.next() {
103+
None => { css.push_str("\\-"); return },
104+
Some(c) => { css.push_char('-'); c },
105+
}
106+
};
107+
serialize_char(c, css, /* is_start = */ true);
108+
for c in iter {
109+
serialize_char(c, css, /* is_start = */ false);
110+
}
111+
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+
}
124+
}
125+
}
126+
127+
pub fn serialize_string(value: &str, css: &mut ~str) {
128+
css.push_char('"');
129+
// TODO: avoid decoding/re-encoding UTF-8?
130+
for c in value.iter() {
131+
match c {
132+
'"' => css.push_str("\""),
133+
'\\' => css.push_str("\\\\"),
134+
'\n' => css.push_str("\\A "),
135+
'\r' => css.push_str("\\D "),
136+
'\x0C' => css.push_str("\\C "),
137+
_ => css.push_char(c),
138+
}
139+
}
140+
css.push_char('"');
141+
}
142+
143+
144+
pub trait ToCss {
145+
fn to_css(&mut self) -> ~str {
146+
let mut css = ~"";
147+
self.to_css_push(&mut css);
148+
css
149+
}
150+
151+
fn to_css_push(&mut self, css: &mut ~str);
152+
}
153+
154+
155+
impl<'self, I: Iterator<&'self ComponentValue>> ToCss for I {
156+
fn to_css_push(&mut self, css: &mut ~str) {
157+
let mut previous = match self.next() {
158+
None => return,
159+
Some(first) => { first.to_css_push(css); first }
160+
};
161+
macro_rules! matches(
162+
($value:expr, $($pattern:pat)|+) => (
163+
match $value { $($pattern)|+ => true, _ => false }
164+
);
165+
)
166+
loop { match self.next() { None => break, Some(component_value) => {
167+
let (a, b) = (previous, component_value);
168+
if (
169+
matches!(*a, Hash(*) | IDHash(*) | AtKeyword(*)) &&
170+
matches!(*b, Number(*) | Percentage(*) | Ident(*) | Dimension(*) |
171+
UnicodeRange(*) | URL(*) | Function(*))
172+
) || (
173+
matches!(*a, Number(*) | Ident(*) | Dimension(*)) &&
174+
matches!(*b, Number(*) | Ident(*) | Dimension(*))
175+
) || (
176+
matches!(*a, Number(*) | Ident(*) | Dimension(*)) &&
177+
matches!(*b, Percentage(*) | UnicodeRange(*) | URL(*) | Function(*))
178+
) || (
179+
matches!(*a, Ident(*)) &&
180+
matches!(*b, ParenthesisBlock(*))
181+
) || (
182+
matches!(*a, Delim('#') | Delim('@')) &&
183+
!matches!(*b, WhiteSpace)
184+
) || (
185+
matches!(*a, Delim('-') | Delim('+') | Delim('.') | Delim('<') |
186+
Delim('>') | Delim('!')) &&
187+
!matches!(*b, WhiteSpace)
188+
) || (
189+
!matches!(*a, WhiteSpace) &&
190+
matches!(*b, Delim('-') | Delim('+') | Delim('.') | Delim('<') |
191+
Delim('>') | Delim('!'))
192+
) || (
193+
matches!(*a, Delim('/')) &&
194+
matches!(*b, Delim('*'))
195+
) || (
196+
matches!(*a, Delim('*')) &&
197+
matches!(*b, Delim('/'))
198+
) {
199+
css.push_str("/**/")
200+
}
201+
component_value.to_css_push(css);
202+
previous = component_value;
203+
}}}
204+
}
205+
}

tests.rs

+12
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,18 @@ fn nth() {
175175
}
176176
177177
178+
// FIXME: serializer tests disabled for now
179+
// https://github.com/mozilla-servo/rust-cssparser/issues/24
180+
//#[test]
181+
fn serializer() {
182+
do run_json_tests(include_str!("css-parsing-tests/component_value_list.json")) |input| {
183+
let component_values = tokenize(input).map(|(c, _)| c).to_owned_vec();
184+
let serialized = component_values.iter().to_css();
185+
tokenize(serialized).map(|(c, _)| c).to_owned_vec()
186+
}
187+
}
188+
189+
178190
impl ToJson for Result<Rule, SyntaxError> {
179191
fn to_json(&self) -> json::Json {
180192
match *self {

0 commit comments

Comments
 (0)