1 | // sass.hpp must go before all system headers to get the |
2 | // __EXTENSIONS__ fix on Solaris. |
3 | #include "sass.hpp" |
4 | |
5 | #include "ast.hpp" |
6 | #include "output.hpp" |
7 | #include "util.hpp" |
8 | |
9 | namespace Sass { |
10 | |
11 | Output::Output(Sass_Output_Options& opt) |
12 | : Inspect(Emitter(opt)), |
13 | charset("" ), |
14 | top_nodes(0) |
15 | {} |
16 | |
17 | Output::~Output() { } |
18 | |
19 | void Output::fallback_impl(AST_Node* n) |
20 | { |
21 | return n->perform(op: this); |
22 | } |
23 | |
24 | void Output::operator()(Number* n) |
25 | { |
26 | // check for a valid unit here |
27 | // includes result for reporting |
28 | if (!n->is_valid_css_unit()) { |
29 | // should be handle in check_expression |
30 | throw Exception::InvalidValue({}, *n); |
31 | } |
32 | // use values to_string facility |
33 | sass::string res = n->to_string(opt); |
34 | // output the final token |
35 | append_token(text: res, node: n); |
36 | } |
37 | |
38 | void Output::operator()(Import* imp) |
39 | { |
40 | top_nodes.push_back(x: imp); |
41 | } |
42 | |
43 | void Output::operator()(Map* m) |
44 | { |
45 | // should be handle in check_expression |
46 | throw Exception::InvalidValue({}, *m); |
47 | } |
48 | |
49 | OutputBuffer Output::get_buffer(void) |
50 | { |
51 | |
52 | Emitter emitter(opt); |
53 | Inspect inspect(emitter); |
54 | |
55 | size_t size_nodes = top_nodes.size(); |
56 | for (size_t i = 0; i < size_nodes; i++) { |
57 | top_nodes[i]->perform(op: &inspect); |
58 | inspect.append_mandatory_linefeed(); |
59 | } |
60 | |
61 | // flush scheduled outputs |
62 | // maybe omit semicolon if possible |
63 | inspect.finalize(final: wbuf.buffer.size() == 0); |
64 | // prepend buffer on top |
65 | prepend_output(out: inspect.output()); |
66 | // make sure we end with a linefeed |
67 | if (!ends_with(str: wbuf.buffer, suffix: opt.linefeed)) { |
68 | // if the output is not completely empty |
69 | if (!wbuf.buffer.empty()) append_string(text: opt.linefeed); |
70 | } |
71 | |
72 | // search for unicode char |
73 | for(const char& chr : wbuf.buffer) { |
74 | // skip all ascii chars |
75 | // static cast to unsigned to handle `char` being signed / unsigned |
76 | if (static_cast<unsigned>(chr) < 128) continue; |
77 | // declare the charset |
78 | if (output_style() != COMPRESSED) |
79 | charset = "@charset \"UTF-8\";" |
80 | + sass::string(opt.linefeed); |
81 | else charset = "\xEF\xBB\xBF" ; |
82 | // abort search |
83 | break; |
84 | } |
85 | |
86 | // add charset as first line, before comments and imports |
87 | if (!charset.empty()) prepend_string(text: charset); |
88 | |
89 | return wbuf; |
90 | |
91 | } |
92 | |
93 | void Output::(Comment* c) |
94 | { |
95 | // if (indentation && txt == "/**/") return; |
96 | bool important = c->is_important(); |
97 | if (output_style() != COMPRESSED || important) { |
98 | if (buffer().size() == 0) { |
99 | top_nodes.push_back(x: c); |
100 | } else { |
101 | in_comment = true; |
102 | append_indentation(); |
103 | c->text()->perform(op: this); |
104 | in_comment = false; |
105 | if (indentation == 0) { |
106 | append_mandatory_linefeed(); |
107 | } else { |
108 | append_optional_linefeed(); |
109 | } |
110 | } |
111 | } |
112 | } |
113 | |
114 | void Output::operator()(StyleRule* r) |
115 | { |
116 | Block_Obj b = r->block(); |
117 | SelectorListObj s = r->selector(); |
118 | |
119 | if (!s || s->empty()) return; |
120 | |
121 | // Filter out rulesets that aren't printable (process its children though) |
122 | if (!Util::isPrintable(r, style: output_style())) { |
123 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
124 | const Statement_Obj& stm = b->get(i); |
125 | if (Cast<ParentStatement>(ptr: stm)) { |
126 | if (!Cast<Declaration>(ptr: stm)) { |
127 | stm->perform(op: this); |
128 | } |
129 | } |
130 | } |
131 | return; |
132 | } |
133 | |
134 | if (output_style() == NESTED) { |
135 | indentation += r->tabs(); |
136 | } |
137 | if (opt.source_comments) { |
138 | sass::ostream ss; |
139 | append_indentation(); |
140 | sass::string path(File::abs2rel(path: r->pstate().getPath())); |
141 | ss << "/* line " << r->pstate().getLine() << ", " << path << " */" ; |
142 | append_string(text: ss.str()); |
143 | append_optional_linefeed(); |
144 | } |
145 | scheduled_crutch = s; |
146 | if (s) s->perform(op: this); |
147 | append_scope_opener(node: b); |
148 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
149 | Statement_Obj stm = b->get(i); |
150 | bool bPrintExpression = true; |
151 | // Check print conditions |
152 | if (Declaration* dec = Cast<Declaration>(ptr: stm)) { |
153 | if (const String_Constant* valConst = Cast<String_Constant>(ptr: dec->value())) { |
154 | const sass::string& val = valConst->value(); |
155 | if (const String_Quoted* qstr = Cast<const String_Quoted>(ptr: valConst)) { |
156 | if (!qstr->quote_mark() && val.empty()) { |
157 | bPrintExpression = false; |
158 | } |
159 | } |
160 | } |
161 | else if (List* list = Cast<List>(ptr: dec->value())) { |
162 | bool all_invisible = true; |
163 | for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) { |
164 | Expression* item = list->get(i: list_i); |
165 | if (!item->is_invisible()) all_invisible = false; |
166 | } |
167 | if (all_invisible && !list->is_bracketed()) bPrintExpression = false; |
168 | } |
169 | } |
170 | // Print if OK |
171 | if (bPrintExpression) { |
172 | stm->perform(op: this); |
173 | } |
174 | } |
175 | if (output_style() == NESTED) indentation -= r->tabs(); |
176 | append_scope_closer(node: b); |
177 | |
178 | } |
179 | void Output::operator()(Keyframe_Rule* r) |
180 | { |
181 | Block_Obj b = r->block(); |
182 | Selector_Obj v = r->name(); |
183 | |
184 | if (!v.isNull()) { |
185 | v->perform(op: this); |
186 | } |
187 | |
188 | if (!b) { |
189 | append_colon_separator(); |
190 | return; |
191 | } |
192 | |
193 | append_scope_opener(); |
194 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
195 | Statement_Obj stm = b->get(i); |
196 | stm->perform(op: this); |
197 | if (i < L - 1) append_special_linefeed(); |
198 | } |
199 | append_scope_closer(); |
200 | } |
201 | |
202 | void Output::operator()(SupportsRule* f) |
203 | { |
204 | if (f->is_invisible()) return; |
205 | |
206 | SupportsConditionObj c = f->condition(); |
207 | Block_Obj b = f->block(); |
208 | |
209 | // Filter out feature blocks that aren't printable (process its children though) |
210 | if (!Util::isPrintable(r: f, style: output_style())) { |
211 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
212 | Statement_Obj stm = b->get(i); |
213 | if (Cast<ParentStatement>(ptr: stm)) { |
214 | stm->perform(op: this); |
215 | } |
216 | } |
217 | return; |
218 | } |
219 | |
220 | if (output_style() == NESTED) indentation += f->tabs(); |
221 | append_indentation(); |
222 | append_token(text: "@supports" , node: f); |
223 | append_mandatory_space(); |
224 | c->perform(op: this); |
225 | append_scope_opener(); |
226 | |
227 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
228 | Statement_Obj stm = b->get(i); |
229 | stm->perform(op: this); |
230 | if (i < L - 1) append_special_linefeed(); |
231 | } |
232 | |
233 | if (output_style() == NESTED) indentation -= f->tabs(); |
234 | |
235 | append_scope_closer(); |
236 | |
237 | } |
238 | |
239 | void Output::operator()(CssMediaRule* rule) |
240 | { |
241 | // Avoid null pointer exception |
242 | if (rule == nullptr) return; |
243 | // Skip empty/invisible rule |
244 | if (rule->isInvisible()) return; |
245 | // Avoid null pointer exception |
246 | if (rule->block() == nullptr) return; |
247 | // Skip empty/invisible rule |
248 | if (rule->block()->isInvisible()) return; |
249 | // Skip if block is empty/invisible |
250 | if (Util::isPrintable(r: rule, style: output_style())) { |
251 | // Let inspect do its magic |
252 | Inspect::operator()(rule); |
253 | } |
254 | } |
255 | |
256 | void Output::operator()(AtRule* a) |
257 | { |
258 | sass::string kwd = a->keyword(); |
259 | Selector_Obj s = a->selector(); |
260 | ExpressionObj v = a->value(); |
261 | Block_Obj b = a->block(); |
262 | |
263 | append_indentation(); |
264 | append_token(text: kwd, node: a); |
265 | if (s) { |
266 | append_mandatory_space(); |
267 | in_wrapped = true; |
268 | s->perform(op: this); |
269 | in_wrapped = false; |
270 | } |
271 | if (v) { |
272 | append_mandatory_space(); |
273 | // ruby sass bug? should use options? |
274 | append_token(text: v->to_string(/* opt */), node: v); |
275 | } |
276 | if (!b) { |
277 | append_delimiter(); |
278 | return; |
279 | } |
280 | |
281 | if (b->is_invisible() || b->length() == 0) { |
282 | append_optional_space(); |
283 | return append_string(text: "{}" ); |
284 | } |
285 | |
286 | append_scope_opener(); |
287 | |
288 | bool format = kwd != "@font-face" ;; |
289 | |
290 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
291 | Statement_Obj stm = b->get(i); |
292 | if (stm) stm->perform(op: this); |
293 | if (i < L - 1 && format) append_special_linefeed(); |
294 | } |
295 | |
296 | append_scope_closer(); |
297 | } |
298 | |
299 | void Output::operator()(String_Quoted* s) |
300 | { |
301 | if (s->quote_mark()) { |
302 | append_token(text: quote(s->value(), q: s->quote_mark()), node: s); |
303 | } else if (!in_comment) { |
304 | append_token(text: string_to_output(str: s->value()), node: s); |
305 | } else { |
306 | append_token(text: s->value(), node: s); |
307 | } |
308 | } |
309 | |
310 | void Output::operator()(String_Constant* s) |
311 | { |
312 | sass::string value(s->value()); |
313 | if (!in_comment && !in_custom_property) { |
314 | append_token(text: string_to_output(str: value), node: s); |
315 | } else { |
316 | append_token(text: value, node: s); |
317 | } |
318 | } |
319 | |
320 | } |
321 | |