1 | /** |
2 | * sass2scss |
3 | * Licensed under the MIT License |
4 | * Copyright (c) Marcel Greter |
5 | */ |
6 | |
7 | #ifdef _MSC_VER |
8 | #define _CRT_SECURE_NO_WARNINGS |
9 | #define _CRT_NONSTDC_NO_DEPRECATE |
10 | #endif |
11 | |
12 | // include library |
13 | #include <stack> |
14 | #include <string> |
15 | #include <cstring> |
16 | #include <cstdlib> |
17 | #include <sstream> |
18 | #include <iostream> |
19 | #include <stdio.h> |
20 | |
21 | ///* |
22 | // |
23 | // src comments: comments in sass syntax (staring with //) |
24 | // css comments: multiline comments in css syntax (starting with /*) |
25 | // |
26 | // KEEP_COMMENT: keep src comments in the resulting css code |
27 | // STRIP_COMMENT: strip out all comments (either src or css) |
28 | // CONVERT_COMMENT: convert all src comments to css comments |
29 | // |
30 | //*/ |
31 | |
32 | // our own header |
33 | #include "sass2scss.h" |
34 | |
35 | // add namespace for c++ |
36 | namespace Sass |
37 | { |
38 | |
39 | // return the actual prettify value from options |
40 | #define PRETTIFY(converter) (converter.options - (converter.options & 248)) |
41 | // query the options integer to check if the option is enables |
42 | #define (converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT) |
43 | #define (converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT) |
44 | #define (converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT) |
45 | |
46 | // some makros to access the indentation stack |
47 | #define INDENT(converter) (converter.indents.top()) |
48 | |
49 | // some makros to query comment parser status |
50 | #define IS_PARSING(converter) (converter.comment == "") |
51 | #define (converter) (converter.comment != "") |
52 | #define (converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter)) |
53 | #define (converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter))) |
54 | |
55 | // pretty printer helper function |
56 | static std::string closer (const converter& converter) |
57 | { |
58 | return PRETTIFY(converter) == 0 ? " }" : |
59 | PRETTIFY(converter) <= 1 ? " }" : |
60 | "\n" + INDENT(converter) + "}" ; |
61 | } |
62 | |
63 | // pretty printer helper function |
64 | static std::string opener (const converter& converter) |
65 | { |
66 | return PRETTIFY(converter) == 0 ? " { " : |
67 | PRETTIFY(converter) <= 2 ? " {" : |
68 | "\n" + INDENT(converter) + "{" ; |
69 | } |
70 | |
71 | // check if the given string is a pseudo selector |
72 | // needed to differentiate from sass property syntax |
73 | static bool isPseudoSelector (std::string& sel) |
74 | { |
75 | |
76 | size_t len = sel.length(); |
77 | if (len < 1) return false; |
78 | size_t pos = sel.find_first_not_of(s: "abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ" , pos: 1); |
79 | if (pos != std::string::npos) sel.erase(pos: pos, n: std::string::npos); |
80 | size_t i = sel.length(); |
81 | while (i -- > 0) { sel.at(n: i) = tolower(c: sel.at(n: i)); } |
82 | |
83 | // CSS Level 1 - Recommendation |
84 | if (sel == ":link" ) return true; |
85 | if (sel == ":visited" ) return true; |
86 | if (sel == ":active" ) return true; |
87 | |
88 | // CSS Level 2 (Revision 1) - Recommendation |
89 | if (sel == ":lang" ) return true; |
90 | if (sel == ":first-child" ) return true; |
91 | if (sel == ":hover" ) return true; |
92 | if (sel == ":focus" ) return true; |
93 | // disabled - also valid properties |
94 | // if (sel == ":left") return true; |
95 | // if (sel == ":right") return true; |
96 | if (sel == ":first" ) return true; |
97 | |
98 | // Selectors Level 3 - Recommendation |
99 | if (sel == ":target" ) return true; |
100 | if (sel == ":root" ) return true; |
101 | if (sel == ":nth-child" ) return true; |
102 | if (sel == ":nth-last-of-child" ) return true; |
103 | if (sel == ":nth-of-type" ) return true; |
104 | if (sel == ":nth-last-of-type" ) return true; |
105 | if (sel == ":last-child" ) return true; |
106 | if (sel == ":first-of-type" ) return true; |
107 | if (sel == ":last-of-type" ) return true; |
108 | if (sel == ":only-child" ) return true; |
109 | if (sel == ":only-of-type" ) return true; |
110 | if (sel == ":empty" ) return true; |
111 | if (sel == ":not" ) return true; |
112 | |
113 | // CSS Basic User Interface Module Level 3 - Working Draft |
114 | if (sel == ":default" ) return true; |
115 | if (sel == ":valid" ) return true; |
116 | if (sel == ":invalid" ) return true; |
117 | if (sel == ":in-range" ) return true; |
118 | if (sel == ":out-of-range" ) return true; |
119 | if (sel == ":required" ) return true; |
120 | if (sel == ":optional" ) return true; |
121 | if (sel == ":read-only" ) return true; |
122 | if (sel == ":read-write" ) return true; |
123 | if (sel == ":dir" ) return true; |
124 | if (sel == ":enabled" ) return true; |
125 | if (sel == ":disabled" ) return true; |
126 | if (sel == ":checked" ) return true; |
127 | if (sel == ":indeterminate" ) return true; |
128 | if (sel == ":nth-last-child" ) return true; |
129 | |
130 | // Selectors Level 4 - Working Draft |
131 | if (sel == ":any-link" ) return true; |
132 | if (sel == ":local-link" ) return true; |
133 | if (sel == ":scope" ) return true; |
134 | if (sel == ":active-drop-target" ) return true; |
135 | if (sel == ":valid-drop-target" ) return true; |
136 | if (sel == ":invalid-drop-target" ) return true; |
137 | if (sel == ":current" ) return true; |
138 | if (sel == ":past" ) return true; |
139 | if (sel == ":future" ) return true; |
140 | if (sel == ":placeholder-shown" ) return true; |
141 | if (sel == ":user-error" ) return true; |
142 | if (sel == ":blank" ) return true; |
143 | if (sel == ":nth-match" ) return true; |
144 | if (sel == ":nth-last-match" ) return true; |
145 | if (sel == ":nth-column" ) return true; |
146 | if (sel == ":nth-last-column" ) return true; |
147 | if (sel == ":matches" ) return true; |
148 | |
149 | // Fullscreen API - Living Standard |
150 | if (sel == ":fullscreen" ) return true; |
151 | |
152 | // not a pseudo selector |
153 | return false; |
154 | |
155 | } |
156 | |
157 | static size_t findFirstCharacter (std::string& sass, size_t pos) |
158 | { |
159 | return sass.find_first_not_of(str: SASS2SCSS_FIND_WHITESPACE, pos: pos); |
160 | } |
161 | |
162 | static size_t findLastCharacter (std::string& sass, size_t pos) |
163 | { |
164 | return sass.find_last_not_of(str: SASS2SCSS_FIND_WHITESPACE, pos: pos); |
165 | } |
166 | |
167 | static bool isUrl (std::string& sass, size_t pos) |
168 | { |
169 | return sass[pos] == 'u' && sass[pos+1] == 'r' && sass[pos+2] == 'l' && sass[pos+3] == '('; |
170 | } |
171 | |
172 | // check if there is some char data |
173 | // will ignore everything in comments |
174 | static bool hasCharData (std::string& sass) |
175 | { |
176 | |
177 | size_t col_pos = 0; |
178 | |
179 | while (true) |
180 | { |
181 | |
182 | // try to find some meaningfull char |
183 | col_pos = sass.find_first_not_of(s: " \t\n\v\f\r" , pos: col_pos); |
184 | |
185 | // there was no meaningfull char found |
186 | if (col_pos == std::string::npos) return false; |
187 | |
188 | // found a multiline comment opener |
189 | if (sass.substr(pos: col_pos, n: 2) == "/*" ) |
190 | { |
191 | // find the multiline comment closer |
192 | col_pos = sass.find(s: "*/" , pos: col_pos); |
193 | // maybe we did not find the closer here |
194 | if (col_pos == std::string::npos) return false; |
195 | // skip closer |
196 | col_pos += 2; |
197 | } |
198 | else |
199 | { |
200 | return true; |
201 | } |
202 | |
203 | } |
204 | |
205 | } |
206 | // EO hasCharData |
207 | |
208 | // find src comment opener |
209 | // correctly skips quoted strings |
210 | static size_t (std::string& sass) |
211 | { |
212 | |
213 | size_t col_pos = 0; |
214 | bool apoed = false; |
215 | bool quoted = false; |
216 | bool = false; |
217 | size_t brackets = 0; |
218 | |
219 | while (col_pos != std::string::npos) |
220 | { |
221 | |
222 | // process all interesting chars |
223 | col_pos = sass.find_first_of(s: "\"\'/\\*()" , pos: col_pos); |
224 | |
225 | // assertion for valid result |
226 | if (col_pos != std::string::npos) |
227 | { |
228 | char character = sass.at(n: col_pos); |
229 | |
230 | if (character == '(') |
231 | { |
232 | if (!quoted && !apoed) brackets ++; |
233 | } |
234 | else if (character == ')') |
235 | { |
236 | if (!quoted && !apoed) brackets --; |
237 | } |
238 | else if (character == '\"') |
239 | { |
240 | // invert quote bool |
241 | if (!apoed && !comment) quoted = !quoted; |
242 | } |
243 | else if (character == '\'') |
244 | { |
245 | // invert quote bool |
246 | if (!quoted && !comment) apoed = !apoed; |
247 | } |
248 | else if (col_pos > 0 && character == '/') |
249 | { |
250 | if (sass.at(n: col_pos - 1) == '*') |
251 | { |
252 | comment = false; |
253 | } |
254 | // next needs to be a slash too |
255 | else if (sass.at(n: col_pos - 1) == '/') |
256 | { |
257 | // only found if not in single or double quote, bracket or comment |
258 | if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1; |
259 | } |
260 | } |
261 | else if (character == '\\') |
262 | { |
263 | // skip next char if in quote |
264 | if (quoted || apoed) col_pos ++; |
265 | } |
266 | // this might be a comment opener |
267 | else if (col_pos > 0 && character == '*') |
268 | { |
269 | // opening a multiline comment |
270 | if (sass.at(n: col_pos - 1) == '/') |
271 | { |
272 | // we are now in a comment |
273 | if (!quoted && !apoed) comment = true; |
274 | } |
275 | } |
276 | |
277 | // skip char |
278 | col_pos ++; |
279 | |
280 | } |
281 | |
282 | } |
283 | // EO while |
284 | |
285 | return col_pos; |
286 | |
287 | } |
288 | // EO findCommentOpener |
289 | |
290 | // remove multiline comments from sass string |
291 | // correctly skips quoted strings |
292 | static std::string (std::string &sass) |
293 | { |
294 | |
295 | std::string clean = "" ; |
296 | size_t col_pos = 0; |
297 | size_t open_pos = 0; |
298 | size_t close_pos = 0; |
299 | bool apoed = false; |
300 | bool quoted = false; |
301 | bool = false; |
302 | |
303 | // process sass til string end |
304 | while (col_pos != std::string::npos) |
305 | { |
306 | |
307 | // process all interesting chars |
308 | col_pos = sass.find_first_of(s: "\"\'/\\*" , pos: col_pos); |
309 | |
310 | // assertion for valid result |
311 | if (col_pos != std::string::npos) |
312 | { |
313 | char character = sass.at(n: col_pos); |
314 | |
315 | // found quoted string delimiter |
316 | if (character == '\"') |
317 | { |
318 | if (!apoed && !comment) quoted = !quoted; |
319 | } |
320 | else if (character == '\'') |
321 | { |
322 | if (!quoted && !comment) apoed = !apoed; |
323 | } |
324 | // found possible comment closer |
325 | else if (character == '/') |
326 | { |
327 | // look back to see if it is actually a closer |
328 | if (comment && col_pos > 0 && sass.at(n: col_pos - 1) == '*') |
329 | { |
330 | close_pos = col_pos + 1; comment = false; |
331 | } |
332 | } |
333 | else if (character == '\\') |
334 | { |
335 | // skip escaped char |
336 | if (quoted || apoed) col_pos ++; |
337 | } |
338 | // this might be a comment opener |
339 | else if (character == '*') |
340 | { |
341 | // look back to see if it is actually an opener |
342 | if (!quoted && !apoed && col_pos > 0 && sass.at(n: col_pos - 1) == '/') |
343 | { |
344 | comment = true; open_pos = col_pos - 1; |
345 | clean += sass.substr(pos: close_pos, n: open_pos - close_pos); |
346 | } |
347 | } |
348 | |
349 | // skip char |
350 | col_pos ++; |
351 | |
352 | } |
353 | |
354 | } |
355 | // EO while |
356 | |
357 | // add final parts (add half open comment text) |
358 | if (comment) clean += sass.substr(pos: open_pos); |
359 | else clean += sass.substr(pos: close_pos); |
360 | |
361 | // return string |
362 | return clean; |
363 | |
364 | } |
365 | // EO removeMultilineComment |
366 | |
367 | // right trim a given string |
368 | std::string rtrim(const std::string &sass) |
369 | { |
370 | std::string trimmed = sass; |
371 | size_t pos_ws = trimmed.find_last_not_of(s: " \t\n\v\f\r" ); |
372 | if (pos_ws != std::string::npos) |
373 | { trimmed.erase(pos: pos_ws + 1); } |
374 | else { trimmed.clear(); } |
375 | return trimmed; |
376 | } |
377 | // EO rtrim |
378 | |
379 | // flush whitespace and print additional text, but |
380 | // only print additional chars and buffer whitespace |
381 | std::string flush (std::string& sass, converter& converter) |
382 | { |
383 | |
384 | // return flushed |
385 | std::string scss = "" ; |
386 | |
387 | // print whitespace buffer |
388 | scss += PRETTIFY(converter) > 0 ? |
389 | converter.whitespace : "" ; |
390 | // reset whitespace buffer |
391 | converter.whitespace = "" ; |
392 | |
393 | // remove possible newlines from string |
394 | size_t pos_right = sass.find_last_not_of(s: "\n\r" ); |
395 | if (pos_right == std::string::npos) return scss; |
396 | |
397 | // get the linefeeds from the string |
398 | std::string lfs = sass.substr(pos: pos_right + 1); |
399 | sass = sass.substr(pos: 0, n: pos_right + 1); |
400 | |
401 | // find some source comment opener |
402 | size_t = findCommentOpener(sass); |
403 | // check if there was a source comment |
404 | if (comment_pos != std::string::npos) |
405 | { |
406 | // convert comment (but only outside other coments) |
407 | if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter)) |
408 | { |
409 | // convert to multiline comment |
410 | sass.at(n: comment_pos + 1) = '*'; |
411 | // add comment node to the whitespace |
412 | sass += " */" ; |
413 | } |
414 | // not at line start |
415 | if (comment_pos > 0) |
416 | { |
417 | // also include whitespace before the actual comment opener |
418 | size_t ws_pos = sass.find_last_not_of(str: SASS2SCSS_FIND_WHITESPACE, pos: comment_pos - 1); |
419 | comment_pos = ws_pos == std::string::npos ? 0 : ws_pos + 1; |
420 | } |
421 | if (!STRIP_COMMENT(converter)) |
422 | { |
423 | // add comment node to the whitespace |
424 | converter.whitespace += sass.substr(pos: comment_pos); |
425 | } |
426 | else |
427 | { |
428 | // sass = removeMultilineComments(sass); |
429 | } |
430 | // update the actual sass code |
431 | sass = sass.substr(pos: 0, n: comment_pos); |
432 | } |
433 | |
434 | // add newline as getline discharged it |
435 | converter.whitespace += lfs + "\n" ; |
436 | |
437 | // maybe remove any leading whitespace |
438 | if (PRETTIFY(converter) == 0) |
439 | { |
440 | // remove leading whitespace and update string |
441 | size_t pos_left = sass.find_first_not_of(str: SASS2SCSS_FIND_WHITESPACE); |
442 | if (pos_left != std::string::npos) sass = sass.substr(pos: pos_left); |
443 | } |
444 | |
445 | // add flushed data |
446 | scss += sass; |
447 | |
448 | // return string |
449 | return scss; |
450 | |
451 | } |
452 | // EO flush |
453 | |
454 | // process a line of the sass text |
455 | std::string process (std::string& sass, converter& converter) |
456 | { |
457 | |
458 | // resulting string |
459 | std::string scss = "" ; |
460 | |
461 | // strip multi line comments |
462 | if (STRIP_COMMENT(converter)) |
463 | { |
464 | sass = removeMultilineComment(sass); |
465 | } |
466 | |
467 | // right trim input |
468 | sass = rtrim(sass); |
469 | |
470 | // get position of first meaningfull character in string |
471 | size_t pos_left = sass.find_first_not_of(str: SASS2SCSS_FIND_WHITESPACE); |
472 | |
473 | // special case for final run |
474 | if (converter.end_of_file) pos_left = 0; |
475 | |
476 | // maybe has only whitespace |
477 | if (pos_left == std::string::npos) |
478 | { |
479 | // just add complete whitespace |
480 | converter.whitespace += sass + "\n" ; |
481 | } |
482 | // have meaningfull first char |
483 | else |
484 | { |
485 | |
486 | // extract and store indentation string |
487 | std::string indent = sass.substr(pos: 0, n: pos_left); |
488 | |
489 | // check if current line starts a comment |
490 | std::string open = sass.substr(pos: pos_left, n: 2); |
491 | |
492 | // line has less or same indentation |
493 | // finalize previous open parser context |
494 | if (indent.length() <= INDENT(converter).length()) |
495 | { |
496 | |
497 | // close multilinie comment |
498 | if (IS_CSS_COMMENT(converter)) |
499 | { |
500 | // check if comments will be stripped anyway |
501 | if (!STRIP_COMMENT(converter)) scss += " */" ; |
502 | } |
503 | // close src comment comment |
504 | else if (IS_SRC_COMMENT(converter)) |
505 | { |
506 | // add a newline to avoid closer on same line |
507 | // this would put the bracket in the comment node |
508 | // no longer needed since we parse them correctly |
509 | // if (KEEP_COMMENT(converter)) scss += "\n"; |
510 | } |
511 | // close css properties |
512 | else if (converter.property) |
513 | { |
514 | // add closer unless in concat mode |
515 | if (!converter.comma) |
516 | { |
517 | // if there was no colon we have a selector |
518 | // looks like there were no inner properties |
519 | if (converter.selector) scss += " {}" ; |
520 | // add final semicolon |
521 | else if (!converter.semicolon) scss += ";" ; |
522 | } |
523 | } |
524 | |
525 | // reset comment state |
526 | converter.comment = "" ; |
527 | |
528 | } |
529 | |
530 | // make sure we close every "higher" block |
531 | while (indent.length() < INDENT(converter).length()) |
532 | { |
533 | // pop stacked context |
534 | converter.indents.pop(); |
535 | // print close bracket |
536 | if (IS_PARSING(converter)) |
537 | { scss += closer(converter); } |
538 | else { scss += " */" ; } |
539 | // reset comment state |
540 | converter.comment = "" ; |
541 | } |
542 | |
543 | // reset converter state |
544 | converter.selector = false; |
545 | |
546 | // looks like some undocumented behavior ... |
547 | // https://github.com/mgreter/sass2scss/issues/29 |
548 | if (sass.substr(pos: pos_left, n: 1) == "\\" ) { |
549 | converter.selector = true; |
550 | sass[pos_left] = ' '; |
551 | } |
552 | |
553 | // check if we have sass property syntax |
554 | if (sass.substr(pos: pos_left, n: 1) == ":" && sass.substr(pos: pos_left, n: 2) != "::" ) |
555 | { |
556 | |
557 | // default to a selector |
558 | // change back if property found |
559 | converter.selector = true; |
560 | // get position of first whitespace char |
561 | size_t pos_wspace = sass.find_first_of(str: SASS2SCSS_FIND_WHITESPACE, pos: pos_left); |
562 | // assertion check for valid result |
563 | if (pos_wspace != std::string::npos) |
564 | { |
565 | // get the possible pseudo selector |
566 | std::string pseudo = sass.substr(pos: pos_left, n: pos_wspace - pos_left); |
567 | // get position of the first real property value char |
568 | // pseudo selectors get this far, but have no actual value |
569 | size_t pos_value = sass.find_first_not_of(str: SASS2SCSS_FIND_WHITESPACE, pos: pos_wspace); |
570 | // assertion check for valid result |
571 | if (pos_value != std::string::npos) |
572 | { |
573 | // only process if not (fallowed by a semicolon or is a pseudo selector) |
574 | if (!(sass.at(n: pos_value) == ':' || isPseudoSelector(sel&: pseudo))) |
575 | { |
576 | // create new string by interchanging the colon sign for property and value |
577 | sass = indent + sass.substr(pos: pos_left + 1, n: pos_wspace - pos_left - 1) + ":" + sass.substr(pos: pos_wspace); |
578 | // try to find a colon in the current line, but only ... |
579 | size_t pos_colon = sass.find_first_not_of(s: ":" , pos: pos_left); |
580 | // assertion for valid result |
581 | if (pos_colon != std::string::npos) |
582 | { |
583 | // ... after the first word (skip beginning colons) |
584 | pos_colon = sass.find_first_of(s: ":" , pos: pos_colon); |
585 | // it is a selector if there was no colon found |
586 | converter.selector = pos_colon == std::string::npos; |
587 | } |
588 | } |
589 | } |
590 | } |
591 | |
592 | // check if we have a BEM property (one colon and no selector) |
593 | if (sass.substr(pos: pos_left, n: 1) == ":" && converter.selector == true) { |
594 | size_t pos_wspace = sass.find_first_of(str: SASS2SCSS_FIND_WHITESPACE, pos: pos_left); |
595 | sass = indent + sass.substr(pos: pos_left + 1, n: pos_wspace) + ":" ; |
596 | } |
597 | |
598 | } |
599 | |
600 | // terminate some statements immediately |
601 | else if ( |
602 | sass.substr(pos: pos_left, n: 5) == "@warn" || |
603 | sass.substr(pos: pos_left, n: 6) == "@debug" || |
604 | sass.substr(pos: pos_left, n: 6) == "@error" || |
605 | sass.substr(pos: pos_left, n: 6) == "@value" || |
606 | sass.substr(pos: pos_left, n: 8) == "@charset" || |
607 | sass.substr(pos: pos_left, n: 10) == "@namespace" |
608 | ) { sass = indent + sass.substr(pos: pos_left); } |
609 | // replace some specific sass shorthand directives (if not fallowed by a white space character) |
610 | else if (sass.substr(pos: pos_left, n: 1) == "=" ) |
611 | { sass = indent + "@mixin " + sass.substr(pos: pos_left + 1); } |
612 | else if (sass.substr(pos: pos_left, n: 1) == "+" ) |
613 | { |
614 | // must be followed by a mixin call (no whitespace afterwards or at ending directly) |
615 | if (sass[pos_left+1] != 0 && sass[pos_left+1] != ' ' && sass[pos_left+1] != '\t') { |
616 | sass = indent + "@include " + sass.substr(pos: pos_left + 1); |
617 | } |
618 | } |
619 | |
620 | // add quotes for import if needed |
621 | else if (sass.substr(pos: pos_left, n: 7) == "@import" ) |
622 | { |
623 | // get positions for the actual import url |
624 | size_t pos_import = sass.find_first_of(str: SASS2SCSS_FIND_WHITESPACE, pos: pos_left + 7); |
625 | size_t pos = sass.find_first_not_of(str: SASS2SCSS_FIND_WHITESPACE, pos: pos_import); |
626 | size_t start = pos; |
627 | bool in_dqstr = false; |
628 | bool in_sqstr = false; |
629 | bool is_escaped = false; |
630 | do { |
631 | if (is_escaped) { |
632 | is_escaped = false; |
633 | } |
634 | else if (sass[pos] == '\\') { |
635 | is_escaped = true; |
636 | } |
637 | else if (sass[pos] == '"') { |
638 | if (!in_sqstr) in_dqstr = ! in_dqstr; |
639 | } |
640 | else if (sass[pos] == '\'') { |
641 | if (!in_dqstr) in_sqstr = ! in_sqstr; |
642 | } |
643 | else if (in_dqstr || in_sqstr) { |
644 | // skip over quoted stuff |
645 | } |
646 | else if (sass[pos] == ',' || sass[pos] == 0) { |
647 | if (sass[start] != '"' && sass[start] != '\'' && !isUrl(sass, pos: start)) { |
648 | size_t end = findLastCharacter(sass, pos: pos - 1) + 1; |
649 | sass = sass.replace(pos: end, n1: 0, s: "\"" ); |
650 | sass = sass.replace(pos: start, n1: 0, s: "\"" ); |
651 | pos += 2; |
652 | } |
653 | start = findFirstCharacter(sass, pos: pos + 1); |
654 | } |
655 | } |
656 | while (sass[pos++] != 0); |
657 | |
658 | } |
659 | else if ( |
660 | sass.substr(pos: pos_left, n: 7) != "@return" && |
661 | sass.substr(pos: pos_left, n: 7) != "@extend" && |
662 | sass.substr(pos: pos_left, n: 8) != "@include" && |
663 | sass.substr(pos: pos_left, n: 8) != "@content" |
664 | ) { |
665 | |
666 | // probably a selector anyway |
667 | converter.selector = true; |
668 | // try to find first colon in the current line |
669 | size_t pos_colon = sass.find_first_of(s: ":" , pos: pos_left); |
670 | // assertion that we have a colon |
671 | if (pos_colon != std::string::npos) |
672 | { |
673 | // it is not a selector if we have a space after a colon |
674 | if (sass[pos_colon+1] == ' ') converter.selector = false; |
675 | if (sass[pos_colon+1] == ' ') converter.selector = false; |
676 | } |
677 | |
678 | } |
679 | |
680 | // current line has more indentation |
681 | if (indent.length() >= INDENT(converter).length()) |
682 | { |
683 | // not in comment mode |
684 | if (IS_PARSING(converter)) |
685 | { |
686 | // has meaningfull chars |
687 | if (hasCharData(sass)) |
688 | { |
689 | // is probably a property |
690 | // also true for selectors |
691 | converter.property = true; |
692 | } |
693 | } |
694 | } |
695 | // current line has more indentation |
696 | if (indent.length() > INDENT(converter).length()) |
697 | { |
698 | // not in comment mode |
699 | if (IS_PARSING(converter)) |
700 | { |
701 | // had meaningfull chars |
702 | if (converter.property) |
703 | { |
704 | // print block opener |
705 | scss += opener(converter); |
706 | // push new stack context |
707 | converter.indents.push(x: "" ); |
708 | // store block indentation |
709 | INDENT(converter) = indent; |
710 | } |
711 | } |
712 | // is and will be a src comment |
713 | else if (!IS_CSS_COMMENT(converter)) |
714 | { |
715 | // scss does not allow multiline src comments |
716 | // therefore add forward slashes to all lines |
717 | sass.at(INDENT(converter).length()+0) = '/'; |
718 | // there is an edge case here if indentation |
719 | // is minimal (will overwrite the fist char) |
720 | sass.at(INDENT(converter).length()+1) = '/'; |
721 | // could code around that, but I dont' think |
722 | // this will ever be the cause for any trouble |
723 | } |
724 | } |
725 | |
726 | // line is opening a new comment |
727 | if (open == "/*" || open == "//" ) |
728 | { |
729 | // reset the property state |
730 | converter.property = false; |
731 | // close previous comment |
732 | if (IS_CSS_COMMENT(converter) && open != "" ) |
733 | { |
734 | if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */" ; |
735 | } |
736 | // force single line comments |
737 | // into a correct css comment |
738 | if (CONVERT_COMMENT(converter)) |
739 | { |
740 | if (IS_PARSING(converter)) |
741 | { sass.at(n: pos_left + 1) = '*'; } |
742 | } |
743 | // set comment flag |
744 | converter.comment = open; |
745 | |
746 | } |
747 | |
748 | // flush data only under certain conditions |
749 | if (!( |
750 | // strip css and src comments if option is set |
751 | (IS_COMMENT(converter) && STRIP_COMMENT(converter)) || |
752 | // strip src comment even if strip option is not set |
753 | // but only if the keep src comment option is not set |
754 | (IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter)) |
755 | )) |
756 | { |
757 | // flush data and buffer whitespace |
758 | scss += flush(sass, converter); |
759 | } |
760 | |
761 | // get position of last meaningfull char |
762 | size_t pos_right = sass.find_last_not_of(str: SASS2SCSS_FIND_WHITESPACE); |
763 | |
764 | // check for invalid result |
765 | if (pos_right != std::string::npos) |
766 | { |
767 | |
768 | // get the last meaningfull char |
769 | std::string close = sass.substr(pos: pos_right, n: 1); |
770 | |
771 | // check if next line should be concatenated (list mode) |
772 | converter.comma = IS_PARSING(converter) && close == "," ; |
773 | converter.semicolon = IS_PARSING(converter) && close == ";" ; |
774 | |
775 | // check if we have more than |
776 | // one meaningfull char |
777 | if (pos_right > 0) |
778 | { |
779 | |
780 | // get the last two chars from string |
781 | std::string close = sass.substr(pos: pos_right - 1, n: 2); |
782 | // update parser status for expicitly closed comment |
783 | if (close == "*/" ) converter.comment = "" ; |
784 | |
785 | } |
786 | |
787 | } |
788 | // EO have meaningfull chars from end |
789 | |
790 | } |
791 | // EO have meaningfull chars from start |
792 | |
793 | // return scss |
794 | return scss; |
795 | |
796 | } |
797 | // EO process |
798 | |
799 | // read line with either CR, LF or CR LF format |
800 | // http://stackoverflow.com/a/6089413/1550314 |
801 | static std::istream& safeGetline(std::istream& is, std::string& t) |
802 | { |
803 | t.clear(); |
804 | |
805 | // The characters in the stream are read one-by-one using a std::streambuf. |
806 | // That is faster than reading them one-by-one using the std::istream. |
807 | // Code that uses streambuf this way must be guarded by a sentry object. |
808 | // The sentry object performs various tasks, |
809 | // such as thread synchronization and updating the stream state. |
810 | |
811 | std::istream::sentry se(is, true); |
812 | std::streambuf* sb = is.rdbuf(); |
813 | |
814 | for(;;) { |
815 | int c = sb->sbumpc(); |
816 | switch (c) { |
817 | case '\n': |
818 | return is; |
819 | case '\r': |
820 | if(sb->sgetc() == '\n') |
821 | sb->sbumpc(); |
822 | return is; |
823 | case EOF: |
824 | // Also handle the case when the last line has no line ending |
825 | if(t.empty()) |
826 | is.setstate(std::ios::eofbit); |
827 | return is; |
828 | default: |
829 | t += (char)c; |
830 | } |
831 | } |
832 | } |
833 | |
834 | // the main converter function for c++ |
835 | char* sass2scss (const std::string& sass, const int options) |
836 | { |
837 | |
838 | // local variables |
839 | std::string line; |
840 | std::string scss = "" ; |
841 | std::stringstream stream(sass); |
842 | |
843 | // create converter variable |
844 | converter converter; |
845 | // initialise all options |
846 | converter.comma = false; |
847 | converter.property = false; |
848 | converter.selector = false; |
849 | converter.semicolon = false; |
850 | converter.end_of_file = false; |
851 | converter.comment = "" ; |
852 | converter.whitespace = "" ; |
853 | converter.indents.push(x: "" ); |
854 | converter.options = options; |
855 | |
856 | // read line by line and process them |
857 | while(safeGetline(is&: stream, t&: line) && !stream.eof()) |
858 | { scss += process(sass&: line, converter); } |
859 | |
860 | // create mutable string |
861 | std::string closer = "" ; |
862 | // set the end of file flag |
863 | converter.end_of_file = true; |
864 | // process to close all open blocks |
865 | scss += process(sass&: closer, converter); |
866 | |
867 | // allocate new memory on the heap |
868 | // caller has to free it after use |
869 | char * cstr = (char*) malloc (size: scss.length() + 1); |
870 | // create a copy of the string |
871 | strcpy (dest: cstr, src: scss.c_str()); |
872 | // return pointer |
873 | return &cstr[0]; |
874 | |
875 | } |
876 | // EO sass2scss |
877 | |
878 | } |
879 | // EO namespace |
880 | |
881 | // implement for c |
882 | extern "C" |
883 | { |
884 | |
885 | char* ADDCALL sass2scss (const char* sass, const int options) |
886 | { |
887 | return Sass::sass2scss(sass, options); |
888 | } |
889 | |
890 | // Get compiled sass2scss version |
891 | const char* ADDCALL sass2scss_version(void) { |
892 | return SASS2SCSS_VERSION; |
893 | } |
894 | |
895 | } |
896 | |