forked from AuroraMiddleware/gtk
0886ade182
We don't want to return a GFile because GFile can't handle can't deal with data: urls. That makes the code a bit more complicated that doesn't deal with those URLs, but it makes the other code actually work. GtkCssImageUrl also now decodes data urls immediately instead of only at the first load. So don't use data urls if you care about performance.
1112 lines
30 KiB
C
1112 lines
30 KiB
C
/*
|
|
* Copyright © 2019 Benjamin Otte
|
|
*
|
|
* 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.1 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/>.
|
|
*
|
|
* Authors: Benjamin Otte <otte@gnome.org>
|
|
*/
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtkcssparserprivate.h"
|
|
|
|
#include "gtkcssenums.h"
|
|
#include "gtkcsserror.h"
|
|
#include "gtkcsslocationprivate.h"
|
|
|
|
typedef struct _GtkCssParserBlock GtkCssParserBlock;
|
|
|
|
struct _GtkCssParser
|
|
{
|
|
volatile int ref_count;
|
|
|
|
GtkCssTokenizer *tokenizer;
|
|
GFile *file;
|
|
GFile *directory;
|
|
GtkCssParserErrorFunc error_func;
|
|
gpointer user_data;
|
|
GDestroyNotify user_destroy;
|
|
|
|
GArray *blocks;
|
|
GtkCssLocation location;
|
|
GtkCssToken token;
|
|
};
|
|
|
|
struct _GtkCssParserBlock
|
|
{
|
|
GtkCssLocation start_location;
|
|
GtkCssTokenType end_token;
|
|
GtkCssTokenType inherited_end_token;
|
|
GtkCssTokenType alternative_token;
|
|
};
|
|
|
|
static GtkCssParser *
|
|
gtk_css_parser_new (GtkCssTokenizer *tokenizer,
|
|
GFile *file,
|
|
GFile *base_directory,
|
|
GtkCssParserErrorFunc error_func,
|
|
gpointer user_data,
|
|
GDestroyNotify user_destroy)
|
|
{
|
|
GtkCssParser *self;
|
|
|
|
self = g_slice_new0 (GtkCssParser);
|
|
|
|
self->ref_count = 1;
|
|
self->tokenizer = gtk_css_tokenizer_ref (tokenizer);
|
|
if (file)
|
|
self->file = g_object_ref (file);
|
|
if (base_directory)
|
|
self->directory = g_object_ref (base_directory);
|
|
else if (file)
|
|
self->directory = g_file_get_parent (file);
|
|
self->error_func = error_func;
|
|
self->user_data = user_data;
|
|
self->user_destroy = user_destroy;
|
|
self->blocks = g_array_new (FALSE, FALSE, sizeof (GtkCssParserBlock));
|
|
|
|
return self;
|
|
}
|
|
|
|
GtkCssParser *
|
|
gtk_css_parser_new_for_file (GFile *file,
|
|
GtkCssParserErrorFunc error_func,
|
|
gpointer user_data,
|
|
GDestroyNotify user_destroy,
|
|
GError **error)
|
|
{
|
|
GBytes *bytes;
|
|
GtkCssParser *result;
|
|
|
|
bytes = g_file_load_bytes (file, NULL, NULL, error);
|
|
if (bytes == NULL)
|
|
return NULL;
|
|
|
|
result = gtk_css_parser_new_for_bytes (bytes, file, NULL, error_func, user_data, user_destroy);
|
|
|
|
g_bytes_unref (bytes);
|
|
|
|
return result;
|
|
}
|
|
|
|
GtkCssParser *
|
|
gtk_css_parser_new_for_bytes (GBytes *bytes,
|
|
GFile *file,
|
|
GFile *base_directory,
|
|
GtkCssParserErrorFunc error_func,
|
|
gpointer user_data,
|
|
GDestroyNotify user_destroy)
|
|
{
|
|
GtkCssTokenizer *tokenizer;
|
|
GtkCssParser *result;
|
|
|
|
tokenizer = gtk_css_tokenizer_new (bytes);
|
|
result = gtk_css_parser_new (tokenizer, file, base_directory, error_func, user_data, user_destroy);
|
|
gtk_css_tokenizer_unref (tokenizer);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
gtk_css_parser_finalize (GtkCssParser *self)
|
|
{
|
|
if (self->user_destroy)
|
|
self->user_destroy (self->user_data);
|
|
|
|
g_clear_pointer (&self->tokenizer, gtk_css_tokenizer_unref);
|
|
g_clear_object (&self->file);
|
|
g_clear_object (&self->directory);
|
|
if (self->blocks->len)
|
|
g_critical ("Finalizing CSS parser with %u remaining blocks", self->blocks->len);
|
|
g_array_free (self->blocks, TRUE);
|
|
|
|
g_slice_free (GtkCssParser, self);
|
|
}
|
|
|
|
GtkCssParser *
|
|
gtk_css_parser_ref (GtkCssParser *self)
|
|
{
|
|
g_atomic_int_inc (&self->ref_count);
|
|
|
|
return self;
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_unref (GtkCssParser *self)
|
|
{
|
|
if (g_atomic_int_dec_and_test (&self->ref_count))
|
|
gtk_css_parser_finalize (self);
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_get_file:
|
|
* @self: a #GtkCssParser
|
|
*
|
|
* Gets the file being parsed. If no file is associated with @self -
|
|
* for example when raw data is parsed - %NULL is returned.
|
|
*
|
|
* Returns: (nullable) (transfer none): The file being parsed
|
|
* or %NULL.
|
|
**/
|
|
GFile *
|
|
gtk_css_parser_get_file (GtkCssParser *self)
|
|
{
|
|
return self->file;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_resolve_url:
|
|
* @self: a #GtkCssParser
|
|
* @url: the URL to resolve
|
|
*
|
|
* Resolves a given URL against the parser's location.
|
|
*
|
|
* Returns: (nullable) (transfer full): a new #GFile for the
|
|
* resolved URL or %NULL if the URI cannot be resolved.
|
|
**/
|
|
GFile *
|
|
gtk_css_parser_resolve_url (GtkCssParser *self,
|
|
const char *url)
|
|
{
|
|
char *scheme;
|
|
|
|
scheme = g_uri_parse_scheme (url);
|
|
if (scheme != NULL)
|
|
{
|
|
GFile *file = g_file_new_for_uri (url);
|
|
g_free (scheme);
|
|
return file;
|
|
}
|
|
g_free (scheme);
|
|
|
|
if (self->directory == NULL)
|
|
return NULL;
|
|
|
|
return g_file_resolve_relative_path (self->directory, url);
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_get_start_location:
|
|
* @self: a #GtkCssParser
|
|
*
|
|
* Queries the location of the current token.
|
|
*
|
|
* This function will return the location of the start of the
|
|
* current token. In the case a token has been consumed, but no
|
|
* new token has been queried yet via gtk_css_parser_peek_token()
|
|
* or gtk_css_parser_get_token(), the previous token's start
|
|
* location will be returned.
|
|
*
|
|
* This function may return the same location as
|
|
* gtk_css_parser_get_end_location() - in particular at the
|
|
* beginning and end of the document.
|
|
*
|
|
* Returns: the start location
|
|
**/
|
|
const GtkCssLocation *
|
|
gtk_css_parser_get_start_location (GtkCssParser *self)
|
|
{
|
|
return &self->location;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_get_end_location:
|
|
* @self: a #GtkCssParser
|
|
* @out_location: (caller-allocates) Place to store the location
|
|
*
|
|
* Queries the location of the current token.
|
|
*
|
|
* This function will return the location of the end of the
|
|
* current token. In the case a token has been consumed, but no
|
|
* new token has been queried yet via gtk_css_parser_peek_token()
|
|
* or gtk_css_parser_get_token(), the previous token's end location
|
|
* will be returned.
|
|
*
|
|
* This function may return the same location as
|
|
* gtk_css_parser_get_start_location() - in particular at the
|
|
* beginning and end of the document.
|
|
*
|
|
* Returns: the end location
|
|
**/
|
|
const GtkCssLocation *
|
|
gtk_css_parser_get_end_location (GtkCssParser *self)
|
|
{
|
|
return gtk_css_tokenizer_get_location (self->tokenizer);
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_get_block_location:
|
|
* @self: a #GtkCssParser
|
|
*
|
|
* Queries the start location of the token that started the current
|
|
* block that is being parsed.
|
|
*
|
|
* If no block is currently parsed, the beginning of the document
|
|
* is returned.
|
|
*
|
|
* Returns: The start location of the current block
|
|
*/
|
|
const GtkCssLocation *
|
|
gtk_css_parser_get_block_location (GtkCssParser *self)
|
|
{
|
|
GtkCssParserBlock *block;
|
|
|
|
if (self->blocks->len == 0)
|
|
{
|
|
static const GtkCssLocation start_of_document = { 0, };
|
|
return &start_of_document;
|
|
}
|
|
|
|
block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
|
|
return &block->start_location;
|
|
}
|
|
|
|
static void
|
|
gtk_css_parser_ensure_token (GtkCssParser *self)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
|
|
return;
|
|
|
|
self->location = *gtk_css_tokenizer_get_location (self->tokenizer);
|
|
if (!gtk_css_tokenizer_read_token (self->tokenizer, &self->token, &error))
|
|
{
|
|
/* We ignore the error here, because the resulting token will
|
|
* likely already trigger an error in the parsing code and
|
|
* duplicate errors are rather useless.
|
|
*/
|
|
g_clear_error (&error);
|
|
}
|
|
}
|
|
|
|
const GtkCssToken *
|
|
gtk_css_parser_peek_token (GtkCssParser *self)
|
|
{
|
|
static const GtkCssToken eof_token = { GTK_CSS_TOKEN_EOF, };
|
|
|
|
gtk_css_parser_ensure_token (self);
|
|
|
|
if (self->blocks->len)
|
|
{
|
|
GtkCssParserBlock *block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
|
|
if (gtk_css_token_is (&self->token, block->end_token) ||
|
|
gtk_css_token_is (&self->token, block->inherited_end_token) ||
|
|
gtk_css_token_is (&self->token, block->alternative_token))
|
|
return &eof_token;
|
|
}
|
|
|
|
return &self->token;
|
|
}
|
|
|
|
const GtkCssToken *
|
|
gtk_css_parser_get_token (GtkCssParser *self)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
for (token = gtk_css_parser_peek_token (self);
|
|
gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT) ||
|
|
gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE);
|
|
token = gtk_css_parser_peek_token (self))
|
|
{
|
|
gtk_css_parser_consume_token (self);
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_consume_token (GtkCssParser *self)
|
|
{
|
|
gtk_css_parser_ensure_token (self);
|
|
|
|
/* unpreserved tokens MUST be consumed via start_block() */
|
|
g_assert (gtk_css_token_is_preserved (&self->token, NULL));
|
|
|
|
/* Don't consume any tokens at the end of a block */
|
|
if (!gtk_css_token_is (gtk_css_parser_peek_token (self), GTK_CSS_TOKEN_EOF))
|
|
gtk_css_token_clear (&self->token);
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_start_block (GtkCssParser *self)
|
|
{
|
|
GtkCssParserBlock block;
|
|
|
|
gtk_css_parser_ensure_token (self);
|
|
|
|
if (gtk_css_token_is_preserved (&self->token, &block.end_token))
|
|
{
|
|
g_critical ("gtk_css_parser_start_block() may only be called for non-preserved tokens");
|
|
return;
|
|
}
|
|
|
|
block.inherited_end_token = GTK_CSS_TOKEN_EOF;
|
|
block.alternative_token = GTK_CSS_TOKEN_EOF;
|
|
block.start_location = self->location;
|
|
g_array_append_val (self->blocks, block);
|
|
|
|
gtk_css_token_clear (&self->token);
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_start_semicolon_block (GtkCssParser *self,
|
|
GtkCssTokenType alternative_token)
|
|
{
|
|
GtkCssParserBlock block;
|
|
|
|
block.end_token = GTK_CSS_TOKEN_SEMICOLON;
|
|
if (self->blocks->len)
|
|
block.inherited_end_token = g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1).end_token;
|
|
else
|
|
block.inherited_end_token = GTK_CSS_TOKEN_EOF;
|
|
block.alternative_token = alternative_token;
|
|
block.start_location = self->location;
|
|
g_array_append_val (self->blocks, block);
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_end_block_prelude (GtkCssParser *self)
|
|
{
|
|
GtkCssParserBlock *block;
|
|
|
|
g_return_if_fail (self->blocks->len > 0);
|
|
|
|
block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
|
|
|
|
if (block->alternative_token == GTK_CSS_TOKEN_EOF)
|
|
return;
|
|
|
|
gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
|
|
|
|
if (gtk_css_token_is (&self->token, block->alternative_token))
|
|
{
|
|
if (gtk_css_token_is_preserved (&self->token, &block->end_token))
|
|
{
|
|
g_critical ("alternative token is not preserved");
|
|
return;
|
|
}
|
|
block->alternative_token = GTK_CSS_TOKEN_EOF;
|
|
block->inherited_end_token = GTK_CSS_TOKEN_EOF;
|
|
gtk_css_token_clear (&self->token);
|
|
}
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_end_block (GtkCssParser *self)
|
|
{
|
|
GtkCssParserBlock *block;
|
|
|
|
g_return_if_fail (self->blocks->len > 0);
|
|
|
|
gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
|
|
|
|
block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
|
|
|
|
if (gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
|
|
{
|
|
gtk_css_parser_warn (self,
|
|
GTK_CSS_PARSER_WARNING_SYNTAX,
|
|
gtk_css_parser_get_block_location (self),
|
|
gtk_css_parser_get_start_location (self),
|
|
"Unterminated block at end of document");
|
|
g_array_set_size (self->blocks, self->blocks->len - 1);
|
|
}
|
|
else if (gtk_css_token_is (&self->token, block->inherited_end_token))
|
|
{
|
|
g_assert (block->end_token == GTK_CSS_TOKEN_SEMICOLON);
|
|
gtk_css_parser_warn (self,
|
|
GTK_CSS_PARSER_WARNING_SYNTAX,
|
|
gtk_css_parser_get_block_location (self),
|
|
gtk_css_parser_get_start_location (self),
|
|
"Expected ';' at end of block");
|
|
g_array_set_size (self->blocks, self->blocks->len - 1);
|
|
}
|
|
else
|
|
{
|
|
g_array_set_size (self->blocks, self->blocks->len - 1);
|
|
if (gtk_css_token_is_preserved (&self->token, NULL))
|
|
{
|
|
gtk_css_token_clear (&self->token);
|
|
}
|
|
else
|
|
{
|
|
gtk_css_parser_start_block (self);
|
|
gtk_css_parser_end_block (self);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* gtk_css_parser_skip:
|
|
* @self: a #GtkCssParser
|
|
*
|
|
* Skips a component value.
|
|
*
|
|
* This means that if the token is a preserved token, only
|
|
* this token will be skipped. If the token starts a block,
|
|
* the whole block will be skipped.
|
|
**/
|
|
void
|
|
gtk_css_parser_skip (GtkCssParser *self)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
if (gtk_css_token_is_preserved (token, NULL))
|
|
{
|
|
gtk_css_parser_consume_token (self);
|
|
}
|
|
else
|
|
{
|
|
gtk_css_parser_start_block (self);
|
|
gtk_css_parser_end_block (self);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* gtk_css_parser_skip_until:
|
|
* @self: a #GtkCssParser
|
|
* @token_type: type of token to skip to
|
|
*
|
|
* Repeatedly skips a token until a certain type is reached.
|
|
* After this called, gtk_css_parser_get_token() will either
|
|
* return a token of this type or the eof token.
|
|
*
|
|
* This function is useful for resyncing a parser after encountering
|
|
* an error.
|
|
*
|
|
* If you want to skip until the end, use %GSK_TOKEN_TYPE_EOF
|
|
* as the token type.
|
|
**/
|
|
void
|
|
gtk_css_parser_skip_until (GtkCssParser *self,
|
|
GtkCssTokenType token_type)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
for (token = gtk_css_parser_get_token (self);
|
|
!gtk_css_token_is (token, token_type) &&
|
|
!gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
|
|
token = gtk_css_parser_get_token (self))
|
|
{
|
|
gtk_css_parser_skip (self);
|
|
}
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_emit_error (GtkCssParser *self,
|
|
const GtkCssLocation *start,
|
|
const GtkCssLocation *end,
|
|
const GError *error)
|
|
{
|
|
if (self->error_func)
|
|
self->error_func (self, start, end, error, self->user_data);
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_error (GtkCssParser *self,
|
|
GtkCssParserError code,
|
|
const GtkCssLocation *start,
|
|
const GtkCssLocation *end,
|
|
const char *format,
|
|
...)
|
|
{
|
|
va_list args;
|
|
GError *error;
|
|
|
|
va_start (args, format);
|
|
error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
|
|
code,
|
|
format, args);
|
|
gtk_css_parser_emit_error (self, start, end, error);
|
|
g_error_free (error);
|
|
va_end (args);
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_error_syntax (GtkCssParser *self,
|
|
const char *format,
|
|
...)
|
|
{
|
|
va_list args;
|
|
GError *error;
|
|
|
|
va_start (args, format);
|
|
error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
|
|
GTK_CSS_PARSER_ERROR_SYNTAX,
|
|
format, args);
|
|
gtk_css_parser_emit_error (self,
|
|
gtk_css_parser_get_start_location (self),
|
|
gtk_css_parser_get_end_location (self),
|
|
error);
|
|
g_error_free (error);
|
|
va_end (args);
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_error_value (GtkCssParser *self,
|
|
const char *format,
|
|
...)
|
|
{
|
|
va_list args;
|
|
GError *error;
|
|
|
|
va_start (args, format);
|
|
error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
|
|
GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
|
|
format, args);
|
|
gtk_css_parser_emit_error (self,
|
|
gtk_css_parser_get_start_location (self),
|
|
gtk_css_parser_get_end_location (self),
|
|
error);
|
|
g_error_free (error);
|
|
va_end (args);
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_error_import (GtkCssParser *self,
|
|
const char *format,
|
|
...)
|
|
{
|
|
va_list args;
|
|
GError *error;
|
|
|
|
va_start (args, format);
|
|
error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
|
|
GTK_CSS_PARSER_ERROR_IMPORT,
|
|
format, args);
|
|
gtk_css_parser_emit_error (self,
|
|
gtk_css_parser_get_start_location (self),
|
|
gtk_css_parser_get_end_location (self),
|
|
error);
|
|
g_error_free (error);
|
|
va_end (args);
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_warn (GtkCssParser *self,
|
|
GtkCssParserWarning code,
|
|
const GtkCssLocation *start,
|
|
const GtkCssLocation *end,
|
|
const char *format,
|
|
...)
|
|
{
|
|
va_list args;
|
|
GError *error;
|
|
|
|
va_start (args, format);
|
|
error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
|
|
code,
|
|
format, args);
|
|
gtk_css_parser_emit_error (self, start, end, error);
|
|
g_error_free (error);
|
|
va_end (args);
|
|
}
|
|
|
|
void
|
|
gtk_css_parser_warn_syntax (GtkCssParser *self,
|
|
const char *format,
|
|
...)
|
|
{
|
|
va_list args;
|
|
GError *error;
|
|
|
|
va_start (args, format);
|
|
error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
|
|
GTK_CSS_PARSER_WARNING_SYNTAX,
|
|
format, args);
|
|
gtk_css_parser_emit_error (self,
|
|
gtk_css_parser_get_start_location (self),
|
|
gtk_css_parser_get_end_location (self),
|
|
error);
|
|
g_error_free (error);
|
|
va_end (args);
|
|
}
|
|
|
|
gboolean
|
|
gtk_css_parser_consume_function (GtkCssParser *self,
|
|
guint min_args,
|
|
guint max_args,
|
|
guint (* parse_func) (GtkCssParser *, guint, gpointer),
|
|
gpointer data)
|
|
{
|
|
const GtkCssToken *token;
|
|
gboolean result = FALSE;
|
|
char *function_name;
|
|
guint arg;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
g_return_val_if_fail (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION), FALSE);
|
|
|
|
function_name = g_strdup (token->string.string);
|
|
gtk_css_parser_start_block (self);
|
|
|
|
arg = 0;
|
|
while (TRUE)
|
|
{
|
|
guint parse_args = parse_func (self, arg, data);
|
|
if (parse_args == 0)
|
|
break;
|
|
arg += parse_args;
|
|
token = gtk_css_parser_get_token (self);
|
|
if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
|
|
{
|
|
if (arg < min_args)
|
|
{
|
|
gtk_css_parser_error_syntax (self, "%s() requires at least %u arguments", function_name, min_args);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
result = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA))
|
|
{
|
|
if (arg >= max_args)
|
|
{
|
|
gtk_css_parser_error_syntax (self, "Expected ')' at end of %s()", function_name);
|
|
break;
|
|
}
|
|
|
|
gtk_css_parser_consume_token (self);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
gtk_css_parser_error_syntax (self, "Unexpected data at end of %s() argument", function_name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
gtk_css_parser_end_block (self);
|
|
g_free (function_name);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_has_token:
|
|
* @self: a #GtkCssParser
|
|
* @token_type: type of the token to check
|
|
*
|
|
* Checks if the next token is of @token_type.
|
|
*
|
|
* Returns: %TRUE if the next token is of @token_type
|
|
**/
|
|
gboolean
|
|
gtk_css_parser_has_token (GtkCssParser *self,
|
|
GtkCssTokenType token_type)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
return gtk_css_token_is (token, token_type);
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_has_ident:
|
|
* @self: a #GtkCssParser
|
|
* @ident: name of identifier
|
|
*
|
|
* Checks if the next token is an identifier with the given @name.
|
|
*
|
|
* Returns: %TRUE if the next token is an identifier with the given @name
|
|
**/
|
|
gboolean
|
|
gtk_css_parser_has_ident (GtkCssParser *self,
|
|
const char *ident)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
return gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
|
|
g_ascii_strcasecmp (token->string.string, ident) == 0;
|
|
}
|
|
|
|
gboolean
|
|
gtk_css_parser_has_integer (GtkCssParser *self)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
return gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
|
|
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_has_function:
|
|
* @self: a #GtkCssParser
|
|
* @name: name of function
|
|
*
|
|
* Checks if the next token is a function with the given @name.
|
|
*
|
|
* Returns: %TRUE if the next token is a function with the given @name
|
|
**/
|
|
gboolean
|
|
gtk_css_parser_has_function (GtkCssParser *self,
|
|
const char *name)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
return gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION) &&
|
|
g_ascii_strcasecmp (token->string.string, name) == 0;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_try_delim:
|
|
* @self: a #GtkCssParser
|
|
* @codepoint: unicode character codepoint to check
|
|
*
|
|
* Checks if the current token is a delimiter matching the given
|
|
* @codepoint. If that is the case, the token is consumed and
|
|
* %TRUE is returned.
|
|
*
|
|
* Keep in mind that not every unicode codepoint can be a delim
|
|
* token.
|
|
*
|
|
* Returns: %TRUE if the token matched and was consumed.
|
|
**/
|
|
gboolean
|
|
gtk_css_parser_try_delim (GtkCssParser *self,
|
|
gunichar codepoint)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_DELIM) ||
|
|
codepoint != token->delim.delim)
|
|
return FALSE;
|
|
|
|
gtk_css_parser_consume_token (self);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_try_ident:
|
|
* @self: a #GtkCssParser
|
|
* @ident: identifier to check for
|
|
*
|
|
* Checks if the current token is an identifier matching the given
|
|
* @ident string. If that is the case, the token is consumed
|
|
* and %TRUE is returned.
|
|
*
|
|
* Returns: %TRUE if the token matched and was consumed.
|
|
**/
|
|
gboolean
|
|
gtk_css_parser_try_ident (GtkCssParser *self,
|
|
const char *ident)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) ||
|
|
g_ascii_strcasecmp (token->string.string, ident) != 0)
|
|
return FALSE;
|
|
|
|
gtk_css_parser_consume_token (self);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_try_at_keyword:
|
|
* @self: a #GtkCssParser
|
|
* @keyword: name of keyword to check for
|
|
*
|
|
* Checks if the current token is an at-keyword token with the
|
|
* given @keyword. If that is the case, the token is consumed
|
|
* and %TRUE is returned.
|
|
*
|
|
* Returns: %TRUE if the token matched and was consumed.
|
|
**/
|
|
gboolean
|
|
gtk_css_parser_try_at_keyword (GtkCssParser *self,
|
|
const char *keyword)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_AT_KEYWORD) ||
|
|
g_ascii_strcasecmp (token->string.string, keyword) != 0)
|
|
return FALSE;
|
|
|
|
gtk_css_parser_consume_token (self);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_try_token:
|
|
* @self: a #GtkCssParser
|
|
* @token_type: type of token to try
|
|
*
|
|
* Consumes the next token if it matches the given @token_type.
|
|
*
|
|
* This function can be used in loops like this:
|
|
* do {
|
|
* ... parse one element ...
|
|
* } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA);
|
|
*
|
|
* Returns: %TRUE if a token was consumed
|
|
**/
|
|
gboolean
|
|
gtk_css_parser_try_token (GtkCssParser *self,
|
|
GtkCssTokenType token_type)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
if (!gtk_css_token_is (token, token_type))
|
|
return FALSE;
|
|
|
|
gtk_css_parser_consume_token (self);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_consume_ident:
|
|
* @self: a #GtkCssParser
|
|
*
|
|
* If the current token is an identifier, consumes it and returns
|
|
* its name.
|
|
* If the current token is not an identifier, an error is emitted
|
|
* and %NULL is returned.
|
|
*
|
|
* Returns: (transfer full): the name of the consumed identifier
|
|
* or %NULL on error
|
|
**/
|
|
char *
|
|
gtk_css_parser_consume_ident (GtkCssParser *self)
|
|
{
|
|
const GtkCssToken *token;
|
|
char *ident;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
|
|
{
|
|
gtk_css_parser_error_syntax (self, "Expected an identifier");
|
|
return NULL;
|
|
}
|
|
|
|
ident = g_strdup (token->string.string);
|
|
gtk_css_parser_consume_token (self);
|
|
|
|
return ident;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_consume_string:
|
|
* @self: a #GtkCssParser
|
|
*
|
|
* If the current token is a string, consumes it and return the string.
|
|
* If the current token is not a string, an error is emitted
|
|
* and %NULL is returned.
|
|
*
|
|
* Returns: (transfer full): the name of the consumed string
|
|
* or %NULL on error
|
|
**/
|
|
char *
|
|
gtk_css_parser_consume_string (GtkCssParser *self)
|
|
{
|
|
const GtkCssToken *token;
|
|
char *ident;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
|
|
{
|
|
gtk_css_parser_error_syntax (self, "Expected a string");
|
|
return NULL;
|
|
}
|
|
|
|
ident = g_strdup (token->string.string);
|
|
gtk_css_parser_consume_token (self);
|
|
|
|
return ident;
|
|
}
|
|
|
|
static guint
|
|
gtk_css_parser_parse_url_arg (GtkCssParser *parser,
|
|
guint arg,
|
|
gpointer data)
|
|
{
|
|
char **out_url = data;
|
|
|
|
*out_url = gtk_css_parser_consume_string (parser);
|
|
if (*out_url == NULL)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* gtk_css_parser_consume_url:
|
|
* @self: a #GtkCssParser
|
|
*
|
|
* If the parser matches the <url> token from the [CSS
|
|
* specification](https://drafts.csswg.org/css-values-4/#url-value),
|
|
* consumes it, resolves the URL and resturns the resulting #GFile.
|
|
* On failure, an error is emitted and %NULL is returned.
|
|
*
|
|
* Returns: (nullable) (transfer full): the resulting URL or %NULL on error
|
|
**/
|
|
char *
|
|
gtk_css_parser_consume_url (GtkCssParser *self)
|
|
{
|
|
const GtkCssToken *token;
|
|
char *url;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
|
|
if (gtk_css_token_is (token, GTK_CSS_TOKEN_URL))
|
|
{
|
|
url = g_strdup (token->string.string);
|
|
gtk_css_parser_consume_token (self);
|
|
}
|
|
else if (gtk_css_token_is_function (token, "url"))
|
|
{
|
|
if (!gtk_css_parser_consume_function (self, 1, 1, gtk_css_parser_parse_url_arg, &url))
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
gtk_css_parser_error_syntax (self, "Expected a URL");
|
|
return NULL;
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
gboolean
|
|
gtk_css_parser_has_number (GtkCssParser *self)
|
|
{
|
|
return gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_NUMBER)
|
|
|| gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_NUMBER)
|
|
|| gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_INTEGER)
|
|
|| gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
|
|
}
|
|
|
|
gboolean
|
|
gtk_css_parser_consume_number (GtkCssParser *self,
|
|
double *number)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
|
|
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
|
|
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
|
|
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
|
|
{
|
|
*number = token->number.number;
|
|
gtk_css_parser_consume_token (self);
|
|
return TRUE;
|
|
}
|
|
|
|
gtk_css_parser_error_syntax (self, "Expected a number");
|
|
/* FIXME: Implement calc() */
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
gtk_css_parser_consume_integer (GtkCssParser *self,
|
|
int *number)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
|
|
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
|
|
{
|
|
*number = token->number.number;
|
|
gtk_css_parser_consume_token (self);
|
|
return TRUE;
|
|
}
|
|
|
|
gtk_css_parser_error_syntax (self, "Expected an integer");
|
|
/* FIXME: Implement calc() */
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
gtk_css_parser_consume_percentage (GtkCssParser *self,
|
|
double *number)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
token = gtk_css_parser_get_token (self);
|
|
if (gtk_css_token_is (token, GTK_CSS_TOKEN_PERCENTAGE))
|
|
{
|
|
*number = token->number.number;
|
|
gtk_css_parser_consume_token (self);
|
|
return TRUE;
|
|
}
|
|
|
|
gtk_css_parser_error_syntax (self, "Expected a percentage");
|
|
/* FIXME: Implement calc() */
|
|
return FALSE;
|
|
}
|
|
|
|
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_syntax (parser, "No valid value given");
|
|
return result;
|
|
}
|
|
|
|
return result;
|
|
}
|