2011-04-14 02:47:18 +00:00
|
|
|
/* 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
|
2012-02-27 13:01:10 +00:00
|
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
2011-04-14 02:47:18 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include "gtkcssparserprivate.h"
|
2012-04-04 16:25:43 +00:00
|
|
|
|
2016-02-12 05:25:50 +00:00
|
|
|
#include "gtkcssdimensionvalueprivate.h"
|
2011-04-14 02:47:18 +00:00
|
|
|
|
|
|
|
#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
|
|
|
|
{
|
2012-04-18 14:54:04 +00:00
|
|
|
const char *data;
|
|
|
|
GFile *file;
|
|
|
|
GtkCssParserErrorFunc error_func;
|
|
|
|
gpointer user_data;
|
2011-04-14 02:47:18 +00:00
|
|
|
|
2012-04-18 14:54:04 +00:00
|
|
|
const char *line_start;
|
|
|
|
guint line;
|
2017-12-31 14:16:22 +00:00
|
|
|
|
|
|
|
/* Use this for parsing identifiers, names and strings. */
|
|
|
|
GString *ident_str;
|
2011-04-14 02:47:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
GtkCssParser *
|
|
|
|
_gtk_css_parser_new (const char *data,
|
2012-04-18 14:54:04 +00:00
|
|
|
GFile *file,
|
2011-04-14 02:47:18 +00:00
|
|
|
GtkCssParserErrorFunc error_func,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
GtkCssParser *parser;
|
|
|
|
|
|
|
|
g_return_val_if_fail (data != NULL, NULL);
|
2012-04-18 14:54:04 +00:00
|
|
|
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
|
2011-04-14 02:47:18 +00:00
|
|
|
|
|
|
|
parser = g_slice_new0 (GtkCssParser);
|
|
|
|
|
|
|
|
parser->data = data;
|
2012-04-18 14:54:04 +00:00
|
|
|
if (file)
|
|
|
|
parser->file = g_object_ref (file);
|
2011-04-14 02:47:18 +00:00
|
|
|
parser->error_func = error_func;
|
|
|
|
parser->user_data = user_data;
|
|
|
|
|
|
|
|
parser->line_start = data;
|
2011-06-17 05:21:46 +00:00
|
|
|
parser->line = 0;
|
2011-04-14 02:47:18 +00:00
|
|
|
|
2017-12-31 14:16:22 +00:00
|
|
|
parser->ident_str = NULL;
|
|
|
|
|
2011-04-14 02:47:18 +00:00
|
|
|
return parser;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gtk_css_parser_free (GtkCssParser *parser)
|
|
|
|
{
|
|
|
|
g_return_if_fail (GTK_IS_CSS_PARSER (parser));
|
|
|
|
|
2012-04-18 14:54:04 +00:00
|
|
|
if (parser->file)
|
|
|
|
g_object_unref (parser->file);
|
|
|
|
|
2017-12-31 14:16:22 +00:00
|
|
|
if (parser->ident_str)
|
|
|
|
g_string_free (parser->ident_str, TRUE);
|
|
|
|
|
2011-04-14 02:47:18 +00:00
|
|
|
g_slice_free (GtkCssParser, parser);
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean
|
|
|
|
_gtk_css_parser_is_eof (GtkCssParser *parser)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
|
|
|
|
|
|
|
|
return *parser->data == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean
|
|
|
|
_gtk_css_parser_begins_with (GtkCssParser *parser,
|
|
|
|
char c)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
|
|
|
|
|
|
|
|
return *parser->data == c;
|
|
|
|
}
|
|
|
|
|
2011-12-20 07:15:18 +00:00
|
|
|
gboolean
|
|
|
|
_gtk_css_parser_has_prefix (GtkCssParser *parser,
|
|
|
|
const char *prefix)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
|
|
|
|
|
|
|
|
return g_ascii_strncasecmp (parser->data, prefix, strlen (prefix)) == 0;
|
|
|
|
}
|
|
|
|
|
2011-04-14 02:47:18 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-04-18 17:43:24 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-05-18 16:32:22 +00:00
|
|
|
void
|
|
|
|
_gtk_css_parser_take_error (GtkCssParser *parser,
|
|
|
|
GError *error)
|
|
|
|
{
|
|
|
|
parser->error_func (parser, error, parser->user_data);
|
|
|
|
|
|
|
|
g_error_free (error);
|
|
|
|
}
|
|
|
|
|
2011-04-14 02:47:18 +00:00
|
|
|
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_PROVIDER_ERROR,
|
|
|
|
GTK_CSS_PROVIDER_ERROR_SYNTAX,
|
|
|
|
format, args);
|
|
|
|
va_end (args);
|
|
|
|
|
2011-05-18 16:32:22 +00:00
|
|
|
_gtk_css_parser_take_error (parser, error);
|
2011-04-14 02:47:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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++;
|
|
|
|
|
2012-11-28 20:09:37 +00:00
|
|
|
if (len > 0 && parser->data[-2] == '*')
|
2011-04-14 02:47:18 +00:00
|
|
|
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
|
2019-03-22 16:37:04 +00:00
|
|
|
_gtk_css_parser_try (GtkCssParser *parser,
|
|
|
|
const char *string,
|
|
|
|
gboolean skip_whitespace)
|
2011-04-14 02:47:18 +00:00
|
|
|
{
|
2019-03-22 16:37:04 +00:00
|
|
|
gsize string_len;
|
|
|
|
|
2011-04-14 02:47:18 +00:00
|
|
|
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
|
|
|
|
g_return_val_if_fail (string != NULL, FALSE);
|
|
|
|
|
2019-03-22 16:37:04 +00:00
|
|
|
string_len = strlen (string);
|
|
|
|
|
2019-01-19 17:32:00 +00:00
|
|
|
if (g_ascii_strncasecmp (parser->data, string, string_len) != 0)
|
2011-04-14 02:47:18 +00:00
|
|
|
return FALSE;
|
|
|
|
|
2019-01-19 17:32:00 +00:00
|
|
|
parser->data += string_len;
|
2011-04-14 02:47:18 +00:00
|
|
|
|
|
|
|
if (skip_whitespace)
|
|
|
|
_gtk_css_parser_skip_whitespace (parser);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-12-31 14:16:22 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-04-14 02:47:18 +00:00
|
|
|
char *
|
|
|
|
_gtk_css_parser_try_name (GtkCssParser *parser,
|
|
|
|
gboolean skip_whitespace)
|
|
|
|
{
|
|
|
|
GString *name;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
|
|
|
|
|
2017-12-31 14:16:22 +00:00
|
|
|
if (parser->ident_str == NULL)
|
|
|
|
parser->ident_str = g_string_new (NULL);
|
|
|
|
|
|
|
|
name = parser->ident_str;
|
2011-04-14 02:47:18 +00:00
|
|
|
|
|
|
|
while (_gtk_css_parser_read_char (parser, name, NMCHAR))
|
|
|
|
;
|
|
|
|
|
|
|
|
if (skip_whitespace)
|
|
|
|
_gtk_css_parser_skip_whitespace (parser);
|
|
|
|
|
2017-12-31 14:16:22 +00:00
|
|
|
return _gtk_css_parser_get_ident (parser);
|
2011-04-14 02:47:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2017-12-31 14:16:22 +00:00
|
|
|
if (parser->ident_str == NULL)
|
|
|
|
parser->ident_str = g_string_new (NULL);
|
|
|
|
|
|
|
|
ident = parser->ident_str;
|
2011-04-14 02:47:18 +00:00
|
|
|
|
|
|
|
if (*parser->data == '-')
|
|
|
|
{
|
|
|
|
g_string_append_c (ident, '-');
|
|
|
|
parser->data++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
|
|
|
|
{
|
|
|
|
parser->data = start;
|
2017-12-31 14:16:22 +00:00
|
|
|
g_string_set_size (ident, 0);
|
2011-04-14 02:47:18 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
|
|
|
|
;
|
|
|
|
|
|
|
|
if (skip_whitespace)
|
|
|
|
_gtk_css_parser_skip_whitespace (parser);
|
|
|
|
|
2017-12-31 14:16:22 +00:00
|
|
|
return _gtk_css_parser_get_ident (parser);
|
2011-04-14 02:47:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
gboolean
|
|
|
|
_gtk_css_parser_is_string (GtkCssParser *parser)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
|
|
|
|
|
|
|
|
return *parser->data == '"' || *parser->data == '\'';
|
|
|
|
}
|
|
|
|
|
|
|
|
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 != '\'')
|
2011-05-29 05:03:59 +00:00
|
|
|
{
|
|
|
|
_gtk_css_parser_error (parser, "Expected a string.");
|
|
|
|
return NULL;
|
|
|
|
}
|
2011-04-14 02:47:18 +00:00
|
|
|
|
|
|
|
parser->data++;
|
2017-12-31 14:16:22 +00:00
|
|
|
|
|
|
|
if (parser->ident_str == NULL)
|
|
|
|
parser->ident_str = g_string_new (NULL);
|
|
|
|
|
|
|
|
str = parser->ident_str;
|
|
|
|
g_assert (str->len == 0);
|
2011-04-14 02:47:18 +00:00
|
|
|
|
|
|
|
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);
|
2017-12-31 14:16:22 +00:00
|
|
|
return _gtk_css_parser_get_ident (parser);
|
2011-04-14 02:47:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
g_string_append_c (str, *parser->data);
|
|
|
|
parser->data++;
|
|
|
|
break;
|
|
|
|
case '\0':
|
|
|
|
/* FIXME: position */
|
|
|
|
_gtk_css_parser_error (parser, "Missing end quote in string.");
|
2017-12-31 14:16:22 +00:00
|
|
|
g_string_set_size (str, 0);
|
2011-04-14 02:47:18 +00:00
|
|
|
return NULL;
|
|
|
|
default:
|
|
|
|
_gtk_css_parser_error (parser,
|
|
|
|
"Invalid character in string. Must be escaped.");
|
2017-12-31 14:16:22 +00:00
|
|
|
g_string_set_size (str, 0);
|
2011-04-14 02:47:18 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-01-13 22:48:42 +00:00
|
|
|
gboolean
|
|
|
|
_gtk_css_parser_has_number (GtkCssParser *parser)
|
|
|
|
{
|
2016-02-23 03:19:42 +00:00
|
|
|
char c;
|
|
|
|
|
|
|
|
if (parser->data[0] == '-' || parser->data[0] == '+')
|
|
|
|
c = parser->data[1];
|
|
|
|
else
|
|
|
|
c = parser->data[0];
|
|
|
|
|
2012-01-13 22:48:42 +00:00
|
|
|
/* ahem */
|
2016-02-23 03:19:42 +00:00
|
|
|
return g_ascii_isdigit (c) || c == '.';
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
|
2012-04-04 16:25:43 +00:00
|
|
|
GtkCssValue *
|
2016-02-12 05:25:50 +00:00
|
|
|
gtk_css_dimension_value_parse (GtkCssParser *parser,
|
|
|
|
GtkCssNumberParseFlags flags)
|
2012-01-13 22:48:42 +00:00
|
|
|
{
|
|
|
|
static const struct {
|
|
|
|
const char *name;
|
|
|
|
GtkCssUnit unit;
|
|
|
|
GtkCssNumberParseFlags required_flags;
|
|
|
|
} units[] = {
|
2012-01-15 16:51:58 +00:00
|
|
|
{ "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 },
|
2016-02-04 18:37:44 +00:00
|
|
|
{ "rem", GTK_CSS_REM, GTK_CSS_PARSE_LENGTH },
|
2012-01-15 16:51:58 +00:00
|
|
|
{ "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 },
|
2012-04-01 02:02:34 +00:00
|
|
|
{ "turn", GTK_CSS_TURN, GTK_CSS_PARSE_ANGLE },
|
|
|
|
{ "s", GTK_CSS_S, GTK_CSS_PARSE_TIME },
|
|
|
|
{ "ms", GTK_CSS_MS, GTK_CSS_PARSE_TIME }
|
2012-01-13 22:48:42 +00:00
|
|
|
};
|
2012-04-04 16:25:43 +00:00
|
|
|
char *end, *unit_name;
|
|
|
|
double value;
|
|
|
|
GtkCssUnit unit;
|
2012-01-13 22:48:42 +00:00
|
|
|
|
2012-04-04 16:25:43 +00:00
|
|
|
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
|
2012-01-13 22:48:42 +00:00
|
|
|
|
|
|
|
errno = 0;
|
2012-04-04 16:25:43 +00:00
|
|
|
value = g_ascii_strtod (parser->data, &end);
|
2012-01-13 22:48:42 +00:00
|
|
|
if (errno)
|
|
|
|
{
|
|
|
|
_gtk_css_parser_error (parser, "not a number: %s", g_strerror (errno));
|
2012-04-04 16:25:43 +00:00
|
|
|
return NULL;
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
if (parser->data == end)
|
|
|
|
{
|
|
|
|
_gtk_css_parser_error (parser, "not a number");
|
2012-04-04 16:25:43 +00:00
|
|
|
return NULL;
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
parser->data = end;
|
|
|
|
|
|
|
|
if (flags & GTK_CSS_POSITIVE_ONLY &&
|
2012-04-04 16:25:43 +00:00
|
|
|
value < 0)
|
2012-01-13 22:48:42 +00:00
|
|
|
{
|
|
|
|
_gtk_css_parser_error (parser, "negative values are not allowed.");
|
2012-04-04 16:25:43 +00:00
|
|
|
return NULL;
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
|
2012-04-04 16:25:43 +00:00
|
|
|
unit_name = _gtk_css_parser_try_ident (parser, FALSE);
|
2012-01-13 22:48:42 +00:00
|
|
|
|
2012-04-04 16:25:43 +00:00
|
|
|
if (unit_name)
|
2012-01-13 22:48:42 +00:00
|
|
|
{
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (units); i++)
|
|
|
|
{
|
|
|
|
if (flags & units[i].required_flags &&
|
2012-04-04 16:25:43 +00:00
|
|
|
g_ascii_strcasecmp (unit_name, units[i].name) == 0)
|
2012-01-13 22:48:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i >= G_N_ELEMENTS (units))
|
|
|
|
{
|
2015-09-20 06:32:48 +00:00
|
|
|
_gtk_css_parser_error (parser, "'%s' is not a valid unit.", unit_name);
|
2012-04-19 15:01:37 +00:00
|
|
|
g_free (unit_name);
|
2012-04-04 16:25:43 +00:00
|
|
|
return NULL;
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
|
2012-04-04 16:25:43 +00:00
|
|
|
unit = units[i].unit;
|
2012-04-19 15:01:37 +00:00
|
|
|
|
|
|
|
g_free (unit_name);
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ((flags & GTK_CSS_PARSE_PERCENT) &&
|
|
|
|
_gtk_css_parser_try (parser, "%", FALSE))
|
|
|
|
{
|
2012-04-04 16:25:43 +00:00
|
|
|
unit = GTK_CSS_PERCENT;
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
2012-04-04 16:25:43 +00:00
|
|
|
else if (value == 0.0)
|
2012-01-13 22:48:42 +00:00
|
|
|
{
|
|
|
|
if (flags & GTK_CSS_PARSE_NUMBER)
|
2012-04-04 16:25:43 +00:00
|
|
|
unit = GTK_CSS_NUMBER;
|
2012-01-13 22:48:42 +00:00
|
|
|
else if (flags & GTK_CSS_PARSE_LENGTH)
|
2012-04-04 16:25:43 +00:00
|
|
|
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;
|
2012-01-13 22:48:42 +00:00
|
|
|
else
|
2012-04-04 16:25:43 +00:00
|
|
|
unit = GTK_CSS_PERCENT;
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
else if (flags & GTK_CSS_PARSE_NUMBER)
|
|
|
|
{
|
2012-04-04 16:25:43 +00:00
|
|
|
unit = GTK_CSS_NUMBER;
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_gtk_css_parser_error (parser, "Unit is missing.");
|
2012-04-04 16:25:43 +00:00
|
|
|
return NULL;
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_gtk_css_parser_skip_whitespace (parser);
|
|
|
|
|
2016-02-12 05:25:50 +00:00
|
|
|
return gtk_css_dimension_value_new (value, unit);
|
2012-01-13 22:48:42 +00:00
|
|
|
}
|
|
|
|
|
2012-04-05 19:09:35 +00:00
|
|
|
gboolean
|
|
|
|
_gtk_css_parser_try_hash_color (GtkCssParser *parser,
|
|
|
|
GdkRGBA *rgba)
|
2011-04-14 02:47:18 +00:00
|
|
|
{
|
|
|
|
if (parser->data[0] == '#' &&
|
|
|
|
g_ascii_isxdigit (parser->data[1]) &&
|
|
|
|
g_ascii_isxdigit (parser->data[2]) &&
|
|
|
|
g_ascii_isxdigit (parser->data[3]))
|
|
|
|
{
|
2018-03-01 00:08:05 +00:00
|
|
|
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;
|
|
|
|
}
|
2011-04-14 02:47:18 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-04-05 19:09:35 +00:00
|
|
|
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;
|
2011-04-14 02:47:18 +00:00
|
|
|
parser->data += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
_gtk_css_parser_skip_whitespace (parser);
|
|
|
|
|
2012-04-05 19:09:35 +00:00
|
|
|
return TRUE;
|
2011-04-14 02:47:18 +00:00
|
|
|
}
|
|
|
|
|
2012-04-05 19:09:35 +00:00
|
|
|
return FALSE;
|
2011-04-14 02:47:18 +00:00
|
|
|
}
|
|
|
|
|
2012-01-13 22:55:53 +00:00
|
|
|
GFile *
|
2012-04-18 17:43:24 +00:00
|
|
|
_gtk_css_parser_read_url (GtkCssParser *parser)
|
2012-01-13 22:55:53 +00:00
|
|
|
{
|
|
|
|
gchar *path;
|
2012-01-16 10:18:42 +00:00
|
|
|
char *scheme;
|
2012-01-13 22:55:53 +00:00
|
|
|
GFile *file;
|
|
|
|
|
|
|
|
if (_gtk_css_parser_try (parser, "url", FALSE))
|
|
|
|
{
|
|
|
|
if (!_gtk_css_parser_try (parser, "(", TRUE))
|
|
|
|
{
|
2017-09-17 21:01:29 +00:00
|
|
|
_gtk_css_parser_error (parser, "Expected '(' after 'url'");
|
|
|
|
return NULL;
|
2012-01-13 22:55:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2012-01-16 10:18:42 +00:00
|
|
|
|
|
|
|
scheme = g_uri_parse_scheme (path);
|
|
|
|
if (scheme != NULL)
|
|
|
|
{
|
|
|
|
file = g_file_new_for_uri (path);
|
|
|
|
g_free (path);
|
|
|
|
g_free (scheme);
|
|
|
|
return file;
|
|
|
|
}
|
2012-01-13 22:55:53 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
path = _gtk_css_parser_try_name (parser, TRUE);
|
|
|
|
if (path == NULL)
|
|
|
|
{
|
|
|
|
_gtk_css_parser_error (parser, "Not a valid url");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-18 17:43:24 +00:00
|
|
|
file = _gtk_css_parser_get_file_for_path (parser, path);
|
2012-01-13 22:55:53 +00:00
|
|
|
g_free (path);
|
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
2012-10-02 17:23:29 +00:00
|
|
|
static void
|
|
|
|
gtk_css_parser_resync_internal (GtkCssParser *parser,
|
|
|
|
gboolean sync_at_semicolon,
|
|
|
|
gboolean read_sync_token,
|
|
|
|
char terminator)
|
2011-04-14 02:47:18 +00:00
|
|
|
{
|
|
|
|
gsize len;
|
|
|
|
|
|
|
|
do {
|
|
|
|
len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
|
|
|
|
parser->data += len;
|
|
|
|
|
|
|
|
if (gtk_css_parser_new_line (parser))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (_gtk_css_parser_is_string (parser))
|
|
|
|
{
|
|
|
|
/* 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)
|
|
|
|
{
|
2011-05-27 22:06:46 +00:00
|
|
|
case '\\':
|
2011-04-14 02:47:18 +00:00
|
|
|
{
|
|
|
|
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, ')');
|
2011-06-17 05:55:17 +00:00
|
|
|
if (*parser->data)
|
|
|
|
parser->data++;
|
2011-04-14 02:47:18 +00:00
|
|
|
break;
|
|
|
|
case '[':
|
|
|
|
parser->data++;
|
|
|
|
_gtk_css_parser_resync (parser, FALSE, ']');
|
2011-06-17 05:55:17 +00:00
|
|
|
if (*parser->data)
|
|
|
|
parser->data++;
|
2011-04-14 02:47:18 +00:00
|
|
|
break;
|
|
|
|
case '{':
|
|
|
|
parser->data++;
|
|
|
|
_gtk_css_parser_resync (parser, FALSE, '}');
|
2011-06-17 05:55:17 +00:00
|
|
|
if (*parser->data)
|
|
|
|
parser->data++;
|
2011-04-14 02:47:18 +00:00
|
|
|
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;
|
2011-06-17 05:55:17 +00:00
|
|
|
case '\0':
|
|
|
|
break;
|
2011-05-27 22:07:10 +00:00
|
|
|
case '/':
|
2011-04-14 02:47:18 +00:00
|
|
|
default:
|
2011-05-27 22:07:10 +00:00
|
|
|
parser->data++;
|
2011-04-14 02:47:18 +00:00
|
|
|
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));
|
|
|
|
|
2012-10-02 17:23:29 +00:00
|
|
|
gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);
|
2011-04-14 02:47:18 +00:00
|
|
|
}
|
2014-05-01 17:47:58 +00:00
|
|
|
|
|
|
|
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 {
|
2014-05-21 17:17:21 +00:00
|
|
|
len = strcspn (string, "\\\"\n\r\f");
|
2014-05-01 17:47:58 +00:00
|
|
|
g_string_append_len (str, string, len);
|
|
|
|
string += len;
|
|
|
|
switch (*string)
|
|
|
|
{
|
|
|
|
case '\0':
|
2014-05-21 17:17:21 +00:00
|
|
|
goto out;
|
2014-05-01 17:47:58 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-05-01 17:48:40 +00:00
|
|
|
string++;
|
2014-05-01 17:47:58 +00:00
|
|
|
} while (*string);
|
|
|
|
|
2014-05-21 17:17:21 +00:00
|
|
|
out:
|
2014-05-01 17:47:58 +00:00
|
|
|
g_string_append_c (str, '"');
|
|
|
|
}
|
|
|
|
|