File Coverage

XS.xs
Criterion Covered Total %
statement 349 370 94.3
branch 234 294 79.5
condition n/a
subroutine n/a
pod n/a
total 583 664 87.8


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