File Coverage

XS.xs
Criterion Covered Total %
statement 329 346 95.0
branch 235 282 83.3
condition n/a
subroutine n/a
pod n/a
total 564 628 89.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 1905           int charIsSpace(char ch) {
15 1905 100         if (ch == ' ') return 1;
16 1165 100         if (ch == '\t') return 1;
17 1162           return 0;
18             }
19 1623           int charIsEndspace(char ch) {
20 1623 100         if (ch == '\n') return 1;
21 1303 50         if (ch == '\r') return 1;
22 1303 50         if (ch == '\f') return 1;
23 1303           return 0;
24             }
25 1905           int charIsWhitespace(char ch) {
26 1905 100         return charIsSpace(ch) || charIsEndspace(ch);
    100          
27             }
28 2001           int charIsIdentifier(char ch) {
29 2001 100         if ((ch >= 'a') && (ch <= 'z')) return 1;
    100          
30 862 100         if ((ch >= 'A') && (ch <= 'Z')) return 1;
    100          
31 860 100         if ((ch >= '0') && (ch <= '9')) return 1;
    100          
32 671 50         if (ch == '_') return 1;
33 671 100         if (ch == '.') return 1;
34 563 100         if (ch == '#') return 1;
35 560 100         if (ch == '@') return 1;
36 556 100         if (ch == '%') return 1;
37 545           return 0;
38             }
39 1026           int charIsInfix(char ch) {
40             /* WS before+after these characters can be removed */
41 1026 100         if (ch == '{') return 1;
42 679 100         if (ch == '}') return 1;
43 366 100         if (ch == ';') return 1;
44 322 100         if (ch == ':') return 1;
45 108 100         if (ch == ',') return 1;
46 96 100         if (ch == '~') return 1;
47 91 100         if (ch == '>') return 1;
48 86           return 0;
49             }
50 641           int charIsPrefix(char ch) {
51             /* WS after these characters can be removed */
52 641 100         if (ch == '(') return 1; /* requires leading WS when used in @media */
53 624           return charIsInfix(ch);
54             }
55 420           int charIsPostfix(char ch) {
56             /* WS before these characters can be removed */
57 420 100         if (ch == ')') return 1; /* requires trailing WS for MSIE */
58 402           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 23           int nodeContains(Node* node, const char* string) {
109 23           const char* haystack = node->contents;
110 23           size_t len = strlen(string);
111 23           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 23 100         if (len > node->length)
115 1           return 0;
116              
117             /* find the needle in the haystack */
118 57 50         while (haystack && *haystack) {
    50          
119             /* find first char of needle */
120 57           haystack = strpbrk( haystack, ul_start );
121 57 100         if (haystack == NULL)
122 19           return 0;
123             /* check if the rest matches */
124 38 100         if (strncasecmp(haystack, string, len) == 0)
125 3           return 1;
126             /* nope, move onto next character in the haystack */
127 35           haystack ++;
128             }
129              
130             /* no match */
131 23           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 23           int nodeEndsWith(Node* node, const char* string) {
145 23           size_t len = strlen(string);
146 23           size_t off = node->length - len;
147 23 50         if (len > node->length)
148 0           return 0;
149 23           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 175           int nodeStartsBANGIMPORTANT(Node* node) {
168 175 50         if (!node) return 0;
169              
170             /* Doesn't start with a "!", nope */
171 175 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 970           Node* CssAllocNode() {
192             Node* node;
193 970           Newz(0, node, 1, Node);
194 970           node->prev = NULL;
195 970           node->next = NULL;
196 970           node->contents = NULL;
197 970           node->length = 0;
198 970           node->type = NODE_EMPTY;
199 970           node->can_prune = 1;
200 970           return node;
201             }
202              
203             /* frees the memory used by a node */
204 970           void CssFreeNode(Node* node) {
205 970 50         if (node->contents)
206 970           Safefree(node->contents);
207 970           Safefree(node);
208 970           }
209 17           void CssFreeNodeList(Node* head) {
210 582 100         while (head) {
211 565           Node* tmp = head->next;
212 565           CssFreeNode(head);
213 565           head = tmp;
214             }
215 17           }
216              
217             /* clears the contents of a node */
218 1410           void CssClearNodeContents(Node* node) {
219 1410 100         if (node->contents)
220 440           Safefree(node->contents);
221 1410           node->contents = NULL;
222 1410           node->length = 0;
223 1410           }
224              
225             /* sets the contents of a node */
226 1410           void CssSetNodeContents(Node* node, const char* string, size_t len) {
227 1410           CssClearNodeContents(node);
228 1410           node->length = len;
229             /* allocate string, fill with NULLs, and copy */
230 1410           Newz(0, node->contents, (len+1), char);
231 1410           strncpy( node->contents, string, len );
232 1410           }
233              
234             /* removes the node from the list and discards it entirely */
235 405           void CssDiscardNode(Node* node) {
236 405 100         if (node->prev)
237 371           node->prev->next = node->next;
238 405 100         if (node->next)
239 402           node->next->prev = node->prev;
240 405           CssFreeNode(node);
241 405           }
242              
243             /* appends the node to the given element */
244 952           void CssAppendNode(Node* element, Node* node) {
245 952 50         if (element->next)
246 0           element->next->prev = node;
247 952           node->next = element->next;
248 952           node->prev = element;
249 952           element->next = node;
250 952           }
251              
252             /* collapses a node to a single whitespace character. If the node contains any
253             * endspace characters, that is what we're collapsed to.
254             */
255 401           void CssCollapseNodeToWhitespace(Node* node) {
256 401 50         if (node->contents) {
257 401           char ws = node->contents[0];
258             size_t idx;
259 759 100         for (idx=0; idxlength; idx++) {
260 461 100         if (charIsEndspace(node->contents[idx])) {
261 103           ws = node->contents[idx];
262 103           break;
263             }
264             }
265 401           CssSetNodeContents(node, &ws, 1);
266             }
267 401           }
268              
269             /* ****************************************************************************
270             * TOKENIZING FUNCTIONS
271             * ****************************************************************************
272             */
273              
274             /* extracts a quoted literal string */
275 1           void _CssExtractLiteral(CssDoc* doc, Node* node) {
276 1           const char* buf = doc->buffer;
277 1           size_t offset = doc->offset;
278 1           char delimiter = buf[offset];
279             /* skip start of literal */
280 1           offset ++;
281             /* search for end of literal */
282 20 50         while (offset < doc->length) {
283 20 50         if (buf[offset] == '\\') {
284             /* escaped character; skip */
285 0           offset ++;
286             }
287 20 100         else if (buf[offset] == delimiter) {
288 1           const char* start = buf + doc->offset;
289 1           size_t length = offset - doc->offset + 1;
290 1           CssSetNodeContents(node, start, length);
291 1           node->type = NODE_LITERAL;
292 1           return;
293             }
294             /* move onto next character */
295 19           offset ++;
296             }
297 0           croak( "unterminated quoted string literal" );
298             }
299              
300             /* extracts a block comment */
301 23           void _CssExtractBlockComment(CssDoc* doc, Node* node) {
302 23           const char* buf = doc->buffer;
303 23           size_t offset = doc->offset;
304              
305             /* skip start of comment */
306 23           offset ++; /* skip "/" */
307 23           offset ++; /* skip "*" */
308              
309             /* search for end of comment block */
310 1291 50         while (offset < doc->length) {
311 1291 100         if (buf[offset] == '*') {
312 27 100         if (buf[offset+1] == '/') {
313 23           const char* start = buf + doc->offset;
314 23           size_t length = offset - doc->offset + 2;
315 23           CssSetNodeContents(node, start, length);
316 23           node->type = NODE_BLOCKCOMMENT;
317 23           return;
318             }
319             }
320             /* move onto next character */
321 1268           offset ++;
322             }
323              
324 0           croak( "unterminated block comment" );
325             }
326              
327             /* extracts a run of whitespace characters */
328 401           void _CssExtractWhitespace(CssDoc* doc, Node* node) {
329 401           const char* buf = doc->buffer;
330 401           size_t offset = doc->offset;
331 960 100         while ((offset < doc->length) && charIsWhitespace(buf[offset]))
    100          
332 559           offset ++;
333 401           CssSetNodeContents(node, doc->buffer+doc->offset, offset-doc->offset);
334 401           node->type = NODE_WHITESPACE;
335 401           }
336              
337             /* extracts an identifier */
338 262           void _CssExtractIdentifier(CssDoc* doc, Node* node) {
339 262           const char* buf = doc->buffer;
340 262           size_t offset = doc->offset;
341 1456 50         while ((offset < doc->length) && charIsIdentifier(buf[offset]))
    100          
342 1194           offset++;
343 262           CssSetNodeContents(node, doc->buffer+doc->offset, offset-doc->offset);
344 262           node->type = NODE_IDENTIFIER;
345 262           }
346              
347             /* extracts a -single- symbol/sigil */
348 283           void _CssExtractSigil(CssDoc* doc, Node* node) {
349 283           CssSetNodeContents(node, doc->buffer+doc->offset, 1);
350 283           node->type = NODE_SIGIL;
351 283           }
352              
353             /* tokenizes the given string and returns the list of nodes */
354 19           Node* CssTokenizeString(const char* string) {
355             CssDoc doc;
356              
357             /* initialize our CSS document object */
358 19           doc.head = NULL;
359 19           doc.tail = NULL;
360 19           doc.buffer = string;
361 19           doc.length = strlen(string);
362 19           doc.offset = 0;
363              
364             /* parse the CSS */
365 989 100         while ((doc.offset < doc.length) && (doc.buffer[doc.offset])) {
    50          
366             /* allocate a new node */
367 970           Node* node = CssAllocNode();
368 970 100         if (!doc.head)
369 18           doc.head = node;
370 970 100         if (!doc.tail)
371 18           doc.tail = node;
372              
373             /* parse the next node out of the CSS */
374 970 100         if ((doc.buffer[doc.offset] == '/') && (doc.buffer[doc.offset+1] == '*'))
    100          
375 23           _CssExtractBlockComment(&doc, node);
376 947 100         else if ((doc.buffer[doc.offset] == '"') || (doc.buffer[doc.offset] == '\''))
    50          
377 1           _CssExtractLiteral(&doc, node);
378 946 100         else if (charIsWhitespace(doc.buffer[doc.offset]))
379 401           _CssExtractWhitespace(&doc, node);
380 545 100         else if (charIsIdentifier(doc.buffer[doc.offset]))
381 262           _CssExtractIdentifier(&doc, node);
382             else
383 283           _CssExtractSigil(&doc, node);
384              
385             /* move ahead to the end of the parsed node */
386 970           doc.offset += node->length;
387              
388             /* add the node to our list of nodes */
389 970 100         if (node != doc.tail)
390 952           CssAppendNode(doc.tail, node);
391 970           doc.tail = node;
392             }
393              
394             /* return the node list */
395 19           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_ the zero value. If no zero value is
405             * found, return NULL.
406             */
407 299           char* CssSkipZeroValue(char* str) {
408 299           int foundZero = 0;
409              
410             /* Find and skip over any leading zero value */
411 396 100         while (*str == '0') { /* leading zeros */
412 97           foundZero ++;
413 97           str++;
414             }
415 299 100         if (*str == '.') { /* decimal point */
416 47           str++;
417             }
418 335 100         while (*str == '0') { /* following zeros */
419 36           foundZero ++;
420 36           str++;
421             }
422              
423             /* If we found a Zero, return the pointer to the next char *after* it */
424 299 100         if (foundZero) {
425 79           return str;
426             }
427 220           return NULL;
428             }
429              
430             /* checks to see if the string contains a known CSS unit */
431 42           int CssIsKnownUnit(char* str) {
432             /* If it ends with a known Unit, its a Zero Unit */
433 42 100         if (0 == strcmp(str, "em")) { return 1; }
434 38 100         if (0 == strcmp(str, "ex")) { return 1; }
435 34 50         if (0 == strcmp(str, "ch")) { return 1; }
436 34 50         if (0 == strcmp(str, "rem")) { return 1; }
437 34 50         if (0 == strcmp(str, "vw")) { return 1; }
438 34 50         if (0 == strcmp(str, "vh")) { return 1; }
439 34 50         if (0 == strcmp(str, "vmin")) { return 1; }
440 34 50         if (0 == strcmp(str, "vmax")) { return 1; }
441 34 100         if (0 == strcmp(str, "cm")) { return 1; }
442 30 100         if (0 == strcmp(str, "mm")) { return 1; }
443 26 100         if (0 == strcmp(str, "in")) { return 1; }
444 22 100         if (0 == strcmp(str, "px")) { return 1; }
445 14 100         if (0 == strcmp(str, "pt")) { return 1; }
446 10 100         if (0 == strcmp(str, "pc")) { return 1; }
447 6 100         if (0 == strcmp(str, "%")) { return 1; }
448              
449             /* Nope */
450 2           return 0;
451             }
452              
453             /* checks to see if the string represents a "zero unit" */
454 262           int CssIsZeroUnit(char* str) {
455             /* Does it start with a zero value? */
456 262           char* ptr = CssSkipZeroValue(str);
457 262 100         if (ptr == NULL) {
458 220           return 0;
459             }
460              
461             /* And how about a known unit? */
462 42           return CssIsKnownUnit(ptr);
463             }
464              
465             /* checks to see if the string represents a "zero percentage" */
466 37           int CssIsZeroPercent(char* str) {
467             /* Does it start with a zero value? */
468 37           char* ptr = CssSkipZeroValue(str);
469 37 50         if (ptr == NULL) {
470 0           return 0;
471             }
472              
473             /* And does it end with a "%"? */
474 37 100         if (0 == strcmp(ptr, "%")) { return 1; }
475 33           return 0;
476             }
477              
478             /* collapses all of the nodes to their shortest possible representation */
479 18           void CssCollapseNodes(Node* curr) {
480 18           int inMacIeCommentHack = 0;
481 18           int inFunction = 0;
482 988 100         while (curr) {
483 970           Node* next = curr->next;
484 970           switch (curr->type) {
485             case NODE_WHITESPACE:
486 401           CssCollapseNodeToWhitespace(curr);
487 401           break;
488             case NODE_BLOCKCOMMENT: {
489 23 100         if (!inMacIeCommentHack && nodeIsMACIECOMMENTHACK(curr)) {
    50          
    100          
490             /* START of mac/ie hack */
491 1           CssSetNodeContents(curr, "/*\\*/", 5);
492 1           curr->can_prune = 0;
493 1           inMacIeCommentHack = 1;
494             }
495 22 100         else if (inMacIeCommentHack && !nodeIsMACIECOMMENTHACK(curr)) {
    50          
    100          
496             /* END of mac/ie hack */
497 1           CssSetNodeContents(curr, "/**/", 4);
498 1           curr->can_prune = 0;
499 1           inMacIeCommentHack = 0;
500             }
501 23           } break;
502             case NODE_IDENTIFIER:
503 262 100         if (CssIsZeroUnit(curr->contents) && !inFunction) {
    100          
504 37 100         if (CssIsZeroPercent(curr->contents)) {
505 4           CssSetNodeContents(curr, "0%", 2);
506             }
507             else {
508 33           CssSetNodeContents(curr, "0", 1);
509             }
510             }
511             case NODE_SIGIL:
512 545 100         if (nodeIsCHAR(curr,'(')) { inFunction = 1; }
    50          
513 545 100         if (nodeIsCHAR(curr,')')) { inFunction = 0; }
    50          
514             default:
515 546           break;
516             }
517 970           curr = next;
518             }
519 18           }
520              
521             /* checks to see whether we can prune the given node from the list.
522             *
523             * THIS is the function that controls the bulk of the minification process.
524             */
525             enum {
526             PRUNE_NO,
527             PRUNE_PREVIOUS,
528             PRUNE_CURRENT,
529             PRUNE_NEXT
530             };
531 1120           int CssCanPrune(Node* node) {
532 1120           Node* prev = node->prev;
533 1120           Node* next = node->next;
534              
535             /* only if node is prunable */
536 1120 100         if (!node->can_prune)
537 3           return PRUNE_NO;
538              
539 1117           switch (node->type) {
540             case NODE_EMPTY:
541             /* prune empty nodes */
542 0           return PRUNE_CURRENT;
543             case NODE_WHITESPACE:
544             /* remove whitespace before comment blocks */
545 180 50         if (next && nodeIsBLOCKCOMMENT(next))
    100          
546 4           return PRUNE_CURRENT;
547             /* remove whitespace after comment blocks */
548 176 100         if (prev && nodeIsBLOCKCOMMENT(prev))
    100          
549 1           return PRUNE_CURRENT;
550             /* remove whitespace before "!important" */
551 175 50         if (next && nodeStartsBANGIMPORTANT(next)) {
    100          
552 1           return PRUNE_CURRENT;
553             }
554             /* leading whitespace gets pruned */
555 174 100         if (!prev)
556 14           return PRUNE_CURRENT;
557             /* trailing whitespace gets pruned */
558 160 50         if (!next)
559 0           return PRUNE_CURRENT;
560             /* keep all other whitespace */
561 160           return PRUNE_NO;
562             case NODE_BLOCKCOMMENT:
563             /* keep comments that contain the word "copyright" */
564 23 100         if (nodeContains(node,"copyright"))
565 3           return PRUNE_NO;
566             /* remove comment blocks */
567 20           return PRUNE_CURRENT;
568             case NODE_IDENTIFIER:
569             /* keep all identifiers */
570 272           return PRUNE_NO;
571             case NODE_LITERAL:
572             /* keep all literals */
573 1           return PRUNE_NO;
574             case NODE_SIGIL:
575             /* remove whitespace after "prefix" sigils */
576 641 50         if (nodeIsPREFIXSIGIL(node) && next && nodeIsWHITESPACE(next))
    100          
    100          
    100          
577 221           return PRUNE_NEXT;
578             /* remove whitespace before "postfix" sigils */
579 420 50         if (nodeIsPOSTFIXSIGIL(node) && prev && nodeIsWHITESPACE(prev))
    100          
    50          
    100          
580 134           return PRUNE_PREVIOUS;
581             /* remove ";" characters at end of selector groups */
582 286 100         if (nodeIsCHAR(node,';') && next && nodeIsSIGIL(next) && nodeIsCHAR(next,'}'))
    50          
    50          
    100          
    50          
    50          
583 10           return PRUNE_CURRENT;
584             /* keep all other sigils */
585 276           return PRUNE_NO;
586             }
587             /* keep anything else */
588 0           return PRUNE_NO;
589             }
590              
591             /* prune nodes from the list */
592 18           Node* CssPruneNodes(Node *head) {
593 18           Node* curr = head;
594 1138 100         while (curr) {
595             /* see if/how we can prune this node */
596 1120           int prune = CssCanPrune(curr);
597             /* prune. each block is responsible for moving onto the next node */
598 1120           Node* prev = curr->prev;
599 1120           Node* next = curr->next;
600 1120           switch (prune) {
601             case PRUNE_PREVIOUS:
602             /* discard previous node */
603 134           CssDiscardNode(prev);
604             /* reset "head" if that's what got pruned */
605 134 50         if (prev == head)
606 0           head = curr;
607 134           break;
608             case PRUNE_CURRENT:
609             /* discard current node */
610 50           CssDiscardNode(curr);
611             /* reset "head" if that's what got pruned */
612 50 100         if (curr == head)
613 34 50         head = prev ? prev : next;
614             /* backup and try again if possible */
615 50 100         curr = prev ? prev : next;
616 50           break;
617             case PRUNE_NEXT:
618             /* discard next node */
619 221           CssDiscardNode(next);
620             /* stay on current node, and try again */
621 221           break;
622             default:
623             /* move ahead to next node */
624 715           curr = next;
625 715           break;
626             }
627             }
628              
629             /* return the (possibly new) head node back to the caller */
630 18           return head;
631             }
632              
633             /* ****************************************************************************
634             * Minifies the given CSS, returning a newly allocated string back to the
635             * caller (YOU'RE responsible for freeing its memory).
636             * ****************************************************************************
637             */
638 19           char* CssMinify(const char* string) {
639             char* results;
640             /* PASS 1: tokenize CSS into a list of nodes */
641 19           Node* head = CssTokenizeString(string);
642 19 100         if (!head) return NULL;
643             /* PASS 2: collapse nodes */
644 18           CssCollapseNodes(head);
645             /* PASS 3: prune nodes */
646 18           head = CssPruneNodes(head);
647 18 100         if (!head) return NULL;
648             /* PASS 4: re-assemble CSS into single string */
649             {
650             Node* curr;
651             char* ptr;
652             /* allocate the result buffer to the same size as the original CSS; in
653             * a worst case scenario that's how much memory we'll need for it.
654             */
655 17           Newz(0, results, (strlen(string)+1), char);
656 17           ptr = results;
657             /* copy node contents into result buffer */
658 17           curr = head;
659 582 100         while (curr) {
660 565           memcpy(ptr, curr->contents, curr->length);
661 565           ptr += curr->length;
662 565           curr = curr->next;
663             }
664 17           *ptr = 0;
665             }
666             /* free memory used by node list */
667 17           CssFreeNodeList(head);
668             /* return resulting minified CSS back to caller */
669 17           return results;
670             }
671              
672              
673              
674             MODULE = CSS::Minifier::XS PACKAGE = CSS::Minifier::XS
675              
676             PROTOTYPES: disable
677              
678             SV*
679             minify(string)
680             SV* string
681             INIT:
682 19           char* buffer = NULL;
683 19           RETVAL = &PL_sv_undef;
684             CODE:
685             /* minify the CSS */
686 19           buffer = CssMinify( SvPVX(string) );
687             /* hand back the minified CSS (if we had any) */
688 19 100         if (buffer != NULL) {
689 17           RETVAL = newSVpv(buffer, 0);
690 17           Safefree( buffer );
691             }
692             OUTPUT:
693             RETVAL