gtk2/gtk/gtkcssparser.c
2019-04-12 19:34:28 +02:00

1297 lines
31 KiB
C

/* GTK - The GIMP Toolkit
* Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkcssparserprivate.h"
#include "gtkcssdimensionvalueprivate.h"
#include <errno.h>
#include <string.h>
/* just for the errors, yay! */
#include "gtkcssprovider.h"
#define NEWLINE_CHARS "\r\n"
#define WHITESPACE_CHARS "\f \t"
#define NMSTART "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
#define NMCHAR NMSTART "01234567890-_"
#define URLCHAR NMCHAR "!#$%&*~"
#define GTK_IS_CSS_PARSER(parser) ((parser) != NULL)
struct _GtkCssParser
{
const char *data;
GFile *file;
GtkCssParserErrorFunc error_func;
gpointer user_data;
const char *line_start;
guint line;
/* Use this for parsing identifiers, names and strings. */
GString *ident_str;
};
GtkCssParser *
_gtk_css_parser_new (const char *data,
GFile *file,
GtkCssParserErrorFunc error_func,
gpointer user_data)
{
GtkCssParser *parser;
g_return_val_if_fail (data != NULL, NULL);
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
parser = g_slice_new0 (GtkCssParser);
parser->data = data;
if (file)
parser->file = g_object_ref (file);
parser->error_func = error_func;
parser->user_data = user_data;
parser->line_start = data;
parser->line = 0;
parser->ident_str = NULL;
return parser;
}
void
_gtk_css_parser_free (GtkCssParser *parser)
{
g_return_if_fail (GTK_IS_CSS_PARSER (parser));
if (parser->file)
g_object_unref (parser->file);
if (parser->ident_str)
g_string_free (parser->ident_str, TRUE);
g_slice_free (GtkCssParser, parser);
}
guint
_gtk_css_parser_get_line (GtkCssParser *parser)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
return parser->line;
}
guint
_gtk_css_parser_get_position (GtkCssParser *parser)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
return parser->data - parser->line_start;
}
static GFile *
gtk_css_parser_get_base_file (GtkCssParser *parser)
{
GFile *base;
if (parser->file)
{
base = g_file_get_parent (parser->file);
}
else
{
char *dir = g_get_current_dir ();
base = g_file_new_for_path (dir);
g_free (dir);
}
return base;
}
GFile *
_gtk_css_parser_get_file_for_path (GtkCssParser *parser,
const char *path)
{
GFile *base, *file;
g_return_val_if_fail (parser != NULL, NULL);
g_return_val_if_fail (path != NULL, NULL);
base = gtk_css_parser_get_base_file (parser);
file = g_file_resolve_relative_path (base, path);
g_object_unref (base);
return file;
}
GFile *
_gtk_css_parser_get_file (GtkCssParser *parser)
{
g_return_val_if_fail (parser != NULL, NULL);
return parser->file;
}
void
_gtk_css_parser_take_error (GtkCssParser *parser,
GError *error)
{
parser->error_func (parser, error, parser->user_data);
g_error_free (error);
}
void
_gtk_css_parser_error (GtkCssParser *parser,
const char *format,
...)
{
GError *error;
va_list args;
va_start (args, format);
error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
GTK_CSS_PARSER_ERROR_SYNTAX,
format, args);
va_end (args);
_gtk_css_parser_take_error (parser, error);
}
static gboolean
gtk_css_parser_new_line (GtkCssParser *parser)
{
gboolean result = FALSE;
if (*parser->data == '\r')
{
result = TRUE;
parser->data++;
}
if (*parser->data == '\n')
{
result = TRUE;
parser->data++;
}
if (result)
{
parser->line++;
parser->line_start = parser->data;
}
return result;
}
static gboolean
gtk_css_parser_skip_comment (GtkCssParser *parser)
{
if (parser->data[0] != '/' ||
parser->data[1] != '*')
return FALSE;
parser->data += 2;
while (*parser->data)
{
gsize len = strcspn (parser->data, NEWLINE_CHARS "/");
parser->data += len;
if (gtk_css_parser_new_line (parser))
continue;
parser->data++;
if (len > 0 && parser->data[-2] == '*')
return TRUE;
if (parser->data[0] == '*')
_gtk_css_parser_error (parser, "'/*' in comment block");
}
/* FIXME: position */
_gtk_css_parser_error (parser, "Unterminated comment");
return TRUE;
}
void
_gtk_css_parser_skip_whitespace (GtkCssParser *parser)
{
size_t len;
while (*parser->data)
{
if (gtk_css_parser_new_line (parser))
continue;
len = strspn (parser->data, WHITESPACE_CHARS);
if (len)
{
parser->data += len;
continue;
}
if (!gtk_css_parser_skip_comment (parser))
break;
}
}
gboolean
_gtk_css_parser_try (GtkCssParser *parser,
const char *string,
gboolean skip_whitespace)
{
gsize string_len;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (string != NULL, FALSE);
string_len = strlen (string);
if (g_ascii_strncasecmp (parser->data, string, string_len) != 0)
return FALSE;
parser->data += string_len;
if (skip_whitespace)
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
gboolean
gtk_css_parser_try_at_keyword (GtkCssParser *parser,
const char *keyword)
{
gsize len;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (keyword != NULL, FALSE);
len = strlen (keyword);
if (parser->data[0] != '@' ||
g_ascii_strncasecmp (&parser->data[1], keyword, len) != 0)
return FALSE;
parser->data += len + 1;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
gboolean
gtk_css_parser_try_ident (GtkCssParser *parser,
const char *ident)
{
gsize len;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (ident != NULL, FALSE);
len = strlen (ident);
if (g_ascii_strncasecmp (parser->data, ident, len) != 0 ||
parser->data[len] == '(')
return FALSE;
parser->data += len;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
gboolean
gtk_css_parser_try_token (GtkCssParser *parser,
GtkCssTokenType type)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
switch (type)
{
case GTK_CSS_TOKEN_OPEN_CURLY:
if (*parser->data != '{')
return FALSE;
parser->data += 1;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
case GTK_CSS_TOKEN_CLOSE_CURLY:
if (*parser->data != '}')
return FALSE;
parser->data += 1;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
case GTK_CSS_TOKEN_OPEN_PARENS:
if (*parser->data != '(')
return FALSE;
parser->data += 1;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
case GTK_CSS_TOKEN_CLOSE_PARENS:
if (*parser->data != ')')
return FALSE;
parser->data += 1;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
case GTK_CSS_TOKEN_COMMA:
if (*parser->data != ',')
return FALSE;
parser->data += 1;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
case GTK_CSS_TOKEN_COLON:
if (*parser->data != ':')
return FALSE;
parser->data += 1;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
case GTK_CSS_TOKEN_SEMICOLON:
if (*parser->data != ';')
return FALSE;
parser->data += 1;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
default:
case GTK_CSS_TOKEN_STRING:
case GTK_CSS_TOKEN_AT_KEYWORD:
case GTK_CSS_TOKEN_IDENT:
case GTK_CSS_TOKEN_FUNCTION:
case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
case GTK_CSS_TOKEN_HASH_ID:
case GTK_CSS_TOKEN_URL:
case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
case GTK_CSS_TOKEN_DIMENSION:
case GTK_CSS_TOKEN_EOF:
case GTK_CSS_TOKEN_WHITESPACE:
case GTK_CSS_TOKEN_OPEN_SQUARE:
case GTK_CSS_TOKEN_CLOSE_SQUARE:
case GTK_CSS_TOKEN_CDC:
case GTK_CSS_TOKEN_CDO:
case GTK_CSS_TOKEN_DELIM:
case GTK_CSS_TOKEN_SIGNED_INTEGER:
case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
case GTK_CSS_TOKEN_SIGNED_NUMBER:
case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
case GTK_CSS_TOKEN_PERCENTAGE:
case GTK_CSS_TOKEN_INCLUDE_MATCH:
case GTK_CSS_TOKEN_DASH_MATCH:
case GTK_CSS_TOKEN_PREFIX_MATCH:
case GTK_CSS_TOKEN_SUFFIX_MATCH:
case GTK_CSS_TOKEN_SUBSTRING_MATCH:
case GTK_CSS_TOKEN_COLUMN:
case GTK_CSS_TOKEN_BAD_STRING:
case GTK_CSS_TOKEN_BAD_URL:
case GTK_CSS_TOKEN_COMMENT:
g_assert_not_reached ();
return FALSE;
}
}
static guint
get_xdigit (char c)
{
if (c >= 'a')
return c - 'a' + 10;
else if (c >= 'A')
return c - 'A' + 10;
else
return c - '0';
}
static void
_gtk_css_parser_unescape (GtkCssParser *parser,
GString *str)
{
guint i;
gunichar result = 0;
g_assert (*parser->data == '\\');
parser->data++;
for (i = 0; i < 6; i++)
{
if (!g_ascii_isxdigit (parser->data[i]))
break;
result = (result << 4) + get_xdigit (parser->data[i]);
}
if (i != 0)
{
g_string_append_unichar (str, result);
parser->data += i;
/* NB: gtk_css_parser_new_line() forward data pointer itself */
if (!gtk_css_parser_new_line (parser) &&
*parser->data &&
strchr (WHITESPACE_CHARS, *parser->data))
parser->data++;
return;
}
if (gtk_css_parser_new_line (parser))
return;
g_string_append_c (str, *parser->data);
parser->data++;
return;
}
static gboolean
_gtk_css_parser_read_char (GtkCssParser *parser,
GString * str,
const char * allowed)
{
if (*parser->data == 0)
return FALSE;
if (strchr (allowed, *parser->data))
{
g_string_append_c (str, *parser->data);
parser->data++;
return TRUE;
}
if (*parser->data >= 127)
{
gsize len = g_utf8_skip[(guint) *(guchar *) parser->data];
g_string_append_len (str, parser->data, len);
parser->data += len;
return TRUE;
}
if (*parser->data == '\\')
{
_gtk_css_parser_unescape (parser, str);
return TRUE;
}
return FALSE;
}
static char *
_gtk_css_parser_get_ident (GtkCssParser *parser)
{
char *result;
gsize len;
len = parser->ident_str->len;
result = g_new (char, len + 1);
memcpy (result, parser->ident_str->str, len + 1);
g_string_set_size (parser->ident_str, 0);
return result;
}
char *
_gtk_css_parser_try_name (GtkCssParser *parser,
gboolean skip_whitespace)
{
GString *name;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
if (parser->ident_str == NULL)
parser->ident_str = g_string_new (NULL);
name = parser->ident_str;
while (_gtk_css_parser_read_char (parser, name, NMCHAR))
;
if (skip_whitespace)
_gtk_css_parser_skip_whitespace (parser);
return _gtk_css_parser_get_ident (parser);
}
char *
_gtk_css_parser_try_ident (GtkCssParser *parser,
gboolean skip_whitespace)
{
const char *start;
GString *ident;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
start = parser->data;
if (parser->ident_str == NULL)
parser->ident_str = g_string_new (NULL);
ident = parser->ident_str;
if (*parser->data == '-')
{
g_string_append_c (ident, '-');
parser->data++;
}
if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
{
parser->data = start;
g_string_set_size (ident, 0);
return NULL;
}
while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
;
if (skip_whitespace)
_gtk_css_parser_skip_whitespace (parser);
return _gtk_css_parser_get_ident (parser);
}
gboolean
gtk_css_parser_has_token (GtkCssParser *parser,
GtkCssTokenType type)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
switch (type)
{
case GTK_CSS_TOKEN_STRING:
return *parser->data == '"' || *parser->data == '\'';
case GTK_CSS_TOKEN_OPEN_CURLY:
return *parser->data == '{';
case GTK_CSS_TOKEN_CLOSE_CURLY:
return *parser->data == '}';
case GTK_CSS_TOKEN_OPEN_PARENS:
return *parser->data == '(';
case GTK_CSS_TOKEN_CLOSE_PARENS:
return *parser->data == ')';
case GTK_CSS_TOKEN_COMMA:
return *parser->data == ',';
case GTK_CSS_TOKEN_COLON:
return *parser->data == ':';
case GTK_CSS_TOKEN_SEMICOLON:
return *parser->data == ';';
case GTK_CSS_TOKEN_AT_KEYWORD:
return *parser->data == '@';
case GTK_CSS_TOKEN_EOF:
return *parser->data == 0;
case GTK_CSS_TOKEN_IDENT:
return *parser->data != 0 &&
strchr (NMSTART "-", *parser->data) != NULL;
default:
case GTK_CSS_TOKEN_FUNCTION:
case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
case GTK_CSS_TOKEN_HASH_ID:
case GTK_CSS_TOKEN_URL:
case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
case GTK_CSS_TOKEN_DIMENSION:
case GTK_CSS_TOKEN_WHITESPACE:
case GTK_CSS_TOKEN_OPEN_SQUARE:
case GTK_CSS_TOKEN_CLOSE_SQUARE:
case GTK_CSS_TOKEN_CDC:
case GTK_CSS_TOKEN_CDO:
case GTK_CSS_TOKEN_DELIM:
case GTK_CSS_TOKEN_SIGNED_INTEGER:
case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
case GTK_CSS_TOKEN_SIGNED_NUMBER:
case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
case GTK_CSS_TOKEN_PERCENTAGE:
case GTK_CSS_TOKEN_INCLUDE_MATCH:
case GTK_CSS_TOKEN_DASH_MATCH:
case GTK_CSS_TOKEN_PREFIX_MATCH:
case GTK_CSS_TOKEN_SUFFIX_MATCH:
case GTK_CSS_TOKEN_SUBSTRING_MATCH:
case GTK_CSS_TOKEN_COLUMN:
case GTK_CSS_TOKEN_BAD_STRING:
case GTK_CSS_TOKEN_BAD_URL:
case GTK_CSS_TOKEN_COMMENT:
g_assert_not_reached ();
return FALSE;
}
}
gboolean
gtk_css_parser_has_ident (GtkCssParser *parser,
const char *ident)
{
gsize len;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (ident != NULL, FALSE);
len = strlen (ident);
return g_ascii_strncasecmp (parser->data, ident, len) == 0 &&
parser->data[len] != '(';
}
gboolean
gtk_css_parser_has_function (GtkCssParser *parser,
const char *name)
{
gsize len;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
len = strlen (name);
return g_ascii_strncasecmp (parser->data, name, len) == 0 &&
parser->data[len] == '(';
}
char *
_gtk_css_parser_read_string (GtkCssParser *parser)
{
GString *str;
char quote;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
quote = *parser->data;
if (quote != '"' && quote != '\'')
{
_gtk_css_parser_error (parser, "Expected a string.");
return NULL;
}
parser->data++;
if (parser->ident_str == NULL)
parser->ident_str = g_string_new (NULL);
str = parser->ident_str;
g_assert (str->len == 0);
while (TRUE)
{
gsize len = strcspn (parser->data, "\\'\"\n\r\f");
g_string_append_len (str, parser->data, len);
parser->data += len;
switch (*parser->data)
{
case '\\':
_gtk_css_parser_unescape (parser, str);
break;
case '"':
case '\'':
if (*parser->data == quote)
{
parser->data++;
_gtk_css_parser_skip_whitespace (parser);
return _gtk_css_parser_get_ident (parser);
}
g_string_append_c (str, *parser->data);
parser->data++;
break;
case '\0':
/* FIXME: position */
_gtk_css_parser_error (parser, "Missing end quote in string.");
g_string_set_size (str, 0);
return NULL;
default:
_gtk_css_parser_error (parser,
"Invalid character in string. Must be escaped.");
g_string_set_size (str, 0);
return NULL;
}
}
g_assert_not_reached ();
return NULL;
}
gboolean
_gtk_css_parser_try_int (GtkCssParser *parser,
int *value)
{
gint64 result;
char *end;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (value != NULL, FALSE);
/* strtoll parses a plus, but we are not allowed to */
if (*parser->data == '+')
return FALSE;
errno = 0;
result = g_ascii_strtoll (parser->data, &end, 10);
if (errno)
return FALSE;
if (result > G_MAXINT || result < G_MININT)
return FALSE;
if (parser->data == end)
return FALSE;
parser->data = end;
*value = result;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
gboolean
_gtk_css_parser_try_double (GtkCssParser *parser,
gdouble *value)
{
gdouble result;
char *end;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (value != NULL, FALSE);
errno = 0;
result = g_ascii_strtod (parser->data, &end);
if (errno)
return FALSE;
if (parser->data == end)
return FALSE;
parser->data = end;
*value = result;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
char *
gtk_css_parser_consume_ident (GtkCssParser *self)
{
char *result;
result = _gtk_css_parser_try_ident (self, TRUE);
if (result == NULL)
_gtk_css_parser_error (self, "Expected an identifier");
return result;
}
gboolean
gtk_css_parser_consume_number (GtkCssParser *self,
double *number)
{
if (_gtk_css_parser_try_double (self, number))
return TRUE;
_gtk_css_parser_error (self, "Expected a number");
return FALSE;
}
gboolean
_gtk_css_parser_has_number (GtkCssParser *parser)
{
char c;
if (parser->data[0] == '-' || parser->data[0] == '+')
c = parser->data[1];
else
c = parser->data[0];
/* ahem */
return g_ascii_isdigit (c) || c == '.';
}
GtkCssValue *
gtk_css_dimension_value_parse (GtkCssParser *parser,
GtkCssNumberParseFlags flags)
{
static const struct {
const char *name;
GtkCssUnit unit;
GtkCssNumberParseFlags required_flags;
} units[] = {
{ "px", GTK_CSS_PX, GTK_CSS_PARSE_LENGTH },
{ "pt", GTK_CSS_PT, GTK_CSS_PARSE_LENGTH },
{ "em", GTK_CSS_EM, GTK_CSS_PARSE_LENGTH },
{ "ex", GTK_CSS_EX, GTK_CSS_PARSE_LENGTH },
{ "rem", GTK_CSS_REM, GTK_CSS_PARSE_LENGTH },
{ "pc", GTK_CSS_PC, GTK_CSS_PARSE_LENGTH },
{ "in", GTK_CSS_IN, GTK_CSS_PARSE_LENGTH },
{ "cm", GTK_CSS_CM, GTK_CSS_PARSE_LENGTH },
{ "mm", GTK_CSS_MM, GTK_CSS_PARSE_LENGTH },
{ "rad", GTK_CSS_RAD, GTK_CSS_PARSE_ANGLE },
{ "deg", GTK_CSS_DEG, GTK_CSS_PARSE_ANGLE },
{ "grad", GTK_CSS_GRAD, GTK_CSS_PARSE_ANGLE },
{ "turn", GTK_CSS_TURN, GTK_CSS_PARSE_ANGLE },
{ "s", GTK_CSS_S, GTK_CSS_PARSE_TIME },
{ "ms", GTK_CSS_MS, GTK_CSS_PARSE_TIME }
};
char *end, *unit_name;
double value;
GtkCssUnit unit;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
errno = 0;
value = g_ascii_strtod (parser->data, &end);
if (errno)
{
_gtk_css_parser_error (parser, "not a number: %s", g_strerror (errno));
return NULL;
}
if (parser->data == end)
{
_gtk_css_parser_error (parser, "not a number");
return NULL;
}
parser->data = end;
if (flags & GTK_CSS_POSITIVE_ONLY &&
value < 0)
{
_gtk_css_parser_error (parser, "negative values are not allowed.");
return NULL;
}
unit_name = _gtk_css_parser_try_ident (parser, FALSE);
if (unit_name)
{
guint i;
for (i = 0; i < G_N_ELEMENTS (units); i++)
{
if (flags & units[i].required_flags &&
g_ascii_strcasecmp (unit_name, units[i].name) == 0)
break;
}
if (i >= G_N_ELEMENTS (units))
{
_gtk_css_parser_error (parser, "'%s' is not a valid unit.", unit_name);
g_free (unit_name);
return NULL;
}
unit = units[i].unit;
g_free (unit_name);
}
else
{
if ((flags & GTK_CSS_PARSE_PERCENT) &&
_gtk_css_parser_try (parser, "%", FALSE))
{
unit = GTK_CSS_PERCENT;
}
else if (value == 0.0)
{
if (flags & GTK_CSS_PARSE_NUMBER)
unit = GTK_CSS_NUMBER;
else if (flags & GTK_CSS_PARSE_LENGTH)
unit = GTK_CSS_PX;
else if (flags & GTK_CSS_PARSE_ANGLE)
unit = GTK_CSS_DEG;
else if (flags & GTK_CSS_PARSE_TIME)
unit = GTK_CSS_S;
else
unit = GTK_CSS_PERCENT;
}
else if (flags & GTK_CSS_PARSE_NUMBER)
{
unit = GTK_CSS_NUMBER;
}
else
{
_gtk_css_parser_error (parser, "Unit is missing.");
return NULL;
}
}
_gtk_css_parser_skip_whitespace (parser);
return gtk_css_dimension_value_new (value, unit);
}
gboolean
_gtk_css_parser_try_hash_color (GtkCssParser *parser,
GdkRGBA *rgba)
{
if (parser->data[0] == '#' &&
g_ascii_isxdigit (parser->data[1]) &&
g_ascii_isxdigit (parser->data[2]) &&
g_ascii_isxdigit (parser->data[3]))
{
if (g_ascii_isxdigit (parser->data[4]))
{
if (g_ascii_isxdigit (parser->data[5]) &&
g_ascii_isxdigit (parser->data[6]))
{
rgba->red = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
rgba->green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
rgba->blue = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
if (g_ascii_isxdigit (parser->data[7]) &&
g_ascii_isxdigit (parser->data[8]))
{
rgba->alpha = ((get_xdigit (parser->data[7]) << 4) + get_xdigit (parser->data[8])) / 255.0;
parser->data += 9;
}
else
{
rgba->alpha = 1.0;
parser->data += 7;
}
}
else
{
rgba->red = get_xdigit (parser->data[1]) / 15.0;
rgba->green = get_xdigit (parser->data[2]) / 15.0;
rgba->blue = get_xdigit (parser->data[3]) / 15.0;
rgba->alpha = get_xdigit (parser->data[4]) / 15.0;
parser->data += 5;
}
}
else
{
rgba->red = get_xdigit (parser->data[1]) / 15.0;
rgba->green = get_xdigit (parser->data[2]) / 15.0;
rgba->blue = get_xdigit (parser->data[3]) / 15.0;
rgba->alpha = 1.0;
parser->data += 4;
}
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
return FALSE;
}
GFile *
_gtk_css_parser_read_url (GtkCssParser *parser)
{
gchar *path;
char *scheme;
GFile *file;
if (_gtk_css_parser_try (parser, "url", FALSE))
{
if (!_gtk_css_parser_try (parser, "(", TRUE))
{
_gtk_css_parser_error (parser, "Expected '(' after 'url'");
return NULL;
}
path = _gtk_css_parser_read_string (parser);
if (path == NULL)
return NULL;
if (!_gtk_css_parser_try (parser, ")", TRUE))
{
_gtk_css_parser_error (parser, "No closing ')' found for 'url'");
g_free (path);
return NULL;
}
scheme = g_uri_parse_scheme (path);
if (scheme != NULL)
{
file = g_file_new_for_uri (path);
g_free (path);
g_free (scheme);
return file;
}
}
else
{
path = _gtk_css_parser_try_name (parser, TRUE);
if (path == NULL)
{
_gtk_css_parser_error (parser, "Not a valid url");
return NULL;
}
}
file = _gtk_css_parser_get_file_for_path (parser, path);
g_free (path);
return file;
}
static void
gtk_css_parser_resync_internal (GtkCssParser *parser,
gboolean sync_at_semicolon,
gboolean read_sync_token,
char terminator)
{
gsize len;
do {
len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
parser->data += len;
if (gtk_css_parser_new_line (parser))
continue;
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_STRING))
{
/* Hrm, this emits errors, and i suspect it shouldn't... */
char *free_me = _gtk_css_parser_read_string (parser);
g_free (free_me);
continue;
}
if (gtk_css_parser_skip_comment (parser))
continue;
switch (*parser->data)
{
case '\\':
{
GString *ignore = g_string_new (NULL);
_gtk_css_parser_unescape (parser, ignore);
g_string_free (ignore, TRUE);
}
break;
case ';':
if (sync_at_semicolon && !read_sync_token)
return;
parser->data++;
if (sync_at_semicolon)
{
_gtk_css_parser_skip_whitespace (parser);
return;
}
break;
case '(':
parser->data++;
_gtk_css_parser_resync (parser, FALSE, ')');
if (*parser->data)
parser->data++;
break;
case '[':
parser->data++;
_gtk_css_parser_resync (parser, FALSE, ']');
if (*parser->data)
parser->data++;
break;
case '{':
parser->data++;
_gtk_css_parser_resync (parser, FALSE, '}');
if (*parser->data)
parser->data++;
if (sync_at_semicolon || !terminator)
{
_gtk_css_parser_skip_whitespace (parser);
return;
}
break;
case '}':
case ')':
case ']':
if (terminator == *parser->data)
{
_gtk_css_parser_skip_whitespace (parser);
return;
}
parser->data++;
continue;
case '\0':
break;
case '/':
default:
parser->data++;
break;
}
} while (*parser->data);
}
void
_gtk_css_parser_resync (GtkCssParser *parser,
gboolean sync_at_semicolon,
char terminator)
{
g_return_if_fail (GTK_IS_CSS_PARSER (parser));
gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);
}
void
_gtk_css_print_string (GString *str,
const char *string)
{
gsize len;
g_return_if_fail (str != NULL);
g_return_if_fail (string != NULL);
g_string_append_c (str, '"');
do {
len = strcspn (string, "\\\"\n\r\f");
g_string_append_len (str, string, len);
string += len;
switch (*string)
{
case '\0':
goto out;
case '\n':
g_string_append (str, "\\A ");
break;
case '\r':
g_string_append (str, "\\D ");
break;
case '\f':
g_string_append (str, "\\C ");
break;
case '\"':
g_string_append (str, "\\\"");
break;
case '\\':
g_string_append (str, "\\\\");
break;
default:
g_assert_not_reached ();
break;
}
string++;
} while (*string);
out:
g_string_append_c (str, '"');
}
gboolean
gtk_css_parser_consume_function (GtkCssParser *self,
guint min_args,
guint max_args,
guint (* parse_func) (GtkCssParser *, guint, gpointer),
gpointer data)
{
gboolean result = FALSE;
char *function_name;
guint arg;
function_name = _gtk_css_parser_try_ident (self, FALSE);
g_return_val_if_fail (function_name != NULL, FALSE);
g_return_val_if_fail (_gtk_css_parser_try (self, "(", TRUE), FALSE);
arg = 0;
while (TRUE)
{
guint parse_args = parse_func (self, arg, data);
if (parse_args == 0)
break;
arg += parse_args;
if (gtk_css_parser_try_token (self, GTK_CSS_TOKEN_CLOSE_PARENS))
{
if (arg < min_args)
{
_gtk_css_parser_error (self, "%s() requires at least %u arguments", function_name, min_args);
break;
}
else
{
result = TRUE;
break;
}
}
else if (gtk_css_parser_try_token (self, GTK_CSS_TOKEN_COMMA))
{
if (arg >= max_args)
{
_gtk_css_parser_error (self, "Expected ')' at end of %s()", function_name);
break;
}
continue;
}
else
{
_gtk_css_parser_error (self, "Unexpected data at end of %s() argument", function_name);
break;
}
}
g_free (function_name);
return result;
}
gsize
gtk_css_parser_consume_any (GtkCssParser *parser,
const GtkCssParseOption *options,
gsize n_options,
gpointer user_data)
{
gsize result;
gsize i;
g_return_val_if_fail (parser != NULL, 0);
g_return_val_if_fail (options != NULL, 0);
g_return_val_if_fail (n_options < sizeof (gsize) * 8 - 1, 0);
result = 0;
while (result != (1 << n_options) - 1)
{
for (i = 0; i < n_options; i++)
{
if (result & (1 << i))
continue;
if (options[i].can_parse && !options[i].can_parse (parser, options[i].data, user_data))
continue;
if (!options[i].parse (parser, options[i].data, user_data))
return 0;
result |= 1 << i;
break;
}
if (i == n_options)
break;
}
if (result == 0)
{
_gtk_css_parser_error (parser, "No valid value given");
return result;
}
return result;
}