[gtk+/parser: 27/80] css: Overhaul value parsing



commit 39c5e2aa7c84c177e831a7c4563bb989a2c6f38b
Author: Benjamin Otte <otte redhat com>
Date:   Fri Apr 8 16:08:28 2011 +0200

    css: Overhaul value parsing
    
    Value parsing only sometimes emitted errors. Sometimes it didn't emit
    errors but ignored the value, sometimes it took a default, sometimes it
    converted it to something it deemed suitable.
    
    While refactoring, I moved the whole GValue <=> char * conversion
    routines to a separate file, to make navigating the core css provider
    easier.

 gtk/Makefile.am                |    2 +
 gtk/gtkcssprovider.c           | 1599 +++++-----------------------------------
 gtk/gtkcssstringfuncs.c        | 1543 ++++++++++++++++++++++++++++++++++++++
 gtk/gtkcssstringfuncsprivate.h |   43 ++
 4 files changed, 1791 insertions(+), 1396 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index efe9315..a8e6d1c 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -386,6 +386,7 @@ gtk_private_h_sources =		\
 	gtkbuttonprivate.h	\
 	gtkcellareaboxcontextprivate.h	\
 	gtkcssproviderprivate.h	\
+	gtkcssstringfuncsprivate.h	\
 	gtkcustompaperunixdialog.h \
 	gtkdndcursors.h		\
 	gtkentryprivate.h	\
@@ -511,6 +512,7 @@ gtk_base_c_sources = 		\
 	gtkcomboboxtext.c	\
 	gtkcontainer.c		\
 	gtkcssprovider.c	\
+	gtkcssstringfuncs.c	\
 	gtkdialog.c		\
 	gtkdrawingarea.c	\
 	gtkeditable.c		\
diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c
index b7be42a..0ed88ea 100644
--- a/gtk/gtkcssprovider.c
+++ b/gtk/gtkcssprovider.c
@@ -27,10 +27,8 @@
 
 #include "gtkcssproviderprivate.h"
 
-#include "gtkanimationdescription.h"
-#include "gtk9slice.h"
-#include "gtkgradient.h"
-#include "gtkthemingengine.h"
+#include "gtkcssstringfuncsprivate.h"
+#include "gtksymboliccolor.h"
 #include "gtkstyleprovider.h"
 #include "gtkstylecontextprivate.h"
 #include "gtkbindings.h"
@@ -841,13 +839,12 @@ static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface);
 static void scanner_apply_scope (GScanner    *scanner,
                                  ParserScope  scope);
 static void     css_provider_reset_parser (GtkCssProvider *css_provider);
-static gboolean css_provider_parse_value (GtkCssProvider  *css_provider,
-                                          const gchar     *value_str,
-                                          GValue          *value);
 static gboolean gtk_css_provider_load_from_path_internal (GtkCssProvider  *css_provider,
                                                           const gchar     *path,
                                                           gboolean         reset,
                                                           GError         **error);
+static void     gtk_css_provider_take_error (GtkCssProvider *provider,
+                                             GError         *error);
 
 GQuark
 gtk_css_provider_error_quark (void)
@@ -1486,13 +1483,17 @@ gtk_css_provider_get_style_property (GtkStyleProvider *provider,
            ((info->state & state) != 0 &&
             (info->state & ~(state)) == 0)))
         {
-          const gchar *val_str;
+          GError *error = NULL;
 
-          val_str = g_value_get_string (val);
-          found = TRUE;
+          found = _gtk_css_value_from_string (value,
+                                              NULL,
+                                              g_value_get_string (val),
+                                              &error);
 
-          css_provider_parse_value (GTK_CSS_PROVIDER (provider), val_str, value);
-          break;
+          if (found)
+            break;
+          
+          gtk_css_provider_take_error (GTK_CSS_PROVIDER (provider), error);
         }
     }
 
@@ -1587,19 +1588,6 @@ gtk_css_provider_take_error (GtkCssProvider *provider,
 }
 
 static void
