File Coverage

XS.xs
Criterion Covered Total %
statement 360 386 93.2
branch 263 330 79.7
condition n/a
subroutine n/a
pod n/a
total 623 716 87.0


line stmt bran cond sub pod time code
1             #include
2             #include
3             #include
4              
5             #include
6             #include
7             #include
8             #include
9              
10             const char* start_ie_hack = "/*\\*/";
11             const char* end_ie_hack = "/**/";
12              
13             /* ****************************************************************************
14             * CHARACTER CLASS METHODS
15             * ****************************************************************************
16             */
17 1536           bool charIsSpace(char ch) {
18 1536 100         if (ch == ' ') return 1;
19 953 100         if (ch == '\t') return 1;
20 947           return 0;
21             }
22 947           bool charIsEndspace(char ch) {
23 947 100         if (ch == '\n') return 1;
24 925 100         if (ch == '\r') return 1;
25 923 50         if (ch == '\f') return 1;
26 923           return 0;
27             }
28 1536           bool charIsWhitespace(char ch) {
29 1536 100         return charIsSpace(ch) || charIsEndspace(ch);
    100          
30             }
31 545           bool charIsNumeric(char ch) {
32 545 100         if ((ch >= '0') && (ch <= '9')) return 1;
    100          
33 325           return 0;
34             }
35 2149           bool charIsIdentifier(char ch) {
36 2149 100         if ((ch >= 'a') && (ch <= 'z')) return 1;
    100          
37 976 100         if ((ch >= 'A') && (ch <= 'Z')) return 1;
    100          
38 974 100         if ((ch >= '0') && (ch <= '9')) return 1;
    100          
39 712 50         if (ch == '_') return 1;
40 712 100         if (ch == '.') return 1;
41 661 100         if (ch == '#') return 1;
42 654 100         if (ch == '@') return 1;
43 648 100         if (ch == '%') return 1;
44 636           return 0;
45             }
46 831           bool charIsInfix(char ch) {
47             /* WS before+after these characters can be removed */
48 831 100         if (ch == '{') return 1;
49 513 100         if (ch == '}') return 1;
50 274 100         if (ch == ';') return 1;
51 239 100         if (ch == ',') return 1;
52 222 100         if (ch == '~') return 1;
53 217 100         if (ch == '>') return 1;
54 212           return 0;
55             }
56 577           bool charIsPrefix(char ch) {
57             /* WS after these characters can be removed */
58 577 100         if (ch == '(') return 1; /* requires leading WS when used in @media */
59 549 100         if (ch == ':') return 1; /* requires leading WS when used in pseudo-selector */
60 418           return charIsInfix(ch);
61             }
62 444           bool charIsPostfix(char ch) {
63             /* WS before these characters can be removed */
64 444 100         if (ch == ')') return 1; /* requires trailing WS for MSIE */
65 413           return charIsInfix(ch);
66             }
67              
68             /* ****************************************************************************
69             * TYPE DEFINITIONS
70             * ****************************************************************************
71             */
72             typedef enum {
73             NODE_EMPTY,
74             NODE_WHITESPACE,
75             NODE_BLOCKCOMMENT,
76             NODE_IDENTIFIER,
77             NODE_LITERAL,
78             NODE_SIGIL
79             } NodeType;
80              
81             struct _Node;
82             typedef struct _Node Node;
83             struct _Node {
84             /* linked list pointers */
85             Node* prev;
86             Node* next;
87             /* node internals */
88             const char* contents;
89             size_t length;
90             NodeType type;
91             bool can_prune;
92             };
93              
94             #define NODE_SET_SIZE 50000
95              
96             struct _NodeSet;
97             typedef struct _NodeSet NodeSet;
98             struct _NodeSet {
99             /* link to next NodeSet */
100             NodeSet* next;
101             /* Nodes in this Set */
102             Node nodes[NODE_SET_SIZE];
103             size_t next_node;
104             };
105              
106             typedef struct {
107             /* singly linked list of NodeSets */
108             NodeSet* head_set;
109             NodeSet* tail_set;
110             /* doubly linked list of Nodes */
111             Node* head;
112             Node* tail;
113             /* doc internals */
114             const char* buffer;
115             size_t length;
116             size_t offset;
117             } CssDoc;
118              
119             /* ****************************************************************************
120             * NODE CHECKING MACROS/FUNCTIONS
121             * ****************************************************************************
122             */
123              
124             /* checks to see if the node is the given string, case INSENSITIVELY */
125 1           bool nodeEquals(Node* node, const char* string) {
126             /* not the same length? Not equal */
127 1           size_t len = strlen(string);
128 1 50         if (len != node->length)
129 0           return 0;
130             /* compare contents to see if they're equal */
131 1           return (strncasecmp(node->contents, string, node->length) == 0);
132             }
133              
134             /* checks to see if the node contains the given string, case INSENSITIVELY */
135 8           bool nodeContains(Node* node, const char* string) {
136 8           const char* haystack = node->contents;
137 8           const char* endofhay = haystack + node->length;
138 8           size_t len = strlen(string);
139 8           char ul_start[2] = { tolower(*string), toupper(*string) };
140              
141             /* if node is shorter we know we're not going to have a match */
142 8 100         if (len > node->length)
143 1           return 0;
144              
145             /* find the needle in the haystack */
146 13 50         while (haystack && *haystack) {
    50          
147             /* find first char of needle */
148 13           haystack = strpbrk( haystack, ul_start );
149             /* didn't find it? Oh well. */
150 13 50         if (haystack == NULL)
151 0           return 0;
152             /* found it, but will the end be past the end of our node? */
153 13 100         if ((haystack+len) > endofhay)
154 3           return 0;
155             /* see if it matches */
156 10 100         if (strncasecmp(haystack, string, len) == 0)
157 4           return 1;
158             /* nope, move onto next character in the haystack */
159 6           haystack ++;
160             }
161              
162             /* no match */
163 0           return 0;
164             }
165              
166             /* checks to see if the node begins with the given string, case INSENSITIVELY.
167             */
168 0           bool nodeBeginsWith(Node* node, const char* string) {
169             /* If the string is longer than the node, it's not going to match */
170 0           size_t len = strlen(string);
171 0 0         if (len > node->length)
172 0           return 0;
173             /* check for match */
174 0           return (strncasecmp(node->contents, string, len) == 0);
175             }
176              
177             /* checks to see if the node ends with the given string, case INSENSITVELY. */
178 10           bool nodeEndsWith(Node* node, const char* string) {
179             /* If the string is longer than the node, it's not going to match */
180 10           size_t len = strlen(string);
181 10 50         if (len > node->length)
182 0           return 0;
183             /* check for match */
184 10           size_t off = node->length - len;
185 10           return (strncasecmp(node->contents+off, string, len) == 0);
186             }
187              
188             /* macros to help see what kind of node we've got */
189             #define nodeIsWHITESPACE(node) ((node->type == NODE_WHITESPACE))
190             #define nodeIsBLOCKCOMMENT(node) ((node->type == NODE_BLOCKCOMMENT))
191             #define nodeIsIDENTIFIER(node) ((node->type == NODE_IDENTIFIER))
192             #define nodeIsLITERAL(node) ((node->type == NODE_LITERAL))
193             #define nodeIsSIGIL(node) ((node->type == NODE_SIGIL))
194              
195             #define nodeIsEMPTY(node) ((node->type == NODE_EMPTY) || ((node->length==0) || (node->contents==NULL)))
196             #define nodeIsMACIECOMMENTHACK(node) (nodeIsBLOCKCOMMENT(node) && nodeEndsWith(node,"\\*/"))
197             #define nodeIsPREFIXSIGIL(node) (nodeIsSIGIL(node) && charIsPrefix(node->contents[0]))
198             #define nodeIsPOSTFIXSIGIL(node) (nodeIsSIGIL(node) && charIsPostfix(node->contents[0]))
199             #define nodeIsCHAR(node,ch) ((node->contents[0]==ch) && (node->length==1))
200              
201             /* checks if this node is the start of "!important" (with optional intravening
202             * whitespace. */
203 151           bool nodeStartsBANGIMPORTANT(Node* node) {
204 151 50         if (!node) return 0;
205              
206             /* Doesn't start with a "!", nope */
207 151 100         if (!nodeIsCHAR(node,'!')) return 0;
    50          
208              
209             /* Skip any following whitespace */
210 1           Node* next = node->next;
211 1 50         while (next && nodeIsWHITESPACE(next)) {
    50          
212 0           next = node->next;
213             }
214 1 50         if (!next) return 0;
215              
216             /* Next node _better be_ "important" */
217 1 50         if (!nodeIsIDENTIFIER(next)) return 0;
218 1 50         if (nodeEquals(next, "important")) return 1;
219 0           return 0;
220             }
221              
222             /* checks if this node contains a numeric value: an optional leading run of
223             * digits, followed by an optional single decimal point, which MUST be followed
224             * by a digit, and an optional trailing unit (letters and/or "%").
225             *
226             * NOTE: "node->contents" may not be NULL terminated (it is a pointer into a
227             * pre-existing string value), so iterate using "->contents" and "->length".
228             */
229 301           bool nodeIsNumber(Node* node) {
230 301           const char* p = node->contents;
231 301           const char* end = p + node->length;
232 301           bool sawDigit = 0;
233              
234             /* leading run of digits */
235 430 100         while ((p < end) && charIsNumeric(*p)) {
    100          
236 129           sawDigit = 1;
237 129           p++;
238             }
239              
240             /* decimal point, which MUST be followed by a digit */
241 301 100         if ((p < end) && (*p == '.')) {
    100          
242 34           p++;
243             /* no trailing digit? Then not a number */
244 34 50         if ((p >= end) || !charIsNumeric(*p))
    100          
245 7           return 0;
246             /* skip past all following digits */
247 79 100         while ((p < end) && charIsNumeric(*p))
    100          
248 52           p++;
249 27           sawDigit = 1;
250             }
251              
252             /* must have seen at least one digit, otherwise it's not a number */
253 294 100         if (!sawDigit)
254 225           return 0;
255              
256             /* optional trailing unit: letters and/or "%" only */
257 151 100         while (p < end) {
258 82           char ch = *p++;
259 82 100         if ( ((ch>='a')&&(ch<='z')) || ((ch>='A')&&(ch<='Z')) || (ch=='%') )
    50          
    50          
    0          
    50          
260 82           continue;
261 0           return 0;
262             }
263              
264             /* looks sane; it's a number */
265 69           return 1;
266             }
267              
268             /* ****************************************************************************
269             * NODE MANIPULATION FUNCTIONS
270             * ****************************************************************************
271             */
272             /* allocates a new node */
273 935           Node* CssAllocNode(CssDoc* doc) {
274             Node* node;
275 935           NodeSet* set = doc->tail_set;
276              
277             /* if our current NodeSet is full, allocate a new NodeSet */
278 935 50         if (set->next_node >= NODE_SET_SIZE) {
279             NodeSet* next_set;
280 0           Newz(0, next_set, 1, NodeSet);
281 0           set->next = next_set;
282 0           doc->tail_set = next_set;
283 0           set = next_set;
284             }
285              
286             /* grab the next Node out of the NodeSet */
287 935           node = set->nodes + set->next_node;
288 935           set->next_node ++;
289              
290             /* initialize the node */
291 935           node->prev = NULL;
292 935           node->next = NULL;
293 935           node->contents = NULL;
294 935           node->length = 0;
295 935           node->type = NODE_EMPTY;
296 935           node->can_prune = 1;
297 935           return node;
298             }
299              
300             /* sets the contents of a node */
301 972           void CssSetNodeContents(Node* node, const char* string, size_t len) {
302 972           node->contents = string;
303 972           node->length = len;
304 972           return;
305             }
306              
307             /* removes the node from the list and discards it entirely */
308 261           void CssDiscardNode(Node* node) {
309 261 100         if (node->prev)
310 255           node->prev->next = node->next;
311 261 100         if (node->next)
312 259           node->next->prev = node->prev;
313 261           }
314              
315             /* appends the node to the given element */
316 863           void CssAppendNode(Node* element, Node* node) {
317 863 50         if (element->next)
318 0           element->next->prev = node;
319 863           node->next = element->next;
320 863           node->prev = element;
321 863           element->next = node;
322 863           }
323              
324             /* ****************************************************************************
325             * TOKENIZING FUNCTIONS
326             * ****************************************************************************
327             */
328              
329             /* extracts a quoted literal string */
330 1           void _CssExtractLiteral(CssDoc* doc, Node* node) {
331 1           const char* buf = doc->buffer;
332 1           size_t offset = doc->offset;
333 1           char delimiter = buf[offset];
334             /* skip start of literal */
335 1           offset ++;
336             /* search for end of literal */
337 20 50         while (offset < doc->length) {
338 20 50         if (buf[offset] == '\\') {
339             /* escaped character; skip */
340 0           offset ++;
341             }
342 20 100         else if (buf[offset] == delimiter) {
343 1           const char* start = buf + doc->offset;
344 1           size_t length = offset - doc->offset + 1;
345 1           CssSetNodeContents(node, start, length);
346 1           node->type = NODE_LITERAL;
347 1           return;
348             }
349             /* move onto next character */
350 19           offset ++;
351             }
352 0           croak( "unterminated quoted string literal" );
353             }
354              
355             /* extracts a block comment */
356 10           void _CssExtractBlockComment(CssDoc* doc, Node* node) {
357 10           const char* buf = doc->buffer;
358 10           size_t offset = doc->offset;
359              
360             /* skip start of comment */
361 10           offset ++; /* skip "/" */
362 10           offset ++; /* skip "*" */
363              
364             /* search for end of comment block */
365 125 50         while (offset < doc->length) {
366 125 100         if (buf[offset] == '*') {
367 10 50         if (buf[offset+1] == '/') {
368 10           const char* start = buf + doc->offset;
369 10           size_t length = offset - doc->offset + 2;
370 10           CssSetNodeContents(node, start, length);
371 10           node->type = NODE_BLOCKCOMMENT;
372 10           return;
373             }
374             }
375             /* move onto next character */
376 115           offset ++;
377             }
378              
379 0           croak( "unterminated block comment" );
380             }
381              
382             /* extracts a run of whitespace characters */
383 288           void _CssExtractWhitespace(CssDoc* doc, Node* node) {
384 288           const char* buf = doc->buffer;
385 288           size_t offset = doc->offset;
386 613 100         while ((offset < doc->length) && charIsWhitespace(buf[offset]))
    100          
387 325           offset ++;
388 288           CssSetNodeContents(node, doc->buffer+doc->offset, offset-doc->offset);
389 288           node->type = NODE_WHITESPACE;
390 288           }
391              
392             /* extracts an identifier */
393 301           void _CssExtractIdentifier(CssDoc* doc, Node* node) {
394 301           const char* buf = doc->buffer;
395 301           size_t offset = doc->offset;
396 1513 50         while ((offset < doc->length) && charIsIdentifier(buf[offset]))
    100          
397 1212           offset++;
398 301           CssSetNodeContents(node, doc->buffer+doc->offset, offset-doc->offset);
399 301           node->type = NODE_IDENTIFIER;
400 301           }
401              
402             /* extracts a -single- symbol/sigil */
403 335           void _CssExtractSigil(CssDoc* doc, Node* node) {
404 335           CssSetNodeContents(node, doc->buffer+doc->offset, 1);
405 335           node->type = NODE_SIGIL;
406 335           }
407              
408             /* tokenizes the given string and returns the list of nodes */
409 72           Node* CssTokenizeString(CssDoc* doc, const char* string) {
410             /* parse the CSS */
411 1007 100         while ((doc->offset < doc->length) && (doc->buffer[doc->offset])) {
    50          
412             /* allocate a new node */
413 935           Node* node = CssAllocNode(doc);
414 935 100         if (!doc->head)
415 72           doc->head = node;
416 935 100         if (!doc->tail)
417 72           doc->tail = node;
418              
419             /* parse the next node out of the CSS */
420 935 100         if ((doc->buffer[doc->offset] == '/') && (doc->buffer[doc->offset+1] == '*'))
    100          
421 10           _CssExtractBlockComment(doc, node);
422 925 50         else if ((doc->buffer[doc->offset] == '"') || (doc->buffer[doc->offset] == '\''))
    100          
423 1           _CssExtractLiteral(doc, node);
424 924 100         else if (charIsWhitespace(doc->buffer[doc->offset]))
425 288           _CssExtractWhitespace(doc, node);
426 636 100         else if (charIsIdentifier(doc->buffer[doc->offset]))
427 301           _CssExtractIdentifier(doc, node);
428             else
429 335           _CssExtractSigil(doc, node);
430              
431             /* move ahead to the end of the parsed node */
432 935           doc->offset += node->length;
433              
434             /* add the node to our list of nodes */
435 935 100         if (node != doc->tail)
436 863           CssAppendNode(doc->tail, node);
437 935           doc->tail = node;
438             }
439              
440             /* return the node list */
441 72           return doc->head;
442             }
443              
444             /* ****************************************************************************
445             * MINIFICATION FUNCTIONS
446             * ****************************************************************************
447             */
448              
449             /* Skips over any "zero value" found in the provided string, returning a
450             * pointer to the next character after those zeros (which may be the same
451             * as the pointer to ther original string, if no zeros were found).
452             */
453 62           const char* CssSkipZeroValue(const char* str) {
454             /* Skip leading zeros */
455 111 100         while (*str == '0') { str ++; }
456 62           const char* after_leading_zeros = str;
457              
458             /* Decimal point, followed by more zeros? */
459 62 100         if (*str == '.') {
460 24           str ++;
461 60 100         while (*str == '0') { str ++; }
462 24 100         if (charIsNumeric(*str)) {
463             /* ends in digit; significant at the decimal point */
464 12           return after_leading_zeros;
465             }
466 12           return str;
467             }
468              
469             /* Done. */
470 38           return after_leading_zeros;
471             }
472              
473             /* checks to see if the string contains a known CSS unit */
474 14           bool CssIsKnownUnit(const char* str) {
475             /* If it ends with a known Unit, its a Zero Unit */
476 14 50         if (0 == strncmp(str, "em", 2)) { return 1; }
477 14 50         if (0 == strncmp(str, "ex", 2)) { return 1; }
478 14 50         if (0 == strncmp(str, "ch", 2)) { return 1; }
479 14 50         if (0 == strncmp(str, "rem", 3)) { return 1; }
480 14 50         if (0 == strncmp(str, "vw", 2)) { return 1; }
481 14 50         if (0 == strncmp(str, "vh", 2)) { return 1; }
482 14 50         if (0 == strncmp(str, "vmin", 3)) { return 1; }
483 14 50         if (0 == strncmp(str, "vmax", 3)) { return 1; }
484 14 50         if (0 == strncmp(str, "cm", 2)) { return 1; }
485 14 50         if (0 == strncmp(str, "mm", 2)) { return 1; }
486 14 50         if (0 == strncmp(str, "in", 2)) { return 1; }
487 14 100         if (0 == strncmp(str, "px", 2)) { return 1; }
488 6 50         if (0 == strncmp(str, "pt", 2)) { return 1; }
489 6 50         if (0 == strncmp(str, "pc", 2)) { return 1; }
490 6 50         if (0 == strncmp(str, "%", 1)) { return 1; }
491              
492             /* Nope */
493 6           return 0;
494             }
495              
496             /* collapses all of the nodes to their shortest possible representation */
497 72           void CssCollapseNodes(Node* curr) {
498 72           bool inMacIeCommentHack = 0;
499 72           bool inFunction = 0;
500 1007 100         while (curr) {
501 935           Node* next = curr->next;
502 935           switch (curr->type) {
503 288           case NODE_WHITESPACE:
504             /* collapse to a single whitespace character */
505 288           curr->length = 1;
506 288           break;
507 10           case NODE_BLOCKCOMMENT:
508 10 100         if (!inMacIeCommentHack && nodeIsMACIECOMMENTHACK(curr)) {
    50          
    100          
509             /* START of mac/ie hack */
510 2           CssSetNodeContents(curr, start_ie_hack, strlen(start_ie_hack));
511 2           curr->can_prune = 0;
512 2           inMacIeCommentHack = 1;
513             }
514 8 100         else if (inMacIeCommentHack && !nodeIsMACIECOMMENTHACK(curr)) {
    50          
    100          
515             /* END of mac/ie hack */
516 2           CssSetNodeContents(curr, end_ie_hack, strlen(end_ie_hack));
517 2           curr->can_prune = 0;
518 2           inMacIeCommentHack = 0;
519             }
520 10           break;
521 301           case NODE_IDENTIFIER:
522             {
523             const char* ptr;
524              
525             /* if the node isn't numeric, there's no point trying to
526             * collapse a Zero (as it won't have any).
527             */
528 301 100         if (!nodeIsNumber(curr))
529 232           break;
530              
531             /* numerics can be part of a NAME or PATH segment, instead of
532             * being an actual numeric value (e.g. ".grid-001",
533             * "--spacing-001", "url(cache/00/foo.png)"). Those need to be
534             * preserved.
535             */
536 69 50         if (curr->prev
537 69 100         && (nodeIsCHAR(curr->prev, '-') || nodeIsCHAR(curr->prev, '/'))
    50          
    100          
    50          
538 8 50         && curr->prev->prev
539 8 100         && nodeIsIDENTIFIER(curr->prev->prev))
540             {
541 7           break;
542             }
543              
544             /* skip all leading zeros */
545 62           ptr = CssSkipZeroValue(curr->contents);
546              
547             /* if we didn't skip anything, no Zeros to collapse */
548 62 100         if (ptr == curr->contents) {
549 28           break;
550             }
551              
552             /* did we skip the entire thing, and thus the Node is "all zeros"? */
553 34           size_t skipped = ptr - curr->contents;
554 34 100         if (skipped == curr->length) {
555             /* nothing but zeros, so truncate to "0" */
556 8           CssSetNodeContents(curr, "0", 1);
557 8           break;
558             }
559              
560             /* was it a zero percentage? */
561 26 100         if (*ptr == '%') {
562             /* a zero percentage; truncate to "0%" */
563 5           CssSetNodeContents(curr, "0%", 2);
564 5           break;
565             }
566              
567             /* if all we're left with is a known CSS unit, and we're NOT in
568             * a function (where we have to preserve units), just truncate
569             * to "0"
570             */
571 21 100         if (!inFunction && CssIsKnownUnit(ptr)) {
    100          
572             /* not in a function, and is a zero unit; truncate to "0" */
573 8           CssSetNodeContents(curr, "0", 1);
574 8           break;
575             }
576              
577             /* otherwise, just skip leading zeros, and preserve any unit */
578             /* ... do we need to back up one char to find a significant zero? */
579 13 100         if (*ptr != '.') { ptr --; }
580             /* ... if that's not the start of the buffer ... */
581 13 100         if (ptr != curr->contents) {
582             /* set the buffer to "0 + units", blowing away the earlier bits */
583 12           size_t len = curr->length - (ptr - curr->contents);
584 12           CssSetNodeContents(curr, ptr, len);
585             }
586 13           break;
587             }
588 335           case NODE_SIGIL:
589 335 100         if (nodeIsCHAR(curr,'(')) { inFunction = 1; }
    50          
590 335 100         if (nodeIsCHAR(curr,')')) { inFunction = 0; }
    50          
591 335           break;
592 1           default:
593 1           break;
594             }
595 935           curr = next;
596             }
597 72           }
598              
599             /* checks to see whether we can prune the given node from the list.
600             *
601             * THIS is the function that controls the bulk of the minification process.
602             */
603             enum {
604             PRUNE_NO,
605             PRUNE_PREVIOUS,
606             PRUNE_CURRENT,
607             PRUNE_NEXT
608             };
609 1057           int CssCanPrune(Node* node) {
610 1057           Node* prev = node->prev;
611 1057           Node* next = node->next;
612              
613             /* only if node is prunable */
614 1057 100         if (!node->can_prune)
615 6           return PRUNE_NO;
616              
617 1051           switch (node->type) {
618 0           case NODE_EMPTY:
619             /* prune empty nodes */
620 0           return PRUNE_CURRENT;
621 155           case NODE_WHITESPACE:
622             /* remove whitespace before comment blocks */
623 155 50         if (next && nodeIsBLOCKCOMMENT(next))
    50          
624 0           return PRUNE_CURRENT;
625             /* remove whitespace after comment blocks */
626 155 100         if (prev && nodeIsBLOCKCOMMENT(prev))
    100          
627 4           return PRUNE_CURRENT;
628             /* remove whitespace before "!important" */
629 151 50         if (next && nodeStartsBANGIMPORTANT(next)) {
    100          
630 1           return PRUNE_CURRENT;
631             }
632             /* leading whitespace gets pruned */
633 150 100         if (!prev)
634 3           return PRUNE_CURRENT;
635             /* trailing whitespace gets pruned */
636 147 50         if (!next)
637 0           return PRUNE_CURRENT;
638             /* keep all other whitespace */
639 147           return PRUNE_NO;
640 8           case NODE_BLOCKCOMMENT:
641             /* keep comments that contain the word "copyright" */
642 8 100         if (nodeContains(node,"copyright"))
643 4           return PRUNE_NO;
644             /* remove comment blocks */
645 4           return PRUNE_CURRENT;
646 309           case NODE_IDENTIFIER:
647             /* keep all identifiers */
648 309           return PRUNE_NO;
649 2           case NODE_LITERAL:
650             /* keep all literals */
651 2           return PRUNE_NO;
652 577           case NODE_SIGIL:
653             /* remove whitespace after "prefix" sigils */
654 577 50         if (nodeIsPREFIXSIGIL(node) && next && nodeIsWHITESPACE(next))
    100          
    100          
    100          
655 133           return PRUNE_NEXT;
656             /* remove whitespace before "postfix" sigils */
657 444 50         if (nodeIsPOSTFIXSIGIL(node) && prev && nodeIsWHITESPACE(prev))
    100          
    50          
    100          
658 107           return PRUNE_PREVIOUS;
659             /* remove ";" characters at end of selector groups */
660 337 100         if (nodeIsCHAR(node,';') && next && nodeIsSIGIL(next) && nodeIsCHAR(next,'}'))
    50          
    50          
    100          
    50          
    50          
661 9           return PRUNE_CURRENT;
662             /* keep all other sigils */
663 328           return PRUNE_NO;
664             }
665             /* keep anything else */
666 0           return PRUNE_NO;
667             }
668              
669             /* prune nodes from the list */
670 72           Node* CssPruneNodes(Node *head) {
671 72           Node* curr = head;
672 1129 100         while (curr) {
673             /* see if/how we can prune this node */
674 1057           int prune = CssCanPrune(curr);
675             /* prune. each block is responsible for moving onto the next node */
676 1057           Node* prev = curr->prev;
677 1057           Node* next = curr->next;
678 1057           switch (prune) {
679 107           case PRUNE_PREVIOUS:
680             /* discard previous node */
681 107           CssDiscardNode(prev);
682             /* reset "head" if that's what got pruned */
683 107 50         if (prev == head)
684 0           head = curr;
685 107           break;
686 21           case PRUNE_CURRENT:
687             /* discard current node */
688 21           CssDiscardNode(curr);
689             /* reset "head" if that's what got pruned */
690 21 100         if (curr == head)
691 6 50         head = prev ? prev : next;
692             /* backup and try again if possible */
693 21 100         curr = prev ? prev : next;
694 21           break;
695 133           case PRUNE_NEXT:
696             /* discard next node */
697 133           CssDiscardNode(next);
698             /* stay on current node, and try again */
699 133           break;
700 796           default:
701             /* move ahead to next node */
702 796           curr = next;
703 796           break;
704             }
705             }
706              
707             /* return the (possibly new) head node back to the caller */
708 72           return head;
709             }
710              
711             /* ****************************************************************************
712             * Minifies the given CSS, returning a newly allocated string back to the
713             * caller (YOU'RE responsible for freeing its memory).
714             * ****************************************************************************
715             */
716 72           char* CssMinify(const char* string) {
717 72           char* results = NULL;
718             CssDoc doc;
719              
720             /* initialize our CSS document object */
721 72           doc.head = NULL;
722 72           doc.tail = NULL;
723 72           doc.buffer = string;
724 72           doc.length = strlen(string);
725 72           doc.offset = 0;
726 72           Newz(0, doc.head_set, 1, NodeSet);
727 72           doc.tail_set = doc.head_set;
728              
729             /* PASS 1: tokenize CSS into a list of nodes */
730 72           Node* head = CssTokenizeString(&doc, string);
731 72 50         if (!head) goto cleanup;
732             /* PASS 2: collapse nodes */
733 72           CssCollapseNodes(head);
734             /* PASS 3: prune nodes */
735 72           head = CssPruneNodes(head);
736 72 100         if (!head) goto cleanup;
737             /* PASS 4: re-assemble CSS into single string */
738             {
739             Node* curr;
740             char* ptr;
741             /* allocate the result buffer to the same size as the original CSS; in
742             * a worst case scenario that's how much memory we'll need for it.
743             */
744 71           Newz(0, results, (strlen(string)+1), char);
745 71           ptr = results;
746             /* copy node contents into result buffer */
747 71           curr = head;
748 745 100         while (curr) {
749 674           memcpy(ptr, curr->contents, curr->length);
750 674           ptr += curr->length;
751 674           curr = curr->next;
752             }
753 71           *ptr = 0;
754             }
755             /* free memory used by the NodeSets */
756 72           cleanup:
757             {
758 72           NodeSet* curr = doc.head_set;
759 144 100         while (curr) {
760 72           NodeSet* next = curr->next;
761 72           Safefree(curr);
762 72           curr = next;
763             }
764             }
765             /* return resulting minified CSS back to caller */
766 72           return results;
767             }
768              
769              
770              
771             MODULE = CSS::Minifier::XS PACKAGE = CSS::Minifier::XS
772              
773             PROTOTYPES: disable
774              
775             SV*
776             minify(string)
777             SV* string
778             INIT:
779 72           char* buffer = NULL;
780 72           RETVAL = &PL_sv_undef;
781             CODE:
782             /* minify the CSS */
783 72           buffer = CssMinify( SvPVX(string) );
784             /* hand back the minified CSS (if we had any) */
785 72 100         if (buffer != NULL) {
786 71           RETVAL = newSVpv(buffer, 0);
787 71           Safefree( buffer );
788             }
789             OUTPUT:
790             RETVAL