1015 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1015 lines
		
	
	
		
			23 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 "gtkcssnumbervalueprivate.h"
 | |
| #include "gtkwin32themeprivate.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;
 | |
| };
 | |
| 
 | |
| 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;
 | |
| 
 | |
|   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);
 | |
| 
 | |
|   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;
 | |
| }
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| 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_PROVIDER_ERROR,
 | |
|                               GTK_CSS_PROVIDER_ERROR_SYNTAX,
 | |
|                               format, args);
 | |
|   va_end (args);
 | |
| 
 | |
|   _gtk_css_parser_take_error (parser, error);
 | |
| }
 | |
| 
 | |
| void
 | |
| _gtk_css_parser_error_full (GtkCssParser        *parser,
 | |
|                             GtkCssProviderError  code,
 | |
|                             const char          *format,
 | |
|                             ...)
 | |
| {
 | |
|   GError *error;
 | |
| 
 | |
|   va_list args;
 | |
| 
 | |
|   va_start (args, format);
 | |
|   error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
 | |
|                               code, 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)
 | |
| {
 | |
|   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
 | |
|   g_return_val_if_fail (string != NULL, FALSE);
 | |
| 
 | |
|   if (g_ascii_strncasecmp (parser->data, string, strlen (string)) != 0)
 | |
|     return FALSE;
 | |
| 
 | |
|   parser->data += strlen (string);
 | |
| 
 | |
|   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;
 | |
| }
 | |
| 
 | |
| char *
 | |
| _gtk_css_parser_try_name (GtkCssParser *parser,
 | |
|                           gboolean      skip_whitespace)
 | |
| {
 | |
|   GString *name;
 | |
| 
 | |
|   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
 | |
| 
 | |
|   name = g_string_new (NULL);
 | |
| 
 | |
|   while (_gtk_css_parser_read_char (parser, name, NMCHAR))
 | |
|     ;
 | |
| 
 | |
|   if (skip_whitespace)
 | |
|     _gtk_css_parser_skip_whitespace (parser);
 | |
| 
 | |
|   return g_string_free (name, FALSE);
 | |
| }
 | |
| 
 | |
| 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;
 | |
|   
 | |
|   ident = g_string_new (NULL);
 | |
| 
 | |
|   if (*parser->data == '-')
 | |
|     {
 | |
|       g_string_append_c (ident, '-');
 | |
|       parser->data++;
 | |
|     }
 | |
| 
 | |
|   if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
 | |
|     {
 | |
|       parser->data = start;
 | |
|       g_string_free (ident, TRUE);
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
 | |
|     ;
 | |
| 
 | |
|   if (skip_whitespace)
 | |
|     _gtk_css_parser_skip_whitespace (parser);
 | |
| 
 | |
|   return g_string_free (ident, FALSE);
 | |
| }
 | |
| 
 | |
| 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 != '\'')
 | |
|     {
 | |
|       _gtk_css_parser_error (parser, "Expected a string.");
 | |
|       return NULL;
 | |
|     }
 | |
|   
 | |
|   parser->data++;
 | |
|   str = g_string_new (NULL);
 | |
| 
 | |
|   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 g_string_free (str, FALSE);
 | |
|             }
 | |
|           
 | |
|           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_free (str, TRUE);
 | |
|           return NULL;
 | |
|         default:
 | |
|           _gtk_css_parser_error (parser, 
 | |
|                                  "Invalid character in string. Must be escaped.");
 | |
|           g_string_free (str, TRUE);
 | |
|           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_uint (GtkCssParser *parser,
 | |
|                           guint        *value)
 | |
| {
 | |
|   guint64 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_strtoull (parser->data, &end, 10);
 | |
|   if (errno)
 | |
|     return FALSE;
 | |
|   if (result > G_MAXUINT)
 | |
|     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;
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| _gtk_css_parser_has_number (GtkCssParser *parser)
 | |
| {
 | |
|   /* ahem */
 | |
|   return strchr ("+-0123456789.", parser->data[0]) != NULL;
 | |
| }
 | |
| 
 | |
| GtkCssValue *
 | |
| _gtk_css_number_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 },
 | |
