blob: a722fc276a9103960e78f26308dbcd72bf8ff207 [file] [log] [blame]
/*
* Copyright 2011 - 2014
* Andr\xe9 Malo or his licensors, as applicable
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cext.h"
EXT_INIT_FUNC;
#ifdef EXT3
typedef Py_UNICODE rchar;
#else
typedef unsigned char rchar;
#endif
#define U(c) ((rchar)(c))
typedef struct {
const rchar *start;
const rchar *sentinel;
const rchar *tsentinel;
Py_ssize_t at_group;
int in_macie5;
int in_rule;
int keep_bang_comments;
} rcssmin_ctx_t;
typedef enum {
NEED_SPACE_MAYBE = 0,
NEED_SPACE_NEVER
} need_space_flag;
#define RCSSMIN_DULL_BIT (1 << 0)
#define RCSSMIN_HEX_BIT (1 << 1)
#define RCSSMIN_ESC_BIT (1 << 2)
#define RCSSMIN_SPACE_BIT (1 << 3)
#define RCSSMIN_STRING_DULL_BIT (1 << 4)
#define RCSSMIN_NMCHAR_BIT (1 << 5)
#define RCSSMIN_URI_DULL_BIT (1 << 6)
#define RCSSMIN_PRE_CHAR_BIT (1 << 7)
#define RCSSMIN_POST_CHAR_BIT (1 << 8)
static const unsigned short rcssmin_charmask[128] = {
21, 21, 21, 21, 21, 21, 21, 21,
21, 28, 8, 21, 8, 8, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
28, 469, 4, 85, 85, 85, 85, 4,
149, 277, 85, 469, 469, 117, 85, 84,
115, 115, 115, 115, 115, 115, 115, 115,
115, 115, 468, 340, 85, 469, 468, 85,
84, 115, 115, 115, 115, 115, 115, 117,
117, 117, 117, 117, 117, 117, 117, 117,
117, 117, 117, 117, 117, 117, 117, 117,
117, 117, 117, 213, 4, 341, 85, 117,
85, 115, 115, 115, 115, 115, 115, 117,
117, 117, 117, 117, 117, 117, 117, 117,
117, 117, 117, 117, 117, 116, 117, 117,
117, 117, 117, 468, 85, 468, 85, 21
};
#define RCSSMIN_IS_DULL(c) ((U(c) > 127) || \
(rcssmin_charmask[U(c) & 0x7F] & RCSSMIN_DULL_BIT))
#define RCSSMIN_IS_HEX(c) ((U(c) <= 127) && \
(rcssmin_charmask[U(c) & 0x7F] & RCSSMIN_HEX_BIT))
#define RCSSMIN_IS_ESC(c) ((U(c) > 127) || \
(rcssmin_charmask[U(c) & 0x7F] & RCSSMIN_ESC_BIT))
#define RCSSMIN_IS_SPACE(c) ((U(c) <= 127) && \
(rcssmin_charmask[U(c) & 0x7F] & RCSSMIN_SPACE_BIT))
#define RCSSMIN_IS_STRING_DULL(c) ((U(c) > 127) || \
(rcssmin_charmask[U(c) & 0x7F] & RCSSMIN_STRING_DULL_BIT))
#define RCSSMIN_IS_NMCHAR(c) ((U(c) > 127) || \
(rcssmin_charmask[U(c) & 0x7F] & RCSSMIN_NMCHAR_BIT))
#define RCSSMIN_IS_URI_DULL(c) ((U(c) > 127) || \
(rcssmin_charmask[U(c) & 0x7F] & RCSSMIN_URI_DULL_BIT))
#define RCSSMIN_IS_PRE_CHAR(c) ((U(c) <= 127) && \
(rcssmin_charmask[U(c) & 0x7F] & RCSSMIN_PRE_CHAR_BIT))
#define RCSSMIN_IS_POST_CHAR(c) ((U(c) <= 127) && \
(rcssmin_charmask[U(c) & 0x7F] & RCSSMIN_POST_CHAR_BIT))
static const rchar pattern_url[] = {
/*U('u'),*/ U('r'), U('l'), U('(')
};
static const rchar pattern_ie7[] = {
/*U('>'),*/ U('/'), U('*'), U('*'), U('/')
};
static const rchar pattern_media[] = {
U('m'), U('e'), U('d'), U('i'), U('a'),
U('M'), U('E'), U('D'), U('I'), U('A')
};
static const rchar pattern_document[] = {
U('d'), U('o'), U('c'), U('u'), U('m'), U('e'), U('n'), U('t'),
U('D'), U('O'), U('C'), U('U'), U('M'), U('E'), U('N'), U('T')
};
static const rchar pattern_supports[] = {
U('s'), U('u'), U('p'), U('p'), U('o'), U('r'), U('t'), U('s'),
U('S'), U('U'), U('P'), U('P'), U('O'), U('R'), U('T'), U('S')
};
static const rchar pattern_keyframes[] = {
U('k'), U('e'), U('y'), U('f'), U('r'), U('a'), U('m'), U('e'), U('s'),
U('K'), U('E'), U('Y'), U('F'), U('R'), U('A'), U('M'), U('E'), U('S')
};
static const rchar pattern_vendor_o[] = {
U('-'), U('o'), U('-'),
U('-'), U('O'), U('-')
};
static const rchar pattern_vendor_moz[] = {
U('-'), U('m'), U('o'), U('z'), U('-'),
U('-'), U('M'), U('O'), U('Z'), U('-')
};
static const rchar pattern_vendor_webkit[] = {
U('-'), U('w'), U('e'), U('b'), U('k'), U('i'), U('t'), U('-'),
U('-'), U('W'), U('E'), U('B'), U('K'), U('I'), U('T'), U('-')
};
static const rchar pattern_vendor_ms[] = {
U('-'), U('m'), U('s'), U('-'),
U('-'), U('M'), U('S'), U('-')
};
static const rchar pattern_first[] = {
U('f'), U('i'), U('r'), U('s'), U('t'), U('-'), U('l'),
U('F'), U('I'), U('R'), U('S'), U('T'), U('-'), U('L')
};
static const rchar pattern_line[] = {
U('i'), U('n'), U('e'),
U('I'), U('N'), U('E'),
};
static const rchar pattern_letter[] = {
U('e'), U('t'), U('t'), U('e'), U('r'),
U('E'), U('T'), U('T'), U('E'), U('R')
};
static const rchar pattern_macie5_init[] = {
U('/'), U('*'), U('\\'), U('*'), U('/')
};
static const rchar pattern_macie5_exit[] = {
U('/'), U('*'), U('*'), U('/')
};
/*
* Match a pattern (and copy immediately to target)
*/
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-overflow"
#endif
static int
copy_match(const rchar *pattern, const rchar *psentinel,
const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_;
rchar *target = *target_;
rchar c;
while (pattern < psentinel
&& source < ctx->sentinel && target < ctx->tsentinel
&& ((c = *source++) == *pattern++))
*target++ = c;
*source_ = source;
*target_ = target;
return (pattern == psentinel);
}
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic pop
#endif
#define MATCH(PAT, source, target, ctx) ( \
copy_match(pattern_##PAT, \
pattern_##PAT + sizeof(pattern_##PAT) / sizeof(rchar), \
source, target, ctx) \
)
/*
* Match a pattern (and copy immediately to target) - CI version
*/
static int
copy_imatch(const rchar *pattern, const rchar *psentinel,
const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_, *pstart = pattern;
rchar *target = *target_;
rchar c;
while (pattern < psentinel
&& source < ctx->sentinel && target < ctx->tsentinel
&& ((c = *source++) == *pattern
|| c == pstart[(pattern - pstart) + (psentinel - pstart)])) {
++pattern;
*target++ = c;
}
*source_ = source;
*target_ = target;
return (pattern == psentinel);
}
#define IMATCH(PAT, source, target, ctx) ( \
copy_imatch(pattern_##PAT, \
pattern_##PAT + sizeof(pattern_##PAT) / sizeof(rchar) / 2, \
source, target, ctx) \
)
/*
* Copy characters
*/
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-overflow"
#endif
static int
copy(const rchar *source, const rchar *sentinel, rchar **target_,
rcssmin_ctx_t *ctx)
{
rchar *target = *target_;
while (source < sentinel && target < ctx->tsentinel)
*target++ = *source++;
*target_ = target;
return (source == sentinel);
}
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic pop
#endif
#define COPY_PAT(PAT, target, ctx) ( \
copy(pattern_##PAT, \
pattern_##PAT + sizeof(pattern_##PAT) / sizeof(rchar), \
target, ctx) \
)
/*
* The ABORT macros work with known local variables!
*/
#define ABORT_(RET) do { \
if (source < ctx->sentinel && !(target < ctx->tsentinel)) { \
*source_ = source; \
*target_ = target; \
} \
return RET; \
} while(0)
#define CRAPPY_C90_COMPATIBLE_EMPTY
#define ABORT ABORT_(CRAPPY_C90_COMPATIBLE_EMPTY)
#define RABORT(RET) ABORT_((RET))
/*
* Copy escape
*/
static void
copy_escape(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_, *hsentinel;
rchar *target = *target_;
rchar c;
*target++ = U('\\');
*target_ = target;
if (source < ctx->sentinel && target < ctx->tsentinel) {
c = *source++;
if (RCSSMIN_IS_ESC(c)) {
*target++ = c;
}
else if (RCSSMIN_IS_HEX(c)) {
*target++ = c;
/* 6 hex chars max, one we got already */
if (ctx->sentinel - source > 5)
hsentinel = source + 5;
else
hsentinel = ctx->sentinel;
while (source < hsentinel && target < ctx->tsentinel
&& (c = *source, RCSSMIN_IS_HEX(c))) {
++source;
*target++ = c;
}
/* One optional space after */
if (source < ctx->sentinel && target < ctx->tsentinel) {
if (source == hsentinel)
c = *source;
if (RCSSMIN_IS_SPACE(c)) {
++source;
*target++ = U(' ');
if (c == U('\r') && source < ctx->sentinel
&& *source == U('\n'))
++source;
}
}
}
}
*target_ = target;
*source_ = source;
}
/*
* Copy string
*/
static void
copy_string(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_;
rchar *target = *target_;
rchar c, quote = source[-1];
*target++ = quote;
*target_ = target;
while (source < ctx->sentinel && target < ctx->tsentinel) {
c = *target++ = *source++;
if (RCSSMIN_IS_STRING_DULL(c))
continue;
switch (c) {
case U('\''): case U('"'):
if (c == quote) {
*target_ = target;
*source_ = source;
return;
}
continue;
case U('\\'):
if (source < ctx->sentinel && target < ctx->tsentinel) {
c = *source++;
switch (c) {
case U('\r'):
if (source < ctx->sentinel && *source == U('\n'))
++source;
/* fall through */
case U('\n'): case U('\f'):
--target;
break;
default:
*target++ = c;
}
}
continue;
}
break; /* forbidden characters */
}
ABORT;
}
/*
* Copy URI string
*/
static int
copy_uri_string(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_;
rchar *target = *target_;
rchar c, quote = source[-1];
*target++ = quote;
*target_ = target;
while (source < ctx->sentinel && target < ctx->tsentinel) {
c = *source++;
if (RCSSMIN_IS_SPACE(c))
continue;
*target++ = c;
if (RCSSMIN_IS_STRING_DULL(c))
continue;
switch (c) {
case U('\''): case U('"'):
if (c == quote) {
*target_ = target;
*source_ = source;
return 0;
}
continue;
case U('\\'):
if (source < ctx->sentinel && target < ctx->tsentinel) {
c = *source;
switch (c) {
case U('\r'):
if ((source + 1) < ctx->sentinel && source[1] == U('\n'))
++source;
/* fall through */
case U('\n'): case U('\f'):
--target;
++source;
break;
default:
--target;
copy_escape(&source, &target, ctx);
}
}
continue;
}
break; /* forbidden characters */
}
RABORT(-1);
}
/*
* Copy URI (unquoted)
*/
static int
copy_uri_unquoted(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_;
rchar *target = *target_;
rchar c;
*target++ = source[-1];
*target_ = target;
while (source < ctx->sentinel && target < ctx->tsentinel) {
c = *source++;
if (RCSSMIN_IS_SPACE(c))
continue;
*target++ = c;
if (RCSSMIN_IS_URI_DULL(c))
continue;
switch (c) {
case U(')'):
*target_ = target - 1;
*source_ = source - 1;
return 0;
case U('\\'):
if (source < ctx->sentinel && target < ctx->tsentinel) {
c = *source;
switch (c) {
case U('\r'):
if ((source + 1) < ctx->sentinel && source[1] == U('\n'))
++source;
/* fall through */
case U('\n'): case U('\f'):
--target;
++source;
break;
default:
--target;
copy_escape(&source, &target, ctx);
}
}
continue;
}
break; /* forbidden characters */
}
RABORT(-1);
}
/*
* Copy url
*/
static void
copy_url(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_;
rchar *target = *target_;
rchar c;
*target++ = U('u');
*target_ = target;
/* Must not be inside an identifier */
if ((source != ctx->start + 1) && RCSSMIN_IS_NMCHAR(source[-2]))
return;
if (!MATCH(url, &source, &target, ctx)
|| !(source < ctx->sentinel && target < ctx->tsentinel))
ABORT;
while (source < ctx->sentinel && RCSSMIN_IS_SPACE(*source))
++source;
if (!(source < ctx->sentinel))
ABORT;
c = *source++;
switch (c) {
case U('"'): case U('\''):
if (copy_uri_string(&source, &target, ctx) == -1)
ABORT;
while (source < ctx->sentinel && RCSSMIN_IS_SPACE(*source))
++source;
break;
default:
if (copy_uri_unquoted(&source, &target, ctx) == -1)
ABORT;
}
if (!(source < ctx->sentinel && target < ctx->tsentinel))
ABORT;
if ((*target++ = *source++) != U(')'))
ABORT;
*target_ = target;
*source_ = source;
}
/*
* Copy @-group
*/
static void
copy_at_group(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_;
rchar *target = *target_;
*target++ = U('@');
*target_ = target;
#define REMATCH(what) ( \
source = *source_, \
target = *target_, \
IMATCH(what, &source, &target, ctx) \
)
#define CMATCH(what) IMATCH(what, &source, &target, ctx)
if (( !CMATCH(media)
&& !REMATCH(supports)
&& !REMATCH(document)
&& !REMATCH(keyframes)
&& !(REMATCH(vendor_webkit) && CMATCH(keyframes))
&& !(REMATCH(vendor_moz) && CMATCH(keyframes))
&& !(REMATCH(vendor_o) && CMATCH(keyframes))
&& !(REMATCH(vendor_ms) && CMATCH(keyframes)))
|| !(source < ctx->sentinel && target < ctx->tsentinel)
|| RCSSMIN_IS_NMCHAR(*source))
ABORT;
#undef CMATCH
#undef REMATCH
++ctx->at_group;
*target_ = target;
*source_ = source;
}
/*
* Skip space
*/
static const rchar *
skip_space(const rchar *source, rcssmin_ctx_t *ctx)
{
const rchar *begin = source;
int res;
rchar c;
while (source < ctx->sentinel) {
c = *source;
if (RCSSMIN_IS_SPACE(c)) {
++source;
continue;
}
else if (c == U('/')) {
++source;
if (!(source < ctx->sentinel && *source == U('*'))) {
--source;
break;
}
++source;
res = 0;
while (source < ctx->sentinel) {
c = *source++;
if (c != U('*'))
continue;
if (!(source < ctx->sentinel))
return begin;
if (*source != U('/'))
continue;
/* Comment complete */
++source;
res = 1;
break;
}
if (!res)
return begin;
continue;
}
break;
}
return source;
}
/*
* Copy space
*/
static void
copy_space(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx,
need_space_flag need_space)
{
const rchar *source = *source_, *end, *comment;
rchar *target = *target_;
int res;
rchar c;
--source;
if (need_space == NEED_SPACE_MAYBE
&& source > ctx->start
&& !RCSSMIN_IS_PRE_CHAR(source[-1])
&& (end = skip_space(source, ctx)) < ctx->sentinel
&& (!RCSSMIN_IS_POST_CHAR(*end)
|| (*end == U(':') && !ctx->in_rule && !ctx->at_group))) {
if (!(target < ctx->tsentinel))
ABORT;
*target++ = U(' ');
}
while (source < ctx->sentinel) {
switch (c = *source) {
/* comment */
case U('/'):
comment = source++;
if (!((source < ctx->sentinel && *source == U('*')))) {
--source;
break;
}
++source;
res = 0;
while (source < ctx->sentinel) {
c = *source++;
if (c != U('*'))
continue;
if (!(source < ctx->sentinel))
ABORT;
if (*source != U('/'))
continue;
/* Comment complete */
++source;
res = 1;
if (ctx->keep_bang_comments && comment[2] == U('!')) {
ctx->in_macie5 = (source[-3] == U('\\'));
if (!copy(comment, source, &target, ctx))
ABORT;
}
else if (source[-3] == U('\\')) {
if (!ctx->in_macie5) {
if (!COPY_PAT(macie5_init, &target, ctx))
ABORT;
}
ctx->in_macie5 = 1;
}
else if (ctx->in_macie5) {
if (!COPY_PAT(macie5_exit, &target, ctx))
ABORT;
ctx->in_macie5 = 0;
}
/* else don't copy anything */
break;
}
if (!res)
ABORT;
continue;
/* space */
case U(' '): case U('\t'): case U('\r'): case U('\n'): case U('\f'):
++source;
continue;
}
break;
}
*source_ = source;
*target_ = target;
}
/*
* Copy space if comment
*/
static int
copy_space_comment(const rchar **source_, rchar **target_,
rcssmin_ctx_t *ctx, need_space_flag need_space)
{
const rchar *source = *source_;
rchar *target = *target_;
if (source < ctx->sentinel && *source == U('*')) {
copy_space(source_, target_, ctx, need_space);
if (*source_ > source)
return 0;
}
if (!(target < ctx->tsentinel))
RABORT(-1);
*target++ = source[-1];
/* *source_ = source; <-- unchanged */
*target_ = target;
return -1;
}
/*
* Copy space if exists
*/
static int
copy_space_optional(const rchar **source_, rchar **target_,
rcssmin_ctx_t *ctx)
{
const rchar *source = *source_;
if (!(source < ctx->sentinel))
return -1;
if (*source == U('/')) {
*source_ = source + 1;
return copy_space_comment(source_, target_, ctx, NEED_SPACE_NEVER);
}
else if (RCSSMIN_IS_SPACE(*source)) {
*source_ = source + 1;
copy_space(source_, target_, ctx, NEED_SPACE_NEVER);
return 0;
}
return -1;
}
/*
* Copy :first-line|letter
*/
static void
copy_first(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_, *next, *source_fork;
rchar *target = *target_, *target_fork;
*target++ = U(':');
*target_ = target;
if (!IMATCH(first, &source, &target, ctx)
|| !(source < ctx->sentinel && target < ctx->tsentinel))
ABORT;
source_fork = source;
target_fork = target;
if (!IMATCH(line, &source, &target, ctx)) {
source = source_fork;
target = target_fork;
if (!IMATCH(letter, &source, &target, ctx)
|| !(source < ctx->sentinel && target < ctx->tsentinel))
ABORT;
}
next = skip_space(source, ctx);
if (!(next < ctx->sentinel && target < ctx->tsentinel
&& (*next == U('{') || *next == U(','))))
ABORT;
*target++ = U(' ');
*target_ = target;
*source_ = source;
(void)copy_space_optional(source_, target_, ctx);
}
/*
* Copy IE7 hack
*/
static void
copy_ie7hack(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_;
rchar *target = *target_;
*target++ = U('>');
*target_ = target;
if (ctx->in_rule || ctx->at_group)
return; /* abort */
if (!MATCH(ie7, &source, &target, ctx))
ABORT;
ctx->in_macie5 = 0;
*target_ = target;
*source_ = source;
(void)copy_space_optional(source_, target_, ctx);
}
/*
* Copy semicolon; miss out duplicates or even this one (before '}')
*/
static void
copy_semicolon(const rchar **source_, rchar **target_, rcssmin_ctx_t *ctx)
{
const rchar *source = *source_, *begin, *end;
rchar *target = *target_;
begin = source;
while (source < ctx->sentinel) {
end = skip_space(source, ctx);
if (!(end < ctx->sentinel)) {
if (!(target < ctx->tsentinel))
ABORT;
*target++ = U(';');
break;
}
switch (*end) {
case U(';'):
source = end + 1;
continue;
case U('}'):
if (ctx->in_rule)
break;
/* fall through */
default:
if (!(target < ctx->tsentinel))
ABORT;
*target++ = U(';');
break;
}
break;
}
source = begin;
*target_ = target;
while (source < ctx->sentinel) {
if (*source == U(';')) {
++source;
continue;
}
if (copy_space_optional(&source, target_, ctx) == 0)
continue;
break;
}
*source_ = source;
}
/*
* Main function
*
* The return value determines the result length (kept in the target buffer).
* However, if the target buffer is too small, the return value is greater
* than tlength. The difference to tlength is the number of unconsumed source
* characters at the time the buffer was full. In this case you should resize
* the target buffer to the return value and call rcssmin again. Repeat as
* often as needed.
*/
static Py_ssize_t
rcssmin(const rchar *source, rchar *target, Py_ssize_t slength,
Py_ssize_t tlength, int keep_bang_comments)
{
rcssmin_ctx_t ctx_, *ctx = &ctx_;
const rchar *tstart = target;
rchar c;
ctx->start = source;
ctx->sentinel = source + slength;
ctx->tsentinel = target + tlength;
ctx->at_group = 0;
ctx->in_macie5 = 0;
ctx->in_rule = 0;
ctx->keep_bang_comments = keep_bang_comments;
while (source < ctx->sentinel && target < ctx->tsentinel) {
c = *source++;
if (RCSSMIN_IS_DULL(c)) {
*target++ = c;
continue;
}
else if (RCSSMIN_IS_SPACE(c)) {
copy_space(&source, &target, ctx, NEED_SPACE_MAYBE);
continue;
}
switch (c) {
/* Escape */
case U('\\'):
copy_escape(&source, &target, ctx);
continue;
/* String */
case U('"'): case U('\''):
copy_string(&source, &target, ctx);
continue;
/* URL */
case U('u'):
copy_url(&source, &target, ctx);
continue;
/* IE7hack */
case U('>'):
copy_ie7hack(&source, &target, ctx);
continue;
/* @-group */
case U('@'):
copy_at_group(&source, &target, ctx);
continue;
/* ; */
case U(';'):
copy_semicolon(&source, &target, ctx);
continue;
/* :first-line|letter followed by [{,] */
/* (apparently needed for IE6) */
case U(':'):
copy_first(&source, &target, ctx);
continue;
/* { */
case U('{'):
if (ctx->at_group)
--ctx->at_group;
else
++ctx->in_rule;
*target++ = c;
continue;
/* } */
case U('}'):
if (ctx->in_rule)
--ctx->in_rule;
*target++ = c;
continue;
/* space starting with comment */
case U('/'):
(void)copy_space_comment(&source, &target, ctx, NEED_SPACE_MAYBE);
continue;
/* Fallback: copy character. Better safe than sorry. Should not be
* reached, though */
default:
*target++ = c;
continue;
}
}
return
(Py_ssize_t)(target - tstart) + (Py_ssize_t)(ctx->sentinel - source);
}
PyDoc_STRVAR(rcssmin_cssmin__doc__,
"cssmin(style, keep_bang_comments=False)\n\
\n\
Minify CSS.\n\
\n\
:Note: This is a hand crafted C implementation built on the regex\n\
semantics.\n\
\n\
:Parameters:\n\
`style` : ``str``\n\
CSS to minify\n\
\n\
:Return: Minified style\n\
:Rtype: ``str``");
static PyObject *
rcssmin_cssmin(PyObject *self, PyObject *args, PyObject *kwds)
{
PyObject *style, *keep_bang_comments_ = NULL, *result;
static char *kwlist[] = {"style", "keep_bang_comments", NULL};
Py_ssize_t rlength, slength, length;
int keep_bang_comments;
#ifdef EXT2
int uni;
#define UOBJ "O"
#endif
#ifdef EXT3
#define UOBJ "U"
#endif
if (!PyArg_ParseTupleAndKeywords(args, kwds, UOBJ "|O", kwlist,
&style, &keep_bang_comments_))
return NULL;
if (!keep_bang_comments_)
keep_bang_comments = 0;
else {
keep_bang_comments = PyObject_IsTrue(keep_bang_comments_);
if (keep_bang_comments == -1)
return NULL;
}
#ifdef EXT2
if (PyUnicode_Check(style)) {
if (!(style = PyUnicode_AsUTF8String(style)))
return NULL;
uni = 1;
}
else {
if (!(style = PyObject_Str(style)))
return NULL;
uni = 0;
}
#endif
#ifdef EXT3
Py_INCREF(style);
#define PyString_GET_SIZE PyUnicode_GET_SIZE
#define PyString_AS_STRING PyUnicode_AS_UNICODE
#define _PyString_Resize PyUnicode_Resize
#define PyString_FromStringAndSize PyUnicode_FromUnicode
#endif
rlength = slength = PyString_GET_SIZE(style);
again:
if (!(result = PyString_FromStringAndSize(NULL, rlength))) {
Py_DECREF(style);
return NULL;
}
Py_BEGIN_ALLOW_THREADS
length = rcssmin((rchar *)PyString_AS_STRING(style),
(rchar *)PyString_AS_STRING(result),
slength, rlength, keep_bang_comments);
Py_END_ALLOW_THREADS
if (length > rlength) {
Py_DECREF(result);
rlength = length;
goto again;
}
Py_DECREF(style);
if (length < 0) {
Py_DECREF(result);
return NULL;
}
if (length != rlength && _PyString_Resize(&result, length) == -1)
return NULL;
#ifdef EXT2
if (uni) {
style = PyUnicode_DecodeUTF8(PyString_AS_STRING(result),
PyString_GET_SIZE(result), "strict");
Py_DECREF(result);
if (!style)
return NULL;
result = style;
}
#endif
return result;
}
/* ------------------------ BEGIN MODULE DEFINITION ------------------------ */
EXT_METHODS = {
{"cssmin",
(PyCFunction)rcssmin_cssmin, METH_VARARGS | METH_KEYWORDS,
rcssmin_cssmin__doc__},
{NULL} /* Sentinel */
};
PyDoc_STRVAR(EXT_DOCS_VAR,
"C implementation of rcssmin\n\
===========================\n\
\n\
C implementation of rcssmin.");
EXT_DEFINE(EXT_MODULE_NAME, EXT_METHODS_VAR, EXT_DOCS_VAR);
EXT_INIT_FUNC {
PyObject *m;
/* Create the module and populate stuff */
if (!(m = EXT_CREATE(&EXT_DEFINE_VAR)))
EXT_INIT_ERROR(NULL);
EXT_ADD_UNICODE(m, "__author__", "Andr\xe9 Malo", "latin-1");
EXT_ADD_STRING(m, "__docformat__", "restructuredtext en");
EXT_INIT_RETURN(m);
}
/* ------------------------- END MODULE DEFINITION ------------------------- */