1156 lines
29 KiB
C
1156 lines
29 KiB
C
/*
|
|
* 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)
|
|
*/
|
|
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;
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wstrict-overflow"
|
|
while (pattern < psentinel
|
|
&& source < ctx->sentinel && target < ctx->tsentinel
|
|
&& ((c = *source++) == *pattern++))
|
|
*target++ = c;
|
|
#pragma GCC diagnostic pop
|
|
|
|
*source_ = source;
|
|
*target_ = target;
|
|
|
|
return (pattern == psentinel);
|
|
}
|
|
|
|
#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
|
|
*/
|
|
static int
|
|
copy(const rchar *source, const rchar *sentinel, rchar **target_,
|
|
rcssmin_ctx_t *ctx)
|
|
{
|
|
rchar *target = *target_;
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wstrict-overflow"
|
|
while (source < sentinel && target < ctx->tsentinel)
|
|
*target++ = *source++;
|
|
#pragma GCC diagnostic pop
|
|
|
|
*target_ = target;
|
|
|
|
return (source == sentinel);
|
|
}
|
|
|
|
#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 ------------------------- */
|