|     { "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_NUMBER_AS_PIXELS)
 | |
|         {
 | |
|           _gtk_css_parser_error_full (parser,
 | |
|                                       GTK_CSS_PROVIDER_ERROR_DEPRECATED,
 | |
|                                       "Not using units is deprecated. Assuming 'px'.");
 | |
|           unit = GTK_CSS_PX;
 | |
|         }
 | |
|       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_number_value_new (value, unit);
 | |
| }
 | |
| 
 | |
| /* XXX: we should introduce GtkCssLenght that deals with
 | |
|  * different kind of units */
 | |
| gboolean
 | |
| _gtk_css_parser_try_length (GtkCssParser *parser,
 | |
|                             int          *value)
 | |
| {
 | |
|   if (!_gtk_css_parser_try_int (parser, value))
 | |
|     return FALSE;
 | |
| 
 | |
|   /* FIXME: _try_uint skips spaces while the
 | |
|    * spec forbids them
 | |
|    */
 | |
|   _gtk_css_parser_try (parser, "px", TRUE);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| _gtk_css_parser_try_enum (GtkCssParser *parser,
 | |
| 			  GType         enum_type,
 | |
| 			  int          *value)
 | |
| {
 | |
|   GEnumClass *enum_class;
 | |
|   gboolean result;
 | |
|   const char *start;
 | |
|   char *str;
 | |
| 
 | |
|   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
 | |
|   g_return_val_if_fail (value != NULL, FALSE);
 | |
| 
 | |
|   result = FALSE;
 | |
| 
 | |
|   enum_class = g_type_class_ref (enum_type);
 | |
| 
 | |
|   start = parser->data;
 | |
| 
 | |
|   str = _gtk_css_parser_try_ident (parser, TRUE);
 | |
|   if (str == NULL)
 | |
|     return FALSE;
 | |
| 
 | |
|   if (enum_class->n_values)
 | |
|     {
 | |
|       GEnumValue *enum_value;
 | |
| 
 | |
|       for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
 | |
| 	{
 | |
| 	  if (enum_value->value_nick &&
 | |
| 	      g_ascii_strcasecmp (str, enum_value->value_nick) == 0)
 | |
| 	    {
 | |
| 	      *value = enum_value->value;
 | |
| 	      result = TRUE;
 | |
| 	      break;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   g_free (str);
 | |
|   g_type_class_unref (enum_class);
 | |
| 
 | |
|   if (!result)
 | |
|     parser->data = start;
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 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]) &&
 | |
|           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;
 | |
|           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 = 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_skip_whitespace (parser);
 | |
|           if (_gtk_css_parser_try (parser, "(", TRUE))
 | |
|             {
 | |
|               _gtk_css_parser_error_full (parser,
 | |
|                                           GTK_CSS_PROVIDER_ERROR_DEPRECATED,
 | |
|                                           "Whitespace between 'url' and '(' is deprecated");
 | |
|             }
 | |
|           else
 | |
|             {
 | |
|               _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_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)
 | |
|       {
 | |
|       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);
 | |
| }
 | |
| 
 | |
| char *
 | |
| _gtk_css_parser_read_value (GtkCssParser *parser)
 | |
| {
 | |
|   const char *start;
 | |
|   char *result;
 | |
| 
 | |
|   g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
 | |
| 
 | |
|   start = parser->data;
 | |
| 
 | |
|   /* This needs to be done better */
 | |
|   gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
 | |
| 
 | |
|   result = g_strndup (start, parser->data - start);
 | |
|   if (result)
 | |
|     {
 | |
|       g_strchomp (result);
 | |
|       if (result[0] == 0)
 | |
|         {
 | |
|           g_free (result);
 | |
|           result = NULL;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   if (result == NULL)
 | |
|     _gtk_css_parser_error (parser, "Expected a property value");
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| }
 | 
