From 4ccdf6d813a88b1f9d50c54eea4fdc2e71946580 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 20 Dec 2014 20:11:36 +0000 Subject: [PATCH 1/7] Refactor ToCss to use TextWriter. https://github.com/SimonSapin/rust-std-candidates#the-textwriter-trait --- .gitignore | 1 + Cargo.toml | 4 + src/ast.rs | 13 -- src/lib.rs | 3 +- src/serializer.rs | 375 ++++++++++++++++++++++++---------------------- src/tests.rs | 2 +- 6 files changed, 204 insertions(+), 194 deletions(-) diff --git a/.gitignore b/.gitignore index 6ba9241b..f0fcd8f1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Makefile /target /Cargo.lock +/.cargo/config diff --git a/Cargo.toml b/Cargo.toml index 12541c8d..3f964400 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,10 @@ name = "cssparser" version = "0.1.0" authors = [ "Simon Sapin " ] +[dependencies] + +text_writer = "0.1" + [dependencies.encoding] git = "https://github.com/lifthrasiir/rust-encoding" diff --git a/src/ast.rs b/src/ast.rs index eca51a73..a0244fa2 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -69,19 +69,6 @@ pub enum ComponentValue { } -impl ComponentValue { - pub fn to_css(&self) -> String { - let mut css = String::new(); - self.to_css_push(&mut css); - css - } - - pub fn to_css_push(&self, css: &mut String) { - ::serializer::to_css_push(self, css) - } -} - - #[deriving(PartialEq)] pub struct Declaration { pub location: SourceLocation, diff --git a/src/lib.rs b/src/lib.rs index 2e626242..640faaac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ #![feature(globs, macro_rules)] -extern crate encoding; // https://github.com/lifthrasiir/rust-encoding +extern crate encoding; +extern crate text_writer; #[cfg(test)] extern crate test; diff --git a/src/serializer.rs b/src/serializer.rs index 075e4a5f..35934559 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -2,223 +2,240 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use text_writer::{mod, TextWriter}; + use ast::*; use ast::ComponentValue::*; -pub fn to_css_push(component_value: &ComponentValue, css: &mut String) { - match *component_value { - Ident(ref value) => serialize_identifier(value.as_slice(), css), - AtKeyword(ref value) => { - css.push('@'); - serialize_identifier(value.as_slice(), css); - }, - Hash(ref value) => { - css.push('#'); - for c in value.as_slice().chars() { - serialize_char(c, css, /* is_identifier_start = */ false); - } - }, - IDHash(ref value) => { - css.push('#'); - serialize_identifier(value.as_slice(), css); - } - QuotedString(ref value) => serialize_string(value.as_slice(), css), - URL(ref value) => { - css.push_str("url("); - serialize_string(value.as_slice(), css); - css.push(')'); - }, - Delim(value) => css.push(value), - - Number(ref value) => css.push_str(value.representation.as_slice()), - Percentage(ref value) => { - css.push_str(value.representation.as_slice()); - css.push('%'); - }, - Dimension(ref value, ref unit) => { - css.push_str(value.representation.as_slice()); - // Disambiguate with scientific notation. - let unit = unit.as_slice(); - if unit == "e" || unit == "E" || unit.starts_with("e-") || unit.starts_with("E-") { - css.push_str("\\65 "); - for c in unit.slice_from(1).chars() { - serialize_char(c, css, /* is_identifier_start = */ false); +pub trait ToCss for Sized? { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter; + + fn to_css_string(&self) -> String { + let mut s = String::new(); + self.to_css(&mut s).unwrap(); + s + } +} + + +impl ToCss for ComponentValue { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + match self { + &Ident(ref value) => try!(serialize_identifier(value.as_slice(), dest)), + &AtKeyword(ref value) => { + try!(dest.write_char('@')); + try!(serialize_identifier(value.as_slice(), dest)); + }, + &Hash(ref value) => { + try!(dest.write_char('#')); + for c in value.as_slice().chars() { + try!(serialize_char(c, dest, /* is_identifier_start = */ false)); } - } else { - serialize_identifier(unit, css) + }, + &IDHash(ref value) => { + try!(dest.write_char('#')); + try!(serialize_identifier(value.as_slice(), dest)); } - }, + &QuotedString(ref value) => try!(serialize_string(value.as_slice(), dest)), + &URL(ref value) => { + try!(dest.write_str("url(")); + try!(serialize_string(value.as_slice(), dest)); + try!(dest.write_char(')')); + }, + &Delim(value) => try!(dest.write_char(value)), + + &Number(ref value) => try!(dest.write_str(value.representation.as_slice())), + &Percentage(ref value) => { + try!(dest.write_str(value.representation.as_slice())); + try!(dest.write_char('%')); + }, + &Dimension(ref value, ref unit) => { + try!(dest.write_str(value.representation.as_slice())); + // Disambiguate with scientific notation. + let unit = unit.as_slice(); + if unit == "e" || unit == "E" || unit.starts_with("e-") || unit.starts_with("E-") { + try!(dest.write_str("\\65 ")); + for c in unit.slice_from(1).chars() { + try!(serialize_char(c, dest, /* is_identifier_start = */ false)); + } + } else { + try!(serialize_identifier(unit, dest)); + } + }, - UnicodeRange(start, end) => { - css.push_str(format!("U+{:X}", start).as_slice()); - if end != start { - css.push_str(format!("-{:X}", end).as_slice()); + &UnicodeRange(start, end) => { + try!(dest.write_str(format!("U+{:X}", start).as_slice())); + if end != start { + try!(dest.write_str(format!("-{:X}", end).as_slice())); + } } - } - WhiteSpace => css.push(' '), - Colon => css.push(':'), - Semicolon => css.push(';'), - Comma => css.push(','), - IncludeMatch => css.push_str("~="), - DashMatch => css.push_str("|="), - PrefixMatch => css.push_str("^="), - SuffixMatch => css.push_str("$="), - SubstringMatch => css.push_str("*="), - Column => css.push_str("||"), - CDO => css.push_str(""), - - Function(ref name, ref arguments) => { - serialize_identifier(name.as_slice(), css); - css.push('('); - arguments.iter().to_css_push(css); - css.push(')'); - }, - ParenthesisBlock(ref content) => { - css.push('('); - content.iter().to_css_push(css); - css.push(')'); - }, - SquareBracketBlock(ref content) => { - css.push('['); - content.iter().to_css_push(css); - css.push(']'); - }, - CurlyBracketBlock(ref content) => { - css.push('{'); - content.iter().map(|t| match *t { (ref c, _) => c }).to_css_push(css); - css.push('}'); - }, - - BadURL => css.push_str("url()"), - BadString => css.push_str("\"\n"), - CloseParenthesis => css.push(')'), - CloseSquareBracket => css.push(']'), - CloseCurlyBracket => css.push('}'), + &WhiteSpace => try!(dest.write_char(' ')), + &Colon => try!(dest.write_char(':')), + &Semicolon => try!(dest.write_char(';')), + &Comma => try!(dest.write_char(',')), + &IncludeMatch => try!(dest.write_str("~=")), + &DashMatch => try!(dest.write_str("|=")), + &PrefixMatch => try!(dest.write_str("^=")), + &SuffixMatch => try!(dest.write_str("$=")), + &SubstringMatch => try!(dest.write_str("*=")), + &Column => try!(dest.write_str("||")), + &CDO => try!(dest.write_str("")), + + &Function(ref name, ref arguments) => { + try!(serialize_identifier(name.as_slice(), dest)); + try!(dest.write_char('(')); + try!(arguments.to_css(dest)); + try!(dest.write_char(')')); + }, + &ParenthesisBlock(ref content) => { + try!(dest.write_char('(')); + try!(content.to_css(dest)); + try!(dest.write_char(')')); + }, + &SquareBracketBlock(ref content) => { + try!(dest.write_char('[')); + try!(content.to_css(dest)); + try!(dest.write_char(']')); + }, + &CurlyBracketBlock(ref content) => { + try!(dest.write_char('{')); + let component_values = content.iter().map(|t| match *t { (ref c, _) => c }); + try!(component_values_to_css(component_values, dest)); + try!(dest.write_char('}')); + }, + + &BadURL => try!(dest.write_str("url()")), + &BadString => try!(dest.write_str("\"\n")), + &CloseParenthesis => try!(dest.write_char(')')), + &CloseSquareBracket => try!(dest.write_char(']')), + &CloseCurlyBracket => try!(dest.write_char('}')), + } + Ok(()) } } -pub fn serialize_identifier(value: &str, css: &mut String) { +pub fn serialize_identifier(value: &str, dest: &mut W) -> text_writer::Result +where W:TextWriter { // TODO: avoid decoding/re-encoding UTF-8? let mut iter = value.chars(); let mut c = iter.next().unwrap(); if c == '-' { c = match iter.next() { - None => { css.push_str("\\-"); return }, - Some(c) => { css.push('-'); c }, + None => return dest.write_str("\\-"), + Some(c) => { try!(dest.write_char('-')); c }, } }; - serialize_char(c, css, /* is_identifier_start = */ true); + try!(serialize_char(c, dest, /* is_identifier_start = */ true)); for c in iter { - serialize_char(c, css, /* is_identifier_start = */ false); + try!(serialize_char(c, dest, /* is_identifier_start = */ false)); } + Ok(()) } #[inline] -fn serialize_char(c: char, css: &mut String, is_identifier_start: bool) { +fn serialize_char(c: char, dest: &mut W, is_identifier_start: bool) -> text_writer::Result +where W: TextWriter { match c { - '0'...'9' if is_identifier_start => css.push_str(format!("\\3{} ", c).as_slice()), - '-' if is_identifier_start => css.push_str("\\-"), - '0'...'9' | 'A'...'Z' | 'a'...'z' | '_' | '-' => css.push(c), - _ if c > '\x7F' => css.push(c), - '\n' => css.push_str("\\A "), - '\r' => css.push_str("\\D "), - '\x0C' => css.push_str("\\C "), - _ => { css.push('\\'); css.push(c) }, - } + '0'...'9' if is_identifier_start => try!(dest.write_str(format!("\\3{} ", c).as_slice())), + '-' if is_identifier_start => try!(dest.write_str("\\-")), + '0'...'9' | 'A'...'Z' | 'a'...'z' | '_' | '-' => try!(dest.write_char(c)), + _ if c > '\x7F' => try!(dest.write_char(c)), + '\n' => try!(dest.write_str("\\A ")), + '\r' => try!(dest.write_str("\\D ")), + '\x0C' => try!(dest.write_str("\\C ")), + _ => { try!(dest.write_char('\\')); try!(dest.write_char(c)) }, + }; + Ok(()) } -pub fn serialize_string(value: &str, css: &mut String) { - css.push('"'); +pub fn serialize_string(value: &str, dest: &mut W) -> text_writer::Result +where W: TextWriter { + try!(dest.write_char('"')); // TODO: avoid decoding/re-encoding UTF-8? for c in value.chars() { match c { - '"' => css.push_str("\\\""), - '\\' => css.push_str("\\\\"), - '\n' => css.push_str("\\A "), - '\r' => css.push_str("\\D "), - '\x0C' => css.push_str("\\C "), - _ => css.push(c), - } + '"' => try!(dest.write_str("\\\"")), + '\\' => try!(dest.write_str("\\\\")), + '\n' => try!(dest.write_str("\\A ")), + '\r' => try!(dest.write_str("\\D ")), + '\x0C' => try!(dest.write_str("\\C ")), + _ => try!(dest.write_char(c)), + }; } - css.push('"'); + dest.write_char('"') } -pub trait ToCss { - fn to_css(&mut self) -> String { - let mut css = String::new(); - self.to_css_push(&mut css); - css +impl<'a> ToCss for [ComponentValue] { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + component_values_to_css(self.iter(), dest) } - - fn to_css_push(&mut self, css: &mut String); } - -impl<'a, I: Iterator<&'a ComponentValue>> ToCss for I { - fn to_css_push(&mut self, css: &mut String) { - let mut previous = match self.next() { - None => return, - Some(first) => { first.to_css_push(css); first } - }; - macro_rules! matches( - ($value:expr, $($pattern:pat)|+) => ( - match $value { $($pattern)|+ => true, _ => false } - ); - ) - // This does not borrow-check: for component_value in self { - loop { match self.next() { None => break, Some(component_value) => { - let (a, b) = (previous, component_value); - if ( - matches!(*a, Ident(..) | AtKeyword(..) | Hash(..) | IDHash(..) | - Dimension(..) | Delim('#') | Delim('-') | Number(..)) && - matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..) | - Number(..) | Percentage(..) | Dimension(..) | UnicodeRange(..)) - ) || ( - matches!(*a, Ident(..)) && - matches!(*b, ParenthesisBlock(..)) - ) || ( - matches!(*a, Ident(..) | AtKeyword(..) | Hash(..) | IDHash(..) | Dimension(..)) && - matches!(*b, Delim('-') | CDC) - ) || ( - matches!(*a, Delim('#') | Delim('-') | Number(..) | Delim('@')) && - matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..)) - ) || ( - matches!(*a, Delim('@')) && - matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..) | - UnicodeRange(..) | Delim('-')) - ) || ( - matches!(*a, UnicodeRange(..) | Delim('.') | Delim('+')) && - matches!(*b, Number(..) | Percentage(..) | Dimension(..)) - ) || ( - matches!(*a, UnicodeRange(..)) && - matches!(*b, Ident(..) | Function(..) | Delim('?')) - ) || (match (a, b) { (&Delim(a), &Delim(b)) => matches!((a, b), - ('#', '-') | - ('$', '=') | - ('*', '=') | - ('^', '=') | - ('~', '=') | - ('|', '=') | - ('|', '|') | - ('/', '*') - ), _ => false }) { - css.push_str("/**/") - } - // Skip whitespace when '\n' was previously written at the previous iteration. - if !matches!((previous, component_value), (&Delim('\\'), &WhiteSpace)) { - component_value.to_css_push(css); - } - if component_value == &Delim('\\') { - css.push('\n'); - } - previous = component_value; - }}} - } +fn component_values_to_css<'a, I, W>(mut iter: I, dest: &mut W) -> text_writer::Result +where I: Iterator<&'a ComponentValue>, W: TextWriter { + let mut previous = match iter.next() { + None => return Ok(()), + Some(first) => { try!(first.to_css(dest)); first } + }; + macro_rules! matches( + ($value:expr, $($pattern:pat)|+) => ( + match $value { $($pattern)|+ => true, _ => false } + ); + ) + // This does not borrow-check: for component_value in iter { + loop { match iter.next() { None => break, Some(component_value) => { + let (a, b) = (previous, component_value); + if ( + matches!(*a, Ident(..) | AtKeyword(..) | Hash(..) | IDHash(..) | + Dimension(..) | Delim('#') | Delim('-') | Number(..)) && + matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..) | + Number(..) | Percentage(..) | Dimension(..) | UnicodeRange(..)) + ) || ( + matches!(*a, Ident(..)) && + matches!(*b, ParenthesisBlock(..)) + ) || ( + matches!(*a, Ident(..) | AtKeyword(..) | Hash(..) | IDHash(..) | Dimension(..)) && + matches!(*b, Delim('-') | CDC) + ) || ( + matches!(*a, Delim('#') | Delim('-') | Number(..) | Delim('@')) && + matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..)) + ) || ( + matches!(*a, Delim('@')) && + matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..) | + UnicodeRange(..) | Delim('-')) + ) || ( + matches!(*a, UnicodeRange(..) | Delim('.') | Delim('+')) && + matches!(*b, Number(..) | Percentage(..) | Dimension(..)) + ) || ( + matches!(*a, UnicodeRange(..)) && + matches!(*b, Ident(..) | Function(..) | Delim('?')) + ) || (match (a, b) { (&Delim(a), &Delim(b)) => matches!((a, b), + ('#', '-') | + ('$', '=') | + ('*', '=') | + ('^', '=') | + ('~', '=') | + ('|', '=') | + ('|', '|') | + ('/', '*') + ), _ => false }) { + try!(dest.write_str("/**/")); + } + // Skip whitespace when '\n' was previously written at the previous iteration. + if !matches!((previous, component_value), (&Delim('\\'), &WhiteSpace)) { + try!(component_value.to_css(dest)); + } + if component_value == &Delim('\\') { + try!(dest.write_char('\n')); + } + previous = component_value; + }}} + Ok(()) } diff --git a/src/tests.rs b/src/tests.rs index 401bb7e0..0d3564f3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -273,7 +273,7 @@ fn nth() { fn serializer() { run_json_tests(include_str!("css-parsing-tests/component_value_list.json"), |input| { let component_values = tokenize(input).map(|(c, _)| c).collect::>(); - let serialized = component_values.iter().to_css(); + let serialized = component_values.to_css_string(); tokenize(serialized.as_slice()).map(|(c, _)| c).collect::>() }); } From 6f43977ff5b12228566024b7e8c2004d59b954e1 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 20 Dec 2014 20:24:33 +0000 Subject: [PATCH 2/7] Implement ToCss for more AST things. --- src/serializer.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/src/serializer.rs b/src/serializer.rs index 35934559..d920214b 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -102,8 +102,7 @@ impl ToCss for ComponentValue { }, &CurlyBracketBlock(ref content) => { try!(dest.write_char('{')); - let component_values = content.iter().map(|t| match *t { (ref c, _) => c }); - try!(component_values_to_css(component_values, dest)); + try!(content.to_css(dest)); try!(dest.write_char('}')); }, @@ -178,6 +177,13 @@ impl<'a> ToCss for [ComponentValue] { } } +impl<'a> ToCss for [Node] { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + let component_values = self.iter().map(|n| match n { &(ref c, _) => c }); + component_values_to_css(component_values, dest) + } +} + fn component_values_to_css<'a, I, W>(mut iter: I, dest: &mut W) -> text_writer::Result where I: Iterator<&'a ComponentValue>, W: TextWriter { let mut previous = match iter.next() { @@ -239,3 +245,62 @@ where I: Iterator<&'a ComponentValue>, W: TextWriter { }}} Ok(()) } + + +impl ToCss for Declaration { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + try!(dest.write_str(self.name.as_slice())); + try!(dest.write_char(':')); + try!(self.value.to_css(dest)); + Ok(()) + } +} + + +impl ToCss for QualifiedRule { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + try!(self.prelude.to_css(dest)); + try!(dest.write_char('{')); + try!(self.block.to_css(dest)); + try!(dest.write_char('}')); + Ok(()) + } +} + + +impl ToCss for AtRule { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + try!(dest.write_char('@')); + try!(dest.write_str(self.name.as_slice())); + try!(self.prelude.to_css(dest)); + match self.block { + Some(ref block) => { + try!(dest.write_char('{')); + try!(block.to_css(dest)); + try!(dest.write_char('}')); + } + None => try!(dest.write_char(';')) + } + Ok(()) + } +} + + +impl ToCss for DeclarationListItem { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + match *self { + DeclarationListItem::Declaration(ref declaration) => declaration.to_css(dest), + DeclarationListItem::AtRule(ref at_rule) => at_rule.to_css(dest), + } + } +} + + +impl ToCss for Rule { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + match *self { + Rule::QualifiedRule(ref rule) => rule.to_css(dest), + Rule::AtRule(ref rule) => rule.to_css(dest), + } + } +} From ff2a0b42b4c7a5649c3e8bf5645402b5cb558378 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 20 Dec 2014 20:44:29 +0000 Subject: [PATCH 3/7] Implement ToCss for Color and RGBA --- Cargo.toml | 2 +- src/color.rs | 31 ++++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3f964400..cbb4d647 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ authors = [ "Simon Sapin " ] [dependencies] -text_writer = "0.1" +text_writer = "0.1.1" [dependencies.encoding] diff --git a/src/color.rs b/src/color.rs index 5d8c8682..a7fd9ccd 100644 --- a/src/color.rs +++ b/src/color.rs @@ -6,8 +6,11 @@ use std::ascii::AsciiExt; use std::fmt; use std::num::{Float, FloatMath}; +use text_writer::{mod, TextWriter}; + use ast::{ComponentValue, SkipWhitespaceIterable}; use ast::ComponentValue::{Number, Percentage, Function, Ident, Hash, IDHash, Comma}; +use serializer::ToCss; #[deriving(Clone, PartialEq)] @@ -22,16 +25,28 @@ pub struct RGBA { impl fmt::Show for RGBA { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_css(f).map_err(|_| fmt::WriteError) + } +} + +impl ToCss for RGBA { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { if self.alpha == 1f32 { - write!(f, "rgb({}, {}, {})", (self.red * 255.).round(), (self.green * 255.).round(), + write!(dest, "rgb({}, {}, {})", + (self.red * 255.).round(), + (self.green * 255.).round(), (self.blue * 255.).round()) } else { - write!(f, "rgba({}, {}, {}, {})", (self.red * 255.).round(), (self.green * 255.).round(), - (self.blue * 255.).round(), self.alpha) + write!(dest, "rgba({}, {}, {}, {})", + (self.red * 255.).round(), + (self.green * 255.).round(), + (self.blue * 255.).round(), + self.alpha) } } } + #[deriving(Clone, PartialEq)] pub enum Color { CurrentColor, @@ -40,9 +55,15 @@ pub enum Color { impl fmt::Show for Color { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_css(f).map_err(|_| fmt::WriteError) + } +} + +impl ToCss for Color { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { match self { - &Color::CurrentColor => write!(f, "currentColor"), - &Color::RGBA(c) => write!(f, "{}", c), + &Color::CurrentColor => dest.write_str("currentColor"), + &Color::RGBA(rgba) => rgba.to_css(dest), } } } From 6c4d5501063b96bf64f9ac9fe0665bf5f32f0576 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 20 Dec 2014 20:55:48 +0000 Subject: [PATCH 4/7] Add ToCss::fmt_to_css, a convenience method for implementing Show. --- src/color.rs | 20 ++++++++------------ src/serializer.rs | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/color.rs b/src/color.rs index a7fd9ccd..83208b6d 100644 --- a/src/color.rs +++ b/src/color.rs @@ -23,12 +23,6 @@ pub struct RGBA { pub alpha: f32, } -impl fmt::Show for RGBA { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f).map_err(|_| fmt::WriteError) - } -} - impl ToCss for RGBA { fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { if self.alpha == 1f32 { @@ -53,12 +47,6 @@ pub enum Color { RGBA(RGBA), } -impl fmt::Show for Color { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f).map_err(|_| fmt::WriteError) - } -} - impl ToCss for Color { fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { match self { @@ -68,6 +56,14 @@ impl ToCss for Color { } } +impl fmt::Show for RGBA { + #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) } +} + +impl fmt::Show for Color { + #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) } +} + /// Return `Err(())` on invalid or unsupported value (not a color). impl Color { pub fn parse(component_value: &ComponentValue) -> Result { diff --git a/src/serializer.rs b/src/serializer.rs index d920214b..0ef260e2 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -2,6 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use std::fmt; + use text_writer::{mod, TextWriter}; use ast::*; @@ -9,13 +11,35 @@ use ast::ComponentValue::*; pub trait ToCss for Sized? { + /// Serialize `self` in CSS syntax, writing to `dest`. fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter; + /// Serialize `self` in CSS syntax and return a string. + /// + /// (This is a convenience wrapper for `to_css` and probably should not be overridden.) + #[inline] fn to_css_string(&self) -> String { let mut s = String::new(); self.to_css(&mut s).unwrap(); s } + + /// Serialize `self` in CSS syntax and return a result compatible with `std::fmt::Show`. + /// + /// Typical usage is, for a `Foo` that implements `ToCss`: + /// + /// ```{rust,ignore} + /// use std::fmt; + /// impl fmt::Show for Foo { + /// #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) } + /// } + /// ``` + /// + /// (This is a convenience wrapper for `to_css` and probably should not be overridden.) + #[inline] + fn fmt_to_css(&self, dest: &mut W) -> fmt::Result where W: TextWriter { + self.to_css(dest).map_err(|_| fmt::WriteError) + } } From d6f2908f5cb7354a5e5e0a95845b6ff2447af57b Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 20 Dec 2014 21:02:01 +0000 Subject: [PATCH 5/7] Implement ToCss for declaration lists --- src/serializer.rs | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/serializer.rs b/src/serializer.rs index 0ef260e2..02f8bf75 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -281,6 +281,17 @@ impl ToCss for Declaration { } +impl ToCss for [Declaration] { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + for declaration in self.iter() { + try!(declaration.to_css(dest)); + try!(dest.write_char(';')); + } + Ok(()) + } +} + + impl ToCss for QualifiedRule { fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { try!(self.prelude.to_css(dest)); @@ -312,19 +323,33 @@ impl ToCss for AtRule { impl ToCss for DeclarationListItem { fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { - match *self { - DeclarationListItem::Declaration(ref declaration) => declaration.to_css(dest), - DeclarationListItem::AtRule(ref at_rule) => at_rule.to_css(dest), + match self { + &DeclarationListItem::Declaration(ref declaration) => declaration.to_css(dest), + &DeclarationListItem::AtRule(ref at_rule) => at_rule.to_css(dest), } } } +impl ToCss for [DeclarationListItem] { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + for item in self.iter() { + try!(item.to_css(dest)); + match item { + &DeclarationListItem::AtRule(_) => {} + &DeclarationListItem::Declaration(_) => try!(dest.write_char(';')) + } + } + Ok(()) + } +} + + impl ToCss for Rule { fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { - match *self { - Rule::QualifiedRule(ref rule) => rule.to_css(dest), - Rule::AtRule(ref rule) => rule.to_css(dest), + match self { + &Rule::QualifiedRule(ref rule) => rule.to_css(dest), + &Rule::AtRule(ref rule) => rule.to_css(dest), } } } From a2b0b6b00ad84dc3a4b4faf77ddd63611c8ce58a Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 20 Dec 2014 22:29:05 +0000 Subject: [PATCH 6/7] Add CssStringWriter --- src/lib.rs | 2 +- src/serializer.rs | 59 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 640faaac..66ce3d66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ pub use parser::{parse_stylesheet_rules, StylesheetParser, pub use from_bytes::{decode_stylesheet_bytes, parse_stylesheet_rules_from_bytes}; pub use color::{RGBA, Color}; pub use nth::parse_nth; -pub use serializer::{ToCss, serialize_identifier, serialize_string}; +pub use serializer::{ToCss, CssStringWriter, serialize_identifier, serialize_string}; pub mod ast; mod tokenizer; diff --git a/src/serializer.rs b/src/serializer.rs index 02f8bf75..87b3ef6c 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -180,18 +180,57 @@ where W: TextWriter { pub fn serialize_string(value: &str, dest: &mut W) -> text_writer::Result where W: TextWriter { try!(dest.write_char('"')); - // TODO: avoid decoding/re-encoding UTF-8? - for c in value.chars() { + try!(CssStringWriter::new(dest).write_str(value)); + try!(dest.write_char('"')); + Ok(()) +} + + +/// A `TextWriter` adaptor that escapes text for writing as a CSS string. +/// Quotes are not included. +/// +/// Typical usage: +/// +/// ```{rust,ignore} +/// fn write_foo(foo: &Foo, dest: &mut W) -> text_writer::Result where W: TextWriter { +/// try!(dest.write_char('"')); +/// { +/// let mut string_dest = CssStringWriter::new(dest); +/// // Write into string_dest... +/// } +/// try!(dest.write_char('"')); +/// Ok(()) +/// } +/// ``` +pub struct CssStringWriter<'a, W: 'a> { + inner: &'a mut W, +} + +impl<'a, W> CssStringWriter<'a, W> where W: TextWriter { + pub fn new(inner: &'a mut W) -> CssStringWriter<'a, W> { + CssStringWriter { inner: inner } + } +} + +impl<'a, W> TextWriter for CssStringWriter<'a, W> where W: TextWriter { + fn write_str(&mut self, s: &str) -> text_writer::Result { + // TODO: avoid decoding/re-encoding UTF-8? + for c in s.chars() { + try!(self.write_char(c)) + } + Ok(()) + } + + fn write_char(&mut self, c: char) -> text_writer::Result { match c { - '"' => try!(dest.write_str("\\\"")), - '\\' => try!(dest.write_str("\\\\")), - '\n' => try!(dest.write_str("\\A ")), - '\r' => try!(dest.write_str("\\D ")), - '\x0C' => try!(dest.write_str("\\C ")), - _ => try!(dest.write_char(c)), - }; + '"' => self.inner.write_str("\\\""), + '\\' => self.inner.write_str("\\\\"), + '\n' => self.inner.write_str("\\A "), + '\r' => self.inner.write_str("\\D "), + '\x0C' => self.inner.write_str("\\C "), + _ => self.inner.write_char(c), + } } - dest.write_char('"') } From d405f03db7e23d21df7b37af8f0ad5c496d671c6 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sun, 21 Dec 2014 00:59:44 +0000 Subject: [PATCH 7/7] Upgrade to rustc 0.13.0-nightly (99d6956c3 2014-12-18 20:32:07 +0000) --- src/serializer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serializer.rs b/src/serializer.rs index e28aa025..ba911edf 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -38,7 +38,7 @@ pub trait ToCss for Sized? { /// (This is a convenience wrapper for `to_css` and probably should not be overridden.) #[inline] fn fmt_to_css(&self, dest: &mut W) -> fmt::Result where W: TextWriter { - self.to_css(dest).map_err(|_| fmt::WriteError) + self.to_css(dest).map_err(|_| fmt::Error) } }