File Coverage

XS.xs
Criterion Covered Total %
statement 322 345 93.3
branch 235 292 80.4
condition n/a
subroutine n/a
pod n/a
total 557 637 87.4


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 1370           bool charIsSpace(char ch) {
18 1370 100         if (ch == ' ') return 1;
19 787 100         if (ch == '\t') return 1;
20 781           return 0;
21             }
22 781           bool charIsEndspace(char ch) {
23 781 100         if (ch == '\n') return 1;
24 759 100         if (ch == '\r') return 1;
25 757 50         if (ch == '\f') return 1;
26 757           return 0;
27             }
28 1370           bool charIsWhitespace(char ch) {
29 1370 100         return charIsSpace(ch) || charIsEndspace(ch);
    100          
30             }
31 19           bool charIsNumeric(char ch) {
32 19 100         if ((ch >= '0') && (ch <= '9')) return 1;
    100          
33 11           return 0;
34             }
35 1595           bool charIsIdentifier(char ch) {
36 1595 100         if ((ch >= 'a') && (ch <= 'z')) return 1;
    100          
37 726 100         if ((ch >= 'A') && (ch <= 'Z')) return 1;
    100          
38 724 100         if ((ch >= '0') && (ch <= '9')) return 1;
    100          
39 528 50         if (ch == '_') return 1;
40 528 100         if (ch == '.') return 1;
41 493 100         if (ch == '#') return 1;
42 488 100         if (ch == '@') return 1;
43 482 100         if (ch == '%') return 1;
44 470           return 0;
45             }
46 688           bool charIsInfix(char ch) {
47             /* WS before+after these characters can be removed */
48 688 100         if (ch == '{') return 1;
49 406 100         if (ch == '}') return 1;
50 203 100         if (ch == ';') return 1;
51 168 100         if (ch == ',') return 1;
52 159 100         if (ch == '~') return 1;
53 154 100         if (ch == '>') return 1;
54 149           return 0;
55             }
56 488           bool charIsPrefix(char ch) {
57             /* WS after these characters can be removed */
58 488 100         if (ch == '(') return 1; /* requires leading WS when used in @media */
59 468 100         if (ch == ':') return 1; /* requires leading WS when used in pseudo-selector */
60 356           return charIsInfix(ch);
61             }
62 355           bool charIsPostfix(char ch) {
63             /* WS before these characters can be removed */
64 355 100         if (ch == ')') return 1; /* requires trailing WS for MSIE */
65 332           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 8           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             /* ****************************************************************************
223             * NODE MANIPULATION FUNCTIONS
224             * ****************************************************************************
225             */
226             /* allocates a new node */
227 769           Node* CssAllocNode(CssDoc* doc) {
228             Node* node;
229 769           NodeSet* set = doc->tail_set;
230              
231             /* if our current NodeSet is full, allocate a new NodeSet */
232 769 50         if (set->next_node >= NODE_SET_SIZE) {
233             NodeSet* next_set;
234 0           Newz(0, next_set, 1, NodeSet);
235 0           set->next = next_set;
236 0           doc->tail_set = next_set;
237 0           set = next_set;
238             }
239              
240             /* grab the next Node out of the NodeSet */
241 769           node = set->nodes + set->next_node;
242 769           set->next_node ++;
243              
244             /* initialize the node */
245 769           node->prev = NULL;
246 769           node->next = NULL;
247 769           node->contents = NULL;
248 769           node->length = 0;
249 769           node->type = NODE_EMPTY;
250 769           node->can_prune = 1;
251 769           return node;
252             }
253              
254             /* sets the contents of a node */
255 796           void CssSetNodeContents(Node* node, const char* string, size_t len) {
256 796           node->contents = string;
257 796           node->length = len;
258 796           return;
259             }
260              
261             /* removes the node from the list and discards it entirely */
262 261           void CssDiscardNode(Node* node) {
263 261 100         if (node->prev)
264 255           node->prev->next = node->next;
265 261 100         if (node->next)
266 259           node->next->prev = node->prev;
267 261           }
268              
269             /* appends the node to the given element */
270 715           void CssAppendNode(Node* element, Node* node) {
271 715 50         if (element->next)
272 0           element->next->prev = node;
273 715           node->next = element->next;
274 715           node->prev = element;
275 715           element->next = node;
276 715           }
277              
278             /* ****************************************************************************
279             * TOKENIZING FUNCTIONS
280             * ****************************************************************************
281             */
282              
283             /* extracts a quoted literal string */
284 1           void _CssExtractLiteral(CssDoc* doc, Node* node) {
285 1           const char* buf = doc->buffer;
286 1           size_t offset = doc->offset;
287 1           char delimiter = buf[offset];
288             /* skip start of literal */
289 1           offset ++;
290             /* search for end of literal */
291 20 50         while (offset < doc->length) {
292 20 50         if (buf[offset] == '\\') {
293             /* escaped character; skip */
294 0           offset ++;
295             }
296 20 100         else if (buf[offset] == delimiter) {
297 1           const char* start = buf + doc->offset;
298 1           size_t length = offset - doc->offset + 1;
299 1           CssSetNodeContents(node, start, length);
300 1           node->type = NODE_LITERAL;
301 1           return;
302             }
303             /* move onto next character */
304 19           offset ++;
305             }
306 0           croak( "unterminated quoted string literal" );
307             }
308              
309             /* extracts a block comment */
310 10           void _CssExtractBlockComment(CssDoc* doc, Node* node) {
311 10           const char* buf = doc->buffer;
312 10           size_t offset = doc->offset;
313              
314             /* skip start of comment */
315 10           offset ++; /* skip "/" */
316 10           offset ++; /* skip "*" */
317              
318             /* search for end of comment block */
319 125 50         while (offset < doc->length) {
320 125 100         if (buf[offset] == '*') {
321 10 50         if (buf[offset+1] == '/') {
322 10           const char* start = buf + doc->offset;
323 10           size_t length = offset - doc->offset + 2;
324 10           CssSetNodeContents(node, start, length);
325 10           node->type = NODE_BLOCKCOMMENT;
326 10           return;
327             }
328             }
329             /* move onto next character */
330 115           offset ++;
331             }
332              
333 0           croak( "unterminated block comment" );
334             }
335              
336             /* extracts a run of whitespace characters */
337 288           void _CssExtractWhitespace(CssDoc* doc, Node* node) {
338 288           const char* buf = doc->buffer;
339 288           size_t offset = doc->offset;
340 613 100         while ((offset < doc->length) && charIsWhitespace(buf[offset]))
    100          
341 325           offset ++;
342 288           CssSetNodeContents(node, doc->buffer+doc->offset, offset-doc->offset);
343 288           node->type = NODE_WHITESPACE;
344 288           }
345              
346             /* extracts an identifier */
347 224           void _CssExtractIdentifier(CssDoc* doc, Node* node) {
348 224           const char* buf = doc->buffer;
349 224           size_t offset = doc->offset;
350 1125 50         while ((offset < doc->length) && charIsIdentifier(buf[offset]))
    100          
351 901           offset++;
352 224           CssSetNodeContents(node, doc->buffer+doc->offset, offset-doc->offset);
353 224           node->type = NODE_IDENTIFIER;
354 224           }
355              
356             /* extracts a -single- symbol/sigil */
357 246           void _CssExtractSigil(CssDoc* doc, Node* node) {
358 246           CssSetNodeContents(node, doc->buffer+doc->offset, 1);
359 246           node->type = NODE_SIGIL;
360 246           }
361              
362             /* tokenizes the given string and returns the list of nodes */
363 54           Node* CssTokenizeString(CssDoc* doc, const char* string) {
364             /* parse the CSS */
365 823 100         while ((doc->offset < doc->length) && (doc->buffer[doc->offset])) {
    50          
366             /* allocate a new node */
367 769           Node* node = CssAllocNode(doc);
368 769 100         if (!doc->head)
369 54           doc->head = node;
370 769 100         if (!doc->tail)
371 54           doc->tail = node;
372              
373             /* parse the next node out of the CSS */
374 769 100         if ((doc->buffer[doc->offset] == '/') && (doc->buffer[doc->offset+1] == '*'))
    100          
375 10           _CssExtractBlockComment(doc, node);
376 759 50         else if ((doc->buffer[doc->offset] == '"') || (doc->buffer[doc->offset] == '\''))
    100          
377 1           _CssExtractLiteral(doc, node);
378 758 100         else if (charIsWhitespace(doc->buffer[doc->offset]))
379 288           _CssExtractWhitespace(doc, node);
380 470 100         else if (charIsIdentifier(doc->buffer[doc->offset]))
381 224           _CssExtractIdentifier(doc, node);
382             else
383 246           _CssExtractSigil(doc, node);
384              
385             /* move ahead to the end of the parsed node */
386 769           doc->offset += node->length;
387              
388             /* add the node to our list of nodes */
389 769 100         if (node != doc->tail)
390 715           CssAppendNode(doc->tail, node);
391 769           doc->tail = node;
392             }
393              
394             /* return the node list */
395 54           return doc->head;
396             }
397              
398             /* ****************************************************************************
399             * MINIFICATION FUNCTIONS
400             * ****************************************************************************
401             */
402              
403             /* Skips over any "zero value" found in the provided string, returning a
404             * pointer to the next character after those zeros (which may be the same
405             * as the pointer to ther original string, if no zeros were found).
406             */
407 28           const char* CssSkipZeroValue(const char* str) {
408             /* Skip leading zeros */
409 67 100         while (*str == '0') { str ++; }
410 28           const char* after_leading_zeros = str;
411              
412             /* Decimal point, followed by more zeros? */
413 28 100         if (*str == '.') {
414 19           str ++;
415 54 100         while (*str == '0') { str ++; }
416 19 100         if (charIsNumeric(*str)) {
417             /* ends in digit; significant at the decimal point */
418 8           return after_leading_zeros;
419             }
420 11           return str;
421             }
422              
423             /* Done. */
424 9           return after_leading_zeros;
425             }
426              
427             /* checks to see if the string contains a known CSS unit */
428 8           bool CssIsKnownUnit(const char* str) {
429             /* If it ends with a known Unit, its a Zero Unit */
430 8 50         if (0 == strncmp(str, "em", 2)) { return 1; }
431 8 50         if (0 == strncmp(str, "ex", 2)) { return 1; }
432 8 50         if (0 == strncmp(str, "ch", 2)) { return 1; }
433 8 50         if (0 == strncmp(str, "rem", 3)) { return 1; }
434 8 50         if (0 == strncmp(str, "vw", 2)) { return 1; }
435 8 50         if (0 == strncmp(str, "vh", 2)) { return 1; }
436 8 50         if (0 == strncmp(str, "vmin", 3)) { return 1; }
437 8 50         if (0 == strncmp(str, "vmax", 3)) { return 1; }
438 8 50         if (0 == strncmp(str, "cm", 2)) { return 1; }
439 8 50         if (0 == strncmp(str, "mm", 2)) { return 1; }
440 8 50         if (0 == strncmp(str, "in", 2)) { return 1; }
441 8 100         if (0 == strncmp(str, "px", 2)) { return 1; }
442 3 50         if (0 == strncmp(str, "pt", 2)) { return 1; }
443 3 50         if (0 == strncmp(str, "pc", 2)) { return 1; }
444 3 50         if (0 == strncmp(str, "%", 1)) { return 1; }
445              
446             /* Nope */
447 3           return 0;
448             }
449              
450             /* collapses all of the nodes to their shortest possible representation */
451 54           void CssCollapseNodes(Node* curr) {
452 54           bool inMacIeCommentHack = 0;
453 54           bool inFunction = 0;
454 823 100         while (curr) {
455 769           Node* next = curr->next;
456 769           switch (curr->type) {
457             case NODE_WHITESPACE:
458             /* collapse to a single whitespace character */
459 288           curr->length = 1;
460 288           break;
461             case NODE_BLOCKCOMMENT:
462 10 100         if (!inMacIeCommentHack && nodeIsMACIECOMMENTHACK(curr)) {
    50          
    100          
463             /* START of mac/ie hack */
464 2           CssSetNodeContents(curr, start_ie_hack, strlen(start_ie_hack));
465 2           curr->can_prune = 0;
466 2           inMacIeCommentHack = 1;
467             }
468 8 100         else if (inMacIeCommentHack && !nodeIsMACIECOMMENTHACK(curr)) {
    50          
    100          
469             /* END of mac/ie hack */
470 2           CssSetNodeContents(curr, end_ie_hack, strlen(end_ie_hack));
471 2           curr->can_prune = 0;
472 2           inMacIeCommentHack = 0;
473             }
474 10           break;
475             case NODE_IDENTIFIER:
476             {
477             /* if the node doesn't begin with a "zero", nothing to collapse */
478 224           const char* ptr = curr->contents;
479 224 100         if ( (*ptr != '0') && (*ptr != '.' )) {
    100          
480             /* not "0" and not "point-something" */
481 195           break;
482             }
483 29 100         if ( (*ptr == '.') && (*(ptr+1) != '0') ) {
    100          
484             /* "point-something", but not "point-zero" */
485 1           break;
486             }
487              
488             /* skip all leading zeros */
489 28           ptr = CssSkipZeroValue(curr->contents);
490              
491             /* if we didn't skip anything, no Zeros to collapse */
492 28 100         if (ptr == curr->contents) {
493 4           break;
494             }
495              
496             /* did we skip the entire thing, and thus the Node is "all zeros"? */
497 24           size_t skipped = ptr - curr->contents;
498 24 100         if (skipped == curr->length) {
499             /* nothing but zeros, so truncate to "0" */
500 5           CssSetNodeContents(curr, "0", 1);
501 5           break;
502             }
503              
504             /* was it a zero percentage? */
505 19 100         if (*ptr == '%') {
506             /* a zero percentage; truncate to "0%" */
507 5           CssSetNodeContents(curr, "0%", 2);
508 5           break;
509             }
510              
511             /* if all we're left with is a known CSS unit, and we're NOT in
512             * a function (where we have to preserve units), just truncate
513             * to "0"
514             */
515 14 100         if (!inFunction && CssIsKnownUnit(ptr)) {
    100          
516             /* not in a function, and is a zero unit; truncate to "0" */
517 5           CssSetNodeContents(curr, "0", 1);
518 5           break;
519             }
520              
521             /* otherwise, just skip leading zeros, and preserve any unit */
522             /* ... do we need to back up one char to find a significant zero? */
523 9 100         if (*ptr != '.') { ptr --; }
524             /* ... if that's not the start of the buffer ... */
525 9 100         if (ptr != curr->contents) {
526             /* set the buffer to "0 + units", blowing away the earlier bits */
527 8           size_t len = curr->length - (ptr - curr->contents);
528 8           CssSetNodeContents(curr, ptr, len);
529             }
530 9           break;
531             }
532             case NODE_SIGIL:
533 246 100         if (nodeIsCHAR(curr,'(')) { inFunction = 1; }
    50          
534 246 100         if (nodeIsCHAR(curr,')')) { inFunction = 0; }
    50          
535 246           break;
536             default:
537 1           break;
538             }
539 769           curr = next;
540             }
541 54           }
542              
543             /* checks to see whether we can prune the given node from the list.
544             *
545             * THIS is the function that controls the bulk of the minification process.
546             */
547             enum {
548             PRUNE_NO,
549             PRUNE_PREVIOUS,
550             PRUNE_CURRENT,
551             PRUNE_NEXT
552             };
553 891           int CssCanPrune(Node* node) {
554 891           Node* prev = node->prev;
555 891           Node* next = node->next;
556              
557             /* only if node is prunable */
558 891 100         if (!node->can_prune)
559 6           return PRUNE_NO;
560              
561 885           switch (node->type) {
562             case NODE_EMPTY:
563             /* prune empty nodes */
564 0           return PRUNE_CURRENT;
565             case NODE_WHITESPACE:
566             /* remove whitespace before comment blocks */
567 155 50         if (next && nodeIsBLOCKCOMMENT(next))
    50          
568 0           return PRUNE_CURRENT;
569             /* remove whitespace after comment blocks */
570 155 100         if (prev && nodeIsBLOCKCOMMENT(prev))
    100          
571 4           return PRUNE_CURRENT;
572             /* remove whitespace before "!important" */
573 151 50         if (next && nodeStartsBANGIMPORTANT(next)) {
    100          
574 1           return PRUNE_CURRENT;
575             }
576             /* leading whitespace gets pruned */
577 150 100         if (!prev)
578 3           return PRUNE_CURRENT;
579             /* trailing whitespace gets pruned */
580 147 50         if (!next)
581 0           return PRUNE_CURRENT;
582             /* keep all other whitespace */
583 147           return PRUNE_NO;
584             case NODE_BLOCKCOMMENT:
585             /* keep comments that contain the word "copyright" */
586 8 100         if (nodeContains(node,"copyright"))
587 4           return PRUNE_NO;
588             /* remove comment blocks */
589 4           return PRUNE_CURRENT;
590             case NODE_IDENTIFIER:
591             /* keep all identifiers */
592 232           return PRUNE_NO;
593             case NODE_LITERAL:
594             /* keep all literals */
595 2           return PRUNE_NO;
596             case NODE_SIGIL:
597             /* remove whitespace after "prefix" sigils */
598 488 50         if (nodeIsPREFIXSIGIL(node) && next && nodeIsWHITESPACE(next))
    100          
    100          
    100          
599 133           return PRUNE_NEXT;
600             /* remove whitespace before "postfix" sigils */
601 355 50         if (nodeIsPOSTFIXSIGIL(node) && prev && nodeIsWHITESPACE(prev))
    100          
    50          
    100          
602 107           return PRUNE_PREVIOUS;
603             /* remove ";" characters at end of selector groups */
604 248 100         if (nodeIsCHAR(node,';') && next && nodeIsSIGIL(next) && nodeIsCHAR(next,'}'))
    50          
    50          
    100          
    50          
    50          
605 9           return PRUNE_CURRENT;
606             /* keep all other sigils */
607 239           return PRUNE_NO;
608             }
609             /* keep anything else */
610 0           return PRUNE_NO;
611             }
612              
613             /* prune nodes from the list */
614 54           Node* CssPruneNodes(Node *head) {
615 54           Node* curr = head;
616 945 100         while (curr) {
617             /* see if/how we can prune this node */
618 891           int prune = CssCanPrune(curr);
619             /* prune. each block is responsible for moving onto the next node */
620 891           Node* prev = curr->prev;
621 891           Node* next = curr->next;
622 891           switch (prune) {
623             case PRUNE_PREVIOUS:
624             /* discard previous node */
625 107           CssDiscardNode(prev);
626             /* reset "head" if that's what got pruned */
627 107 50         if (prev == head)
628 0           head = curr;
629 107           break;
630             case PRUNE_CURRENT:
631             /* discard current node */
632 21           CssDiscardNode(curr);
633             /* reset "head" if that's what got pruned */
634 21 100         if (curr == head)
635 6 50         head = prev ? prev : next;
636             /* backup and try again if possible */
637 21 100         curr = prev ? prev : next;
638 21           break;
639             case PRUNE_NEXT:
640             /* discard next node */
641 133           CssDiscardNode(next);
642             /* stay on current node, and try again */
643 133           break;
644             default:
645             /* move ahead to next node */
646 630           curr = next;
647 630           break;
648             }
649             }
650              
651             /* return the (possibly new) head node back to the caller */
652 54           return head;
653             }
654              
655             /* ****************************************************************************
656             * Minifies the given CSS, returning a newly allocated string back to the
657             * caller (YOU'RE responsible for freeing its memory).
658             * ****************************************************************************
659             */
660 54           char* CssMinify(const char* string) {
661             char* results;
662             CssDoc doc;
663              
664             /* initialize our CSS document object */
665 54           doc.head = NULL;
666 54           doc.tail = NULL;
667 54           doc.buffer = string;
668 54           doc.length = strlen(string);
669 54           doc.offset = 0;
670 54           Newz(0, doc.head_set, 1, NodeSet);
671 54           doc.tail_set = doc.head_set;
672              
673             /* PASS 1: tokenize CSS into a list of nodes */
674 54           Node* head = CssTokenizeString(&doc, string);
675 54 50         if (!head) return NULL;
676             /* PASS 2: collapse nodes */
677 54           CssCollapseNodes(head);
678             /* PASS 3: prune nodes */
679 54           head = CssPruneNodes(head);
680 54 100         if (!head) return NULL;
681             /* PASS 4: re-assemble CSS into single string */
682             {
683             Node* curr;
684             char* ptr;
685             /* allocate the result buffer to the same size as the original CSS; in
686             * a worst case scenario that's how much memory we'll need for it.
687             */
688 53           Newz(0, results, (strlen(string)+1), char);
689 53           ptr = results;
690             /* copy node contents into result buffer */
691 53           curr = head;
692 561 100         while (curr) {
693 508           memcpy(ptr, curr->contents, curr->length);
694 508           ptr += curr->length;
695 508           curr = curr->next;
696             }
697 53           *ptr = 0;
698             }
699             /* free memory used by the NodeSets */
700             {
701 53           NodeSet* curr = doc.head_set;
702 106 100         while (curr) {
703 53           NodeSet* next = curr->next;
704 53           Safefree(curr);
705 53           curr = next;
706             }
707             }
708             /* return resulting minified CSS back to caller */
709 54           return results;
710             }
711              
712              
713              
714             MODULE = CSS::Minifier::XS PACKAGE = CSS::Minifier::XS
715              
716             PROTOTYPES: disable
717              
718             SV*
719             minify(string)
720             SV* string
721             INIT:
722 54           char* buffer = NULL;
723 54           RETVAL = &PL_sv_undef;
724             CODE:
725             /* minify the CSS */
726 54           buffer = CssMinify( SvPVX(string) );
727             /* hand back the minified CSS (if we had any) */
728 54 100         if (buffer != NULL) {
729 53           RETVAL = newSVpv(buffer, 0);
730 53           Safefree( buffer );
731             }
732             OUTPUT:
733             RETVAL