summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2021-11-13 11:01:27 -0500
committerMatthias Clasen <mclasen@redhat.com>2021-11-13 23:34:21 -0500
commitcf06531bc15786a4f2a2292b532733ea41c4fdee (patch)
tree2758225c394c149b97ffdf38193721d49980475f
parentdb7ba0648c2f2eeebfa18622962698618086c26a (diff)
downloadpango-cf06531bc15786a4f2a2292b532733ea41c4fdee.tar.gz
Wedge the gtk css parser into pango
This is a brute-force job, just to see how far I get.
-rw-r--r--pango/css/gtkcss.h40
-rw-r--r--pango/css/gtkcssdataurl.c174
-rw-r--r--pango/css/gtkcssdataurlprivate.h35
-rw-r--r--pango/css/gtkcssenums.h72
-rw-r--r--pango/css/gtkcssenumtypes.c.template38
-rw-r--r--pango/css/gtkcssenumtypes.h.template24
-rw-r--r--pango/css/gtkcsserror.c35
-rw-r--r--pango/css/gtkcsserror.h48
-rw-r--r--pango/css/gtkcsslocation.c72
-rw-r--r--pango/css/gtkcsslocation.h43
-rw-r--r--pango/css/gtkcsslocationprivate.h39
-rw-r--r--pango/css/gtkcssparser.c1108
-rw-r--r--pango/css/gtkcssparserprivate.h155
-rw-r--r--pango/css/gtkcsssection.c256
-rw-r--r--pango/css/gtkcsssection.h59
-rw-r--r--pango/css/gtkcssserializer.c72
-rw-r--r--pango/css/gtkcssserializerprivate.h34
-rw-r--r--pango/css/gtkcsstokenizer.c1487
-rw-r--r--pango/css/gtkcsstokenizerprivate.h141
-rw-r--r--pango/css/meson.build54
-rw-r--r--pango/meson.build34
21 files changed, 4019 insertions, 1 deletions
diff --git a/pango/css/gtkcss.h b/pango/css/gtkcss.h
new file mode 100644
index 00000000..71ebc2be
--- /dev/null
+++ b/pango/css/gtkcss.h
@@ -0,0 +1,40 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_CSS_H__
+#define __GTK_CSS_H__
+
+#define __GTK_CSS_H_INSIDE__
+
+#include <glib.h>
+
+#include <pango/css/gtkcssenums.h>
+#include <pango/css/gtkcssenumtypes.h>
+#include <pango/css/gtkcsserror.h>
+#include <pango/css/gtkcsslocation.h>
+#include <pango/css/gtkcsssection.h>
+
+#undef __GTK_CSS_H_INSIDE__
+
+#endif /* __GTK_CSS_H__ */
diff --git a/pango/css/gtkcssdataurl.c b/pango/css/gtkcssdataurl.c
new file mode 100644
index 00000000..53b1ed93
--- /dev/null
+++ b/pango/css/gtkcssdataurl.c
@@ -0,0 +1,174 @@
+/* GStreamer data:// uri source element
+ * Copyright (C) 2009 Igalia S.L
+ * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*<private>
+ * # Data URLs
+ *
+ * These function allow encoding and decoding of data: URLs, see
+ * [RFC 2397](http://tools.ietf.org/html/rfc2397) for more information.
+ */
+
+#include "config.h"
+
+#include "gtkcssdataurlprivate.h"
+
+#include <string.h>
+
+#define _(x) x
+
+/*<private>
+ * gtk_css_data_url_parse:
+ * @url: the URL to parse
+ * @out_mimetype: (out nullable optional): Return location to set the contained
+ * mime type to. If no mime type was specified, this value is set to %NULL.
+ * @error: error location
+ *
+ * Decodes a data URL according to RFC2397 and returns the decoded data.
+ *
+ * Returns: a new `GBytes` with the decoded data
+ */
+GBytes *
+gtk_css_data_url_parse (const char *url,
+ char **out_mimetype,
+ GError **error)
+{
+ char *mimetype = NULL;
+ const char *parameters_start;
+ const char *data_start;
+ GBytes *bytes;
+ gboolean base64 = FALSE;
+ char *charset = NULL;
+ gpointer bdata;
+ gsize bsize;
+
+ /* url must be an URI as defined in RFC 2397
+ * data:[<mediatype>][;base64],<data>
+ */
+ if (g_ascii_strncasecmp ("data:", url, 5) != 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_FILENAME,
+ _("Not a data: URL"));
+ return NULL;
+ }
+
+ url += 5;
+
+ parameters_start = strchr (url, ';');
+ data_start = strchr (url, ',');
+ if (data_start == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_FILENAME,
+ _("Malformed data: URL"));
+ return NULL;
+ }
+ if (parameters_start > data_start)
+ parameters_start = NULL;
+
+ if (data_start != url && parameters_start != url)
+ {
+ mimetype = g_strndup (url,
+ (parameters_start ? parameters_start
+ : data_start) - url);
+ }
+ else
+ {
+ mimetype = NULL;
+ }
+
+ if (parameters_start != NULL)
+ {
+ char *parameters_str;
+ char **parameters;
+ guint i;
+
+ parameters_str = g_strndup (parameters_start + 1, data_start - parameters_start - 1);
+ parameters = g_strsplit (parameters_str, ";", -1);
+
+ for (i = 0; parameters[i] != NULL; i++)
+ {
+ if (g_ascii_strcasecmp ("base64", parameters[i]) == 0)
+ {
+ base64 = TRUE;
+ }
+ else if (g_ascii_strncasecmp ("charset=", parameters[i], 8) == 0)
+ {
+ g_free (charset);
+ charset = g_strdup (parameters[i] + 8);
+ }
+ }
+ g_free (parameters_str);
+ g_strfreev (parameters);
+ }
+
+ /* Skip comma */
+ data_start += 1;
+ if (base64)
+ {
+ bdata = g_base64_decode (data_start, &bsize);
+ }
+ else
+ {
+ /* URI encoded, i.e. "percent" encoding */
+ /* XXX: This doesn't allow nul bytes */
+ bdata = g_uri_unescape_string (data_start, NULL);
+ if (bdata == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_FILENAME,
+ _("Could not unescape string"));
+ g_free (mimetype);
+ return NULL;
+ }
+ bsize = strlen (bdata);
+ }
+
+ /* Convert to UTF8 */
+ if ((mimetype == NULL || g_ascii_strcasecmp ("text/plain", mimetype) == 0) &&
+ charset && g_ascii_strcasecmp ("US-ASCII", charset) != 0
+ && g_ascii_strcasecmp ("UTF-8", charset) != 0)
+ {
+ gsize read;
+ gsize written;
+ gpointer data;
+
+ data = g_convert_with_fallback (bdata, bsize,
+ "UTF-8", charset,
+ (char *) "*",
+ &read, &written, NULL);
+ g_free (bdata);
+
+ bdata = data;
+ bsize = written;
+ }
+ bytes = g_bytes_new_take (bdata, bsize);
+
+ g_free (charset);
+ if (out_mimetype)
+ *out_mimetype = mimetype;
+ else
+ g_free (mimetype);
+
+ return bytes;
+}
diff --git a/pango/css/gtkcssdataurlprivate.h b/pango/css/gtkcssdataurlprivate.h
new file mode 100644
index 00000000..5d0d6a85
--- /dev/null
+++ b/pango/css/gtkcssdataurlprivate.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+
+#ifndef __GTK_CSS_DATA_URL_PRIVATE_H__
+#define __GTK_CSS_DATA_URL_PRIVATE_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+GBytes * gtk_css_data_url_parse (const char *url,
+ char **out_mimetype,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_DATA_URL_PRIVATE_H__ */
+
diff --git a/pango/css/gtkcssenums.h b/pango/css/gtkcssenums.h
new file mode 100644
index 00000000..b7f965f8
--- /dev/null
+++ b/pango/css/gtkcssenums.h
@@ -0,0 +1,72 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_CSS_ENUMS_H__
+#define __GTK_CSS_ENUMS_H__
+
+#include <glib.h>
+
+/**
+ * GtkCssParserError:
+ * @GTK_CSS_PARSER_ERROR_FAILED: Unknown failure.
+ * @GTK_CSS_PARSER_ERROR_SYNTAX: The given text does not form valid syntax
+ * @GTK_CSS_PARSER_ERROR_IMPORT: Failed to import a resource
+ * @GTK_CSS_PARSER_ERROR_NAME: The given name has not been defined
+ * @GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE: The given value is not correct
+ *
+ * Errors that can occur while parsing CSS.
+ *
+ * These errors are unexpected and will cause parts of the given CSS
+ * to be ignored.
+ */
+typedef enum
+{
+ GTK_CSS_PARSER_ERROR_FAILED,
+ GTK_CSS_PARSER_ERROR_SYNTAX,
+ GTK_CSS_PARSER_ERROR_IMPORT,
+ GTK_CSS_PARSER_ERROR_NAME,
+ GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE
+} GtkCssParserError;
+
+/**
+ * GtkCssParserWarning:
+ * @GTK_CSS_PARSER_WARNING_DEPRECATED: The given construct is
+ * deprecated and will be removed in a future version
+ * @GTK_CSS_PARSER_WARNING_SYNTAX: A syntax construct was used
+ * that should be avoided
+ * @GTK_CSS_PARSER_WARNING_UNIMPLEMENTED: A feature is not implemented
+ *
+ * Warnings that can occur while parsing CSS.
+ *
+ * Unlike `GtkCssParserError`s, warnings do not cause the parser to
+ * skip any input, but they indicate issues that should be fixed.
+ */
+typedef enum
+{
+ GTK_CSS_PARSER_WARNING_DEPRECATED,
+ GTK_CSS_PARSER_WARNING_SYNTAX,
+ GTK_CSS_PARSER_WARNING_UNIMPLEMENTED
+} GtkCssParserWarning;
+
+#endif /* __GTK_CSS_ENUMS_H__ */
diff --git a/pango/css/gtkcssenumtypes.c.template b/pango/css/gtkcssenumtypes.c.template
new file mode 100644
index 00000000..cb4c9ac2
--- /dev/null
+++ b/pango/css/gtkcssenumtypes.c.template
@@ -0,0 +1,38 @@
+/*** BEGIN file-header ***/
+#include "config.h"
+#include "gtkcssenumtypes.h"
+#include <gtkcss.h>
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@basename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static gsize g_define_type_id__volatile = 0;
+
+ if (g_once_init_enter (&g_define_type_id__volatile))
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ GType g_define_type_id =
+ g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+ g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+ }
+
+ return g_define_type_id__volatile;
+}
+
+/*** END value-tail ***/
diff --git a/pango/css/gtkcssenumtypes.h.template b/pango/css/gtkcssenumtypes.h.template
new file mode 100644
index 00000000..6ae067f1
--- /dev/null
+++ b/pango/css/gtkcssenumtypes.h.template
@@ -0,0 +1,24 @@
+/*** BEGIN file-header ***/
+#ifndef __GTK_CSS_ENUM_TYPES_H__
+#define __GTK_CSS_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@basename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __GTK_CSS_ENUM_TYPES_H__ */
+/*** END file-tail ***/
diff --git a/pango/css/gtkcsserror.c b/pango/css/gtkcsserror.c
new file mode 100644
index 00000000..82428cdf
--- /dev/null
+++ b/pango/css/gtkcsserror.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "gtkcsserror.h"
+
+GQuark
+gtk_css_parser_error_quark (void)
+{
+ return g_quark_from_static_string ("gtk-css-parser-error-quark");
+}
+
+GQuark
+gtk_css_parser_warning_quark (void)
+{
+ return g_quark_from_static_string ("gtk-css-parser-warning-quark");
+}
+
diff --git a/pango/css/gtkcsserror.h b/pango/css/gtkcsserror.h
new file mode 100644
index 00000000..0897d2a7
--- /dev/null
+++ b/pango/css/gtkcsserror.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+
+#ifndef __GTK_CSS_ERROR_H__
+#define __GTK_CSS_ERROR_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GTK_CSS_PARSER_ERROR:
+ *
+ * Domain for `GtkCssParser` errors.
+ */
+#define GTK_CSS_PARSER_ERROR (gtk_css_parser_error_quark ())
+
+GQuark gtk_css_parser_error_quark (void);
+
+/**
+ * GTK_CSS_PARSER_WARNING:
+ *
+ * Domain for `GtkCssParser` warnings.
+ */
+#define GTK_CSS_PARSER_WARNING (gtk_css_parser_warning_quark ())
+
+GQuark gtk_css_parser_warning_quark (void);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_ERROR_H__ */
diff --git a/pango/css/gtkcsslocation.c b/pango/css/gtkcsslocation.c
new file mode 100644
index 00000000..41725ad2
--- /dev/null
+++ b/pango/css/gtkcsslocation.c
@@ -0,0 +1,72 @@
+/* GSK - The GIMP Toolkit
+ * Copyright (C) 2019 Benjamin Otte <otte@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcsslocationprivate.h"
+
+/**
+ * GtkCssLocation:
+ * @bytes: number of bytes parsed since the beginning
+ * @chars: number of characters parsed since the beginning
+ * @lines: number of full lines that have been parsed. If you want to
+ * display this as a line number, you need to add 1 to this.
+ * @line_bytes: Number of bytes parsed since the last line break
+ * @line_chars: Number of characters parsed since the last line break
+ *
+ * Represents a location in a file or other source of data parsed
+ * by the CSS engine.
+ *
+ * The @bytes and @line_bytes offsets are meant to be used to
+ * programmatically match data. The @lines and @line_chars offsets
+ * can be used for printing the location in a file.
+ *
+ * Note that the @lines parameter starts from 0 and is increased
+ * whenever a CSS line break is encountered. (CSS defines the C character
+ * sequences "\r\n", "\r", "\n" and "\f" as newlines.)
+ * If your document uses different rules for line breaking, you might want
+ * run into problems here.
+ */
+
+void
+gtk_css_location_init (GtkCssLocation *location)
+{
+ memset (location, 0, sizeof (GtkCssLocation));
+}
+
+void
+gtk_css_location_advance (GtkCssLocation *location,
+ gsize bytes,
+ gsize chars)
+{
+ location->bytes += bytes;
+ location->chars += chars;
+ location->line_bytes += bytes;
+ location->line_chars += chars;
+}
+
+void
+gtk_css_location_advance_newline (GtkCssLocation *location,
+ gboolean is_windows)
+{
+ gtk_css_location_advance (location, is_windows ? 2 : 1, is_windows ? 2 : 1);
+
+ location->lines++;
+ location->line_bytes = 0;
+ location->line_chars = 0;
+}
+
diff --git a/pango/css/gtkcsslocation.h b/pango/css/gtkcsslocation.h
new file mode 100644
index 00000000..7fc4559f
--- /dev/null
+++ b/pango/css/gtkcsslocation.h
@@ -0,0 +1,43 @@
+/* GSK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_CSS_LOCATION_H__
+#define __GTK_CSS_LOCATION_H__
+
+#if !defined (__GTK_CSS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/css/gtkcss.h> can be included directly."
+#endif
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCssLocation GtkCssLocation;
+
+struct _GtkCssLocation
+{
+ gsize bytes;
+ gsize chars;
+ gsize lines;
+ gsize line_bytes;
+ gsize line_chars;
+};
+
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_LOCATION_H__ */
diff --git a/pango/css/gtkcsslocationprivate.h b/pango/css/gtkcsslocationprivate.h
new file mode 100644
index 00000000..46ed3c80
--- /dev/null
+++ b/pango/css/gtkcsslocationprivate.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+
+#ifndef __GTK_CSS_LOCATION_PRIVATE_H__
+#define __GTK_CSS_LOCATION_PRIVATE_H__
+
+#include "gtkcsslocation.h"
+
+G_BEGIN_DECLS
+
+void gtk_css_location_init (GtkCssLocation *location);
+
+void gtk_css_location_advance (GtkCssLocation *location,
+ gsize bytes,
+ gsize chars);
+void gtk_css_location_advance_newline (GtkCssLocation *location,
+ gboolean is_windows);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_LOCATION_PRIVATE_H__ */
+
diff --git a/pango/css/gtkcssparser.c b/pango/css/gtkcssparser.c
new file mode 100644
index 00000000..7668bfc7
--- /dev/null
+++ b/pango/css/gtkcssparser.c
@@ -0,0 +1,1108 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+
+#include "config.h"
+
+#include "gtkcssparserprivate.h"
+
+#include "gtkcssenums.h"
+#include "gtkcsserror.h"
+#include "gtkcsslocationprivate.h"
+
+typedef struct _GtkCssParserBlock GtkCssParserBlock;
+
+struct _GtkCssParser
+{
+ volatile int ref_count;
+
+ GtkCssTokenizer *tokenizer;
+ GFile *file;
+ GFile *directory;
+ GtkCssParserErrorFunc error_func;
+ gpointer user_data;
+ GDestroyNotify user_destroy;
+
+ GArray *blocks;
+ GtkCssLocation location;
+ GtkCssToken token;
+};
+
+struct _GtkCssParserBlock
+{
+ GtkCssLocation start_location;
+ GtkCssTokenType end_token;
+ GtkCssTokenType inherited_end_token;
+ GtkCssTokenType alternative_token;
+};
+
+static GtkCssParser *
+gtk_css_parser_new (GtkCssTokenizer *tokenizer,
+ GFile *file,
+ GtkCssParserErrorFunc error_func,
+ gpointer user_data,
+ GDestroyNotify user_destroy)
+{
+ GtkCssParser *self;
+
+ self = g_slice_new0 (GtkCssParser);
+
+ self->ref_count = 1;
+ self->tokenizer = gtk_css_tokenizer_ref (tokenizer);
+ if (file)
+ {
+ self->file = g_object_ref (file);
+ self->directory = g_file_get_parent (file);
+ }
+
+ self->error_func = error_func;
+ self->user_data = user_data;
+ self->user_destroy = user_destroy;
+ self->blocks = g_array_new (FALSE, FALSE, sizeof (GtkCssParserBlock));
+
+ return self;
+}
+
+GtkCssParser *
+gtk_css_parser_new_for_file (GFile *file,
+ GtkCssParserErrorFunc error_func,
+ gpointer user_data,
+ GDestroyNotify user_destroy,
+ GError **error)
+{
+ GBytes *bytes;
+ GtkCssParser *result;
+
+ bytes = g_file_load_bytes (file, NULL, NULL, error);
+ if (bytes == NULL)
+ return NULL;
+
+ result = gtk_css_parser_new_for_bytes (bytes, file, error_func, user_data, user_destroy);
+
+ g_bytes_unref (bytes);
+
+ return result;
+}
+
+GtkCssParser *
+gtk_css_parser_new_for_bytes (GBytes *bytes,
+ GFile *file,
+ GtkCssParserErrorFunc error_func,
+ gpointer user_data,
+ GDestroyNotify user_destroy)
+{
+ GtkCssTokenizer *tokenizer;
+ GtkCssParser *result;
+
+ tokenizer = gtk_css_tokenizer_new (bytes);
+ result = gtk_css_parser_new (tokenizer, file, error_func, user_data, user_destroy);
+ gtk_css_tokenizer_unref (tokenizer);
+
+ return result;
+}
+
+static void
+gtk_css_parser_finalize (GtkCssParser *self)
+{
+ if (self->user_destroy)
+ self->user_destroy (self->user_data);
+
+ g_clear_pointer (&self->tokenizer, gtk_css_tokenizer_unref);
+ g_clear_object (&self->file);
+ g_clear_object (&self->directory);
+ if (self->blocks->len)
+ g_critical ("Finalizing CSS parser with %u remaining blocks", self->blocks->len);
+ g_array_free (self->blocks, TRUE);
+
+ g_slice_free (GtkCssParser, self);
+}
+
+GtkCssParser *
+gtk_css_parser_ref (GtkCssParser *self)
+{
+ g_atomic_int_inc (&self->ref_count);
+
+ return self;
+}
+
+void
+gtk_css_parser_unref (GtkCssParser *self)
+{
+ if (g_atomic_int_dec_and_test (&self->ref_count))
+ gtk_css_parser_finalize (self);
+}
+
+/**
+ * gtk_css_parser_get_file:
+ * @self: a `GtkCssParser`
+ *
+ * Gets the file being parsed. If no file is associated with @self -
+ * for example when raw data is parsed - %NULL is returned.
+ *
+ * Returns: (nullable) (transfer none): The file being parsed
+ */
+GFile *
+gtk_css_parser_get_file (GtkCssParser *self)
+{
+ return self->file;
+}
+
+/**
+ * gtk_css_parser_resolve_url:
+ * @self: a `GtkCssParser`
+ * @url: the URL to resolve
+ *
+ * Resolves a given URL against the parser's location.
+ *
+ * Returns: (nullable) (transfer full): a new `GFile` for the
+ * resolved URL
+ */
+GFile *
+gtk_css_parser_resolve_url (GtkCssParser *self,
+ const char *url)
+{
+ char *scheme;
+
+ scheme = g_uri_parse_scheme (url);
+ if (scheme != NULL)
+ {
+ GFile *file = g_file_new_for_uri (url);
+ g_free (scheme);
+ return file;
+ }
+ g_free (scheme);
+
+ if (self->directory == NULL)
+ return NULL;
+
+ return g_file_resolve_relative_path (self->directory, url);
+}
+
+/**
+ * gtk_css_parser_get_start_location:
+ * @self: a `GtkCssParser`
+ *
+ * Queries the location of the current token.
+ *
+ * This function will return the location of the start of the
+ * current token. In the case a token has been consumed, but no
+ * new token has been queried yet via gtk_css_parser_peek_token()
+ * or gtk_css_parser_get_token(), the previous token's start
+ * location will be returned.
+ *
+ * This function may return the same location as
+ * gtk_css_parser_get_end_location() - in particular at the
+ * beginning and end of the document.
+ *
+ * Returns: the start location
+ **/
+const GtkCssLocation *
+gtk_css_parser_get_start_location (GtkCssParser *self)
+{
+ return &self->location;
+}
+
+/**
+ * gtk_css_parser_get_end_location:
+ * @self: a `GtkCssParser`
+ * @out_location: (caller-allocates) Place to store the location
+ *
+ * Queries the location of the current token.
+ *
+ * This function will return the location of the end of the
+ * current token. In the case a token has been consumed, but no
+ * new token has been queried yet via gtk_css_parser_peek_token()
+ * or gtk_css_parser_get_token(), the previous token's end location
+ * will be returned.
+ *
+ * This function may return the same location as
+ * gtk_css_parser_get_start_location() - in particular at the
+ * beginning and end of the document.
+ *
+ * Returns: the end location
+ **/
+const GtkCssLocation *
+gtk_css_parser_get_end_location (GtkCssParser *self)
+{
+ return gtk_css_tokenizer_get_location (self->tokenizer);
+}
+
+/**
+ * gtk_css_parser_get_block_location:
+ * @self: a `GtkCssParser`
+ *
+ * Queries the start location of the token that started the current
+ * block that is being parsed.
+ *
+ * If no block is currently parsed, the beginning of the document
+ * is returned.
+ *
+ * Returns: The start location of the current block
+ */
+const GtkCssLocation *
+gtk_css_parser_get_block_location (GtkCssParser *self)
+{
+ const GtkCssParserBlock *block;
+
+ if (self->blocks->len == 0)
+ {
+ static const GtkCssLocation start_of_document = { 0, };
+ return &start_of_document;
+ }
+
+ block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+ return &block->start_location;
+}
+
+static void
+gtk_css_parser_ensure_token (GtkCssParser *self)
+{
+ GError *error = NULL;
+
+ if (!gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
+ return;
+
+ self->location = *gtk_css_tokenizer_get_location (self->tokenizer);
+ if (!gtk_css_tokenizer_read_token (self->tokenizer, &self->token, &error))
+ {
+ /* We ignore the error here, because the resulting token will
+ * likely already trigger an error in the parsing code and
+ * duplicate errors are rather useless.
+ */
+ g_clear_error (&error);
+ }
+}
+
+const GtkCssToken *
+gtk_css_parser_peek_token (GtkCssParser *self)
+{
+ static const GtkCssToken eof_token = { GTK_CSS_TOKEN_EOF, };
+
+ gtk_css_parser_ensure_token (self);
+
+ if (self->blocks->len)
+ {
+ const GtkCssParserBlock *block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+ if (gtk_css_token_is (&self->token, block->end_token) ||
+ gtk_css_token_is (&self->token, block->inherited_end_token) ||
+ gtk_css_token_is (&self->token, block->alternative_token))
+ return &eof_token;
+ }
+
+ return &self->token;
+}
+
+const GtkCssToken *
+gtk_css_parser_get_token (GtkCssParser *self)
+{
+ const GtkCssToken *token;
+
+ for (token = gtk_css_parser_peek_token (self);
+ gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT) ||
+ gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE);
+ token = gtk_css_parser_peek_token (self))
+ {
+ gtk_css_parser_consume_token (self);
+ }
+
+ return token;
+}
+
+void
+gtk_css_parser_consume_token (GtkCssParser *self)
+{
+ gtk_css_parser_ensure_token (self);
+
+ /* unpreserved tokens MUST be consumed via start_block() */
+ g_assert (gtk_css_token_is_preserved (&self->token, NULL));
+
+ /* Don't consume any tokens at the end of a block */
+ if (!gtk_css_token_is (gtk_css_parser_peek_token (self), GTK_CSS_TOKEN_EOF))
+ gtk_css_token_clear (&self->token);
+}
+
+void
+gtk_css_parser_start_block (GtkCssParser *self)
+{
+ GtkCssParserBlock block;
+
+ gtk_css_parser_ensure_token (self);
+
+ if (gtk_css_token_is_preserved (&self->token, &block.end_token))
+ {
+ g_critical ("gtk_css_parser_start_block() may only be called for non-preserved tokens");
+ return;
+ }
+
+ block.inherited_end_token = GTK_CSS_TOKEN_EOF;
+ block.alternative_token = GTK_CSS_TOKEN_EOF;
+ block.start_location = self->location;
+ g_array_append_val (self->blocks, block);
+
+ gtk_css_token_clear (&self->token);
+}
+
+void
+gtk_css_parser_start_semicolon_block (GtkCssParser *self,
+ GtkCssTokenType alternative_token)
+{
+ GtkCssParserBlock block;
+
+ block.end_token = GTK_CSS_TOKEN_SEMICOLON;
+ if (self->blocks->len)
+ block.inherited_end_token = g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1).end_token;
+ else
+ block.inherited_end_token = GTK_CSS_TOKEN_EOF;
+ block.alternative_token = alternative_token;
+ block.start_location = self->location;
+ g_array_append_val (self->blocks, block);
+}
+
+void
+gtk_css_parser_end_block_prelude (GtkCssParser *self)
+{
+ GtkCssParserBlock *block;
+
+ g_return_if_fail (self->blocks->len > 0);
+
+ block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+
+ if (block->alternative_token == GTK_CSS_TOKEN_EOF)
+ return;
+
+ gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
+
+ if (gtk_css_token_is (&self->token, block->alternative_token))
+ {
+ if (gtk_css_token_is_preserved (&self->token, &block->end_token))
+ {
+ g_critical ("alternative token is not preserved");
+ return;
+ }
+ block->alternative_token = GTK_CSS_TOKEN_EOF;
+ block->inherited_end_token = GTK_CSS_TOKEN_EOF;
+ gtk_css_token_clear (&self->token);
+ }
+}
+
+void
+gtk_css_parser_end_block (GtkCssParser *self)
+{
+ GtkCssParserBlock *block;
+
+ g_return_if_fail (self->blocks->len > 0);
+
+ gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
+
+ block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+
+ if (gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
+ {
+ gtk_css_parser_warn (self,
+ GTK_CSS_PARSER_WARNING_SYNTAX,
+ gtk_css_parser_get_block_location (self),
+ gtk_css_parser_get_start_location (self),
+ "Unterminated block at end of document");
+ g_array_set_size (self->blocks, self->blocks->len - 1);
+ }
+ else if (gtk_css_token_is (&self->token, block->inherited_end_token))
+ {
+ g_assert (block->end_token == GTK_CSS_TOKEN_SEMICOLON);
+ gtk_css_parser_warn (self,
+ GTK_CSS_PARSER_WARNING_SYNTAX,
+ gtk_css_parser_get_block_location (self),
+ gtk_css_parser_get_start_location (self),
+ "Expected ';' at end of block");
+ g_array_set_size (self->blocks, self->blocks->len - 1);
+ }
+ else
+ {
+ g_array_set_size (self->blocks, self->blocks->len - 1);
+ if (gtk_css_token_is_preserved (&self->token, NULL))
+ {
+ gtk_css_token_clear (&self->token);
+ }
+ else
+ {
+ gtk_css_parser_start_block (self);
+ gtk_css_parser_end_block (self);
+ }
+ }
+}
+
+/*
+ * gtk_css_parser_skip:
+ * @self: a `GtkCssParser`
+ *
+ * Skips a component value.
+ *
+ * This means that if the token is a preserved token, only
+ * this token will be skipped. If the token starts a block,
+ * the whole block will be skipped.
+ **/
+void
+gtk_css_parser_skip (GtkCssParser *self)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+ if (gtk_css_token_is_preserved (token, NULL))
+ {
+ gtk_css_parser_consume_token (self);
+ }
+ else
+ {
+ gtk_css_parser_start_block (self);
+ gtk_css_parser_end_block (self);
+ }
+}
+
+/*
+ * gtk_css_parser_skip_until:
+ * @self: a `GtkCssParser`
+ * @token_type: type of token to skip to
+ *
+ * Repeatedly skips a token until a certain type is reached.
+ * After this called, gtk_css_parser_get_token() will either
+ * return a token of this type or the eof token.
+ *
+ * This function is useful for resyncing a parser after encountering
+ * an error.
+ *
+ * If you want to skip until the end, use %GSK_TOKEN_TYPE_EOF
+ * as the token type.
+ **/
+void
+gtk_css_parser_skip_until (GtkCssParser *self,
+ GtkCssTokenType token_type)
+{
+ const GtkCssToken *token;
+
+ for (token = gtk_css_parser_get_token (self);
+ !gtk_css_token_is (token, token_type) &&
+ !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
+ token = gtk_css_parser_get_token (self))
+ {
+ gtk_css_parser_skip (self);
+ }
+}
+
+void
+gtk_css_parser_emit_error (GtkCssParser *self,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const GError *error)
+{
+ if (self->error_func)
+ self->error_func (self, start, end, error, self->user_data);
+}
+
+void
+gtk_css_parser_error (GtkCssParser *self,
+ GtkCssParserError code,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const char *format,
+ ...)
+{
+ va_list args;
+ GError *error;
+
+ va_start (args, format);
+ error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+ code,
+ format, args);
+ gtk_css_parser_emit_error (self, start, end, error);
+ g_error_free (error);
+ va_end (args);
+}
+
+void
+gtk_css_parser_error_syntax (GtkCssParser *self,
+ const char *format,
+ ...)
+{
+ va_list args;
+ GError *error;
+
+ va_start (args, format);
+ error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+ GTK_CSS_PARSER_ERROR_SYNTAX,
+ format, args);
+ gtk_css_parser_emit_error (self,
+ gtk_css_parser_get_start_location (self),
+ gtk_css_parser_get_end_location (self),
+ error);
+ g_error_free (error);
+ va_end (args);
+}
+
+void
+gtk_css_parser_error_value (GtkCssParser *self,
+ const char *format,
+ ...)
+{
+ va_list args;
+ GError *error;
+
+ va_start (args, format);
+ error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+ GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
+ format, args);
+ gtk_css_parser_emit_error (self,
+ gtk_css_parser_get_start_location (self),
+ gtk_css_parser_get_end_location (self),
+ error);
+ g_error_free (error);
+ va_end (args);
+}
+
+void
+gtk_css_parser_error_import (GtkCssParser *self,
+ const char *format,
+ ...)
+{
+ va_list args;
+ GError *error;
+
+ va_start (args, format);
+ error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+ GTK_CSS_PARSER_ERROR_IMPORT,
+ format, args);
+ gtk_css_parser_emit_error (self,
+ gtk_css_parser_get_start_location (self),
+ gtk_css_parser_get_end_location (self),
+ error);
+ g_error_free (error);
+ va_end (args);
+}
+
+void
+gtk_css_parser_warn (GtkCssParser *self,
+ GtkCssParserWarning code,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const char *format,
+ ...)
+{
+ va_list args;
+ GError *error;
+
+ va_start (args, format);
+ error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
+ code,
+ format, args);
+ gtk_css_parser_emit_error (self, start, end, error);
+ g_error_free (error);
+ va_end (args);
+}
+
+void
+gtk_css_parser_warn_syntax (GtkCssParser *self,
+ const char *format,
+ ...)
+{
+ va_list args;
+ GError *error;
+
+ va_start (args, format);
+ error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
+ GTK_CSS_PARSER_WARNING_SYNTAX,
+ format, args);
+ gtk_css_parser_emit_error (self,
+ gtk_css_parser_get_start_location (self),
+ gtk_css_parser_get_end_location (self),
+ error);
+ g_error_free (error);
+ va_end (args);
+}
+
+gboolean
+gtk_css_parser_consume_function (GtkCssParser *self,
+ guint min_args,
+ guint max_args,
+ guint (* parse_func) (GtkCssParser *, guint, gpointer),
+ gpointer data)
+{
+ const GtkCssToken *token;
+ gboolean result = FALSE;
+ char *function_name;
+ guint arg;
+
+ token = gtk_css_parser_get_token (self);
+ g_return_val_if_fail (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION), FALSE);
+
+ function_name = g_strdup (token->string.string);
+ gtk_css_parser_start_block (self);
+
+ arg = 0;
+ while (TRUE)
+ {
+ guint parse_args = parse_func (self, arg, data);
+ if (parse_args == 0)
+ break;
+ arg += parse_args;
+ token = gtk_css_parser_get_token (self);
+ if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+ {
+ if (arg < min_args)
+ {
+ gtk_css_parser_error_syntax (self, "%s() requires at least %u arguments", function_name, min_args);
+ break;
+ }
+ else
+ {
+ result = TRUE;
+ break;
+ }
+ }
+ else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA))
+ {
+ if (arg >= max_args)
+ {
+ gtk_css_parser_error_syntax (self, "Expected ')' at end of %s()", function_name);
+ break;
+ }
+
+ gtk_css_parser_consume_token (self);
+ continue;
+ }
+ else
+ {
+ gtk_css_parser_error_syntax (self, "Unexpected data at end of %s() argument", function_name);
+ break;
+ }
+ }
+
+ gtk_css_parser_end_block (self);
+ g_free (function_name);
+
+ return result;
+}
+
+/**
+ * gtk_css_parser_has_token:
+ * @self: a `GtkCssParser`
+ * @token_type: type of the token to check
+ *
+ * Checks if the next token is of @token_type.
+ *
+ * Returns: %TRUE if the next token is of @token_type
+ **/
+gboolean
+gtk_css_parser_has_token (GtkCssParser *self,
+ GtkCssTokenType token_type)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+
+ return gtk_css_token_is (token, token_type);
+}
+
+/**
+ * gtk_css_parser_has_ident:
+ * @self: a `GtkCssParser`
+ * @ident: name of identifier
+ *
+ * Checks if the next token is an identifier with the given @name.
+ *
+ * Returns: %TRUE if the next token is an identifier with the given @name
+ **/
+gboolean
+gtk_css_parser_has_ident (GtkCssParser *self,
+ const char *ident)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+
+ return gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
+ g_ascii_strcasecmp (token->string.string, ident) == 0;
+}
+
+gboolean
+gtk_css_parser_has_integer (GtkCssParser *self)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+
+ return gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+ gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
+}
+
+/**
+ * gtk_css_parser_has_function:
+ * @self: a `GtkCssParser`
+ * @name: name of function
+ *
+ * Checks if the next token is a function with the given @name.
+ *
+ * Returns: %TRUE if the next token is a function with the given @name
+ **/
+gboolean
+gtk_css_parser_has_function (GtkCssParser *self,
+ const char *name)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+
+ return gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION) &&
+ g_ascii_strcasecmp (token->string.string, name) == 0;
+}
+
+/**
+ * gtk_css_parser_try_delim:
+ * @self: a `GtkCssParser`
+ * @codepoint: unicode character codepoint to check
+ *
+ * Checks if the current token is a delimiter matching the given
+ * @codepoint. If that is the case, the token is consumed and
+ * %TRUE is returned.
+ *
+ * Keep in mind that not every unicode codepoint can be a delim
+ * token.
+ *
+ * Returns: %TRUE if the token matched and was consumed.
+ **/
+gboolean
+gtk_css_parser_try_delim (GtkCssParser *self,
+ gunichar codepoint)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+
+ if (!gtk_css_token_is (token, GTK_CSS_TOKEN_DELIM) ||
+ codepoint != token->delim.delim)
+ return FALSE;
+
+ gtk_css_parser_consume_token (self);
+ return TRUE;
+}
+
+/**
+ * gtk_css_parser_try_ident:
+ * @self: a `GtkCssParser`
+ * @ident: identifier to check for
+ *
+ * Checks if the current token is an identifier matching the given
+ * @ident string. If that is the case, the token is consumed
+ * and %TRUE is returned.
+ *
+ * Returns: %TRUE if the token matched and was consumed.
+ **/
+gboolean
+gtk_css_parser_try_ident (GtkCssParser *self,
+ const char *ident)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+
+ if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) ||
+ g_ascii_strcasecmp (token->string.string, ident) != 0)
+ return FALSE;
+
+ gtk_css_parser_consume_token (self);
+ return TRUE;
+}
+
+/**
+ * gtk_css_parser_try_at_keyword:
+ * @self: a `GtkCssParser`
+ * @keyword: name of keyword to check for
+ *
+ * Checks if the current token is an at-keyword token with the
+ * given @keyword. If that is the case, the token is consumed
+ * and %TRUE is returned.
+ *
+ * Returns: %TRUE if the token matched and was consumed.
+ **/
+gboolean
+gtk_css_parser_try_at_keyword (GtkCssParser *self,
+ const char *keyword)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+
+ if (!gtk_css_token_is (token, GTK_CSS_TOKEN_AT_KEYWORD) ||
+ g_ascii_strcasecmp (token->string.string, keyword) != 0)
+ return FALSE;
+
+ gtk_css_parser_consume_token (self);
+ return TRUE;
+}
+
+/**
+ * gtk_css_parser_try_token:
+ * @self: a `GtkCssParser`
+ * @token_type: type of token to try
+ *
+ * Consumes the next token if it matches the given @token_type.
+ *
+ * This function can be used in loops like this:
+ * do {
+ * ... parse one element ...
+ * } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA);
+ *
+ * Returns: %TRUE if a token was consumed
+ **/
+gboolean
+gtk_css_parser_try_token (GtkCssParser *self,
+ GtkCssTokenType token_type)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+
+ if (!gtk_css_token_is (token, token_type))
+ return FALSE;
+
+ gtk_css_parser_consume_token (self);
+ return TRUE;
+}
+
+/**
+ * gtk_css_parser_consume_ident:
+ * @self: a `GtkCssParser`
+ *
+ * If the current token is an identifier, consumes it and returns
+ * its name.
+ *
+ * If the current token is not an identifier, an error is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): the name of the consumed identifier
+ */
+char *
+gtk_css_parser_consume_ident (GtkCssParser *self)
+{
+ const GtkCssToken *token;
+ char *ident;
+
+ token = gtk_css_parser_get_token (self);
+
+ if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+ {
+ gtk_css_parser_error_syntax (self, "Expected an identifier");
+ return NULL;
+ }
+
+ ident = g_strdup (token->string.string);
+ gtk_css_parser_consume_token (self);
+
+ return ident;
+}
+
+/**
+ * gtk_css_parser_consume_string:
+ * @self: a `GtkCssParser`
+ *
+ * If the current token is a string, consumes it and return the string.
+ *
+ * If the current token is not a string, an error is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): the name of the consumed string
+ **/
+char *
+gtk_css_parser_consume_string (GtkCssParser *self)
+{
+ const GtkCssToken *token;
+ char *ident;
+
+ token = gtk_css_parser_get_token (self);
+
+ if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
+ {
+ gtk_css_parser_error_syntax (self, "Expected a string");
+ return NULL;
+ }
+
+ ident = g_strdup (token->string.string);
+ gtk_css_parser_consume_token (self);
+
+ return ident;
+}
+
+static guint
+gtk_css_parser_parse_url_arg (GtkCssParser *parser,
+ guint arg,
+ gpointer data)
+{
+ char **out_url = data;
+
+ *out_url = gtk_css_parser_consume_string (parser);
+ if (*out_url == NULL)
+ return 0;
+
+ return 1;
+}
+
+/**
+ * gtk_css_parser_consume_url:
+ * @self: a `GtkCssParser`
+ *
+ * If the parser matches the <url> token from the [CSS
+ * specification](https://drafts.csswg.org/css-values-4/#url-value),
+ * consumes it, resolves the URL and returns the resulting `GFile`.
+ * On failure, an error is emitted and %NULL is returned.
+ *
+ * Returns: (nullable) (transfer full): the resulting URL
+ **/
+char *
+gtk_css_parser_consume_url (GtkCssParser *self)
+{
+ const GtkCssToken *token;
+ char *url;
+
+ token = gtk_css_parser_get_token (self);
+
+ if (gtk_css_token_is (token, GTK_CSS_TOKEN_URL))
+ {
+ url = g_strdup (token->string.string);
+ gtk_css_parser_consume_token (self);
+ }
+ else if (gtk_css_token_is_function (token, "url"))
+ {
+ if (!gtk_css_parser_consume_function (self, 1, 1, gtk_css_parser_parse_url_arg, &url))
+ return NULL;
+ }
+ else
+ {
+ gtk_css_parser_error_syntax (self, "Expected a URL");
+ return NULL;
+ }
+
+ return url;
+}
+
+gboolean
+gtk_css_parser_has_number (GtkCssParser *self)
+{
+ return gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_NUMBER)
+ || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_NUMBER)
+ || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_INTEGER)
+ || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
+}
+
+gboolean
+gtk_css_parser_consume_number (GtkCssParser *self,
+ double *number)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+ if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
+ gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
+ gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+ gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+ {
+ *number = token->number.number;
+ gtk_css_parser_consume_token (self);
+ return TRUE;
+ }
+
+ gtk_css_parser_error_syntax (self, "Expected a number");
+ /* FIXME: Implement calc() */
+ return FALSE;
+}
+
+gboolean
+gtk_css_parser_consume_integer (GtkCssParser *self,
+ int *number)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+ if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+ gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+ {
+ *number = token->number.number;
+ gtk_css_parser_consume_token (self);
+ return TRUE;
+ }
+
+ gtk_css_parser_error_syntax (self, "Expected an integer");
+ /* FIXME: Implement calc() */
+ return FALSE;
+}
+
+gboolean
+gtk_css_parser_consume_percentage (GtkCssParser *self,
+ double *number)
+{
+ const GtkCssToken *token;
+
+ token = gtk_css_parser_get_token (self);
+ if (gtk_css_token_is (token, GTK_CSS_TOKEN_PERCENTAGE))
+ {
+ *number = token->number.number;
+ gtk_css_parser_consume_token (self);
+ return TRUE;
+ }
+
+ gtk_css_parser_error_syntax (self, "Expected a percentage");
+ /* FIXME: Implement calc() */
+ return FALSE;
+}
+
+gsize
+gtk_css_parser_consume_any (GtkCssParser *parser,
+ const GtkCssParseOption *options,
+ gsize n_options,
+ gpointer user_data)
+{
+ gsize result;
+ gsize i;
+
+ g_return_val_if_fail (parser != NULL, 0);
+ g_return_val_if_fail (options != NULL, 0);
+ g_return_val_if_fail (n_options < sizeof (gsize) * 8 - 1, 0);
+
+ result = 0;
+ while (result != (1u << n_options) - 1u)
+ {
+ for (i = 0; i < n_options; i++)
+ {
+ if (result & (1 << i))
+ continue;
+ if (options[i].can_parse && !options[i].can_parse (parser, options[i].data, user_data))
+ continue;
+ if (!options[i].parse (parser, options[i].data, user_data))
+ return 0;
+ result |= 1 << i;
+ break;
+ }
+ if (i == n_options)
+ break;
+ }
+
+ if (result == 0)
+ {
+ gtk_css_parser_error_syntax (parser, "No valid value given");
+ return result;
+ }
+
+ return result;
+}
diff --git a/pango/css/gtkcssparserprivate.h b/pango/css/gtkcssparserprivate.h
new file mode 100644
index 00000000..5659fe0c
--- /dev/null
+++ b/pango/css/gtkcssparserprivate.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+
+#ifndef __GTK_CSS_PARSER_H__
+#define __GTK_CSS_PARSER_H__
+
+#include "gtkcssenums.h"
+#include "gtkcsstokenizerprivate.h"
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCssParser GtkCssParser;
+
+typedef struct _GtkCssParseOption GtkCssParseOption;
+
+struct _GtkCssParseOption
+{
+ gboolean (* can_parse) (GtkCssParser *parser,
+ gpointer option_data,
+ gpointer user_data);
+ gboolean (* parse) (GtkCssParser *parser,
+ gpointer option_data,
+ gpointer user_data);
+ gpointer data;
+};
+
+typedef void (* GtkCssParserErrorFunc) (GtkCssParser *parser,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const GError *error,
+ gpointer user_data);
+
+GtkCssParser * gtk_css_parser_new_for_file (GFile *file,
+ GtkCssParserErrorFunc error_func,
+ gpointer user_data,
+ GDestroyNotify user_destroy,
+ GError **error);
+GtkCssParser * gtk_css_parser_new_for_bytes (GBytes *bytes,
+ GFile *file,
+ GtkCssParserErrorFunc error_func,
+ gpointer user_data,
+ GDestroyNotify user_destroy);
+GtkCssParser * gtk_css_parser_ref (GtkCssParser *self);
+void gtk_css_parser_unref (GtkCssParser *self);
+
+GFile * gtk_css_parser_get_file (GtkCssParser *self) G_GNUC_PURE;
+GFile * gtk_css_parser_resolve_url (GtkCssParser *self,
+ const char *url);
+
+const GtkCssLocation * gtk_css_parser_get_start_location (GtkCssParser *self) G_GNUC_PURE;
+const GtkCssLocation * gtk_css_parser_get_end_location (GtkCssParser *self) G_GNUC_PURE;
+const GtkCssLocation * gtk_css_parser_get_block_location (GtkCssParser *self) G_GNUC_PURE;
+
+const GtkCssToken * gtk_css_parser_peek_token (GtkCssParser *self);
+const GtkCssToken * gtk_css_parser_get_token (GtkCssParser *self);
+void gtk_css_parser_consume_token (GtkCssParser *self);
+
+void gtk_css_parser_start_block (GtkCssParser *self);
+void gtk_css_parser_start_semicolon_block (GtkCssParser *self,
+ GtkCssTokenType alternative_token);
+void gtk_css_parser_end_block_prelude (GtkCssParser *self);
+void gtk_css_parser_end_block (GtkCssParser *self);
+void gtk_css_parser_skip (GtkCssParser *self);
+void gtk_css_parser_skip_until (GtkCssParser *self,
+ GtkCssTokenType token_type);
+
+void gtk_css_parser_emit_error (GtkCssParser *self,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const GError *error);
+void gtk_css_parser_error (GtkCssParser *self,
+ GtkCssParserError code,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const char *format,
+ ...) G_GNUC_PRINTF(5, 6);
+void gtk_css_parser_error_syntax (GtkCssParser *self,
+ const char *format,
+ ...) G_GNUC_PRINTF(2, 3);
+void gtk_css_parser_error_value (GtkCssParser *self,
+ const char *format,
+ ...) G_GNUC_PRINTF(2, 3);
+void gtk_css_parser_error_import (GtkCssParser *self,
+ const char *format,
+ ...) G_GNUC_PRINTF(2, 3);
+void gtk_css_parser_warn (GtkCssParser *self,
+ GtkCssParserWarning code,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const char *format,
+ ...) G_GNUC_PRINTF(5, 6);
+void gtk_css_parser_warn_syntax (GtkCssParser *self,
+ const char *format,
+ ...) G_GNUC_PRINTF(2, 3);
+
+
+gboolean gtk_css_parser_has_token (GtkCssParser *self,
+ GtkCssTokenType token_type);
+gboolean gtk_css_parser_has_ident (GtkCssParser *self,
+ const char *ident);
+gboolean gtk_css_parser_has_number (GtkCssParser *self);
+gboolean gtk_css_parser_has_integer (GtkCssParser *self);
+gboolean gtk_css_parser_has_function (GtkCssParser *self,
+ const char *name);
+
+gboolean gtk_css_parser_try_delim (GtkCssParser *self,
+ gunichar codepoint);
+gboolean gtk_css_parser_try_ident (GtkCssParser *self,
+ const char *ident);
+gboolean gtk_css_parser_try_at_keyword (GtkCssParser *self,
+ const char *keyword);
+gboolean gtk_css_parser_try_token (GtkCssParser *self,
+ GtkCssTokenType token_type);
+
+char * gtk_css_parser_consume_ident (GtkCssParser *self) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+char * gtk_css_parser_consume_string (GtkCssParser *self) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+char * gtk_css_parser_consume_url (GtkCssParser *self) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+gboolean gtk_css_parser_consume_number (GtkCssParser *self,
+ double *number);
+gboolean gtk_css_parser_consume_integer (GtkCssParser *self,
+ int *number);
+gboolean gtk_css_parser_consume_percentage (GtkCssParser *self,
+ double *number);
+gboolean gtk_css_parser_consume_function (GtkCssParser *self,
+ guint min_args,
+ guint max_args,
+ guint (* parse_func) (GtkCssParser *, guint, gpointer),
+ gpointer data);
+gsize gtk_css_parser_consume_any (GtkCssParser *parser,
+ const GtkCssParseOption *options,
+ gsize n_options,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_PARSER_H__ */
diff --git a/pango/css/gtkcsssection.c b/pango/css/gtkcsssection.c
new file mode 100644
index 00000000..5851cc20
--- /dev/null
+++ b/pango/css/gtkcsssection.c
@@ -0,0 +1,256 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcsssection.h"
+
+#include "gtkcssparserprivate.h"
+
+struct _GtkCssSection
+{
+ int ref_count;
+ GtkCssSection *parent;
+ GFile *file;
+ GtkCssLocation start_location;
+ GtkCssLocation end_location; /* end location if parser is %NULL */
+};
+
+G_DEFINE_BOXED_TYPE (GtkCssSection, gtk_css_section, gtk_css_section_ref, gtk_css_section_unref)
+
+/**
+ * gtk_css_section_new: (constructor)
+ * @file: (nullable) (transfer none): The file this section refers to
+ * @start: The start location
+ * @end: The end location
+ *
+ * Creates a new `GtkCssSection` referring to the section
+ * in the given `file` from the `start` location to the
+ * `end` location.
+ *
+ * Returns: (transfer full): a new `GtkCssSection`
+ **/
+GtkCssSection *
+gtk_css_section_new (GFile *file,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end)
+{
+ GtkCssSection *result;
+
+ g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
+ g_return_val_if_fail (start != NULL, NULL);
+ g_return_val_if_fail (end != NULL, NULL);
+
+ result = g_slice_new0 (GtkCssSection);
+
+ result->ref_count = 1;
+ if (file)
+ result->file = g_object_ref (file);
+ result->start_location = *start;
+ result->end_location = *end;
+
+ return result;
+}
+
+/**
+ * gtk_css_section_ref:
+ * @section: a `GtkCssSection`
+ *
+ * Increments the reference count on `section`.
+ *
+ * Returns: (transfer full): the CSS section itself.
+ **/
+GtkCssSection *
+gtk_css_section_ref (GtkCssSection *section)
+{
+ g_return_val_if_fail (section != NULL, NULL);
+
+ section->ref_count += 1;
+
+ return section;
+}
+
+/**
+ * gtk_css_section_unref:
+ * @section: (transfer full): a `GtkCssSection`
+ *
+ * Decrements the reference count on `section`, freeing the
+ * structure if the reference count reaches 0.
+ **/
+void
+gtk_css_section_unref (GtkCssSection *section)
+{
+ g_return_if_fail (section != NULL);
+
+ section->ref_count -= 1;
+ if (section->ref_count > 0)
+ return;
+
+ if (section->parent)
+ gtk_css_section_unref (section->parent);
+ if (section->file)
+ g_object_unref (section->file);
+
+ g_slice_free (GtkCssSection, section);
+}
+
+/**
+ * gtk_css_section_get_parent:
+ * @section: the section
+ *
+ * Gets the parent section for the given `section`.
+ *
+ * The parent section is the section that contains this `section`. A special
+ * case are sections of type `GTK_CSS_SECTION_DOCUMEN`T. Their parent will
+ * either be `NULL` if they are the original CSS document that was loaded by
+ * [method@Gtk.CssProvider.load_from_file] or a section of type
+ * `GTK_CSS_SECTION_IMPORT` if it was loaded with an `@import` rule from
+ * a different file.
+ *
+ * Returns: (nullable) (transfer none): the parent section
+ **/
+GtkCssSection *
+gtk_css_section_get_parent (const GtkCssSection *section)
+{
+ g_return_val_if_fail (section != NULL, NULL);
+
+ return section->parent;
+}
+
+/**
+ * gtk_css_section_get_file:
+ * @section: the section
+ *
+ * Gets the file that @section was parsed from.
+ *
+ * If no such file exists, for example because the CSS was loaded via
+ * [method@Gtk.CssProvider.load_from_data], then `NULL` is returned.
+ *
+ * Returns: (transfer none): the `GFile` from which the `section`
+ * was parsed
+ **/
+GFile *
+gtk_css_section_get_file (const GtkCssSection *section)
+{
+ g_return_val_if_fail (section != NULL, NULL);
+
+ return section->file;
+}
+
+/**
+ * gtk_css_section_get_start_location:
+ * @section: the section
+ *
+ * Returns the location in the CSS document where this section starts.
+ *
+ * Returns: (transfer none) (not nullable): The start location of
+ * this section
+ */
+const GtkCssLocation *
+gtk_css_section_get_start_location (const GtkCssSection *section)
+{
+ g_return_val_if_fail (section != NULL, NULL);
+
+ return &section->start_location;
+}
+
+/**
+ * gtk_css_section_get_end_location:
+ * @section: the section
+ *
+ * Returns the location in the CSS document where this section ends.
+ *
+ * Returns: (transfer none) (not nullable): The end location of
+ * this section
+ */
+const GtkCssLocation *
+gtk_css_section_get_end_location (const GtkCssSection *section)
+{
+ g_return_val_if_fail (section != NULL, NULL);
+
+ return &section->end_location;
+}
+
+/**
+ * gtk_css_section_print:
+ * @section: a section
+ * @string: a `GString` to print to
+ *
+ * Prints the `section` into `string` in a human-readable form.
+ *
+ * This is a form like `gtk.css:32:1-23` to denote line 32, characters
+ * 1 to 23 in the file `gtk.css`.
+ **/
+void
+gtk_css_section_print (const GtkCssSection *section,
+ GString *string)
+{
+ if (section->file)
+ {
+ GFileInfo *info;
+
+ info = g_file_query_info (section->file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, 0, NULL, NULL);
+
+ if (info)
+ {
+ g_string_append (string, g_file_info_get_display_name (info));
+ g_object_unref (info);
+ }
+ else
+ {
+ g_string_append (string, "<broken file>");
+ }
+ }
+ else
+ {
+ g_string_append (string, "<data>");
+ }
+
+ g_string_append_printf (string, ":%zu:%zu",
+ section->start_location.lines + 1,
+ section->start_location.line_chars + 1);
+ if (section->start_location.lines != section->end_location.lines ||
+ section->start_location.line_chars != section->end_location.line_chars)
+ {
+ g_string_append (string, "-");
+ if (section->start_location.lines != section->end_location.lines)
+ g_string_append_printf (string, "%zu:", section->end_location.lines + 1);
+ g_string_append_printf (string, "%zu", section->end_location.line_chars + 1);
+ }
+}
+
+/**
+ * gtk_css_section_to_string:
+ * @section: a `GtkCssSection`
+ *
+ * Prints the section into a human-readable text form using
+ * [method@Gtk.CssSection.print].
+ *
+ * Returns: (transfer full): A new string.
+ **/
+char *
+gtk_css_section_to_string (const GtkCssSection *section)
+{
+ GString *string;
+
+ g_return_val_if_fail (section != NULL, NULL);
+
+ string = g_string_new (NULL);
+ gtk_css_section_print (section, string);
+
+ return g_string_free (string, FALSE);
+}
diff --git a/pango/css/gtkcsssection.h b/pango/css/gtkcsssection.h
new file mode 100644
index 00000000..8892a2d8
--- /dev/null
+++ b/pango/css/gtkcsssection.h
@@ -0,0 +1,59 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_CSS_SECTION_H__
+#define __GTK_CSS_SECTION_H__
+
+#include <gio/gio.h>
+#include <pango/css/gtkcsslocation.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CSS_SECTION (gtk_css_section_get_type ())
+
+/**
+ * GtkCssSection:
+ *
+ * Defines a part of a CSS document.
+ *
+ * Because sections are nested into one another, you can use
+ * gtk_css_section_get_parent() to get the containing region.
+ */
+typedef struct _GtkCssSection GtkCssSection;
+
+GType gtk_css_section_get_type (void) G_GNUC_CONST;
+
+GtkCssSection * gtk_css_section_new (GFile *file,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end);
+GtkCssSection * gtk_css_section_ref (GtkCssSection *section);
+void gtk_css_section_unref (GtkCssSection *section);
+
+void gtk_css_section_print (const GtkCssSection *section,
+ GString *string);
+char * gtk_css_section_to_string (const GtkCssSection *section);
+
+GtkCssSection * gtk_css_section_get_parent (const GtkCssSection *section);
+GFile * gtk_css_section_get_file (const GtkCssSection *section);
+const GtkCssLocation *
+ gtk_css_section_get_start_location (const GtkCssSection *section);
+const GtkCssLocation *
+ gtk_css_section_get_end_location (const GtkCssSection *section);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_SECTION_H__ */
diff --git a/pango/css/gtkcssserializer.c b/pango/css/gtkcssserializer.c
new file mode 100644
index 00000000..d939f38c
--- /dev/null
+++ b/pango/css/gtkcssserializer.c
@@ -0,0 +1,72 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcssserializerprivate.h"
+
+/* Escape a string so that it can be parsed
+ * as a css string again.
+ */
+void
+gtk_css_print_string (GString *str,
+ const char *string,
+ gboolean multiline)
+{
+ gsize len;
+
+ g_return_if_fail (str != NULL);
+ g_return_if_fail (string != NULL);
+
+ g_string_append_c (str, '"');
+
+ do {
+ len = strcspn (string, "\\\"\n\r\f");
+ g_string_append_len (str, string, len);
+ string += len;
+ switch (*string)
+ {
+ case '\0':
+ goto out;
+ case '\n':
+ if (multiline)
+ g_string_append (str, "\\A\\\n");
+ else
+ g_string_append (str, "\\A ");
+ break;
+ case '\r':
+ g_string_append (str, "\\D ");
+ break;
+ case '\f':
+ g_string_append (str, "\\C ");
+ break;
+ case '\"':
+ g_string_append (str, "\\\"");
+ break;
+ case '\\':
+ g_string_append (str, "\\\\");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ string++;
+ } while (*string);
+
+out:
+ g_string_append_c (str, '"');
+}
diff --git a/pango/css/gtkcssserializerprivate.h b/pango/css/gtkcssserializerprivate.h
new file mode 100644
index 00000000..86f9934f
--- /dev/null
+++ b/pango/css/gtkcssserializerprivate.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2020 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen@redhat.com>
+ */
+
+
+#ifndef __GTK_CSS_SERIALIZER_H__
+#define __GTK_CSS_SERIALIZER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void gtk_css_print_string (GString *str,
+ const char *string,
+ gboolean multiline);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_SERIALIZER_H__ */
diff --git a/pango/css/gtkcsstokenizer.c b/pango/css/gtkcsstokenizer.c
new file mode 100644
index 00000000..a6d14abc
--- /dev/null
+++ b/pango/css/gtkcsstokenizer.c
@@ -0,0 +1,1487 @@
+/* GSK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcsstokenizerprivate.h"
+
+#include "gtkcssenums.h"
+#include "gtkcsserror.h"
+#include "gtkcsslocationprivate.h"
+
+#include <math.h>
+#include <string.h>
+
+struct _GtkCssTokenizer
+{
+ int ref_count;
+ GBytes *bytes;
+ GString *name_buffer;
+
+ const char *data;
+ const char *end;
+
+ GtkCssLocation position;
+};
+
+void
+gtk_css_token_clear (GtkCssToken *token)
+{
+ switch (token->type)
+ {
+ case GTK_CSS_TOKEN_STRING:
+ case GTK_CSS_TOKEN_IDENT:
+ case GTK_CSS_TOKEN_FUNCTION:
+ case GTK_CSS_TOKEN_AT_KEYWORD:
+ case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+ case GTK_CSS_TOKEN_HASH_ID:
+ case GTK_CSS_TOKEN_URL:
+ g_free (token->string.string);
+ break;
+
+ case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+ g_free (token->dimension.dimension);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ case GTK_CSS_TOKEN_EOF:
+ case GTK_CSS_TOKEN_WHITESPACE:
+ case GTK_CSS_TOKEN_OPEN_PARENS:
+ case GTK_CSS_TOKEN_CLOSE_PARENS:
+ case GTK_CSS_TOKEN_OPEN_SQUARE:
+ case GTK_CSS_TOKEN_CLOSE_SQUARE:
+ case GTK_CSS_TOKEN_OPEN_CURLY:
+ case GTK_CSS_TOKEN_CLOSE_CURLY:
+ case GTK_CSS_TOKEN_COMMA:
+ case GTK_CSS_TOKEN_COLON:
+ case GTK_CSS_TOKEN_SEMICOLON:
+ case GTK_CSS_TOKEN_CDC:
+ case GTK_CSS_TOKEN_CDO:
+ case GTK_CSS_TOKEN_DELIM:
+ case GTK_CSS_TOKEN_SIGNED_INTEGER:
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+ case GTK_CSS_TOKEN_SIGNED_NUMBER:
+ case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+ case GTK_CSS_TOKEN_PERCENTAGE:
+ case GTK_CSS_TOKEN_INCLUDE_MATCH:
+ case GTK_CSS_TOKEN_DASH_MATCH:
+ case GTK_CSS_TOKEN_PREFIX_MATCH:
+ case GTK_CSS_TOKEN_SUFFIX_MATCH:
+ case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+ case GTK_CSS_TOKEN_COLUMN:
+ case GTK_CSS_TOKEN_BAD_STRING:
+ case GTK_CSS_TOKEN_BAD_URL:
+ case GTK_CSS_TOKEN_COMMENT:
+ break;
+ }
+
+ token->type = GTK_CSS_TOKEN_EOF;
+}
+
+static void
+gtk_css_token_init (GtkCssToken *token,
+ GtkCssTokenType type)
+{
+ token->type = type;
+
+ switch ((guint)type)
+ {
+ case GTK_CSS_TOKEN_EOF:
+ case GTK_CSS_TOKEN_WHITESPACE:
+ case GTK_CSS_TOKEN_OPEN_PARENS:
+ case GTK_CSS_TOKEN_CLOSE_PARENS:
+ case GTK_CSS_TOKEN_OPEN_SQUARE:
+ case GTK_CSS_TOKEN_CLOSE_SQUARE:
+ case GTK_CSS_TOKEN_OPEN_CURLY:
+ case GTK_CSS_TOKEN_CLOSE_CURLY:
+ case GTK_CSS_TOKEN_COMMA:
+ case GTK_CSS_TOKEN_COLON:
+ case GTK_CSS_TOKEN_SEMICOLON:
+ case GTK_CSS_TOKEN_CDC:
+ case GTK_CSS_TOKEN_CDO:
+ case GTK_CSS_TOKEN_INCLUDE_MATCH:
+ case GTK_CSS_TOKEN_DASH_MATCH:
+ case GTK_CSS_TOKEN_PREFIX_MATCH:
+ case GTK_CSS_TOKEN_SUFFIX_MATCH:
+ case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+ case GTK_CSS_TOKEN_COLUMN:
+ case GTK_CSS_TOKEN_BAD_STRING:
+ case GTK_CSS_TOKEN_BAD_URL:
+ case GTK_CSS_TOKEN_COMMENT:
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+append_ident (GString *string,
+ const char *ident)
+{
+ /* XXX */
+ g_string_append (string, ident);
+}
+
+static void
+append_string (GString *string,
+ const char *s)
+{
+ g_string_append_c (string, '"');
+ /* XXX */
+ g_string_append (string, s);
+ g_string_append_c (string, '"');
+}
+
+/*
+ * gtk_css_token_is_finite:
+ * @token: a `GtkCssToken`
+ *
+ * A token is considered finite when it would stay the same no matter
+ * what bytes follow it in the data stream.
+ *
+ * An obvious example for this is the ';' token.
+ *
+ * Returns: %TRUE if the token is considered finite.
+ **/
+gboolean
+gtk_css_token_is_finite (const GtkCssToken *token)
+{
+ switch (token->type)
+ {
+ case GTK_CSS_TOKEN_EOF:
+ case GTK_CSS_TOKEN_STRING:
+ case GTK_CSS_TOKEN_FUNCTION:
+ case GTK_CSS_TOKEN_URL:
+ case GTK_CSS_TOKEN_PERCENTAGE:
+ case GTK_CSS_TOKEN_OPEN_PARENS:
+ case GTK_CSS_TOKEN_CLOSE_PARENS:
+ case GTK_CSS_TOKEN_OPEN_SQUARE:
+ case GTK_CSS_TOKEN_CLOSE_SQUARE:
+ case GTK_CSS_TOKEN_OPEN_CURLY:
+ case GTK_CSS_TOKEN_CLOSE_CURLY:
+ case GTK_CSS_TOKEN_COMMA:
+ case GTK_CSS_TOKEN_COLON:
+ case GTK_CSS_TOKEN_SEMICOLON:
+ case GTK_CSS_TOKEN_CDC:
+ case GTK_CSS_TOKEN_CDO:
+ case GTK_CSS_TOKEN_INCLUDE_MATCH:
+ case GTK_CSS_TOKEN_DASH_MATCH:
+ case GTK_CSS_TOKEN_PREFIX_MATCH:
+ case GTK_CSS_TOKEN_SUFFIX_MATCH:
+ case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+ case GTK_CSS_TOKEN_COLUMN:
+ case GTK_CSS_TOKEN_COMMENT:
+ return TRUE;
+
+ default:
+ g_assert_not_reached ();
+ case GTK_CSS_TOKEN_WHITESPACE:
+ case GTK_CSS_TOKEN_IDENT:
+ case GTK_CSS_TOKEN_AT_KEYWORD:
+ case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+ case GTK_CSS_TOKEN_HASH_ID:
+ case GTK_CSS_TOKEN_DELIM:
+ case GTK_CSS_TOKEN_SIGNED_INTEGER:
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+ case GTK_CSS_TOKEN_SIGNED_NUMBER:
+ case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+ case GTK_CSS_TOKEN_BAD_STRING:
+ case GTK_CSS_TOKEN_BAD_URL:
+ case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+ return FALSE;
+ }
+}
+
+/*
+ * gtk_css_token_is_preserved:
+ * @token: a `GtkCssToken`
+ * @out_closing: (nullable): Type of the token that closes a block
+ * started with this token
+ *
+ * A token is considered preserved when it does not start a block.
+ *
+ * Tokens that start a block require different error recovery when parsing,
+ * so CSS parsers want to look at this function
+ *
+ * Returns: %TRUE if the token is considered preserved.
+ */
+gboolean
+gtk_css_token_is_preserved (const GtkCssToken *token,
+ GtkCssTokenType *out_closing)
+{
+ switch (token->type)
+ {
+ case GTK_CSS_TOKEN_FUNCTION:
+ case GTK_CSS_TOKEN_OPEN_PARENS:
+ if (out_closing)
+ *out_closing = GTK_CSS_TOKEN_CLOSE_PARENS;
+ return FALSE;
+
+ case GTK_CSS_TOKEN_OPEN_SQUARE:
+ if (out_closing)
+ *out_closing = GTK_CSS_TOKEN_CLOSE_SQUARE;
+ return FALSE;
+
+ case GTK_CSS_TOKEN_OPEN_CURLY:
+ if (out_closing)
+ *out_closing = GTK_CSS_TOKEN_CLOSE_CURLY;
+ return FALSE;
+
+ default:
+ g_assert_not_reached ();
+ case GTK_CSS_TOKEN_EOF:
+ case GTK_CSS_TOKEN_WHITESPACE:
+ case GTK_CSS_TOKEN_STRING:
+ case GTK_CSS_TOKEN_URL:
+ case GTK_CSS_TOKEN_PERCENTAGE:
+ case GTK_CSS_TOKEN_CLOSE_PARENS:
+ case GTK_CSS_TOKEN_CLOSE_SQUARE:
+ case GTK_CSS_TOKEN_CLOSE_CURLY:
+ case GTK_CSS_TOKEN_COMMA:
+ case GTK_CSS_TOKEN_COLON:
+ case GTK_CSS_TOKEN_SEMICOLON:
+ case GTK_CSS_TOKEN_CDC:
+ case GTK_CSS_TOKEN_CDO:
+ case GTK_CSS_TOKEN_INCLUDE_MATCH:
+ case GTK_CSS_TOKEN_DASH_MATCH:
+ case GTK_CSS_TOKEN_PREFIX_MATCH:
+ case GTK_CSS_TOKEN_SUFFIX_MATCH:
+ case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+ case GTK_CSS_TOKEN_COLUMN:
+ case GTK_CSS_TOKEN_COMMENT:
+ case GTK_CSS_TOKEN_IDENT:
+ case GTK_CSS_TOKEN_AT_KEYWORD:
+ case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+ case GTK_CSS_TOKEN_HASH_ID:
+ case GTK_CSS_TOKEN_DELIM:
+ case GTK_CSS_TOKEN_SIGNED_INTEGER:
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+ case GTK_CSS_TOKEN_SIGNED_NUMBER:
+ case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+ case GTK_CSS_TOKEN_BAD_STRING:
+ case GTK_CSS_TOKEN_BAD_URL:
+ case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+ if (out_closing)
+ *out_closing = GTK_CSS_TOKEN_EOF;
+ return TRUE;
+ }
+}
+
+gboolean
+gtk_css_token_is_ident (const GtkCssToken *token,
+ const char *ident)
+{
+ return gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)
+ && (g_ascii_strcasecmp (token->string.string, ident) == 0);
+}
+
+gboolean
+gtk_css_token_is_function (const GtkCssToken *token,
+ const char *ident)
+{
+ return gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION)
+ && (g_ascii_strcasecmp (token->string.string, ident) == 0);
+}
+
+gboolean
+gtk_css_token_is_delim (const GtkCssToken *token,
+ gunichar delim)
+{
+ return gtk_css_token_is (token, GTK_CSS_TOKEN_DELIM)
+ && token->delim.delim == delim;
+}
+
+void
+gtk_css_token_print (const GtkCssToken *token,
+ GString *string)
+{
+ char buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+ switch (token->type)
+ {
+ case GTK_CSS_TOKEN_STRING:
+ append_string (string, token->string.string);
+ break;
+
+ case GTK_CSS_TOKEN_IDENT:
+ append_ident (string, token->string.string);
+ break;
+
+ case GTK_CSS_TOKEN_URL:
+ g_string_append (string, "url(");
+ append_ident (string, token->string.string);
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_TOKEN_FUNCTION:
+ append_ident (string, token->string.string);
+ g_string_append_c (string, '(');
+ break;
+
+ case GTK_CSS_TOKEN_AT_KEYWORD:
+ g_string_append_c (string, '@');
+ append_ident (string, token->string.string);
+ break;
+
+ case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+ case GTK_CSS_TOKEN_HASH_ID:
+ g_string_append_c (string, '#');
+ append_ident (string, token->string.string);
+ break;
+
+ case GTK_CSS_TOKEN_DELIM:
+ g_string_append_unichar (string, token->delim.delim);
+ break;
+
+ case GTK_CSS_TOKEN_SIGNED_INTEGER:
+ case GTK_CSS_TOKEN_SIGNED_NUMBER:
+ if (token->number.number >= 0)
+ g_string_append_c (string, '+');
+ G_GNUC_FALLTHROUGH;
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+ case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, token->number.number);
+ g_string_append (string, buf);
+ break;
+
+ case GTK_CSS_TOKEN_PERCENTAGE:
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, token->number.number);
+ g_string_append (string, buf);
+ g_string_append_c (string, '%');
+ break;
+
+ case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+ if (token->dimension.value >= 0)
+ g_string_append_c (string, '+');
+ G_GNUC_FALLTHROUGH;
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, token->dimension.value);
+ g_string_append (string, buf);
+ append_ident (string, token->dimension.dimension);
+ break;
+
+ case GTK_CSS_TOKEN_EOF:
+ break;
+
+ case GTK_CSS_TOKEN_WHITESPACE:
+ g_string_append (string, " ");
+ break;
+
+ case GTK_CSS_TOKEN_OPEN_PARENS:
+ g_string_append (string, "(");
+ break;
+
+ case GTK_CSS_TOKEN_CLOSE_PARENS:
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_TOKEN_OPEN_SQUARE:
+ g_string_append (string, "[");
+ break;
+
+ case GTK_CSS_TOKEN_CLOSE_SQUARE:
+ g_string_append (string, "]");
+ break;
+
+ case GTK_CSS_TOKEN_OPEN_CURLY:
+ g_string_append (string, "{");
+ break;
+
+ case GTK_CSS_TOKEN_CLOSE_CURLY:
+ g_string_append (string, "}");
+ break;
+
+ case GTK_CSS_TOKEN_COMMA:
+ g_string_append (string, ",");
+ break;
+
+ case GTK_CSS_TOKEN_COLON:
+ g_string_append (string, ":");
+ break;
+
+ case GTK_CSS_TOKEN_SEMICOLON:
+ g_string_append (string, ";");
+ break;
+
+ case GTK_CSS_TOKEN_CDO:
+ g_string_append (string, "<!--");
+ break;
+
+ case GTK_CSS_TOKEN_CDC:
+ g_string_append (string, "-->");
+ break;
+
+ case GTK_CSS_TOKEN_INCLUDE_MATCH:
+ g_string_append (string, "~=");
+ break;
+
+ case GTK_CSS_TOKEN_DASH_MATCH:
+ g_string_append (string, "|=");
+ break;
+
+ case GTK_CSS_TOKEN_PREFIX_MATCH:
+ g_string_append (string, "^=");
+ break;
+
+ case GTK_CSS_TOKEN_SUFFIX_MATCH:
+ g_string_append (string, "$=");
+ break;
+
+ case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+ g_string_append (string, "*=");
+ break;
+
+ case GTK_CSS_TOKEN_COLUMN:
+ g_string_append (string, "||");
+ break;
+
+ case GTK_CSS_TOKEN_BAD_STRING:
+ g_string_append (string, "\"\n");
+ break;
+
+ case GTK_CSS_TOKEN_BAD_URL:
+ g_string_append (string, "url(bad url)");
+ break;
+
+ case GTK_CSS_TOKEN_COMMENT:
+ g_string_append (string, "/* comment */");
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+char *
+gtk_css_token_to_string (const GtkCssToken *token)
+{
+ GString *string;
+
+ string = g_string_new (NULL);
+ gtk_css_token_print (token, string);
+ return g_string_free (string, FALSE);
+}
+
+static void
+gtk_css_token_init_string (GtkCssToken *token,
+ GtkCssTokenType type,
+ char *string)
+{
+ token->type = type;
+
+ switch ((guint)type)
+ {
+ case GTK_CSS_TOKEN_STRING:
+ case GTK_CSS_TOKEN_IDENT:
+ case GTK_CSS_TOKEN_FUNCTION:
+ case GTK_CSS_TOKEN_AT_KEYWORD:
+ case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+ case GTK_CSS_TOKEN_HASH_ID:
+ case GTK_CSS_TOKEN_URL:
+ token->string.string = string;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gtk_css_token_init_delim (GtkCssToken *token,
+ gunichar delim)
+{
+ token->type = GTK_CSS_TOKEN_DELIM;
+ token->delim.delim = delim;
+}
+
+static void
+gtk_css_token_init_number (GtkCssToken *token,
+ GtkCssTokenType type,
+ double value)
+{
+ token->type = type;
+
+ switch ((guint)type)
+ {
+ case GTK_CSS_TOKEN_SIGNED_INTEGER:
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+ case GTK_CSS_TOKEN_SIGNED_NUMBER:
+ case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+ case GTK_CSS_TOKEN_PERCENTAGE:
+ token->number.number = value;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gtk_css_token_init_dimension (GtkCssToken *token,
+ GtkCssTokenType type,
+ double value,
+ char *dimension)
+{
+ token->type = type;
+
+ switch ((guint)type)
+ {
+ case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+ case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+ token->dimension.value = value;
+ token->dimension.dimension = dimension;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+GtkCssTokenizer *
+gtk_css_tokenizer_new (GBytes *bytes)
+{
+ GtkCssTokenizer *tokenizer;
+
+ tokenizer = g_slice_new0 (GtkCssTokenizer);
+ tokenizer->ref_count = 1;
+ tokenizer->bytes = g_bytes_ref (bytes);
+ tokenizer->name_buffer = g_string_new (NULL);
+
+ tokenizer->data = g_bytes_get_data (bytes, NULL);
+ tokenizer->end = tokenizer->data + g_bytes_get_size (bytes);
+
+ gtk_css_location_init (&tokenizer->position);
+
+ return tokenizer;
+}
+
+GtkCssTokenizer *
+gtk_css_tokenizer_ref (GtkCssTokenizer *tokenizer)
+{
+ tokenizer->ref_count++;
+
+ return tokenizer;
+}
+
+void
+gtk_css_tokenizer_unref (GtkCssTokenizer *tokenizer)
+{
+ tokenizer->ref_count--;
+ if (tokenizer->ref_count > 0)
+ return;
+
+ g_string_free (tokenizer->name_buffer, TRUE);
+ g_bytes_unref (tokenizer->bytes);
+ g_slice_free (GtkCssTokenizer, tokenizer);
+}
+
+const GtkCssLocation *
+gtk_css_tokenizer_get_location (GtkCssTokenizer *tokenizer)
+{
+ return &tokenizer->position;
+}
+
+static void G_GNUC_PRINTF(2, 3)
+gtk_css_tokenizer_parse_error (GError **error,
+ const char *format,
+ ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ if (error)
+ {
+ *error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+ GTK_CSS_PARSER_ERROR_SYNTAX,
+ format, args);
+ }
+ else
+ {
+ char *s = g_strdup_vprintf (format, args);
+ g_print ("error: %s\n", s);
+ g_free (s);
+ }
+ va_end (args);
+}
+
+static gboolean
+is_newline (char c)
+{
+ return c == '\n'
+ || c == '\r'
+ || c == '\f';
+}
+
+static gboolean
+is_whitespace (char c)
+{
+ return is_newline (c)
+ || c == '\t'
+ || c == ' ';
+}
+
+static gboolean
+is_multibyte (char c)
+{
+ return c & 0x80;
+}
+
+static gboolean
+is_name_start (char c)
+{
+ return is_multibyte (c)
+ || g_ascii_isalpha (c)
+ || c == '_';
+}
+
+static gboolean
+is_name (char c)
+{
+ return is_name_start (c)
+ || g_ascii_isdigit (c)
+ || c == '-';
+}
+
+static gboolean
+is_non_printable (char c)
+{
+ return (c >= 0 && c <= 0x08)
+ || c == 0x0B
+ || c == 0x0E
+ || c == 0x1F
+ || c == 0x7F;
+}
+
+static gboolean
+is_valid_escape (const char *data,
+ const char *end)
+{
+ switch (end - data)
+ {
+ default:
+ if (is_newline (data[1]))
+ return FALSE;
+ G_GNUC_FALLTHROUGH;
+
+ case 1:
+ return data[0] == '\\';
+
+ case 0:
+ return FALSE;
+ }
+}
+
+static inline gsize
+gtk_css_tokenizer_remaining (GtkCssTokenizer *tokenizer)
+{
+ return tokenizer->end - tokenizer->data;
+}
+
+static gboolean
+gtk_css_tokenizer_has_valid_escape (GtkCssTokenizer *tokenizer)
+{
+ return is_valid_escape (tokenizer->data, tokenizer->end);
+}
+
+static gboolean
+gtk_css_tokenizer_has_identifier (GtkCssTokenizer *tokenizer)
+{
+ const char *data = tokenizer->data;
+
+ if (data == tokenizer->end)
+ return FALSE;
+
+ if (*data == '-')
+ {
+ data++;
+ if (data == tokenizer->end)
+ return FALSE;
+ if (*data == '-')
+ return TRUE;
+ }
+
+ if (is_name_start (*data))
+ return TRUE;
+
+ if (*data == '\\')
+ {
+ data++;
+ if (data == tokenizer->end)
+ return TRUE; /* really? */
+ if (is_newline (*data))
+ return FALSE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_css_tokenizer_has_number (GtkCssTokenizer *tokenizer)
+{
+ const char *data = tokenizer->data;
+
+ if (data == tokenizer->end)
+ return FALSE;
+
+ if (*data == '-' || *data == '+')
+ {
+ data++;
+ if (data == tokenizer->end)
+ return FALSE;
+ }
+
+ if (*data == '.')
+ {
+ data++;
+ if (data == tokenizer->end)
+ return FALSE;
+ }
+
+ return g_ascii_isdigit (*data);
+}
+
+static void
+gtk_css_tokenizer_consume_newline (GtkCssTokenizer *tokenizer)
+{
+ gsize n;
+
+ if (gtk_css_tokenizer_remaining (tokenizer) > 1 &&
+ tokenizer->data[0] == '\r' && tokenizer->data[1] == '\n')
+ n = 2;
+ else
+ n = 1;
+
+ tokenizer->data += n;
+ gtk_css_location_advance_newline (&tokenizer->position, n == 2 ? TRUE : FALSE);
+}
+
+static inline void
+gtk_css_tokenizer_consume (GtkCssTokenizer *tokenizer,
+ gsize n_bytes,
+ gsize n_characters)
+{
+ /* NB: must not contain newlines! */
+ tokenizer->data += n_bytes;
+
+ gtk_css_location_advance (&tokenizer->position, n_bytes, n_characters);
+}
+
+static inline void
+gtk_css_tokenizer_consume_ascii (GtkCssTokenizer *tokenizer)
+{
+ /* NB: must not contain newlines! */
+ gtk_css_tokenizer_consume (tokenizer, 1, 1);
+}
+
+static inline void
+gtk_css_tokenizer_consume_whitespace (GtkCssTokenizer *tokenizer)
+{
+ if (is_newline (*tokenizer->data))
+ gtk_css_tokenizer_consume_newline (tokenizer);
+ else
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+}
+
+static inline void
+gtk_css_tokenizer_consume_char (GtkCssTokenizer *tokenizer,
+ GString *string)
+{
+ if (is_newline (*tokenizer->data))
+ gtk_css_tokenizer_consume_newline (tokenizer);
+ else
+ {
+ gsize char_size = g_utf8_next_char (tokenizer->data) - tokenizer->data;
+
+ if (string)
+ g_string_append_len (string, tokenizer->data, char_size);
+ gtk_css_tokenizer_consume (tokenizer, char_size, 1);
+ }
+}
+
+static void
+gtk_css_tokenizer_read_whitespace (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token)
+{
+ do {
+ gtk_css_tokenizer_consume_whitespace (tokenizer);
+ } while (tokenizer->data != tokenizer->end &&
+ is_whitespace (*tokenizer->data));
+
+ gtk_css_token_init (token, GTK_CSS_TOKEN_WHITESPACE);
+}
+
+static gunichar
+gtk_css_tokenizer_read_escape (GtkCssTokenizer *tokenizer)
+{
+ gunichar value = 0;
+ guint i;
+
+ gtk_css_tokenizer_consume (tokenizer, 1, 1);
+
+ for (i = 0; i < 6 && tokenizer->data < tokenizer->end && g_ascii_isxdigit (*tokenizer->data); i++)
+ {
+ value = value * 16 + g_ascii_xdigit_value (*tokenizer->data);
+ gtk_css_tokenizer_consume (tokenizer, 1, 1);
+ }
+
+ if (i == 0)
+ {
+ gsize remaining = gtk_css_tokenizer_remaining (tokenizer);
+ if (remaining == 0)
+ return 0xFFFD;
+
+ value = g_utf8_get_char_validated (tokenizer->data, remaining);
+ if (value == (gunichar) -1 || value == (gunichar) -2)
+ value = 0;
+
+ gtk_css_tokenizer_consume_char (tokenizer, NULL);
+ }
+ else
+ {
+ if (is_whitespace (*tokenizer->data))
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ }
+
+ if (!g_unichar_validate (value) || g_unichar_type (value) == G_UNICODE_SURROGATE)
+ return 0xFFFD;
+
+ return value;
+}
+
+static char *
+gtk_css_tokenizer_read_name (GtkCssTokenizer *tokenizer)
+{
+ g_string_set_size (tokenizer->name_buffer, 0);
+
+ do {
+ if (*tokenizer->data == '\\')
+ {
+ if (gtk_css_tokenizer_has_valid_escape (tokenizer))
+ {
+ gunichar value = gtk_css_tokenizer_read_escape (tokenizer);
+ g_string_append_unichar (tokenizer->name_buffer, value);
+ }
+ else
+ {
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+
+ if (tokenizer->data == tokenizer->end)
+ {
+ g_string_append_unichar (tokenizer->name_buffer, 0xFFFD);
+ break;
+ }
+
+ gtk_css_tokenizer_consume_char (tokenizer, tokenizer->name_buffer);
+ }
+ }
+ else if (is_name (*tokenizer->data))
+ {
+ gtk_css_tokenizer_consume_char (tokenizer, tokenizer->name_buffer);
+ }
+ else
+ {
+ break;
+ }
+ }
+ while (tokenizer->data != tokenizer->end);
+
+ return g_strndup (tokenizer->name_buffer->str, tokenizer->name_buffer->len);
+}
+
+static void
+gtk_css_tokenizer_read_bad_url (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token)
+{
+ while (tokenizer->data < tokenizer->end && *tokenizer->data != ')')
+ {
+ if (gtk_css_tokenizer_has_valid_escape (tokenizer))
+ gtk_css_tokenizer_read_escape (tokenizer);
+ else
+ gtk_css_tokenizer_consume_char (tokenizer, NULL);
+ }
+
+ if (tokenizer->data < tokenizer->end)
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+
+ gtk_css_token_init (token, GTK_CSS_TOKEN_BAD_URL);
+}
+
+static gboolean
+gtk_css_tokenizer_read_url (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token,
+ GError **error)
+{
+ GString *url = g_string_new (NULL);
+
+ while (tokenizer->data < tokenizer->end && is_whitespace (*tokenizer->data))
+ gtk_css_tokenizer_consume_whitespace (tokenizer);
+
+ while (tokenizer->data < tokenizer->end)
+ {
+ if (*tokenizer->data == ')')
+ {
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ break;
+ }
+ else if (is_whitespace (*tokenizer->data))
+ {
+ do
+ gtk_css_tokenizer_consume_whitespace (tokenizer);
+ while (tokenizer->data < tokenizer->end && is_whitespace (*tokenizer->data));
+
+ if (*tokenizer->data == ')')
+ {
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ break;
+ }
+ else if (tokenizer->data >= tokenizer->end)
+ {
+ break;
+ }
+ else
+ {
+ gtk_css_tokenizer_read_bad_url (tokenizer, token);
+ gtk_css_tokenizer_parse_error (error, "Whitespace only allowed at start and end of url");
+ return FALSE;
+ }
+ }
+ else if (is_non_printable (*tokenizer->data))
+ {
+ gtk_css_tokenizer_read_bad_url (tokenizer, token);
+ g_string_free (url, TRUE);
+ gtk_css_tokenizer_parse_error (error, "Nonprintable character 0x%02X in url", *tokenizer->data);
+ return FALSE;
+ }
+ else if (*tokenizer->data == '"' ||
+ *tokenizer->data == '\'' ||
+ *tokenizer->data == '(')
+ {
+ gtk_css_tokenizer_read_bad_url (tokenizer, token);
+ gtk_css_tokenizer_parse_error (error, "Invalid character %c in url", *tokenizer->data);
+ g_string_free (url, TRUE);
+ return FALSE;
+ }
+ else if (gtk_css_tokenizer_has_valid_escape (tokenizer))
+ {
+ g_string_append_unichar (url, gtk_css_tokenizer_read_escape (tokenizer));
+ }
+ else if (*tokenizer->data == '\\')
+ {
+ gtk_css_tokenizer_read_bad_url (tokenizer, token);
+ gtk_css_tokenizer_parse_error (error, "Newline may not follow '\' escape character");
+ g_string_free (url, TRUE);
+ return FALSE;
+ }
+ else
+ {
+ gtk_css_tokenizer_consume_char (tokenizer, url);
+ }
+ }
+
+ gtk_css_token_init_string (token, GTK_CSS_TOKEN_URL, g_string_free (url, FALSE));
+
+ return TRUE;
+}
+
+static gboolean
+gtk_css_tokenizer_read_ident_like (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token,
+ GError **error)
+{
+ char *name = gtk_css_tokenizer_read_name (tokenizer);
+
+ if (*tokenizer->data == '(')
+ {
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ if (g_ascii_strcasecmp (name, "url") == 0)
+ {
+ const char *data = tokenizer->data;
+
+ while (is_whitespace (*data))
+ data++;
+
+ if (*data != '"' && *data != '\'')
+ {
+ g_free (name);
+ return gtk_css_tokenizer_read_url (tokenizer, token, error);
+ }
+ }
+
+ gtk_css_token_init_string (token, GTK_CSS_TOKEN_FUNCTION, name);
+ return TRUE;
+ }
+ else
+ {
+ gtk_css_token_init_string (token, GTK_CSS_TOKEN_IDENT, name);
+ return TRUE;
+ }
+}
+
+static void
+gtk_css_tokenizer_read_numeric (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token)
+{
+ int sign = 1, exponent_sign = 1;
+ gint64 integer, fractional = 0, fractional_length = 1, exponent = 0;
+ gboolean is_int = TRUE, has_sign = FALSE;
+ const char *data = tokenizer->data;
+ double value;
+
+ if (*data == '-')
+ {
+ has_sign = TRUE;
+ sign = -1;
+ data++;
+ }
+ else if (*data == '+')
+ {
+ has_sign = TRUE;
+ data++;
+ }
+
+ for (integer = 0; data < tokenizer->end && g_ascii_isdigit (*data); data++)
+ {
+ /* check for overflow here? */
+ integer = 10 * integer + g_ascii_digit_value (*data);
+ }
+
+ if (data + 1 < tokenizer->end && *data == '.' && g_ascii_isdigit (data[1]))
+ {
+ is_int = FALSE;
+ data++;
+
+ fractional = g_ascii_digit_value (*data);
+ fractional_length = 10;
+ data++;
+
+ while (data < tokenizer->end && g_ascii_isdigit (*data))
+ {
+ if (fractional_length < G_MAXINT64 / 10)
+ {
+ fractional = 10 * fractional + g_ascii_digit_value (*data);
+ fractional_length *= 10;
+ }
+ data++;
+ }
+ }
+
+ if (data + 1 < tokenizer->end && (*data == 'e' || *data == 'E') &&
+ (g_ascii_isdigit (data[1]) ||
+ (data + 2 < tokenizer->end && (data[1] == '+' || data[1] == '-') && g_ascii_isdigit (data[2]))))
+ {
+ is_int = FALSE;
+ data++;
+
+ if (*data == '-')
+ {
+ exponent_sign = -1;
+ data++;
+ }
+ else if (*data == '+')
+ {
+ data++;
+ }
+
+ while (data < tokenizer->end && g_ascii_isdigit (*data))
+ {
+ exponent = 10 * exponent + g_ascii_digit_value (*data);
+ data++;
+ }
+ }
+
+ gtk_css_tokenizer_consume (tokenizer, data - tokenizer->data, data - tokenizer->data);
+
+ value = sign * (integer + ((double) fractional / fractional_length)) * pow (10, exponent_sign * exponent);
+
+ if (gtk_css_tokenizer_has_identifier (tokenizer))
+ {
+ GtkCssTokenType type;
+
+ if (is_int)
+ type = has_sign ? GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION : GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION;
+ else
+ type = has_sign ? GTK_CSS_TOKEN_SIGNED_DIMENSION : GTK_CSS_TOKEN_SIGNLESS_DIMENSION;
+
+ gtk_css_token_init_dimension (token, type, value, gtk_css_tokenizer_read_name (tokenizer));
+ }
+ else if (gtk_css_tokenizer_remaining (tokenizer) > 0 && *tokenizer->data == '%')
+ {
+ gtk_css_token_init_number (token, GTK_CSS_TOKEN_PERCENTAGE, value);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ }
+ else
+ {
+ GtkCssTokenType type;
+
+ if (is_int)
+ type = has_sign ? GTK_CSS_TOKEN_SIGNED_INTEGER : GTK_CSS_TOKEN_SIGNLESS_INTEGER;
+ else
+ type = has_sign ? GTK_CSS_TOKEN_SIGNED_NUMBER : GTK_CSS_TOKEN_SIGNLESS_NUMBER;
+
+ gtk_css_token_init_number (token, type,value);
+ }
+}
+
+static void
+gtk_css_tokenizer_read_delim (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token)
+{
+ gtk_css_token_init_delim (token, g_utf8_get_char (tokenizer->data));
+ gtk_css_tokenizer_consume_char (tokenizer, NULL);
+}
+
+static gboolean
+gtk_css_tokenizer_read_dash (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token,
+ GError **error)
+{
+ if (gtk_css_tokenizer_remaining (tokenizer) == 1)
+ {
+ gtk_css_tokenizer_read_delim (tokenizer, token);
+ return TRUE;
+ }
+ else if (gtk_css_tokenizer_has_number (tokenizer))
+ {
+ gtk_css_tokenizer_read_numeric (tokenizer, token);
+ return TRUE;
+ }
+ else if (gtk_css_tokenizer_remaining (tokenizer) >= 3 &&
+ tokenizer->data[1] == '-' &&
+ tokenizer->data[2] == '>')
+ {
+ gtk_css_token_init (token, GTK_CSS_TOKEN_CDC);
+ gtk_css_tokenizer_consume (tokenizer, 3, 3);
+ return TRUE;
+ }
+ else if (gtk_css_tokenizer_has_identifier (tokenizer))
+ {
+ return gtk_css_tokenizer_read_ident_like (tokenizer, token, error);
+ }
+ else
+ {
+ gtk_css_tokenizer_read_delim (tokenizer, token);
+ return TRUE;
+ }
+}
+
+static gboolean
+gtk_css_tokenizer_read_string (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token,
+ GError **error)
+{
+ GString *string = g_string_new (NULL);
+ char end = *tokenizer->data;
+
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+
+ while (tokenizer->data < tokenizer->end)
+ {
+ if (*tokenizer->data == end)
+ {
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ break;
+ }
+ else if (*tokenizer->data == '\\')
+ {
+ if (gtk_css_tokenizer_remaining (tokenizer) == 1)
+ {
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ break;
+ }
+ else if (is_newline (tokenizer->data[1]))
+ {
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ gtk_css_tokenizer_consume_newline (tokenizer);
+ }
+ else
+ {
+ g_string_append_unichar (string, gtk_css_tokenizer_read_escape (tokenizer));
+ }
+ }
+ else if (is_newline (*tokenizer->data))
+ {
+ g_string_free (string, TRUE);
+ gtk_css_token_init (token, GTK_CSS_TOKEN_BAD_STRING);
+ gtk_css_tokenizer_parse_error (error, "Newlines inside strings must be escaped");
+ return FALSE;
+ }
+ else
+ {
+ gtk_css_tokenizer_consume_char (tokenizer, string);
+ }
+ }
+
+ gtk_css_token_init_string (token, GTK_CSS_TOKEN_STRING, g_string_free (string, FALSE));
+
+ return TRUE;
+}
+
+static gboolean
+gtk_css_tokenizer_read_comment (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token,
+ GError **error)
+{
+ gtk_css_tokenizer_consume (tokenizer, 2, 2);
+
+ while (tokenizer->data < tokenizer->end)
+ {
+ if (gtk_css_tokenizer_remaining (tokenizer) > 1 &&
+ tokenizer->data[0] == '*' && tokenizer->data[1] == '/')
+ {
+ gtk_css_tokenizer_consume (tokenizer, 2, 2);
+ gtk_css_token_init (token, GTK_CSS_TOKEN_COMMENT);
+ return TRUE;
+ }
+ gtk_css_tokenizer_consume_char (tokenizer, NULL);
+ }
+
+ gtk_css_token_init (token, GTK_CSS_TOKEN_COMMENT);
+ gtk_css_tokenizer_parse_error (error, "Comment not terminated at end of document.");
+ return FALSE;
+}
+
+static void
+gtk_css_tokenizer_read_match (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token,
+ GtkCssTokenType type)
+{
+ if (gtk_css_tokenizer_remaining (tokenizer) > 1 && tokenizer->data[1] == '=')
+ {
+ gtk_css_token_init (token, type);
+ gtk_css_tokenizer_consume (tokenizer, 2, 2);
+ }
+ else
+ {
+ gtk_css_tokenizer_read_delim (tokenizer, token);
+ }
+}
+
+gboolean
+gtk_css_tokenizer_read_token (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token,
+ GError **error)
+{
+ if (tokenizer->data == tokenizer->end)
+ {
+ gtk_css_token_init (token, GTK_CSS_TOKEN_EOF);
+ return TRUE;
+ }
+
+ if (tokenizer->data[0] == '/' && gtk_css_tokenizer_remaining (tokenizer) > 1 &&
+ tokenizer->data[1] == '*')
+ return gtk_css_tokenizer_read_comment (tokenizer, token, error);
+
+ switch (*tokenizer->data)
+ {
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\f':
+ case ' ':
+ gtk_css_tokenizer_read_whitespace (tokenizer, token);
+ return TRUE;
+
+ case '"':
+ return gtk_css_tokenizer_read_string (tokenizer, token, error);
+
+ case '#':
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ if (is_name (*tokenizer->data) || gtk_css_tokenizer_has_valid_escape (tokenizer))
+ {
+ GtkCssTokenType type;
+
+ if (gtk_css_tokenizer_has_identifier (tokenizer))
+ type = GTK_CSS_TOKEN_HASH_ID;
+ else
+ type = GTK_CSS_TOKEN_HASH_UNRESTRICTED;
+
+ gtk_css_token_init_string (token,
+ type,
+ gtk_css_tokenizer_read_name (tokenizer));
+ }
+ else
+ {
+ gtk_css_token_init_delim (token, '#');
+ }
+ return TRUE;
+
+ case '$':
+ gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_SUFFIX_MATCH);
+ return TRUE;
+
+ case '\'':
+ return gtk_css_tokenizer_read_string (tokenizer, token, error);
+
+ case '(':
+ gtk_css_token_init (token, GTK_CSS_TOKEN_OPEN_PARENS);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ return TRUE;
+
+ case ')':
+ gtk_css_token_init (token, GTK_CSS_TOKEN_CLOSE_PARENS);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ return TRUE;
+
+ case '*':
+ gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_SUBSTRING_MATCH);
+ return TRUE;
+
+ case '+':
+ if (gtk_css_tokenizer_has_number (tokenizer))
+ gtk_css_tokenizer_read_numeric (tokenizer, token);
+ else
+ gtk_css_tokenizer_read_delim (tokenizer, token);
+ return TRUE;
+
+ case ',':
+ gtk_css_token_init (token, GTK_CSS_TOKEN_COMMA);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ return TRUE;
+
+ case '-':
+ return gtk_css_tokenizer_read_dash (tokenizer, token, error);
+
+ case '.':
+ if (gtk_css_tokenizer_has_number (tokenizer))
+ gtk_css_tokenizer_read_numeric (tokenizer, token);
+ else
+ gtk_css_tokenizer_read_delim (tokenizer, token);
+ return TRUE;
+
+ case ':':
+ gtk_css_token_init (token, GTK_CSS_TOKEN_COLON);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ return TRUE;
+
+ case ';':
+ gtk_css_token_init (token, GTK_CSS_TOKEN_SEMICOLON);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ return TRUE;
+
+ case '<':
+ if (gtk_css_tokenizer_remaining (tokenizer) >= 4 &&
+ tokenizer->data[1] == '!' &&
+ tokenizer->data[2] == '-' &&
+ tokenizer->data[3] == '-')
+ {
+ gtk_css_token_init (token, GTK_CSS_TOKEN_CDO);
+ gtk_css_tokenizer_consume (tokenizer, 4, 4);
+ }
+ else
+ {
+ gtk_css_tokenizer_read_delim (tokenizer, token);
+ }
+ return TRUE;
+
+ case '@':
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ if (gtk_css_tokenizer_has_identifier (tokenizer))
+ {
+ gtk_css_token_init_string (token,
+ GTK_CSS_TOKEN_AT_KEYWORD,
+ gtk_css_tokenizer_read_name (tokenizer));
+ }
+ else
+ {
+ gtk_css_token_init_delim (token, '@');
+ }
+ return TRUE;
+
+ case '[':
+ gtk_css_token_init (token, GTK_CSS_TOKEN_OPEN_SQUARE);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ return TRUE;
+
+ case '\\':
+ if (gtk_css_tokenizer_has_valid_escape (tokenizer))
+ {
+ return gtk_css_tokenizer_read_ident_like (tokenizer, token, error);
+ }
+ else
+ {
+ gtk_css_token_init_delim (token, '\\');
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ gtk_css_tokenizer_parse_error (error, "Newline may not follow '\' escape character");
+ return FALSE;
+ }
+
+ case ']':
+ gtk_css_token_init (token, GTK_CSS_TOKEN_CLOSE_SQUARE);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ return TRUE;
+
+ case '^':
+ gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_PREFIX_MATCH);
+ return TRUE;
+
+ case '{':
+ gtk_css_token_init (token, GTK_CSS_TOKEN_OPEN_CURLY);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ return TRUE;
+
+ case '}':
+ gtk_css_token_init (token, GTK_CSS_TOKEN_CLOSE_CURLY);
+ gtk_css_tokenizer_consume_ascii (tokenizer);
+ return TRUE;
+
+ case '|':
+ if (gtk_css_tokenizer_remaining (tokenizer) > 1 && tokenizer->data[1] == '|')
+ {
+ gtk_css_token_init (token, GTK_CSS_TOKEN_COLUMN);
+ gtk_css_tokenizer_consume (tokenizer, 2, 2);
+ }
+ else
+ {
+ gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_DASH_MATCH);
+ }
+ return TRUE;
+
+ case '~':
+ gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_INCLUDE_MATCH);
+ return TRUE;
+
+ default:
+ if (g_ascii_isdigit (*tokenizer->data))
+ {
+ gtk_css_tokenizer_read_numeric (tokenizer, token);
+ return TRUE;
+ }
+ else if (is_name_start (*tokenizer->data))
+ {
+ return gtk_css_tokenizer_read_ident_like (tokenizer, token, error);
+ }
+ else
+ {
+ gtk_css_tokenizer_read_delim (tokenizer, token);
+ return TRUE;
+ }
+ }
+}
+
diff --git a/pango/css/gtkcsstokenizerprivate.h b/pango/css/gtkcsstokenizerprivate.h
new file mode 100644
index 00000000..c490d2a0
--- /dev/null
+++ b/pango/css/gtkcsstokenizerprivate.h
@@ -0,0 +1,141 @@
+/* GSK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_CSS_TOKENIZER_PRIVATE_H__
+#define __GTK_CSS_TOKENIZER_PRIVATE_H__
+
+#include <glib.h>
+
+#include <pango/css/gtkcsslocation.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ /* no content */
+ GTK_CSS_TOKEN_EOF,
+ GTK_CSS_TOKEN_WHITESPACE,
+ GTK_CSS_TOKEN_OPEN_PARENS,
+ GTK_CSS_TOKEN_CLOSE_PARENS,
+ GTK_CSS_TOKEN_OPEN_SQUARE,
+ GTK_CSS_TOKEN_CLOSE_SQUARE,
+ GTK_CSS_TOKEN_OPEN_CURLY,
+ GTK_CSS_TOKEN_CLOSE_CURLY,
+ GTK_CSS_TOKEN_COMMA,
+ GTK_CSS_TOKEN_COLON,
+ GTK_CSS_TOKEN_SEMICOLON,
+ GTK_CSS_TOKEN_CDO,
+ GTK_CSS_TOKEN_CDC,
+ GTK_CSS_TOKEN_INCLUDE_MATCH,
+ GTK_CSS_TOKEN_DASH_MATCH,
+ GTK_CSS_TOKEN_PREFIX_MATCH,
+ GTK_CSS_TOKEN_SUFFIX_MATCH,
+ GTK_CSS_TOKEN_SUBSTRING_MATCH,
+ GTK_CSS_TOKEN_COLUMN,
+ GTK_CSS_TOKEN_BAD_STRING,
+ GTK_CSS_TOKEN_BAD_URL,
+ GTK_CSS_TOKEN_COMMENT,
+ /* delim */
+ GTK_CSS_TOKEN_DELIM,
+ /* string */
+ GTK_CSS_TOKEN_STRING,
+ GTK_CSS_TOKEN_IDENT,
+ GTK_CSS_TOKEN_FUNCTION,
+ GTK_CSS_TOKEN_AT_KEYWORD,
+ GTK_CSS_TOKEN_HASH_UNRESTRICTED,
+ GTK_CSS_TOKEN_HASH_ID,
+ GTK_CSS_TOKEN_URL,
+ /* number */
+ GTK_CSS_TOKEN_SIGNED_INTEGER,
+ GTK_CSS_TOKEN_SIGNLESS_INTEGER,
+ GTK_CSS_TOKEN_SIGNED_NUMBER,
+ GTK_CSS_TOKEN_SIGNLESS_NUMBER,
+ GTK_CSS_TOKEN_PERCENTAGE,
+ /* dimension */
+ GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION,
+ GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION,
+ GTK_CSS_TOKEN_SIGNED_DIMENSION,
+ GTK_CSS_TOKEN_SIGNLESS_DIMENSION
+} GtkCssTokenType;
+
+typedef union _GtkCssToken GtkCssToken;
+typedef struct _GtkCssTokenizer GtkCssTokenizer;
+
+typedef struct _GtkCssStringToken GtkCssStringToken;
+typedef struct _GtkCssDelimToken GtkCssDelimToken;
+typedef struct _GtkCssNumberToken GtkCssNumberToken;
+typedef struct _GtkCssDimensionToken GtkCssDimensionToken;
+
+struct _GtkCssStringToken {
+ GtkCssTokenType type;
+ char *string;
+};
+
+struct _GtkCssDelimToken {
+ GtkCssTokenType type;
+ gunichar delim;
+};
+
+struct _GtkCssNumberToken {
+ GtkCssTokenType type;
+ double number;
+};
+
+struct _GtkCssDimensionToken {
+ GtkCssTokenType type;
+ double value;
+ char *dimension;
+};
+
+union _GtkCssToken {
+ GtkCssTokenType type;
+ GtkCssStringToken string;
+ GtkCssDelimToken delim;
+ GtkCssNumberToken number;
+ GtkCssDimensionToken dimension;
+};
+
+void gtk_css_token_clear (GtkCssToken *token);
+
+gboolean gtk_css_token_is_finite (const GtkCssToken *token) G_GNUC_PURE;
+gboolean gtk_css_token_is_preserved (const GtkCssToken *token,
+ GtkCssTokenType *out_closing) G_GNUC_PURE;
+#define gtk_css_token_is(token, _type) ((token)->type == (_type))
+gboolean gtk_css_token_is_ident (const GtkCssToken *token,
+ const char *ident) G_GNUC_PURE;
+gboolean gtk_css_token_is_function (const GtkCssToken *token,
+ const char *ident) G_GNUC_PURE;
+gboolean gtk_css_token_is_delim (const GtkCssToken *token,
+ gunichar delim) G_GNUC_PURE;
+
+void gtk_css_token_print (const GtkCssToken *token,
+ GString *string);
+char * gtk_css_token_to_string (const GtkCssToken *token);
+
+GtkCssTokenizer * gtk_css_tokenizer_new (GBytes *bytes);
+
+GtkCssTokenizer * gtk_css_tokenizer_ref (GtkCssTokenizer *tokenizer);
+void gtk_css_tokenizer_unref (GtkCssTokenizer *tokenizer);
+
+const GtkCssLocation * gtk_css_tokenizer_get_location (GtkCssTokenizer *tokenizer) G_GNUC_CONST;
+
+gboolean gtk_css_tokenizer_read_token (GtkCssTokenizer *tokenizer,
+ GtkCssToken *token,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_TOKENIZER_PRIVATE_H__ */
diff --git a/pango/css/meson.build b/pango/css/meson.build
new file mode 100644
index 00000000..353724e7
--- /dev/null
+++ b/pango/css/meson.build
@@ -0,0 +1,54 @@
+gtk_css_public_sources = files([
+ 'gtkcsslocation.c',
+ 'gtkcsserror.c',
+ 'gtkcsssection.c',
+])
+
+gtk_css_private_sources = files([
+ 'gtkcssdataurl.c',
+ 'gtkcssparser.c',
+ 'gtkcsstokenizer.c',
+ 'gtkcssserializer.c',
+])
+
+gtk_css_public_headers = files([
+ 'gtkcssenums.h',
+ 'gtkcsserror.h',
+ 'gtkcsslocation.h',
+ 'gtkcsssection.h',
+])
+
+gtk_css_deps = [
+ libm,
+ glib_dep,
+ gobject_dep,
+ platform_gio_dep,
+]
+
+gtk_css_enums = gnome.mkenums('gtkcssenumtypes',
+ sources: gtk_css_public_headers,
+ c_template: 'gtkcssenumtypes.c.template',
+ h_template: 'gtkcssenumtypes.h.template',
+)
+
+gtk_css_enum_h = gtk_css_enums[1]
+
+libgtk_css = static_library('gtk_css',
+ sources: [
+ gtk_css_public_sources,
+ gtk_css_private_sources,
+ gtk_css_enums,
+ ],
+ dependencies: gtk_css_deps,
+ include_directories: [ confinc, ],
+ c_args: [
+ '-DGTK_COMPILATION',
+ '-DG_LOG_DOMAIN="Gtk"',
+ ] + common_cflags,
+)
+
+libgtk_css_dep = declare_dependency(include_directories: [ confinc, ],
+ sources: [ gtk_css_enum_h ],
+ dependencies: gtk_css_deps,
+ link_with: libgtk_css,
+)
diff --git a/pango/meson.build b/pango/meson.build
index 7c7bb280..dfa1720b 100644
--- a/pango/meson.build
+++ b/pango/meson.build
@@ -1,3 +1,35 @@
+# Maths functions might be implemented in libm
+libm = cc.find_library('m', required: false)
+
+glib_req = glib_req_version
+confinc = root_inc
+os_unix = false
+os_win32 = false
+
+if host_machine.system() == 'windows'
+ os_win32 = true
+else
+ os_unix = true
+endif
+
+if os_unix
+ giounix_dep = dependency('gio-unix-2.0', version: glib_req, required: false,
+ fallback : ['glib', 'libgio_dep'])
+endif
+if os_win32
+ giowin32_dep = dependency('gio-windows-2.0', version: glib_req, required: win32_enabled,
+ fallback : ['glib', 'libgio_dep'])
+endif
+
+if os_win32
+ platform_gio_dep = giowin32_dep
+endif
+if os_unix
+ platform_gio_dep = giounix_dep
+endif
+
+subdir('css')
+
pango_sources = [
'break.c',
'ellipsize.c',
@@ -117,7 +149,7 @@ libpango = library(
soversion: pango_soversion,
darwin_versions : pango_osxversion,
install: true,
- dependencies: pango_deps,
+ dependencies: [pango_deps, libgtk_css_dep],
include_directories: [ root_inc, pango_inc ],
c_args: common_cflags + pango_debug_cflags + pango_cflags,
link_args: common_ldflags,