|
| 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 => { |
| 104 | + css.push_str("\\-"); |
| 105 | + return |
| 106 | + } |
| 107 | + Some(c) => { |
| 108 | + css.push_char('-'); |
| 109 | + c |
| 110 | + }, |
| 111 | + } |
| 112 | + }; |
| 113 | + match c { |
| 114 | + 'A'..'Z' | 'a'..'z' | '_' => css.push_char(c), |
| 115 | + _ if c > '\x7F' => css.push_char(c), |
| 116 | + '\n' => css.push_str("\\A "), |
| 117 | + '\r' => css.push_str("\\D "), |
| 118 | + '\x0C' => css.push_str("\\C "), |
| 119 | + '0'..'9' => css.push_str(format!("\\\\3{} ", c)), |
| 120 | + _ => { css.push_char('\\'); css.push_char(c) }, |
| 121 | + } |
| 122 | + for c in iter { |
| 123 | + match c { |
| 124 | + '0'..'9' | 'A'..'Z' | 'a'..'z' | '_' | '-' => css.push_char(c), |
| 125 | + _ if c > '\x7F' => css.push_char(c), |
| 126 | + '\n' => css.push_str("\\A "), |
| 127 | + '\r' => css.push_str("\\D "), |
| 128 | + '\x0C' => css.push_str("\\C "), |
| 129 | + _ => { css.push_char('\\'); css.push_char(c) }, |
| 130 | + } |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +pub fn serialize_string(value: &str, css: &mut ~str) { |
| 135 | + css.push_char('"'); |
| 136 | + // TODO: avoid decoding/re-encoding UTF-8? |
| 137 | + for c in value.iter() { |
| 138 | + match c { |
| 139 | + '"' => css.push_str("\""), |
| 140 | + '\\' => css.push_str("\\\\"), |
| 141 | + '\n' => css.push_str("\\A "), |
| 142 | + '\r' => css.push_str("\\D "), |
| 143 | + '\x0C' => css.push_str("\\C "), |
| 144 | + _ => css.push_char(c), |
| 145 | + } |
| 146 | + } |
| 147 | + css.push_char('"'); |
| 148 | +} |
| 149 | + |
| 150 | + |
| 151 | +pub trait ToCss { |
| 152 | + fn to_css(&mut self) -> ~str { |
| 153 | + let mut css = ~""; |
| 154 | + self.to_css_push(&mut css); |
| 155 | + css |
| 156 | + } |
| 157 | + |
| 158 | + fn to_css_push(&mut self, css: &mut ~str); |
| 159 | +} |
| 160 | + |
| 161 | + |
| 162 | +impl<'self, I: Iterator<&'self ComponentValue>> ToCss for I { |
| 163 | + fn to_css_push(&mut self, css: &mut ~str) { |
| 164 | + let mut previous = match self.next() { |
| 165 | + None => return, |
| 166 | + Some(first) => { first.to_css_push(css); first } |
| 167 | + }; |
| 168 | + macro_rules! matches( |
| 169 | + ($value:expr, $($pattern:pat)|+) => ( |
| 170 | + match $value { $($pattern)|+ => true, _ => false } |
| 171 | + ); |
| 172 | + ) |
| 173 | + loop { match self.next() { None => break, Some(component_value) => { |
| 174 | + let (a, b) = (previous, component_value); |
| 175 | + if ( |
| 176 | + matches!(*a, Hash(*) | IDHash(*) | AtKeyword(*)) && |
| 177 | + matches!(*b, Number(*) | Percentage(*) | Ident(*) | Dimension(*) | |
| 178 | + UnicodeRange(*) | URL(*) | Function(*)) |
| 179 | + ) || ( |
| 180 | + matches!(*a, Number(*) | Ident(*) | Dimension(*)) && |
| 181 | + matches!(*b, Number(*) | Ident(*) | Dimension(*)) |
| 182 | + ) || ( |
| 183 | + matches!(*a, Number(*) | Ident(*) | Dimension(*)) && |
| 184 | + matches!(*b, Percentage(*) | UnicodeRange(*) | URL(*) | Function(*)) |
| 185 | + ) || ( |
| 186 | + matches!(*a, Ident(*)) && |
| 187 | + matches!(*b, ParenthesisBlock(*)) |
| 188 | + ) || ( |
| 189 | + matches!(*a, Delim('#') | Delim('@')) && |
| 190 | + !matches!(*b, WhiteSpace) |
| 191 | + ) || ( |
| 192 | + matches!(*a, Delim('-') | Delim('+') | Delim('.') | Delim('<') | |
| 193 | + Delim('>') | Delim('!')) && |
| 194 | + !matches!(*b, WhiteSpace) |
| 195 | + ) || ( |
| 196 | + !matches!(*a, WhiteSpace) && |
| 197 | + matches!(*b, Delim('-') | Delim('+') | Delim('.') | Delim('<') | |
| 198 | + Delim('>') | Delim('!')) |
| 199 | + ) || ( |
| 200 | + matches!(*a, Delim('/')) && |
| 201 | + matches!(*b, Delim('*')) |
| 202 | + ) || ( |
| 203 | + matches!(*a, Delim('*')) && |
| 204 | + matches!(*b, Delim('/')) |
| 205 | + ) { |
| 206 | + css.push_str("/**/") |
| 207 | + } |
| 208 | + component_value.to_css_push(css); |
| 209 | + previous = component_value; |
| 210 | + }}} |
| 211 | + } |
| 212 | +} |
0 commit comments