-gtk_css_provider_error_literal (GtkCssProvider *provider,
-                                GQuark          domain,
-                                gint            code,
-                                const char     *message)
-{
-  GError *error;
-
-  error = g_error_new_literal (domain, code, message);
-
-  gtk_css_provider_take_error (provider, error);
-}
-
-static void
 gtk_css_provider_error (GtkCssProvider *provider,
                         GQuark          domain,
                         gint            code,
@@ -1944,1286 +1932,177 @@ parse_selector (GtkCssProvider  *css_provider,
 
           is_class = (scanner->token == '.');
 
-          g_scanner_get_next_token (scanner);
-
-          if (scanner->token != G_TOKEN_IDENTIFIER)
-            return G_TOKEN_IDENTIFIER;
-
-          selector_path_prepend_glob (path);
-          selector_path_prepend_combinator (path, COMBINATOR_CHILD);
-
-          if (is_class)
-            parse_classes (path, scanner->value.v_identifier);
-          else
-            {
-              if ((pos = strchr (scanner->value.v_identifier, '.')) != NULL)
-                *pos = '\0';
-
-              selector_path_prepend_name (path, scanner->value.v_identifier);
-
-              /* Parse any remaining classes */
-              if (pos)
-                parse_classes (path, pos + 1);
-            }
-        }
-      else if (is_widget_class_name (scanner->value.v_identifier))
-        {
-          gchar *pos;
-
-          if ((pos = strchr (scanner->value.v_identifier, '#')) != NULL ||
-              (pos = strchr (scanner->value.v_identifier, '.')) != NULL)
-            {
-              gchar *type_name, *name;
-              gboolean is_class;
-
-              is_class = (*pos == '.');
-
-              /* Widget type and name/class put together */
-              name = pos + 1;
-              *pos = '\0';
-              type_name = scanner->value.v_identifier;
-
-              selector_path_prepend_type (path, type_name);
-
-              /* This is only so there is a direct relationship
-               * between widget type and its name.
-               */
-              selector_path_prepend_combinator (path, COMBINATOR_CHILD);
-
-              if (is_class)
-                parse_classes (path, name);
-              else
-                {
-                  if ((pos = strchr (name, '.')) != NULL)
-                    *pos = '\0';
-
-                  selector_path_prepend_name (path, name);
-
-                  /* Parse any remaining classes */
-                  if (pos)
-                    parse_classes (path, pos + 1);
-                }
-            }
-          else
-            selector_path_prepend_type (path, scanner->value.v_identifier);
-        }
-      else if (_gtk_style_context_check_region_name (scanner->value.v_identifier))
-        {
-          GtkRegionFlags flags = 0;
-          gchar *region_name;
-
-          region_name = g_strdup (scanner->value.v_identifier);
-
-          if (g_scanner_peek_next_token (scanner) == ':')
-            {
-              ParserSymbol symbol;
-
-              g_scanner_get_next_token (scanner);
-              css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS);
-
-              /* Check for the next token being nth-child, parse in that
-               * case, and fallback into common state parsing if not.
-               */
-              if (g_scanner_peek_next_token (scanner) != G_TOKEN_SYMBOL)
-                return G_TOKEN_SYMBOL;
-
-              symbol = GPOINTER_TO_INT (scanner->next_value.v_symbol);
-
-              if (symbol == SYMBOL_FIRST_CHILD ||
-                  symbol == SYMBOL_LAST_CHILD ||
-                  symbol == SYMBOL_NTH_CHILD ||
-                  symbol == SYMBOL_SORTED_CHILD)
-                {
-                  GTokenType token;
-
-                  if ((token = parse_nth_child (css_provider, scanner, &flags)) != G_TOKEN_NONE)
-                    return token;
-
-                  css_provider_pop_scope (css_provider);
-                }
-              else
-                {
-                  css_provider_pop_scope (css_provider);
-                  selector_path_prepend_region (path, region_name, 0);
-                  g_free (region_name);
-                  break;
-                }
-            }
-
-          selector_path_prepend_region (path, region_name, flags);
-          g_free (region_name);
-        }
-      else if (scanner->value.v_identifier[0] == '*')
-        selector_path_prepend_glob (path);
-      else
-        return G_TOKEN_IDENTIFIER;
-
-      g_scanner_get_next_token (scanner);
-
-      if (scanner->token == '>')
-        {
-          selector_path_prepend_combinator (path, COMBINATOR_CHILD);
-          g_scanner_get_next_token (scanner);
-        }
-    }
-
-  if (scanner->token == ':')
-    {
-      /* Add glob selector if path is empty */
-      if (selector_path_depth (path) == 0)
-        selector_path_prepend_glob (path);
-
-      css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS);
-
-      while (scanner->token == ':')
-        {
-          GTokenType token;
-
-          if ((token = parse_pseudo_class (css_provider, scanner, path)) != G_TOKEN_NONE)
-            return token;
-
-          g_scanner_get_next_token (scanner);
-        }
-
-      css_provider_pop_scope (css_provider);
-    }
-
-  return G_TOKEN_NONE;
-}
-
-#define SKIP_SPACES(s) while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n') s++;
-#define SKIP_SPACES_BACK(s) while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n') s--;
-
-static GtkSymbolicColor *
-symbolic_color_parse_str (const gchar  *string,
-                          gchar       **end_ptr)
-{
-  GtkSymbolicColor *symbolic_color = NULL;
-  gchar *str;
-
-  str = (gchar *) string;
-  *end_ptr = str;
-
-  if (str[0] == '@')
-    {
-      const gchar *end;
-      gchar *name;
-
-      str++;
-      end = str;
-
-      while (*end == '-' || *end == '_' || g_ascii_isalnum (*end))
-        end++;
-
-      name = g_strndup (str, end - str);
-      symbolic_color = gtk_symbolic_color_new_name (name);
-      g_free (name);
-
-      *end_ptr = (gchar *) end;
-    }
-  else if (g_str_has_prefix (str, "lighter") ||
-           g_str_has_prefix (str, "darker"))
-    {
-      GtkSymbolicColor *param_color;
-      gboolean is_lighter = FALSE;
-
-      is_lighter = g_str_has_prefix (str, "lighter");
-
-      if (is_lighter)
-        str += strlen ("lighter");
-      else
-        str += strlen ("darker");
-
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      param_color = symbolic_color_parse_str (str, end_ptr);
-
-      if (!param_color)
-        return NULL;
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-      *end_ptr = (gchar *) str;
-
-      if (*str != ')')
-        {
-          gtk_symbolic_color_unref (param_color);
-          return NULL;
-        }
-
-      if (is_lighter)
-        symbolic_color = gtk_symbolic_color_new_shade (param_color, 1.3);
-      else
-        symbolic_color = gtk_symbolic_color_new_shade (param_color, 0.7);
-
-      gtk_symbolic_color_unref (param_color);
-      (*end_ptr)++;
-    }
-  else if (g_str_has_prefix (str, "shade") ||
-           g_str_has_prefix (str, "alpha"))
-    {
-      GtkSymbolicColor *param_color;
-      gboolean is_shade = FALSE;
-      gdouble factor;
-
-      is_shade = g_str_has_prefix (str, "shade");
-
-      if (is_shade)
-        str += strlen ("shade");
-      else
-        str += strlen ("alpha");
-
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      param_color = symbolic_color_parse_str (str, end_ptr);
-
-      if (!param_color)
-        return NULL;
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-
-      if (str[0] != ',')
-        {
-          gtk_symbolic_color_unref (param_color);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      factor = g_ascii_strtod (str, end_ptr);
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-      *end_ptr = (gchar *) str;
-
-      if (str[0] != ')')
-        {
-          gtk_symbolic_color_unref (param_color);
-          return NULL;
-        }
-
-      if (is_shade)
-        symbolic_color = gtk_symbolic_color_new_shade (param_color, factor);
-      else
-        symbolic_color = gtk_symbolic_color_new_alpha (param_color, factor);
-
-      gtk_symbolic_color_unref (param_color);
-      (*end_ptr)++;
-    }
-  else if (g_str_has_prefix (str, "mix"))
-    {
-      GtkSymbolicColor *color1, *color2;
-      gdouble factor;
-
-      str += strlen ("mix");
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      color1 = symbolic_color_parse_str (str, end_ptr);
-
-      if (!color1)
-        return NULL;
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-
-      if (str[0] != ',')
-        {
-          gtk_symbolic_color_unref (color1);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      color2 = symbolic_color_parse_str (str, end_ptr);
-
-      if (!color2 || *end_ptr[0] != ',')
-        {
-          gtk_symbolic_color_unref (color1);
-          return NULL;
-        }
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-
-      if (str[0] != ',')
-        {
-          gtk_symbolic_color_unref (color1);
-          gtk_symbolic_color_unref (color2);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      factor = g_ascii_strtod (str, end_ptr);
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-      *end_ptr = (gchar *) str;
-
-      if (str[0] != ')')
-        {
-          gtk_symbolic_color_unref (color1);
-          gtk_symbolic_color_unref (color2);
-          return NULL;
-        }
-
-      symbolic_color = gtk_symbolic_color_new_mix (color1, color2, factor);
-      gtk_symbolic_color_unref (color1);
-      gtk_symbolic_color_unref (color2);
-      (*end_ptr)++;
-    }
-  else
-    {
-      GdkRGBA color;
-      gchar *color_str;
-      const gchar *end;
-
-      end = str + 1;
-
-      if (str[0] == '#')
-        {
-          /* Color in hex format */
-          while (g_ascii_isxdigit (*end))
-            end++;
-        }
-      else if (g_str_has_prefix (str, "rgb"))
-        {
-          /* color in rgb/rgba format */
-          while (*end != ')' && *end != '\0')
-            end++;
-
-          if (*end == ')')
-            end++;
-        }
-      else
-        {
-          /* Color name */
-          while (*end != '\0' &&
-                 (g_ascii_isalnum (*end) || *end == ' '))
-            end++;
-        }
-
-      color_str = g_strndup (str, end - str);
-      *end_ptr = (gchar *) end;
-
-      if (!gdk_rgba_parse (&color, color_str))
-        {
-          g_free (color_str);
-          return NULL;
-        }
-
-      symbolic_color = gtk_symbolic_color_new_literal (&color);
-      g_free (color_str);
-    }
-
-  return symbolic_color;
-}
-
-static GtkSymbolicColor *
-symbolic_color_parse (const gchar  *str)
-{
-  GtkSymbolicColor *color;
-  gchar *end;
-
-  color = symbolic_color_parse_str (str, &end);
-
-  if (*end != '\0')
-    {
-      if (color)
-        {
-          gtk_symbolic_color_unref (color);
-          color = NULL;
-        }
-    }
-
-  return color;
-}
-
-static GtkGradient *
-gradient_parse_str (const gchar  *str,
-                    gchar       **end_ptr)
-{
-  GtkGradient *gradient = NULL;
-  gdouble coords[6];
-  gchar *end;
-  guint i;
-
-  if (g_str_has_prefix (str, "-gtk-gradient"))
-    {
-      cairo_pattern_type_t type;
-
-      str += strlen ("-gtk-gradient");
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-
-      /* Parse gradient type */
-      if (g_str_has_prefix (str, "linear"))
-        {
-          type = CAIRO_PATTERN_TYPE_LINEAR;
-          str += strlen ("linear");
-        }
-      else if (g_str_has_prefix (str, "radial"))
-        {
-          type = CAIRO_PATTERN_TYPE_RADIAL;
-          str += strlen ("radial");
-        }
-      else
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      SKIP_SPACES (str);
-
-      /* Parse start/stop position parameters */
-      for (i = 0; i < 2; i++)
-        {
-          if (*str != ',')
-            {
-              *end_ptr = (gchar *) str;
-              return NULL;
-            }
-
-          str++;
-          SKIP_SPACES (str);
-
-          if (strncmp (str, "left", 4) == 0)
-            {
-              coords[i * 3] = 0;
-              str += strlen ("left");
-            }
-          else if (strncmp (str, "right", 5) == 0)
-            {
-              coords[i * 3] = 1;
-              str += strlen ("right");
-            }
-          else if (strncmp (str, "center", 6) == 0)
-            {
-              coords[i * 3] = 0.5;
-              str += strlen ("center");
-            }
-          else
-            {
-              coords[i * 3] = g_ascii_strtod (str, &end);
-
-              if (str == end)
-                {
-                  *end_ptr = (gchar *) str;
-                  return NULL;
-                }
-
-              str = end;
-            }
-
-          SKIP_SPACES (str);
-
-          if (strncmp (str, "top", 3) == 0)
-            {
-              coords[(i * 3) + 1] = 0;
-              str += strlen ("top");
-            }
-          else if (strncmp (str, "bottom", 6) == 0)
-            {
-              coords[(i * 3) + 1] = 1;
-              str += strlen ("bottom");
-            }
-          else if (strncmp (str, "center", 6) == 0)
-            {
-              coords[(i * 3) + 1] = 0.5;
-              str += strlen ("center");
-            }
-          else
-            {
-              coords[(i * 3) + 1] = g_ascii_strtod (str, &end);
-
-              if (str == end)
-                {
-                  *end_ptr = (gchar *) str;
-                  return NULL;
-                }
-
-              str = end;
-            }
-
-          SKIP_SPACES (str);
-
-          if (type == CAIRO_PATTERN_TYPE_RADIAL)
-            {
-              /* Parse radius */
-              if (*str != ',')
-                {
-                  *end_ptr = (gchar *) str;
-                  return NULL;
-                }
-
-              str++;
-              SKIP_SPACES (str);
-
-              coords[(i * 3) + 2] = g_ascii_strtod (str, &end);
-              str = end;
-
-              SKIP_SPACES (str);
-            }
-        }
-
-      if (type == CAIRO_PATTERN_TYPE_LINEAR)
-        gradient = gtk_gradient_new_linear (coords[0], coords[1], coords[3], coords[4]);
-      else
-        gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2],
-                                            coords[3], coords[4], coords[5]);
-
-      while (*str == ',')
-        {
-          GtkSymbolicColor *color;
-          gdouble position;
-
-          if (*str != ',')
-            {
-              *end_ptr = (gchar *) str;
-              return gradient;
-            }
-
-          str++;
-          SKIP_SPACES (str);
-
-          if (g_str_has_prefix (str, "from"))
-            {
-              position = 0;
-              str += strlen ("from");
-              SKIP_SPACES (str);
-
-              if (*str != '(')
-                {
-                  *end_ptr = (gchar *) str;
-                  return gradient;
-                }
-            }
-          else if (g_str_has_prefix (str, "to"))
-            {
-              position = 1;
-              str += strlen ("to");
-              SKIP_SPACES (str);
-
-              if (*str != '(')
-                {
-                  *end_ptr = (gchar *) str;
-                  return gradient;
-                }
-            }
-          else if (g_str_has_prefix (str, "color-stop"))
-            {
-              str += strlen ("color-stop");
-              SKIP_SPACES (str);
-
-              if (*str != '(')
-                {
-                  *end_ptr = (gchar *) str;
-                  return gradient;
-                }
-
-              str++;
-              SKIP_SPACES (str);
-
-              position = g_ascii_strtod (str, &end);
-
-              str = end;
-              SKIP_SPACES (str);
-
-              if (*str != ',')
-                {
-                  *end_ptr = (gchar *) str;
-                  return gradient;
-                }
-            }
-          else
-            {
-              *end_ptr = (gchar *) str;
-              return gradient;
-            }
-
-          str++;
-          SKIP_SPACES (str);
-
-          color = symbolic_color_parse_str (str, &end);
-
-          str = end;
-          SKIP_SPACES (str);
-
-          if (*str != ')')
-            {
-              if (color)
-                gtk_symbolic_color_unref (color);
-              *end_ptr = (gchar *) str;
-              return gradient;
-            }
-
-          str++;
-          SKIP_SPACES (str);
-
-          if (color)
-            {
-              gtk_gradient_add_color_stop (gradient, position, color);
-              gtk_symbolic_color_unref (color);
-            }
-        }
-
-      if (*str != ')')
-        {
-          *end_ptr = (gchar *) str;
-          return gradient;
-        }
-
-      str++;
-    }
-
-  *end_ptr = (gchar *) str;
-
-  return gradient;
-}
-
-static gchar *
-path_parse_str (GtkCssProvider  *css_provider,
-                const gchar     *str,
-                gchar          **end_ptr)
-{
-  gchar *path, *chr;
-  const gchar *start, *end;
-  start = str;
-
-  if (g_str_has_prefix (str, "url"))
-    {
-      str += strlen ("url");
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      chr = strchr (str, ')');
-      if (!chr)
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      end = chr + 1;
-
-      str++;
-      SKIP_SPACES (str);
-
-      if (*str == '"' || *str == '\'')
-        {
-          const gchar *p;
-          p = str;
-          str++;
-
-          chr--;
-          SKIP_SPACES_BACK (chr);
-
-          if (*chr != *p || chr == p)
-            {
-              *end_ptr = (gchar *)str;
-              return NULL;
-            }
-        }
-      else
-        {
-          *end_ptr = (gchar *)str;
-          return NULL;
-        }
-
-      path = g_strndup (str, chr - str);
-      g_strstrip (path);
-
-      *end_ptr = (gchar *)end;
-    }
-  else
-    {
-      path = g_strdup (str);
-      *end_ptr = (gchar *)str + strlen (str);
-    }
-
-  /* Always return an absolute path */
-  if (!g_path_is_absolute (path))
-    {
-      GtkCssProviderPrivate *priv;
-      gchar *dirname, *full_path;
-
-      priv = css_provider->priv;
-
-      /* Use relative path to the current CSS file path, if any */
-      if (priv->scanner->input_name)
-        dirname = g_path_get_dirname (priv->scanner->input_name);
-      else
-        dirname = g_get_current_dir ();
-
-      full_path = g_build_filename (dirname, path, NULL);
-      g_free (path);
-      g_free (dirname);
-
-      path = full_path;
-    }
-
-  if (!g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
-    {
-      gtk_css_provider_error (css_provider, G_FILE_ERROR, G_FILE_ERROR_EXIST,
-                              "File doesn't exist: %s", path);
-      g_free (path);
-      path = NULL;
-      *end_ptr = (gchar *)start;
-    }
-
-  return path;
-}
-
-static gchar *
-path_parse (GtkCssProvider  *css_provider,
-            const gchar     *str)
-{
-  gchar *path;
-  gchar *end;
-
-  path = path_parse_str (css_provider, str, &end);
-
-  if (!path)
-    return NULL;
-
-  if (*end != '\0')
-    {
-      gtk_css_provider_error (css_provider,
-                              GTK_CSS_PROVIDER_ERROR,
-                              GTK_CSS_PROVIDER_ERROR_FAILED,
-                              "Error parsing path");
-      g_free (path);
-      path = NULL;
-    }
-
-  return path;
-}
-
-static Gtk9Slice *
-slice_parse_str (GtkCssProvider  *css_provider,
-                 const gchar     *str,
-                 gchar          **end_ptr)
-{
-  gdouble distance_top, distance_bottom;
-  gdouble distance_left, distance_right;
-  GtkSliceSideModifier mods[2];
-  GdkPixbuf *pixbuf;
-  Gtk9Slice *slice;
-  gchar *path;
-  gint i = 0;
-  GError *error = NULL;
-
-  SKIP_SPACES (str);
-
-  /* Parse image url */
-  path = path_parse_str (css_provider, str, end_ptr);
-
-  if (!path)
-      return NULL;
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
-
-  /* Parse top/left/bottom/right distances */
-  distance_top = g_ascii_strtod (str, end_ptr);
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
-
-  distance_right = g_ascii_strtod (str, end_ptr);
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
-
-  distance_bottom = g_ascii_strtod (str, end_ptr);
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
-
-  distance_left = g_ascii_strtod (str, end_ptr);
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
-
-  while (*str && i < 2)
-    {
-      if (g_str_has_prefix (str, "stretch"))
-        {
-          str += strlen ("stretch");
-          mods[i] = GTK_SLICE_STRETCH;
-        }
-      else if (g_str_has_prefix (str, "repeat"))
-        {
-          str += strlen ("repeat");
-          mods[i] = GTK_SLICE_REPEAT;
-        }
-      else
-        {
-          g_free (path);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      SKIP_SPACES (str);
-      i++;
-    }
-
-  *end_ptr = (gchar *) str;
-
-  if (*str != '\0')
-    {
-      g_free (path);
-      return NULL;
-    }
-
-  if (i != 2)
-    {
-      /* Fill in second modifier, same as the first */
-      mods[1] = mods[0];
-    }
-
-  pixbuf = gdk_pixbuf_new_from_file (path, &error);
-  g_free (path);
-
-  if (!pixbuf)
-    {
-      gtk_css_provider_take_error (css_provider, error);
-      *end_ptr = (gchar *) str;
-      return NULL;
-    }
-
-  slice = _gtk_9slice_new (pixbuf,
-                           distance_top, distance_bottom,
-                           distance_left, distance_right,
-                           mods[0], mods[1]);
-  g_object_unref (pixbuf);
-
-  return slice;
-}
-
-static gdouble
-unit_parse_str (const gchar     *str,
-                gchar          **end_str)
-{
-  gdouble unit;
-
-  SKIP_SPACES (str);
-  unit = g_ascii_strtod (str, end_str);
-  str = *end_str;
-
-  /* Now parse the unit type, if any. We
-   * don't admit spaces between these.
-   */
-  if (*str != ' ' && *str != '\0')
-    {
-      while (**end_str != ' ' && **end_str != '\0')
-        (*end_str)++;
-
-      /* Only handle pixels at the moment */
-      if (strncmp (str, "px", 2) != 0)
-        {
-          gchar *type;
-
-          type = g_strndup (str, *end_str - str);
-          g_warning ("Unknown unit '%s', only pixel units are "
-                     "currently supported in CSS style", type);
-          g_free (type);
-        }
-    }
-
-  return unit;
-}
-
-static GtkBorder *
-border_parse_str (const gchar  *str,
-                  gchar       **end_str)
-{
-  gdouble first, second, third, fourth;
-  GtkBorder *border;
-
-  border = gtk_border_new ();
-
-  SKIP_SPACES (str);
-  if (!g_ascii_isdigit (*str) && *str != '-')
-    return border;
-
-  first = unit_parse_str (str, end_str);
-  str = *end_str;
-  SKIP_SPACES (str);
-
-  if (!g_ascii_isdigit (*str) && *str != '-')
-    {
-      border->left = border->right = border->top = border->bottom = (gint) first;
-      *end_str = (gchar *) str;
-      return border;
-    }
-
-  second = unit_parse_str (str, end_str);
-  str = *end_str;
-  SKIP_SPACES (str);
-
-  if (!g_ascii_isdigit (*str) && *str != '-')
-    {
-      border->top = border->bottom = (gint) first;
-      border->left = border->right = (gint) second;
-      *end_str = (gchar *) str;
-      return border;
-    }
-
-  third = unit_parse_str (str, end_str);
-  str = *end_str;
-  SKIP_SPACES (str);
-
-  if (!g_ascii_isdigit (*str) && *str != '-')
-    {
-      border->top = (gint) first;
-      border->left = border->right = (gint) second;
-      border->bottom = (gint) third;
-      *end_str = (gchar *) str;
-      return border;
-    }
-
-  fourth = unit_parse_str (str, end_str);
-
-  border->top = (gint) first;
-  border->right = (gint) second;
-  border->bottom = (gint) third;
-  border->left = (gint) fourth;
-
-  return border;
-}
-
-static void
-resolve_binding_sets (const gchar *value_str,
-                      GValue      *value)
-{
-  GPtrArray *array;
-  gchar **bindings, **str;
-
-  bindings = g_strsplit (value_str, ",", -1);
-  array = g_ptr_array_new ();
-
-  for (str = bindings; *str; str++)
-    {
-      GtkBindingSet *binding_set;
-
-      binding_set = gtk_binding_set_find (g_strstrip (*str));
-
-      if (!binding_set)
-        continue;
-
-      g_ptr_array_add (array, binding_set);
-    }
-
-  g_value_take_boxed (value, array);
-  g_strfreev (bindings);
-}
-
-static gboolean
-css_provider_parse_value (GtkCssProvider  *css_provider,
-                          const gchar     *value_str,
-                          GValue          *value)
-{
-  GtkCssProviderPrivate *priv;
-  GType type;
-  gboolean parsed = TRUE;
-  gchar *end = NULL;
-
-  priv = css_provider->priv;
-  type = G_VALUE_TYPE (value);
+          g_scanner_get_next_token (scanner);
 
-  if (type == GDK_TYPE_RGBA ||
-      type == GDK_TYPE_COLOR)
-    {
-      GdkRGBA rgba;
-      GdkColor color;
-
-      if (type == GDK_TYPE_RGBA &&
-          gdk_rgba_parse (&rgba, value_str))
-        g_value_set_boxed (value, &rgba);
-      else if (type == GDK_TYPE_COLOR &&
-               gdk_color_parse (value_str, &color))
-        g_value_set_boxed (value, &color);
-      else
-        {
-          GtkSymbolicColor *symbolic_color;
+          if (scanner->token != G_TOKEN_IDENTIFIER)
+            return G_TOKEN_IDENTIFIER;
 
-          symbolic_color = symbolic_color_parse_str (value_str, &end);
+          selector_path_prepend_glob (path);
+          selector_path_prepend_combinator (path, COMBINATOR_CHILD);
 
-          if (symbolic_color)
+          if (is_class)
+            parse_classes (path, scanner->value.v_identifier);
+          else
             {
-              g_value_unset (value);
-              g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
-              g_value_take_boxed (value, symbolic_color);
+              if ((pos = strchr (scanner->value.v_identifier, '.')) != NULL)
+                *pos = '\0';
+
+              selector_path_prepend_name (path, scanner->value.v_identifier);
+
+              /* Parse any remaining classes */
+              if (pos)
+                parse_classes (path, pos + 1);
             }
-          else
-            parsed = FALSE;
         }
-    }
-  else if (type == PANGO_TYPE_FONT_DESCRIPTION)
-    {
-      PangoFontDescription *font_desc;
+      else if (is_widget_class_name (scanner->value.v_identifier))
+        {
+          gchar *pos;
 
-      font_desc = pango_font_description_from_string (value_str);
-      g_value_take_boxed (value, font_desc);
-    }
-  else if (type == G_TYPE_BOOLEAN)
-    {
-      if (value_str[0] == '1' ||
-          g_ascii_strcasecmp (value_str, "true") == 0)
-        g_value_set_boolean (value, TRUE);
-      else
-        g_value_set_boolean (value, FALSE);
-    }
-  else if (type == G_TYPE_INT)
-    g_value_set_int (value, atoi (value_str));
-  else if (type == G_TYPE_UINT)
-    g_value_set_uint (value, (guint) atoi (value_str));
-  else if (type == G_TYPE_DOUBLE)
-    g_value_set_double (value, g_ascii_strtod (value_str, NULL));
-  else if (type == G_TYPE_FLOAT)
-    g_value_set_float (value, (gfloat) g_ascii_strtod (value_str, NULL));
-  else if (type == GTK_TYPE_THEMING_ENGINE)
-    {
-      GtkThemingEngine *engine;
+          if ((pos = strchr (scanner->value.v_identifier, '#')) != NULL ||
+              (pos = strchr (scanner->value.v_identifier, '.')) != NULL)
+            {
+              gchar *type_name, *name;
+              gboolean is_class;
 
-      engine = gtk_theming_engine_load (value_str);
-      if (engine)
-        g_value_set_object (value, engine);
-      else
-        parsed = FALSE;
-    }
-  else if (type == GTK_TYPE_ANIMATION_DESCRIPTION)
-    {
-      GtkAnimationDescription *desc;
+              is_class = (*pos == '.');
 
-      desc = _gtk_animation_description_from_string (value_str);
+              /* Widget type and name/class put together */
+              name = pos + 1;
+              *pos = '\0';
+              type_name = scanner->value.v_identifier;
 
-      if (desc)
-        g_value_take_boxed (value, desc);
-      else
-        parsed = FALSE;
-    }
-  else if (type == GTK_TYPE_BORDER)
-    {
-      GtkBorder *border;
+              selector_path_prepend_type (path, type_name);
 
-      border = border_parse_str (value_str, &end);
-      g_value_take_boxed (value, border);
-    }
-  else if (type == CAIRO_GOBJECT_TYPE_PATTERN)
-    {
-      GtkGradient *gradient;
+              /* This is only so there is a direct relationship
+               * between widget type and its name.
+               */
+              selector_path_prepend_combinator (path, COMBINATOR_CHILD);
+
+              if (is_class)
+                parse_classes (path, name);
+              else
+                {
+                  if ((pos = strchr (name, '.')) != NULL)
+                    *pos = '\0';
 
-      gradient = gradient_parse_str (value_str, &end);
+                  selector_path_prepend_name (path, name);
 
-      if (gradient)
-        {
-          g_value_unset (value);
-          g_value_init (value, GTK_TYPE_GRADIENT);
-          g_value_take_boxed (value, gradient);
+                  /* Parse any remaining classes */
+                  if (pos)
+                    parse_classes (path, pos + 1);
+                }
+            }
+          else
+            selector_path_prepend_type (path, scanner->value.v_identifier);
         }
-      else
+      else if (_gtk_style_context_check_region_name (scanner->value.v_identifier))
         {
-          gchar *path;
-          GdkPixbuf *pixbuf;
+          GtkRegionFlags flags = 0;
+          gchar *region_name;
 
-          path = path_parse_str (css_provider, value_str, &end);
+          region_name = g_strdup (scanner->value.v_identifier);
 
-          if (path)
+          if (g_scanner_peek_next_token (scanner) == ':')
             {
-              pixbuf = gdk_pixbuf_new_from_file (path, NULL);
-              g_free (path);
+              ParserSymbol symbol;
+
+              g_scanner_get_next_token (scanner);
+              css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS);
+
+              /* Check for the next token being nth-child, parse in that
+               * case, and fallback into common state parsing if not.
+               */
+              if (g_scanner_peek_next_token (scanner) != G_TOKEN_SYMBOL)
+                return G_TOKEN_SYMBOL;
+
+              symbol = GPOINTER_TO_INT (scanner->next_value.v_symbol);
 
-              if (pixbuf)
+              if (symbol == SYMBOL_FIRST_CHILD ||
+                  symbol == SYMBOL_LAST_CHILD ||
+                  symbol == SYMBOL_NTH_CHILD ||
+                  symbol == SYMBOL_SORTED_CHILD)
                 {
-                  cairo_surface_t *surface;
-                  cairo_pattern_t *pattern;
-                  cairo_t *cr;
-                  cairo_matrix_t matrix;
-
-                  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                                        gdk_pixbuf_get_width (pixbuf),
-                                                        gdk_pixbuf_get_height (pixbuf));
-                  cr = cairo_create (surface);
-                  gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
-                  cairo_paint (cr);
-                  pattern = cairo_pattern_create_for_surface (surface);
-
-                  cairo_matrix_init_scale (&matrix,
-                                           gdk_pixbuf_get_width (pixbuf),
-                                           gdk_pixbuf_get_height (pixbuf));
-                  cairo_pattern_set_matrix (pattern, &matrix);
-
-                  cairo_surface_destroy (surface);
-                  cairo_destroy (cr);
-                  g_object_unref (pixbuf);
-
-                  g_value_take_boxed (value, pattern);
+                  GTokenType token;
+
+                  if ((token = parse_nth_child (css_provider, scanner, &flags)) != G_TOKEN_NONE)
+                    return token;
+
+                  css_provider_pop_scope (css_provider);
                 }
               else
-                parsed = FALSE;
+                {
+                  css_provider_pop_scope (css_provider);
+                  selector_path_prepend_region (path, region_name, 0);
+                  g_free (region_name);
+                  break;
+                }
             }
-          else
-            parsed = FALSE;
+
+          selector_path_prepend_region (path, region_name, flags);
+          g_free (region_name);
         }
-    }
-  else if (G_TYPE_IS_ENUM (type))
-    {
-      GEnumClass *enum_class;
-      GEnumValue *enum_value;
+      else if (scanner->value.v_identifier[0] == '*')
+        selector_path_prepend_glob (path);
+      else
+        return G_TOKEN_IDENTIFIER;
 
-      enum_class = g_type_class_ref (type);
-      enum_value = g_enum_get_value_by_nick (enum_class, value_str);
+      g_scanner_get_next_token (scanner);
 
-      if (!enum_value)
+      if (scanner->token == '>')
         {
-          gtk_css_provider_error (css_provider,
-                                  GTK_CSS_PROVIDER_ERROR,
-                                  GTK_CSS_PROVIDER_ERROR_FAILED,
-                                  "Unknown value '%s' for enum type '%s'",
-                                  value_str, g_type_name (type));
-          parsed = FALSE;
+          selector_path_prepend_combinator (path, COMBINATOR_CHILD);
+          g_scanner_get_next_token (scanner);
         }
-      else
-        g_value_set_enum (value, enum_value->value);
-
-      g_type_class_unref (enum_class);
     }
-  else if (G_TYPE_IS_FLAGS (type))
-    {
-      GFlagsClass *flags_class;
-      GFlagsValue *flag_value;
-      guint flags = 0;
-      gchar *ptr;
 
-      flags_class = g_type_class_ref (type);
+  if (scanner->token == ':')
+    {
+      /* Add glob selector if path is empty */
+      if (selector_path_depth (path) == 0)
+        selector_path_prepend_glob (path);
 
-      /* Parse comma separated values */
-      ptr = strchr (value_str, ',');
+      css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS);
 
-      while (ptr && parsed)
+      while (scanner->token == ':')
         {
-          gchar *flag_str;
-
-          *ptr = '\0';
-          ptr++;
-
-          flag_str = (gchar *) value_str;
-          flag_value = g_flags_get_value_by_nick (flags_class,
-                                                  g_strstrip (flag_str));
+          GTokenType token;
 
-          if (!flag_value)
-            {
-              gtk_css_provider_error (css_provider,
-                                      GTK_CSS_PROVIDER_ERROR,
-                                      GTK_CSS_PROVIDER_ERROR_FAILED,
-                                      "Unknown flag '%s' for type '%s'",
-                                      value_str, g_type_name (type));
-              parsed = FALSE;
-            }
-          else
-            flags |= flag_value->value;
+          if ((token = parse_pseudo_class (css_provider, scanner, path)) != G_TOKEN_NONE)
+            return token;
 
-          value_str = ptr;
-          ptr = strchr (value_str, ',');
+          g_scanner_get_next_token (scanner);
         }
 
-      /* Store last/only value */
-      flag_value = g_flags_get_value_by_nick (flags_class, value_str);
-
-      if (!flag_value)
-        {
-          gtk_css_provider_error (css_provider,
-                                  GTK_CSS_PROVIDER_ERROR,
-                                  GTK_CSS_PROVIDER_ERROR_FAILED,
-                                  "Unknown flag '%s' for type '%s'",
-                                  value_str, g_type_name (type));
-          parsed = FALSE;
-        }
-      else
-        flags |= flag_value->value;
+      css_provider_pop_scope (css_provider);
+    }
 
-      if (parsed)
-        g_value_set_enum (value, flags);
+  return G_TOKEN_NONE;
+}
 
-      g_type_class_unref (flags_class);
-    }
-  else if (type == GTK_TYPE_9SLICE)
-    {
-      Gtk9Slice *slice;
+static void
+resolve_binding_sets (const gchar *value_str,
+                      GValue      *value)
+{
+  GPtrArray *array;
+  gchar **bindings, **str;
 
-      slice = slice_parse_str (css_provider, value_str, &end);
+  bindings = g_strsplit (value_str, ",", -1);
+  array = g_ptr_array_new ();
 
-      if (slice)
-        g_value_take_boxed (value, slice);
-      else
-        parsed = FALSE;
-    }
-  else
+  for (str = bindings; *str; str++)
     {
-      gtk_css_provider_error (css_provider,
-                              GTK_CSS_PROVIDER_ERROR,
-                              GTK_CSS_PROVIDER_ERROR_FAILED,
-                              "Cannot parse string '%s' for type %s",
-                              value_str, g_type_name (type));
-      parsed = FALSE;
-    }
+      GtkBindingSet *binding_set;
 
-  if (end)
-    SKIP_SPACES (end);
+      binding_set = gtk_binding_set_find (g_strstrip (*str));
 
-  if (end && *end)
-    {
-      parsed = FALSE;
+      if (!binding_set)
+        continue;
 
-      gtk_css_provider_error_literal (css_provider,
-                                      GTK_CSS_PROVIDER_ERROR,
-                                      GTK_CSS_PROVIDER_ERROR_FAILED,
-                                      "Failed to parse value");
+      g_ptr_array_add (array, binding_set);
     }
 
-  return parsed;
+  g_value_take_boxed (value, array);
+  g_strfreev (bindings);
 }
 
 static GTokenType
@@ -3250,6 +2129,7 @@ parse_rule (GtkCssProvider  *css_provider,
         {
           GtkSymbolicColor *color;
           gchar *color_name, *color_str;
+          GError *error = NULL;
 
           /* Directive is a color mapping */
           g_scanner_get_next_token (scanner);
@@ -3271,11 +2151,10 @@ parse_rule (GtkCssProvider  *css_provider,
             }
 
           color_str = g_strstrip (scanner->value.v_identifier);
-          color = symbolic_color_parse (color_str);
-
+          color = _gtk_css_parse_symbolic_color (color_str, &error);
           if (!color)
             {
-              gtk_css_provider_invalid_token (css_provider, "Color definition");
+              gtk_css_provider_take_error (css_provider, error);
               return G_TOKEN_IDENTIFIER;
             }
 
@@ -3295,33 +2174,53 @@ parse_rule (GtkCssProvider  *css_provider,
           GSList *state_backup;
           gboolean loaded;
           gchar *path = NULL;
+          GFile *base, *actual;
+          char *dirname;
+          GError *error = NULL;
 
           css_provider_push_scope (css_provider, SCOPE_VALUE);
           g_scanner_get_next_token (scanner);
 
           if (scanner->token == G_TOKEN_IDENTIFIER &&
               g_str_has_prefix (scanner->value.v_identifier, "url"))
-            path = path_parse (css_provider,
-                               g_strstrip (scanner->value.v_identifier));
+            path = g_strstrip (scanner->value.v_identifier);
           else if (scanner->token == G_TOKEN_STRING)
-            path = path_parse (css_provider,
-                               g_strstrip (scanner->value.v_string));
-
-          if (path == NULL)
+            path = g_strstrip (scanner->value.v_string);
+          else
             {
               gtk_css_provider_invalid_token (css_provider, "File URL");
               return G_TOKEN_IDENTIFIER;
             }
 
+          if (priv->scanner->input_name)
+            dirname = g_path_get_dirname (priv->scanner->input_name);
+          else
+            dirname = g_get_current_dir ();
+
+          base = g_file_new_for_path (dirname);
+          g_free (dirname);
+
+          actual = _gtk_css_parse_url (base, path, NULL, &error);
+          g_object_unref (base);
+
+          if (actual == NULL)
+            {
+              gtk_css_provider_take_error (css_provider, error);
+              return G_TOKEN_IDENTIFIER;
+            }
+
           css_provider_pop_scope (css_provider);
           g_scanner_get_next_token (scanner);
 
           if (scanner->token != ';')
             {
-              g_free (path);
+              g_object_unref (actual);
               return ';';
             }
 
+          path = g_file_get_path (actual);
+          g_object_unref (actual);
+
           /* Snapshot current parser state and scanner in order to restore after importing */
           state_backup = priv->state;
           scanner_backup = priv->scanner;
@@ -3342,10 +2241,7 @@ parse_rule (GtkCssProvider  *css_provider,
           g_free (path);
 
           if (!loaded)
-            {
-              gtk_css_provider_invalid_token (css_provider, "File URL");
-              return G_TOKEN_IDENTIFIER;
-            }
+            return G_TOKEN_IDENTIFIER;
           else
             return G_TOKEN_NONE;
         }
@@ -3495,7 +2391,6 @@ parse_rule (GtkCssProvider  *css_provider,
         }
 
       value_str = scanner->value.v_identifier;
-      SKIP_SPACES (value_str);
       g_strchomp (value_str);
 
       if (pspec)
@@ -3524,10 +2419,6 @@ parse_rule (GtkCssProvider  *css_provider,
               g_value_set_string (val, value_str);
               g_hash_table_insert (priv->cur_properties, prop, val);
             }
-          else if (!parse_func && css_provider_parse_value (css_provider, value_str, val))
-            {
-              g_hash_table_insert (priv->cur_properties, prop, val);
-            }
           else if (parse_func)
             {
               GError *error = NULL;
@@ -3539,12 +2430,38 @@ parse_rule (GtkCssProvider  *css_provider,
             }
           else
             {
-              g_value_unset (val);
-              g_slice_free (GValue, val);
-              g_free (prop);
+              GError *error = NULL;
+              GFile *base;
+              char *dirname;
+              gboolean success;
+
+              if (priv->scanner->input_name)
+                dirname = g_path_get_dirname (priv->scanner->input_name);
+              else
+                dirname = g_get_current_dir ();
+
+              base = g_file_new_for_path (dirname);
+              g_free (dirname);
+
+              success = _gtk_css_value_from_string (val,
+                                                    base,
+                                                    value_str,
+                                                    &error);
 
-              gtk_css_provider_invalid_token (css_provider, "Property value");
-              goto find_end_of_declaration;
+              g_object_unref (base);
+
+              if (success)
+                {
+                  g_hash_table_insert (priv->cur_properties, prop, val);
+                }
+              else
+                {
+                  g_value_unset (val);
+                  g_slice_free (GValue, val);
+                  g_free (prop);
+
+                  gtk_css_provider_take_error (css_provider, error);
+                }
             }
         }
       else if (prop[0] == '-')
@@ -4323,119 +3240,6 @@ gtk_css_provider_get_named (const gchar *name,
 }
 
 static void
-gtk_css_provider_print_value (const GValue *value,
-                              GString *     str)
-{
-  char *s;
-
-  if (G_VALUE_TYPE (value) == GTK_TYPE_BORDER)
-    {
-      const GtkBorder *border = g_value_get_boxed (value);
-
-      if (border == NULL)
-        g_string_append (str, "none");
-      else if (border->left != border->right)
-        g_string_append_printf (str, "%d %d %d %d", border->top, border->right, border->bottom, border->left);
-      else if (border->top != border->bottom)
-        g_string_append_printf (str, "%d %d %d", border->top, border->right, border->bottom);
-      else if (border->top != border->left)
-        g_string_append_printf (str, "%d %d", border->top, border->right);
-      else
-        g_string_append_printf (str, "%d", border->top);
-
-      return;
-    }
-  else if (G_VALUE_TYPE (value) == GDK_TYPE_RGBA)
-    {
-      const GdkRGBA *rgba = g_value_get_boxed (value);
-      if (rgba == NULL)
-        g_string_append (str, "none");
-      else
-        {
-          s = gdk_rgba_to_string (g_value_get_boxed (value));
-          g_string_append (str, s);
-          g_free (s);
-        }
-      return;
-    }
-  else if (G_VALUE_TYPE (value) == GTK_TYPE_SYMBOLIC_COLOR)
-    {
-      GtkSymbolicColor *color = g_value_get_boxed (value);
-
-      if (color == NULL)
-        g_string_append (str, "none");
-      else
-        {
-          s = gtk_symbolic_color_to_string (color);
-          g_string_append (str, s);
-          g_free (s);
-        }
-      return;
-    }
-  else if (G_VALUE_TYPE (value) == GTK_TYPE_ANIMATION_DESCRIPTION)
-    {
-      GtkAnimationDescription *desc = g_value_get_boxed (value);
-
-      if (desc == NULL)
-        g_string_append (str, "none");
-      else
-        {
-          s = _gtk_animation_description_to_string (desc);
-          g_string_append (str, s);
-          g_free (s);
-        }
-      return;
-    }
-  else if (G_VALUE_TYPE (value) == GTK_TYPE_GRADIENT)
-    {
-      GtkGradient *gradient = g_value_get_boxed (value);
-
-      if (gradient == NULL)
-        g_string_append (str, "none");
-      else
-        {
-          s = gtk_gradient_to_string (gradient);
-          g_string_append (str, s);
-          g_free (s);
-        }
-      return;
-    }
-  else if (G_VALUE_TYPE (value) == PANGO_TYPE_FONT_DESCRIPTION)
-    {
-      PangoFontDescription *desc = g_value_get_boxed (value);
-
-      if (desc == NULL)
-        g_string_append (str, "none");
-      else
-        {
-          s = pango_font_description_to_string (desc);
-          g_string_append (str, s);
-          g_free (s);
-        }
-      return;
-    }
-  
-  if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_ENUM))
-    {
-      GEnumClass *enum_class;
-      GEnumValue *enum_value;
-
-      enum_class = g_type_class_ref (G_VALUE_TYPE (value));
-      enum_value = g_enum_get_value (enum_class, g_value_get_enum (value));
-
-      g_string_append (str, enum_value->value_nick);
-
-      g_type_class_unref (enum_class);
-      return;
-    }
-  
-  /* fall back to boring strdup in the worst case */
-  s = g_strdup_value_contents (value);
-  g_string_append (str, s);
-  g_free (s);
-}
-
-static void
 selector_path_print (const SelectorPath *path,
                      GString *           str)
 {
@@ -4544,6 +3348,7 @@ selector_style_info_print (const SelectorStyleInfo *info,
                            GString                 *str)
 {
   GList *keys, *walk;
+  char *s;
 
   selector_path_print (info->path, str);
 
@@ -4561,7 +3366,9 @@ selector_style_info_print (const SelectorStyleInfo *info,
       g_string_append (str, "  ");
       g_string_append (str, name);
       g_string_append (str, ": ");
-      gtk_css_provider_print_value (value, str);
+      s = _gtk_css_value_to_string (value);
+      g_string_append (str, s);
+      g_free (s);
       g_string_append (str, ";\n");
     }
 
diff --git a/gtk/gtkcssstringfuncs.c b/gtk/gtkcssstringfuncs.c
new file mode 100644
index 0000000..f995faf
--- /dev/null
+++ b/gtk/gtkcssstringfuncs.c
@@ -0,0 +1,1543 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2010 Carlos Garnacho <carlosg 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "gtkcssstringfuncsprivate.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <cairo-gobject.h>
+
+#include "gtkcssprovider.h"
+
+/* the actual parsers we have */
+#include "gtkanimationdescription.h"
+#include "gtk9slice.h"
+#include "gtkgradient.h"
+#include "gtkthemingengine.h"
+
+typedef gboolean (* FromStringFunc)   (const char    *str,
+                                       GFile         *base,
+                                       GValue        *value,
+                                       GError       **error);
+typedef char *   (* ToStringFunc)     (const GValue  *value);
+
+static GHashTable *from_string_funcs = NULL;
+static GHashTable *to_string_funcs = NULL;
+
+static void
+register_conversion_function (GType          type,
+                              FromStringFunc from_string,
+                              ToStringFunc   to_string)
+{
+  if (from_string)
+    g_hash_table_insert (from_string_funcs, GSIZE_TO_POINTER (type), from_string);
+  if (to_string)
+    g_hash_table_insert (to_string_funcs, GSIZE_TO_POINTER (type), to_string);
+}
+
+static gboolean
+set_default_error (GError **error,
+                   GType    type)
+{
+  g_set_error (error,
+               GTK_CSS_PROVIDER_ERROR,
+               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+               "Could not convert property value to type '%s'",
+               g_type_name (type));
+  return FALSE;
+}
+
+/*** IMPLEMENTATIONS ***/
+
+#define SKIP_SPACES(s) while (g_ascii_isspace (*(s))) (s)++
+#define SKIP_SPACES_BACK(s) while (g_ascii_isspace (*(s))) (s)--
+
+static GtkSymbolicColor *
+symbolic_color_parse_str (const gchar  *string,
+                          gchar       **end_ptr)
+{
+  GtkSymbolicColor *symbolic_color = NULL;
+  gchar *str;
+
+  str = (gchar *) string;
+  *end_ptr = str;
+
+  if (str[0] == '@')
+    {
+      const gchar *end;
+      gchar *name;
+
+      str++;
+      end = str;
+
+      while (*end == '-' || *end == '_' || g_ascii_isalnum (*end))
+        end++;
+
+      name = g_strndup (str, end - str);
+      symbolic_color = gtk_symbolic_color_new_name (name);
+      g_free (name);
+
+      *end_ptr = (gchar *) end;
+    }
+  else if (g_str_has_prefix (str, "lighter") ||
+           g_str_has_prefix (str, "darker"))
+    {
+      GtkSymbolicColor *param_color;
+      gboolean is_lighter = FALSE;
+
+      is_lighter = g_str_has_prefix (str, "lighter");
+
+      if (is_lighter)
+        str += strlen ("lighter");
+      else
+        str += strlen ("darker");
+
+      SKIP_SPACES (str);
+
+      if (*str != '(')
+        {
+          *end_ptr = (gchar *) str;
+          return NULL;
+        }
+
+      str++;
+      SKIP_SPACES (str);
+      param_color = symbolic_color_parse_str (str, end_ptr);
+
+      if (!param_color)
+        return NULL;
+
+      str = *end_ptr;
+      SKIP_SPACES (str);
+      *end_ptr = (gchar *) str;
+
+      if (*str != ')')
+        {
+          gtk_symbolic_color_unref (param_color);
+          return NULL;
+        }
+
+      if (is_lighter)
+        symbolic_color = gtk_symbolic_color_new_shade (param_color, 1.3);
+      else
+        symbolic_color = gtk_symbolic_color_new_shade (param_color, 0.7);
+
+      gtk_symbolic_color_unref (param_color);
+      (*end_ptr)++;
+    }
+  else if (g_str_has_prefix (str, "shade") ||
+           g_str_has_prefix (str, "alpha"))
+    {
+      GtkSymbolicColor *param_color;
+      gboolean is_shade = FALSE;
+      gdouble factor;
+
+      is_shade = g_str_has_prefix (str, "shade");
+
+      if (is_shade)
+        str += strlen ("shade");
+      else
+        str += strlen ("alpha");
+
+      SKIP_SPACES (str);
+
+      if (*str != '(')
+        {
+          *end_ptr = (gchar *) str;
+          return NULL;
+        }
+
+      str++;
+      SKIP_SPACES (str);
+      param_color = symbolic_color_parse_str (str, end_ptr);
+
+      if (!param_color)
+        return NULL;
+
+      str = *end_ptr;
+      SKIP_SPACES (str);
+
+      if (str[0] != ',')
+        {
+          gtk_symbolic_color_unref (param_color);
+          *end_ptr = (gchar *) str;
+          return NULL;
+        }
+
+      str++;
+      SKIP_SPACES (str);
+      factor = g_ascii_strtod (str, end_ptr);
+
+      str = *end_ptr;
+      SKIP_SPACES (str);
+      *end_ptr = (gchar *) str;
+
+      if (str[0] != ')')
+        {
+          gtk_symbolic_color_unref (param_color);
+          return NULL;
+        }
+
+      if (is_shade)
+        symbolic_color = gtk_symbolic_color_new_shade (param_color, factor);
+      else
+        symbolic_color = gtk_symbolic_color_new_alpha (param_color, factor);
+
+      gtk_symbolic_color_unref (param_color);
+      (*end_ptr)++;
+    }
+  else if (g_str_has_prefix (str, "mix"))
+    {
+      GtkSymbolicColor *color1, *color2;
+      gdouble factor;
+
+      str += strlen ("mix");
+      SKIP_SPACES (str);
+
+      if (*str != '(')
+        {
+          *end_ptr = (gchar *) str;
+          return NULL;
+        }
+
+      str++;
+      SKIP_SPACES (str);
+      color1 = symbolic_color_parse_str (str, end_ptr);
+
+      if (!color1)
+        return NULL;
+
+      str = *end_ptr;
+      SKIP_SPACES (str);
+
+      if (str[0] != ',')
+        {
+          gtk_symbolic_color_unref (color1);
+          *end_ptr = (gchar *) str;
+          return NULL;
+        }
+
+      str++;
+      SKIP_SPACES (str);
+      color2 = symbolic_color_parse_str (str, end_ptr);
+
+      if (!color2 || *end_ptr[0] != ',')
+        {
+          gtk_symbolic_color_unref (color1);
+          return NULL;
+        }
+
+      str = *end_ptr;
+      SKIP_SPACES (str);
+
+      if (str[0] != ',')
+        {
+          gtk_symbolic_color_unref (color1);
+          gtk_symbolic_color_unref (color2);
+          *end_ptr = (gchar *) str;
+          return NULL;
+        }
+
+      str++;
+      SKIP_SPACES (str);
+      factor = g_ascii_strtod (str, end_ptr);
+
+      str = *end_ptr;
+      SKIP_SPACES (str);
+      *end_ptr = (gchar *) str;
+
+      if (str[0] != ')')
+        {
+          gtk_symbolic_color_unref (color1);
+          gtk_symbolic_color_unref (color2);
+          return NULL;
+        }
+
+      symbolic_color = gtk_symbolic_color_new_mix (color1, color2, factor);
+      gtk_symbolic_color_unref (color1);
+      gtk_symbolic_color_unref (color2);
+      (*end_ptr)++;
+    }
+  else
+    {
+      GdkRGBA color;
+      gchar *color_str;
+      const gchar *end;
+
+      end = str + 1;
+
+      if (str[0] == '#')
+        {
+          /* Color in hex format */
+          while (g_ascii_isxdigit (*end))
+            end++;
+        }
+      else if (g_str_has_prefix (str, "rgb"))
+        {
+          /* color in rgb/rgba format */
+          while (*end != ')' && *end != '\0')
+            end++;
+
+          if (*end == ')')
+            end++;
+        }
+      else
+        {
+          /* Color name */
+          while (*end != '\0' &&
+                 (g_ascii_isalnum (*end) || *end == ' '))
+            end++;
+        }
+
+      color_str = g_strndup (str, end - str);
+      *end_ptr = (gchar *) end;
+
+      if (!gdk_rgba_parse (&color, color_str))
+        {
+          g_free (color_str);
+          return NULL;
+        }
+
+      symbolic_color = gtk_symbolic_color_new_literal (&color);
+      g_free (color_str);
+    }
+
+  return symbolic_color;
+}
+
+static gboolean 
+rgba_value_from_string (const char  *str,
+                        GFile       *base,
+                        GValue      *value,
+                        GError     **error)
+{
+  GtkSymbolicColor *symbolic;
+  GdkRGBA rgba;
+
+  if (gdk_rgba_parse (&rgba, str))
+    {
+      g_value_set_boxed (value, &rgba);
+      return TRUE;
+    }
+
+  symbolic = _gtk_css_parse_symbolic_color (str, error);
+  if (symbolic == NULL)
+    return FALSE;
+
+  g_value_unset (value);
+  g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
+  g_value_take_boxed (value, symbolic);
+  return TRUE;
+}
+
+static char *
+rgba_value_to_string (const GValue *value)
+{
+  const GdkRGBA *rgba = g_value_get_boxed (value);
+
+  if (rgba == NULL)
+    return g_strdup ("none");
+
+  return gdk_rgba_to_string (rgba);
+}
+
+static gboolean 
+color_value_from_string (const char  *str,
+                         GFile       *base,
+                         GValue      *value,
+                         GError     **error)
+{
+  GtkSymbolicColor *symbolic;
+  GdkColor color;
+
+  if (gdk_color_parse (str, &color))
+    {
+      g_value_set_boxed (value, &color);
+      return TRUE;
+    }
+
+  symbolic = _gtk_css_parse_symbolic_color (str, error);
+  if (symbolic == NULL)
+    return FALSE;
+
+  g_value_unset (value);
+  g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
+  g_value_take_boxed (value, symbolic);
+  return TRUE;
+}
+
+static char *
+color_value_to_string (const GValue *value)
+{
+  const GdkColor *color = g_value_get_boxed (value);
+
+  if (color == NULL)
+    return g_strdup ("none");
+
+  return gdk_color_to_string (color);
+}
+
+static gboolean 
+symbolic_color_value_from_string (const char  *str,
+                                  GFile       *base,
+                                  GValue      *value,
+                                  GError     **error)
+{
+  GtkSymbolicColor *symbolic;
+
+  symbolic = _gtk_css_parse_symbolic_color (str, error);
+  if (symbolic == NULL)
+    return FALSE;
+
+  g_value_take_boxed (value, symbolic);
+  return TRUE;
+}
+
+static char *
+symbolic_color_value_to_string (const GValue *value)
+{
+  GtkSymbolicColor *symbolic = g_value_get_boxed (value);
+
+  if (symbolic == NULL)
+    return g_strdup ("none");
+
+  return gtk_symbolic_color_to_string (symbolic);
+}
+
+static gboolean 
+font_description_value_from_string (const char  *str,
+                                    GFile       *base,
+                                    GValue      *value,
+                                    GError     **error)
+{
+  PangoFontDescription *font_desc;
+
+  font_desc = pango_font_description_from_string (str);
+  g_value_take_boxed (value, font_desc);
+  return TRUE;
+}
+
+static char *
+font_description_value_to_string (const GValue *value)
+{
+  const PangoFontDescription *desc = g_value_get_boxed (value);
+
+  if (desc == NULL)
+    return g_strdup ("none");
+
+  return pango_font_description_to_string (desc);
+}
+
+static gboolean 
+boolean_value_from_string (const char  *str,
+                           GFile       *base,
+                           GValue      *value,
+                           GError     **error)
+{
+  if (g_ascii_strcasecmp (str, "true") == 0 ||
+      g_ascii_strcasecmp (str, "1") == 0)
+    {
+      g_value_set_boolean (value, TRUE);
+      return TRUE;
+    }
+  else if (g_ascii_strcasecmp (str, "false") == 0 ||
+           g_ascii_strcasecmp (str, "0") == 0)
+    {
+      g_value_set_boolean (value, FALSE);
+      return TRUE;
+    }
+
+  return set_default_error (error, G_VALUE_TYPE (value));
+}
+
+static char *
+boolean_value_to_string (const GValue *value)
+{
+  if (g_value_get_boolean (value))
+    return g_strdup ("true");
+  else
+    return g_strdup ("false");
+}
+
+static gboolean 
+int_value_from_string (const char  *str,
+                       GFile       *base,
+                       GValue      *value,
+                       GError     **error)
+{
+  gint64 i;
+  char *end;
+
+  i = g_ascii_strtoll (str, &end, 10);
+
+  if (*end != '\0')
+    return set_default_error (error, G_VALUE_TYPE (value));
+
+  if (i > G_MAXINT || i < G_MININT)
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Number too big");
+      return FALSE;
+    }
+
+  g_value_set_int (value, i);
+  return TRUE;
+}
+
+static char *
+int_value_to_string (const GValue *value)
+{
+  return g_strdup_printf ("%d", g_value_get_int (value));
+}
+
+static gboolean 
+uint_value_from_string (const char  *str,
+                        GFile       *base,
+                        GValue      *value,
+                        GError     **error)
+{
+  guint64 u;
+  char *end;
+
+  u = g_ascii_strtoull (str, &end, 10);
+
+  if (*end != '\0')
+    return set_default_error (error, G_VALUE_TYPE (value));
+
+  if (u > G_MAXUINT)
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Number too big");
+      return FALSE;
+    }
+
+  g_value_set_uint (value, u);
+  return TRUE;
+}
+
+static char *
+uint_value_to_string (const GValue *value)
+{
+  return g_strdup_printf ("%u", g_value_get_uint (value));
+}
+
+static gboolean 
+double_value_from_string (const char  *str,
+                          GFile       *base,
+                          GValue      *value,
+                          GError     **error)
+{
+  double d;
+  char *end;
+
+  d = g_ascii_strtod (str, &end);
+
+  if (*end != '\0')
+    return set_default_error (error, G_VALUE_TYPE (value));
+
+  if (errno == ERANGE)
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Number not representable");
+      return FALSE;
+    }
+
+  g_value_set_double (value, d);
+  return TRUE;
+}
+
+static char *
+double_value_to_string (const GValue *value)
+{
+  char buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+  g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value));
+
+  return g_strdup (buf);
+}
+
+static gboolean 
+float_value_from_string (const char  *str,
+                         GFile       *base,
+                         GValue      *value,
+                         GError     **error)
+{
+  double d;
+  char *end;
+
+  d = g_ascii_strtod (str, &end);
+
+  if (*end != '\0')
+    return set_default_error (error, G_VALUE_TYPE (value));
+
+  if (errno == ERANGE)
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Number not representable");
+      return FALSE;
+    }
+
+  g_value_set_float (value, d);
+  return TRUE;
+}
+
+static char *
+float_value_to_string (const GValue *value)
+{
+  char buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+  g_ascii_dtostr (buf, sizeof (buf), g_value_get_float (value));
+
+  return g_strdup (buf);
+}
+
+static gboolean 
+theming_engine_value_from_string (const char  *str,
+                                  GFile       *base,
+                                  GValue      *value,
+                                  GError     **error)
+{
+  GtkThemingEngine *engine;
+
+  engine = gtk_theming_engine_load (str);
+  if (engine == NULL)
+    {
+      g_set_error (error,
+                   GTK_CSS_PROVIDER_ERROR,
+                   GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                   "Themeing engine '%s' not found", str);
+      return FALSE;
+    }
+
+  g_value_set_object (value, engine);
+  return TRUE;
+}
+
+static char *
+theming_engine_value_to_string (const GValue *value)
+{
+  GtkThemingEngine *engine;
+  char *name;
+
+  engine = g_value_get_object (value);
+  if (engine == NULL)
+    return g_strdup ("none");
+
+  /* XXX: gtk_theming_engine_get_name()? */
+  g_object_get (engine, "name", &name, NULL);
+
+  return name;
+}
+
+static gboolean 
+animation_description_value_from_string (const char  *str,
+                                         GFile       *base,
+                                         GValue      *value,
+                                         GError     **error)
+{
+  GtkAnimationDescription *desc;
+
+  desc = _gtk_animation_description_from_string (str);
+
+  if (desc == NULL)
+    return set_default_error (error, G_VALUE_TYPE (value));
+  
+  g_value_take_boxed (value, desc);
+  return TRUE;
+}
+
+static char *
+animation_description_value_to_string (const GValue *value)
+{
+  GtkAnimationDescription *desc = g_value_get_boxed (value);
+
+  if (desc == NULL)
+    return g_strdup ("none");
+
+  return _gtk_animation_description_to_string (desc);
+}
+
+static gboolean
+parse_border_value (const char  *str,
+                    gint16      *value,
+                    const char **end,
+                    GError     **error)
+{
+  gint64 d;
+
+  d = g_ascii_strtoll (str, (char **) end, 10);
+
+  if (d > G_MAXINT16 || d < 0)
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Number out of range for border");
+      return FALSE;
+    }
+
+  if (str == *end)
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "No number given for border value");
+      return FALSE;
+    }
+
+  /* Skip optional unit type.
+   * We only handle pixels at the moment.
+   */
+  if (strncmp (*end, "px", 2) == 0)
+    *end += 2;
+
+  if (**end != '\0' &&
+      !g_ascii_isspace (**end))
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Junk at end of border value");
+      return FALSE;
+    }
+
+  SKIP_SPACES (*end);
+
+  *value = d;
+  return TRUE;
+}
+
+static gboolean 
+border_value_from_string (const char  *str,
+                          GFile       *base,
+                          GValue      *value,
+                          GError     **error)
+{
+  GtkBorder *border;
+
+  border = gtk_border_new ();
+
+  if (!parse_border_value (str, &border->top, &str, error))
+    return FALSE;
+
+  if (*str == '\0')
+    border->right = border->top;
+  else
+    if (!parse_border_value (str, &border->right, &str, error))
+      return FALSE;
+
+  if (*str == '\0')
+    border->bottom = border->top;
+  else
+    if (!parse_border_value (str, &border->bottom, &str, error))
+      return FALSE;
+
+  if (*str == '\0')
+    border->left = border->right;
+  else
+    if (!parse_border_value (str, &border->left, &str, error))
+      return FALSE;
+
+  if (*str != '\0')
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Junk at end of border value");
+      return FALSE;
+    }
+
+  g_value_take_boxed (value, border);
+  return TRUE;
+}
+
+static char *
+border_value_to_string (const GValue *value)
+{
+  const GtkBorder *border = g_value_get_boxed (value);
+
+  if (border == NULL)
+    return g_strdup ("none");
+  else if (border->left != border->right)
+    return g_strdup_printf ("%d %d %d %d", border->top, border->right, border->bottom, border->left);
+  else if (border->top != border->bottom)
+    return g_strdup_printf ("%d %d %d", border->top, border->right, border->bottom);
+  else if (border->top != border->left)
+    return g_strdup_printf ("%d %d", border->top, border->right);
+  else
+    return g_strdup_printf ("%d", border->top);
+}
+
+static gboolean 
+gradient_value_from_string (const char  *str,
+                            GFile       *base,
+                            GValue      *value,
+                            GError     **error)
+{
+  GtkGradient *gradient;
+  cairo_pattern_type_t type;
+  gdouble coords[6];
+  gchar *end;
+  guint i;
+
+  str += strlen ("-gtk-gradient");
+  SKIP_SPACES (str);
+
+  if (*str != '(')
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Expected '(' after '-gtk-gradient'");
+      return FALSE;
+    }
+
+  str++;
+  SKIP_SPACES (str);
+
+  /* Parse gradient type */
+  if (g_str_has_prefix (str, "linear"))
+    {
+      type = CAIRO_PATTERN_TYPE_LINEAR;
+      str += strlen ("linear");
+    }
+  else if (g_str_has_prefix (str, "radial"))
+    {
+      type = CAIRO_PATTERN_TYPE_RADIAL;
+      str += strlen ("radial");
+    }
+  else
+    {
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Gradient type must be 'radial' or 'linear'");
+      return FALSE;
+    }
+
+  SKIP_SPACES (str);
+
+  /* Parse start/stop position parameters */
+  for (i = 0; i < 2; i++)
+    {
+      if (*str != ',')
+        {
+          g_set_error_literal (error,
+                               GTK_CSS_PROVIDER_ERROR,
+                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                               "Expected ','");
+          return FALSE;
+        }
+
+      str++;
+      SKIP_SPACES (str);
+
+      if (strncmp (str, "left", 4) == 0)
+        {
+          coords[i * 3] = 0;
+          str += strlen ("left");
+        }
+      else if (strncmp (str, "right", 5) == 0)
+        {
+          coords[i * 3] = 1;
+          str += strlen ("right");
+        }
+      else if (strncmp (str, "center", 6) == 0)
+        {
+          coords[i * 3] = 0.5;
+          str += strlen ("center");
+        }
+      else
+        {
+          coords[i * 3] = g_ascii_strtod (str, &end);
+
+          if (str == end)
+            return set_default_error (error, G_VALUE_TYPE (value));
+
+          str = end;
+        }
+
+      SKIP_SPACES (str);
+
+      if (strncmp (str, "top", 3) == 0)
+        {
+          coords[(i * 3) + 1] = 0;
+          str += strlen ("top");
+        }
+      else if (strncmp (str, "bottom", 6) == 0)
+        {
+          coords[(i * 3) + 1] = 1;
+          str += strlen ("bottom");
+        }
+      else if (strncmp (str, "center", 6) == 0)
+        {
+          coords[(i * 3) + 1] = 0.5;
+          str += strlen ("center");
+        }
+      else
+        {
+          coords[(i * 3) + 1] = g_ascii_strtod (str, &end);
+
+          if (str == end)
+            return set_default_error (error, G_VALUE_TYPE (value));
+
+          str = end;
+        }
+
+      SKIP_SPACES (str);
+
+      if (type == CAIRO_PATTERN_TYPE_RADIAL)
+        {
+          /* Parse radius */
+          if (*str != ',')
+            {
+              g_set_error_literal (error,
+                                   GTK_CSS_PROVIDER_ERROR,
+                                   GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                                   "Expected ','");
+              return FALSE;
+            }
+
+          str++;
+          SKIP_SPACES (str);
+
+          coords[(i * 3) + 2] = g_ascii_strtod (str, &end);
+          str = end;
+
+          SKIP_SPACES (str);
+        }
+    }
+
+  if (type == CAIRO_PATTERN_TYPE_LINEAR)
+    gradient = gtk_gradient_new_linear (coords[0], coords[1], coords[3], coords[4]);
+  else
+    gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2],
+                                        coords[3], coords[4], coords[5]);
+
+  while (*str == ',')
+    {
+      GtkSymbolicColor *color;
+      gdouble position;
+
+      str++;
+      SKIP_SPACES (str);
+
+      if (g_str_has_prefix (str, "from"))
+        {
+          position = 0;
+          str += strlen ("from");
+          SKIP_SPACES (str);
+
+          if (*str != '(')
+            {
+              g_object_unref (gradient);
+              return set_default_error (error, G_VALUE_TYPE (value));
+            }
+        }
+      else if (g_str_has_prefix (str, "to"))
+        {
+          position = 1;
+          str += strlen ("to");
+          SKIP_SPACES (str);
+
+          if (*str != '(')
+            {
+              g_object_unref (gradient);
+              return set_default_error (error, G_VALUE_TYPE (value));
+            }
+        }
+      else if (g_str_has_prefix (str, "color-stop"))
+        {
+          str += strlen ("color-stop");
+          SKIP_SPACES (str);
+
+          if (*str != '(')
+            {
+              g_object_unref (gradient);
+              return set_default_error (error, G_VALUE_TYPE (value));
+            }
+
+          str++;
+          SKIP_SPACES (str);
+
+          position = g_ascii_strtod (str, &end);
+
+          str = end;
+          SKIP_SPACES (str);
+
+          if (*str != ',')
+            {
+              g_object_unref (gradient);
+              return set_default_error (error, G_VALUE_TYPE (value));
+            }
+        }
+      else
+        {
+          g_object_unref (gradient);
+          return set_default_error (error, G_VALUE_TYPE (value));
+        }
+
+      str++;
+      SKIP_SPACES (str);
+
+      color = symbolic_color_parse_str (str, &end);
+
+      str = end;
+      SKIP_SPACES (str);
+
+      if (*str != ')')
+        {
+          if (color)
+            gtk_symbolic_color_unref (color);
+          g_object_unref (gradient);
+          return set_default_error (error, G_VALUE_TYPE (value));
+        }
+
+      str++;
+      SKIP_SPACES (str);
+
+      if (color)
+        {
+          gtk_gradient_add_color_stop (gradient, position, color);
+          gtk_symbolic_color_unref (color);
+        }
+    }
+
+  if (*str != ')')
+    {
+      g_object_unref (gradient);
+      return set_default_error (error, G_VALUE_TYPE (value));
+    }
+
+  g_value_take_boxed (value, gradient);
+  return TRUE;
+}
+
+static char *
+gradient_value_to_string (const GValue *value)
+{
+  GtkGradient *gradient = g_value_get_boxed (value);
+
+  if (gradient == NULL)
+    return g_strdup ("none");
+
+  return gtk_gradient_to_string (gradient);
+}
+
+static gboolean 
+pattern_value_from_string (const char  *str,
+                           GFile       *base,
+                           GValue      *value,
+                           GError     **error)
+{
+  if (g_str_has_prefix (str, "-gtk-gradient"))
+    {
+      g_value_unset (value);
+      g_value_init (value, GTK_TYPE_GRADIENT);
+      return gradient_value_from_string (str, base, value, error);
+    }
+  else
+    {
+      gchar *path;
+      GdkPixbuf *pixbuf;
+      GFile *file;
+
+      file = _gtk_css_parse_url (base, str, NULL, error);
+      if (file == NULL)
+        return FALSE;
+
+      path = g_file_get_path (file);
+      g_object_unref (file);
+
+      pixbuf = gdk_pixbuf_new_from_file (path, error);
+      g_free (path);
+      if (pixbuf == NULL)
+        return FALSE;
+
+      cairo_surface_t *surface;
+      cairo_pattern_t *pattern;
+      cairo_t *cr;
+      cairo_matrix_t matrix;
+
+      surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+                                            gdk_pixbuf_get_width (pixbuf),
+                                            gdk_pixbuf_get_height (pixbuf));
+      cr = cairo_create (surface);
+      gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+      cairo_paint (cr);
+      pattern = cairo_pattern_create_for_surface (surface);
+
+      cairo_matrix_init_scale (&matrix,
+                               gdk_pixbuf_get_width (pixbuf),
+                               gdk_pixbuf_get_height (pixbuf));
+      cairo_pattern_set_matrix (pattern, &matrix);
+
+      cairo_surface_destroy (surface);
+      cairo_destroy (cr);
+      g_object_unref (pixbuf);
+
+      g_value_take_boxed (value, pattern);
+    }
+  
+  return TRUE;
+}
+
+static gboolean 
+slice_value_from_string (const char  *str,
+                         GFile       *base,
+                         GValue      *value,
+                         GError     **error)
+{
+  gdouble distance_top, distance_bottom;
+  gdouble distance_left, distance_right;
+  GtkSliceSideModifier mods[2];
+  GdkPixbuf *pixbuf;
+  Gtk9Slice *slice;
+  GFile *file;
+  gint i = 0;
+  char *path;
+
+  SKIP_SPACES (str);
+
+  /* Parse image url */
+  file = _gtk_css_parse_url (base, str, (char **) &str, error);
+  if (!file)
+      return FALSE;
+
+  SKIP_SPACES (str);
+
+  /* Parse top/left/bottom/right distances */
+  distance_top = g_ascii_strtod (str, (char **) &str);
+
+  SKIP_SPACES (str);
+
+  distance_right = g_ascii_strtod (str, (char **) &str);
+
+  SKIP_SPACES (str);
+
+  distance_bottom = g_ascii_strtod (str, (char **) &str);
+
+  SKIP_SPACES (str);
+
+  distance_left = g_ascii_strtod (str, (char **) &str);
+
+  SKIP_SPACES (str);
+
+  while (*str && i < 2)
+    {
+      if (g_str_has_prefix (str, "stretch"))
+        {
+          str += strlen ("stretch");
+          mods[i] = GTK_SLICE_STRETCH;
+        }
+      else if (g_str_has_prefix (str, "repeat"))
+        {
+          str += strlen ("repeat");
+          mods[i] = GTK_SLICE_REPEAT;
+        }
+      else
+        {
+          g_object_unref (file);
+          return set_default_error (error, G_VALUE_TYPE (value));
+        }
+
+      SKIP_SPACES (str);
+      i++;
+    }
+
+  if (*str != '\0')
+    {
+      g_object_unref (file);
+      return set_default_error (error, G_VALUE_TYPE (value));
+    }
+
+  if (i != 2)
+    {
+      /* Fill in second modifier, same as the first */
+      mods[1] = mods[0];
+    }
+
+  path = g_file_get_path (file);
+  pixbuf = gdk_pixbuf_new_from_file (path, error);
+  g_free (path);
+  if (!pixbuf)
+    return FALSE;
+
+  slice = _gtk_9slice_new (pixbuf,
+                           distance_top, distance_bottom,
+                           distance_left, distance_right,
+                           mods[0], mods[1]);
+  g_object_unref (pixbuf);
+
+  g_value_take_boxed (value, slice);
+  return TRUE;
+}
+
+static gboolean 
+enum_value_from_string (const char  *str,
+                        GFile       *base,
+                        GValue      *value,
+                        GError     **error)
+{
+  GEnumClass *enum_class;
+  GEnumValue *enum_value;
+
+  enum_class = g_type_class_ref (G_VALUE_TYPE (value));
+  enum_value = g_enum_get_value_by_nick (enum_class, str);
+
+  if (!enum_value)
+    {
+      g_set_error (error,
+                   GTK_CSS_PROVIDER_ERROR,
+                   GTK_CSS_PROVIDER_ERROR_FAILED,
+                   "Unknown value '%s' for enum type '%s'",
+                   str, g_type_name (G_VALUE_TYPE (value)));
+      g_type_class_unref (enum_class);
+      return FALSE;
+    }
+  
+  g_value_set_enum (value, enum_value->value);
+  g_type_class_unref (enum_class);
+  return TRUE;
+}
+
+static char *
+enum_value_to_string (const GValue *value)
+{
+  GEnumClass *enum_class;
+  GEnumValue *enum_value;
+  char *s;
+
+  enum_class = g_type_class_ref (G_VALUE_TYPE (value));
+  enum_value = g_enum_get_value (enum_class, g_value_get_enum (value));
+
+  s = g_strdup (enum_value->value_nick);
+
+  g_type_class_unref (enum_class);
+
+  return s;
+}
+
+static gboolean 
+flags_value_from_string (const char  *str,
+                         GFile       *base,
+                         GValue      *value,
+                         GError     **error)
+{
+  GFlagsClass *flags_class;
+  GFlagsValue *flag_value;
+  guint flags = 0;
+  char **strv;
+  guint i;
+
+  strv = g_strsplit (str, ",", -1);
+
+  flags_class = g_type_class_ref (G_VALUE_TYPE (value));
+
+  for (i = 0; strv[i]; i++)
+    {
+      strv[i] = g_strstrip (strv[i]);
+
+      flag_value = g_flags_get_value_by_nick (flags_class, strv[i]);
+      if (!flag_value)
+        {
+          g_set_error (error,
+                       GTK_CSS_PROVIDER_ERROR,
+                       GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
+                       "Unknown flag value '%s' for type '%s'",
+                       strv[i], g_type_name (G_VALUE_TYPE (value)));
+          g_type_class_unref (flags_class);
+          return FALSE;
+        }
+      
+      flags |= flag_value->value;
+    }
+
+  g_strfreev (strv);
+  g_type_class_unref (flags_class);
+
+  g_value_set_enum (value, flags);
+
+  return TRUE;
+}
+
+static char *
+flags_value_to_string (const GValue *value)
+{
+  GFlagsClass *flags_class;
+  GString *string;
+  guint i, flags;
+
+  flags_class = g_type_class_ref (G_VALUE_TYPE (value));
+  flags = g_value_get_flags (value);
+  string = g_string_new (NULL);
+
+  for (i = 0; i < flags_class->n_values; i++)
+    {
+      GFlagsValue *flags_value = &flags_class->values[i];
+
+      if (flags & flags_value->value)
+        {
+          if (string->len != 0)
+            g_string_append (string, ", ");
+
+          g_string_append (string, flags_value->value_nick);
+        }
+    }
+
+  g_type_class_unref (flags_class);
+
+  return g_string_free (string, FALSE);
+}
+
+/*** API ***/
+
+static void
+css_string_funcs_init (void)
+{
+  if (G_LIKELY (from_string_funcs != NULL))
+    return;
+
+  from_string_funcs = g_hash_table_new (NULL, NULL);
+  to_string_funcs = g_hash_table_new (NULL, NULL);
+
+  register_conversion_function (GDK_TYPE_RGBA,
+                                rgba_value_from_string,
+                                rgba_value_to_string);
+  register_conversion_function (GDK_TYPE_COLOR,
+                                color_value_from_string,
+                                color_value_to_string);
+  register_conversion_function (GTK_TYPE_SYMBOLIC_COLOR,
+                                symbolic_color_value_from_string,
+                                symbolic_color_value_to_string);
+  register_conversion_function (PANGO_TYPE_FONT_DESCRIPTION,
+                                font_description_value_from_string,
+                                font_description_value_to_string);
+  register_conversion_function (G_TYPE_BOOLEAN,
+                                boolean_value_from_string,
+                                boolean_value_to_string);
+  register_conversion_function (G_TYPE_INT,
+                                int_value_from_string,
+                                int_value_to_string);
+  register_conversion_function (G_TYPE_UINT,
+                                uint_value_from_string,
+                                uint_value_to_string);
+  register_conversion_function (G_TYPE_DOUBLE,
+                                double_value_from_string,
+                                double_value_to_string);
+  register_conversion_function (G_TYPE_FLOAT,
+                                float_value_from_string,
+                                float_value_to_string);
+  register_conversion_function (GTK_TYPE_THEMING_ENGINE,
+                                theming_engine_value_from_string,
+                                theming_engine_value_to_string);
+  register_conversion_function (GTK_TYPE_ANIMATION_DESCRIPTION,
+                                animation_description_value_from_string,
+                                animation_description_value_to_string);
+  register_conversion_function (GTK_TYPE_BORDER,
+                                border_value_from_string,
+                                border_value_to_string);
+  register_conversion_function (GTK_TYPE_GRADIENT,
+                                gradient_value_from_string,
+                                gradient_value_to_string);
+  register_conversion_function (CAIRO_GOBJECT_TYPE_PATTERN,
+                                pattern_value_from_string,
+                                NULL);
+  register_conversion_function (GTK_TYPE_9SLICE,
+                                slice_value_from_string,
+                                NULL);
+  register_conversion_function (G_TYPE_ENUM,
+                                enum_value_from_string,
+                                enum_value_to_string);
+  register_conversion_function (G_TYPE_FLAGS,
+                                flags_value_from_string,
+                                flags_value_to_string);
+}
+
+gboolean
+_gtk_css_value_from_string (GValue        *value,
+                            GFile         *base,
+                            const char    *string,
+                            GError       **error)
+{
+  FromStringFunc func;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+  g_return_val_if_fail (string[0] != 0, FALSE);
+
+  css_string_funcs_init ();
+
+  func = g_hash_table_lookup (from_string_funcs,
+                              GSIZE_TO_POINTER (G_VALUE_TYPE (value)));
+  if (func == NULL)
+    func = g_hash_table_lookup (from_string_funcs,
+                                GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value))));
+
+  if (func == NULL)
+    {
+      g_set_error (error,
+                   GTK_CSS_PROVIDER_ERROR,
+                   GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                   "Cannot convert to type '%s'",
+                   g_type_name (G_VALUE_TYPE (value)));
+      return FALSE;
+    }
+
+  return (*func) (string, base, value, error);
+}
+
+char *
+_gtk_css_value_to_string (const GValue *value)
+{
+  ToStringFunc func;
+
+  css_string_funcs_init ();
+
+  func = g_hash_table_lookup (to_string_funcs,
+                              GSIZE_TO_POINTER (G_VALUE_TYPE (value)));
+  if (func == NULL)
+    func = g_hash_table_lookup (to_string_funcs,
+                                GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value))));
+
+  if (func)
+    return func (value);
+
+  return g_strdup_value_contents (value);
+}
+
+GtkSymbolicColor *
+_gtk_css_parse_symbolic_color (const char    *str,
+                               GError       **error)
+{
+  GtkSymbolicColor *color;
+  gchar *end;
+
+  color = symbolic_color_parse_str (str, &end);
+
+  if (*end != '\0')
+    {
+      if (color)
+        {
+          gtk_symbolic_color_unref (color);
+          color = NULL;
+        }
+
+      g_set_error_literal (error,
+                           GTK_CSS_PROVIDER_ERROR,
+                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           "Failed to parse symbolic color");
+    }
+
+  return color;
+}
+
+GFile *
+_gtk_css_parse_url (GFile       *base,
+                    const char  *str,
+                    char       **end,
+                    GError     **error)
+{
+  gchar *path, *chr;
+  GFile *file;
+
+  if (g_str_has_prefix (str, "url"))
+    {
+      str += strlen ("url");
+      SKIP_SPACES (str);
+
+      if (*str != '(')
+        {
+          g_set_error_literal (error,
+                               GTK_CSS_PROVIDER_ERROR,
+                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                               "Expected '(' after 'url'");
+          return NULL;
+        }
+
+      chr = strchr (str, ')');
+      if (!chr)
+        {
+          g_set_error_literal (error,
+                               GTK_CSS_PROVIDER_ERROR,
+                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                               "No closing ')' found for 'url'");
+          return NULL;
+        }
+
+      if (end)
+        *end = chr + 1;
+
+      str++;
+      SKIP_SPACES (str);
+
+      if (*str == '"' || *str == '\'')
+        {
+          const gchar *p;
+          p = str;
+
+          str++;
+          chr--;
+          SKIP_SPACES_BACK (chr);
+
+          if (*chr != *p || chr == p)
+            {
+              g_set_error_literal (error,
+                                   GTK_CSS_PROVIDER_ERROR,
+                                   GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                                   "Did not find closing quote for url");
+              return NULL;
+            }
+        }
+      else
+        {
+          g_set_error_literal (error,
+                               GTK_CSS_PROVIDER_ERROR,
+                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                               "url not properly escaped");
+          return NULL;
+        }
+
+      path = g_strndup (str, chr - str);
+      g_strstrip (path);
+    }
+  else
+    {
+      path = g_strdup (str);
+      if (end)
+        *end = (gchar *) str + strlen (str);
+    }
+
+  file = g_file_resolve_relative_path (base, path);
+  g_free (path);
+
+  return file;
+}
diff --git a/gtk/gtkcssstringfuncsprivate.h b/gtk/gtkcssstringfuncsprivate.h
new file mode 100644
index 0000000..900d38c
--- /dev/null
+++ b/gtk/gtkcssstringfuncsprivate.h
@@ -0,0 +1,43 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2010 Carlos Garnacho <carlosg 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_CSS_STRINGFUNCS_PRIVATE_H__
+#define __GTK_CSS_STRINGFUNCS_PRIVATE_H__
+
+#include <gtk/gtksymboliccolor.h>
+
+G_BEGIN_DECLS
+
+gboolean                _gtk_css_value_from_string        (GValue        *value,
+                                                           GFile         *base,
+                                                           const char    *string,
+                                                           GError       **error);
+char *                  _gtk_css_value_to_string          (const GValue  *value);
+
+GtkSymbolicColor *      _gtk_css_parse_symbolic_color     (const char    *str,
+                                                           GError       **error);
+
+GFile *                 _gtk_css_parse_url                (GFile         *base,
+                                                           const char    *str,
+                                                           char         **end,
+                                                           GError       **error);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_STRINGFUNCS_PRIVATE_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]