use std::ascii::AsciiExt;
use std::cmp;
use std::fmt::{self, Write};
use super::{Token, NumericValue, PercentageValue};
pub trait ToCss {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write;
#[inline]
fn to_css_string(&self) -> String {
let mut s = String::new();
self.to_css(&mut s).unwrap();
s
}
#[inline]
fn fmt_to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.to_css(dest).map_err(|_| fmt::Error)
}
}
#[inline]
fn write_numeric<W>(value: NumericValue, dest: &mut W) -> fmt::Result where W: fmt::Write {
if value.has_sign && value.value.is_sign_positive() {
try!(dest.write_str("+"));
}
if value.value == 0.0 && value.value.is_sign_negative() {
try!(dest.write_str("-0"))
} else {
try!(write!(dest, "{}", value.value))
}
if value.int_value.is_none() && value.value.fract() == 0. {
try!(dest.write_str(".0"));
}
Ok(())
}
impl<'a> ToCss for Token<'a> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
Token::Ident(ref value) => try!(serialize_identifier(&**value, dest)),
Token::AtKeyword(ref value) => {
try!(dest.write_str("@"));
try!(serialize_identifier(&**value, dest));
},
Token::Hash(ref value) => {
try!(dest.write_str("#"));
try!(serialize_name(value, dest));
},
Token::IDHash(ref value) => {
try!(dest.write_str("#"));
try!(serialize_identifier(&**value, dest));
}
Token::QuotedString(ref value) => try!(serialize_string(&**value, dest)),
Token::Url(ref value) => {
try!(dest.write_str("url("));
try!(serialize_string(&**value, dest));
try!(dest.write_str(")"));
},
Token::Delim(value) => try!(write!(dest, "{}", value)),
Token::Number(value) => try!(write_numeric(value, dest)),
Token::Percentage(PercentageValue { unit_value, int_value, has_sign }) => {
let value = NumericValue {
value: unit_value * 100.,
int_value: int_value,
has_sign: has_sign,
};
try!(write_numeric(value, dest));
try!(dest.write_str("%"));
},
Token::Dimension(value, ref unit) => {
try!(write_numeric(value, dest));
let unit = &**unit;
if unit == "e" || unit == "E" || unit.starts_with("e-") || unit.starts_with("E-") {
try!(dest.write_str("\\65 "));
try!(serialize_name(&unit[1..], dest));
} else {
try!(serialize_identifier(unit, dest));
}
},
Token::UnicodeRange(start, end) => {
try!(dest.write_str("U+"));
let bits = cmp::min(start.trailing_zeros(), (!end).trailing_zeros());
let question_marks = bits / 4;
let bits = question_marks * 4;
let truncated_start = start >> bits;
let truncated_end = end >> bits;
if truncated_start == truncated_end {
if truncated_start != 0 {
try!(write!(dest, "{:X}", truncated_start));
}
for _ in (0..question_marks) {
try!(dest.write_str("?"));
}
} else {
try!(write!(dest, "{:X}", start));
if end != start {
try!(write!(dest, "-{:X}", end));
}
}
}
Token::WhiteSpace(content) => try!(dest.write_str(content)),
Token::Comment(content) => try!(write!(dest, "/*{}*/", content)),
Token::Colon => try!(dest.write_str(":")),
Token::Semicolon => try!(dest.write_str(";")),
Token::Comma => try!(dest.write_str(",")),
Token::IncludeMatch => try!(dest.write_str("~=")),
Token::DashMatch => try!(dest.write_str("|=")),
Token::PrefixMatch => try!(dest.write_str("^=")),
Token::SuffixMatch => try!(dest.write_str("$=")),
Token::SubstringMatch => try!(dest.write_str("*=")),
Token::Column => try!(dest.write_str("||")),
Token::CDO => try!(dest.write_str("<!--")),
Token::CDC => try!(dest.write_str("-->")),
Token::Function(ref name) => {
try!(serialize_identifier(&**name, dest));
try!(dest.write_str("("));
},
Token::ParenthesisBlock => try!(dest.write_str("(")),
Token::SquareBracketBlock => try!(dest.write_str("[")),
Token::CurlyBracketBlock => try!(dest.write_str("{")),
Token::BadUrl => try!(dest.write_str("url(<bad url>)")),
Token::BadString => try!(dest.write_str("\"<bad string>\n")),
Token::CloseParenthesis => try!(dest.write_str(")")),
Token::CloseSquareBracket => try!(dest.write_str("]")),
Token::CloseCurlyBracket => try!(dest.write_str("}")),
}
Ok(())
}
}
pub fn serialize_identifier<W>(mut value: &str, dest: &mut W) -> fmt::Result where W:fmt::Write {
if value.is_empty() {
return Ok(())
}
if value.starts_with("--") {
try!(dest.write_str("--"));
serialize_name(&value[2..], dest)
} else if value == "-" {
dest.write_str("\\-")
} else {
if value.as_bytes()[0] == b'-' {
try!(dest.write_str("-"));
value = &value[1..];
}
if let digit @ b'0'...b'9' = value.as_bytes()[0] {
try!(write!(dest, "\\3{} ", digit as char));
value = &value[1..];
}
serialize_name(value, dest)
}
}
fn serialize_name<W>(value: &str, dest: &mut W) -> fmt::Result where W:fmt::Write {
let mut chunk_start = 0;
for (i, b) in value.bytes().enumerate() {
let escaped = match b {
b'0'...b'9' | b'A'...b'Z' | b'a'...b'z' | b'_' | b'-' => continue,
_ if !b.is_ascii() => continue,
b'\n' => Some("\\A "),
b'\r' => Some("\\D "),
b'\x0C' => Some("\\C "),
_ => None,
};
try!(dest.write_str(&value[chunk_start..i]));
if let Some(escaped) = escaped {
try!(dest.write_str(escaped));
} else {
try!(write!(dest, "\\{}", b as char));
}
chunk_start = i + 1;
}
dest.write_str(&value[chunk_start..])
}
pub fn serialize_string<W>(value: &str, dest: &mut W) -> fmt::Result where W: fmt::Write {
try!(dest.write_str("\""));
try!(CssStringWriter::new(dest).write_str(value));
try!(dest.write_str("\""));
Ok(())
}
pub struct CssStringWriter<'a, W: 'a> {
inner: &'a mut W,
}
impl<'a, W> CssStringWriter<'a, W> where W: fmt::Write {
pub fn new(inner: &'a mut W) -> CssStringWriter<'a, W> {
CssStringWriter { inner: inner }
}
}
impl<'a, W> fmt::Write for CssStringWriter<'a, W> where W: fmt::Write {
fn write_str(&mut self, s: &str) -> fmt::Result {
let mut chunk_start = 0;
for (i, b) in s.bytes().enumerate() {
let escaped = match b {
b'"' => "\\\"",
b'\\' => "\\\\",
b'\n' => "\\A ",
b'\r' => "\\D ",
b'\x0C' => "\\C ",
_ => continue,
};
try!(self.inner.write_str(&s[chunk_start..i]));
try!(self.inner.write_str(escaped));
chunk_start = i + 1;
}
self.inner.write_str(&s[chunk_start..])
}
}
macro_rules! impl_tocss_for_number {
($T: ty) => {
impl<'a> ToCss for $T {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
write!(dest, "{}", *self)
}
}
}
}
impl_tocss_for_number!(f32);
impl_tocss_for_number!(f64);
impl_tocss_for_number!(i8);
impl_tocss_for_number!(u8);
impl_tocss_for_number!(i16);
impl_tocss_for_number!(u16);
impl_tocss_for_number!(i32);
impl_tocss_for_number!(u32);
impl_tocss_for_number!(i64);
impl_tocss_for_number!(u64);
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "heap_size", derive(HeapSizeOf))]
pub struct TokenSerializationType(TokenSerializationTypeVariants);
impl TokenSerializationType {
pub fn nothing() -> TokenSerializationType {
TokenSerializationType(TokenSerializationTypeVariants::Nothing)
}
pub fn set_if_nothing(&mut self, new_value: TokenSerializationType) {
if self.0 == TokenSerializationTypeVariants::Nothing {
self.0 = new_value.0
}
}
pub fn needs_separator_when_before(self, other: TokenSerializationType) -> bool {
use self::TokenSerializationTypeVariants::*;
match self.0 {
Ident => matches!(other.0,
Ident | Function | UrlOrBadUrl | DelimMinus | Number | Percentage | Dimension |
UnicodeRange | CDC | OpenParen),
AtKeywordOrHash | Dimension => matches!(other.0,
Ident | Function | UrlOrBadUrl | DelimMinus | Number | Percentage | Dimension |
UnicodeRange | CDC),
DelimHash | DelimMinus | Number => matches!(other.0,
Ident | Function | UrlOrBadUrl | DelimMinus | Number | Percentage | Dimension |
UnicodeRange),
DelimAt => matches!(other.0,
Ident | Function | UrlOrBadUrl | DelimMinus | UnicodeRange),
UnicodeRange => matches!(other.0,
Ident | Function | Number | Percentage | Dimension | DelimQuestion),
DelimDotOrPlus => matches!(other.0, Number | Percentage | Dimension),
DelimAssorted | DelimAsterisk => matches!(other.0, DelimEquals),
DelimBar => matches!(other.0, DelimEquals | DelimBar | DashMatch),
DelimSlash => matches!(other.0, DelimAsterisk | SubstringMatch),
Nothing | WhiteSpace | Percentage | UrlOrBadUrl | Function | CDC | OpenParen |
DashMatch | SubstringMatch | DelimQuestion | DelimEquals | Other => false,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "heap_size", derive(HeapSizeOf))]
enum TokenSerializationTypeVariants {
Nothing,
WhiteSpace,
AtKeywordOrHash,
Number,
Dimension,
Percentage,
UnicodeRange,
UrlOrBadUrl,
Function,
Ident,
CDC,
DashMatch,
SubstringMatch,
OpenParen, DelimHash, DelimAt, DelimDotOrPlus, DelimMinus, DelimQuestion, DelimAssorted, DelimEquals, DelimBar, DelimSlash, DelimAsterisk, Other, }
impl<'a> Token<'a> {
pub fn serialization_type(&self) -> TokenSerializationType {
use self::TokenSerializationTypeVariants::*;
TokenSerializationType(match *self {
Token::Ident(_) => Ident,
Token::AtKeyword(_) | Token::Hash(_) | Token::IDHash(_) => AtKeywordOrHash,
Token::Url(_) | Token::BadUrl => UrlOrBadUrl,
Token::Delim('#') => DelimHash,
Token::Delim('@') => DelimAt,
Token::Delim('.') | Token::Delim('+') => DelimDotOrPlus,
Token::Delim('-') => DelimMinus,
Token::Delim('?') => DelimQuestion,
Token::Delim('$') | Token::Delim('^') | Token::Delim('~') => DelimAssorted,
Token::Delim('=') => DelimEquals,
Token::Delim('|') => DelimBar,
Token::Delim('/') => DelimSlash,
Token::Delim('*') => DelimAsterisk,
Token::Number(_) => Number,
Token::Percentage(_) => Percentage,
Token::Dimension(..) => Dimension,
Token::UnicodeRange(..) => UnicodeRange,
Token::WhiteSpace(_) => WhiteSpace,
Token::Comment(_) => DelimSlash,
Token::DashMatch => DashMatch,
Token::SubstringMatch => SubstringMatch,
Token::Column => DelimBar,
Token::CDC => CDC,
Token::Function(_) => Function,
Token::ParenthesisBlock => OpenParen,
Token::SquareBracketBlock | Token::CurlyBracketBlock |
Token::CloseParenthesis | Token::CloseSquareBracket | Token::CloseCurlyBracket |
Token::QuotedString(_) | Token::BadString |
Token::Delim(_) | Token::Colon | Token::Semicolon | Token::Comma | Token::CDO |
Token::IncludeMatch | Token::PrefixMatch | Token::SuffixMatch
=> Other,
})
}
}