gtk2/gtk/gtkcssparser.c
Timm Bäder 9ef700b161 css parser: Reuse one GString
Instead of creating a new one for every ident, name and string, just
create one GString and reuse it. This means the GString we keep around
will grow to the maximum size of any ident, name or string we parse,
which is still not terribly large.
2018-01-02 08:10:06 +01:00

1085 lines
24 KiB
C

/* GTK - The GIMP Toolkit
* Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkcssparserprivate.h"
#include "gtkcssdimensionvalueprivate.h"
#include <errno.h>
#include <string.h>
/* just for the errors, yay! */
#include "gtkcssprovider.h"
#define NEWLINE_CHARS "\r\n"
#define WHITESPACE_CHARS "\f \t"
#define NMSTART "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
#define NMCHAR NMSTART "01234567890-_"
#define URLCHAR NMCHAR "!#$%&*~"
#define GTK_IS_CSS_PARSER(parser) ((parser) != NULL)
struct _GtkCssParser
{
const char *data;
GFile *file;
GtkCssParserErrorFunc error_func;
gpointer user_data;
const char *line_start;
guint line;
/* Use this for parsing identifiers, names and strings. */
GString *ident_str;
};
GtkCssParser *
_gtk_css_parser_new (const char *data,
GFile *file,
GtkCssParserErrorFunc error_func,
gpointer user_data)
{
GtkCssParser *parser;
g_return_val_if_fail (data != NULL, NULL);
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
parser = g_slice_new0 (GtkCssParser);
parser->data = data;
if (file)
parser->file = g_object_ref (file);
parser->error_func = error_func;
parser->user_data = user_data;
parser->line_start = data;
parser->line = 0;
parser->ident_str = NULL;
return parser;
}
void
_gtk_css_parser_free (GtkCssParser *parser)
{
g_return_if_fail (GTK_IS_CSS_PARSER (parser));
if (parser->file)
g_object_unref (parser->file);
if (parser->ident_str)
g_string_free (parser->ident_str, TRUE);
g_slice_free (GtkCssParser, parser);
}
gboolean
_gtk_css_parser_is_eof (GtkCssParser *parser)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
return *parser->data == 0;
}
gboolean
_gtk_css_parser_begins_with (GtkCssParser *parser,
char c)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
return *parser->data == c;
}
gboolean
_gtk_css_parser_has_prefix (GtkCssParser *parser,
const char *prefix)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
return g_ascii_strncasecmp (parser->data, prefix, strlen (prefix)) == 0;
}
guint
_gtk_css_parser_get_line (GtkCssParser *parser)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
return parser->line;
}
guint
_gtk_css_parser_get_position (GtkCssParser *parser)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
return parser->data - parser->line_start;
}
static GFile *
gtk_css_parser_get_base_file (GtkCssParser *parser)
{
GFile *base;
if (parser->file)
{
base = g_file_get_parent (parser->file);
}
else
{
char *dir = g_get_current_dir ();
base = g_file_new_for_path (dir);
g_free (dir);
}
return base;
}
GFile *
_gtk_css_parser_get_file_for_path (GtkCssParser *parser,
const char *path)
{
GFile *base, *file;
g_return_val_if_fail (parser != NULL, NULL);
g_return_val_if_fail (path != NULL, NULL);
base = gtk_css_parser_get_base_file (parser);
file = g_file_resolve_relative_path (base, path);
g_object_unref (base);
return file;
}
GFile *
_gtk_css_parser_get_file (GtkCssParser *parser)
{
g_return_val_if_fail (parser != NULL, NULL);
return parser->file;
}
void
_gtk_css_parser_take_error (GtkCssParser *parser,
GError *error)
{
parser->error_func (parser, error, parser->user_data);
g_error_free (error);
}
void
_gtk_css_parser_error (GtkCssParser *parser,
const char *format,
...)
{
GError *error;
va_list args;
va_start (args, format);
error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_ERROR_SYNTAX,
format, args);
va_end (args);
_gtk_css_parser_take_error (parser, error);
}
void
_gtk_css_parser_error_full (GtkCssParser *parser,
GtkCssProviderError code,
const char *format,
...)
{
GError *error;
va_list args;
va_start (args, format);
error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
code, format, args);
va_end (args);
_gtk_css_parser_take_error (parser, error);
}
static gboolean
gtk_css_parser_new_line (GtkCssParser *parser)
{
gboolean result = FALSE;
if (*parser->data == '\r')
{
result = TRUE;
parser->data++;
}
if (*parser->data == '\n')
{
result = TRUE;
parser->data++;
}
if (result)
{
parser->line++;
parser->line_start = parser->data;
}
return result;
}
static gboolean
gtk_css_parser_skip_comment (GtkCssParser *parser)
{
if (parser->data[0] != '/' ||
parser->data[1] != '*')
return FALSE;
parser->data += 2;
while (*parser->data)
{
gsize len = strcspn (parser->data, NEWLINE_CHARS "/");
parser->data += len;
if (gtk_css_parser_new_line (parser))
continue;
parser->data++;
if (len > 0 && parser->data[-2] == '*')
return TRUE;
if (parser->data[0] == '*')
_gtk_css_parser_error (parser, "'/*' in comment block");
}
/* FIXME: position */
_gtk_css_parser_error (parser, "Unterminated comment");
return TRUE;
}
void
_gtk_css_parser_skip_whitespace (GtkCssParser *parser)
{
size_t len;
while (*parser->data)
{
if (gtk_css_parser_new_line (parser))
continue;
len = strspn (parser->data, WHITESPACE_CHARS);
if (len)
{
parser->data += len;
continue;
}
if (!gtk_css_parser_skip_comment (parser))
break;
}
}
gboolean
_gtk_css_parser_try (GtkCssParser *parser,
const char *string,
gboolean skip_whitespace)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (string != NULL, FALSE);
if (g_ascii_strncasecmp (parser->data, string, strlen (string)) != 0)
return FALSE;
parser->data += strlen (string);
if (skip_whitespace)
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
static guint
get_xdigit (char c)
{
if (c >= 'a')
return c - 'a' + 10;
else if (c >= 'A')
return c - 'A' + 10;
else
return c - '0';
}
static void
_gtk_css_parser_unescape (GtkCssParser *parser,
GString *str)
{
guint i;
gunichar result = 0;
g_assert (*parser->data == '\\');
parser->data++;
for (i = 0; i < 6; i++)
{
if (!g_ascii_isxdigit (parser->data[i]))
break;
result = (result << 4) + get_xdigit (parser->data[i]);
}
if (i != 0)
{
g_string_append_unichar (str, result);
parser->data += i;
/* NB: gtk_css_parser_new_line() forward data pointer itself */
if (!gtk_css_parser_new_line (parser) &&
*parser->data &&
strchr (WHITESPACE_CHARS, *parser->data))
parser->data++;
return;
}
if (gtk_css_parser_new_line (parser))
return;
g_string_append_c (str, *parser->data);
parser->data++;
return;
}
static gboolean
_gtk_css_parser_read_char (GtkCssParser *parser,
GString * str,
const char * allowed)
{
if (*parser->data == 0)
return FALSE;
if (strchr (allowed, *parser->data))
{
g_string_append_c (str, *parser->data);
parser->data++;
return TRUE;
}
if (*parser->data >= 127)
{
gsize len = g_utf8_skip[(guint) *(guchar *) parser->data];
g_string_append_len (str, parser->data, len);
parser->data += len;
return TRUE;
}
if (*parser->data == '\\')
{
_gtk_css_parser_unescape (parser, str);
return TRUE;
}
return FALSE;
}
static char *
_gtk_css_parser_get_ident (GtkCssParser *parser)
{
char *result;
gsize len;
len = parser->ident_str->len;
result = g_new (char, len + 1);
memcpy (result, parser->ident_str->str, len + 1);
g_string_set_size (parser->ident_str, 0);
return result;
}
char *
_gtk_css_parser_try_name (GtkCssParser *parser,
gboolean skip_whitespace)
{
GString *name;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
if (parser->ident_str == NULL)
parser->ident_str = g_string_new (NULL);
name = parser->ident_str;
while (_gtk_css_parser_read_char (parser, name, NMCHAR))
;
if (skip_whitespace)
_gtk_css_parser_skip_whitespace (parser);
return _gtk_css_parser_get_ident (parser);
}
char *
_gtk_css_parser_try_ident (GtkCssParser *parser,
gboolean skip_whitespace)
{
const char *start;
GString *ident;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
start = parser->data;
if (parser->ident_str == NULL)
parser->ident_str = g_string_new (NULL);
ident = parser->ident_str;
if (*parser->data == '-')
{
g_string_append_c (ident, '-');
parser->data++;
}
if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
{
parser->data = start;
g_string_set_size (ident, 0);
return NULL;
}
while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
;
if (skip_whitespace)
_gtk_css_parser_skip_whitespace (parser);
return _gtk_css_parser_get_ident (parser);
}
gboolean
_gtk_css_parser_is_string (GtkCssParser *parser)
{
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
return *parser->data == '"' || *parser->data == '\'';
}
char *
_gtk_css_parser_read_string (GtkCssParser *parser)
{
GString *str;
char quote;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
quote = *parser->data;
if (quote != '"' && quote != '\'')
{
_gtk_css_parser_error (parser, "Expected a string.");
return NULL;
}
parser->data++;
if (parser->ident_str == NULL)
parser->ident_str = g_string_new (NULL);
str = parser->ident_str;
g_assert (str->len == 0);
while (TRUE)
{
gsize len = strcspn (parser->data, "\\'\"\n\r\f");
g_string_append_len (str, parser->data, len);
parser->data += len;
switch (*parser->data)
{
case '\\':
_gtk_css_parser_unescape (parser, str);
break;
case '"':
case '\'':
if (*parser->data == quote)
{
parser->data++;
_gtk_css_parser_skip_whitespace (parser);
return _gtk_css_parser_get_ident (parser);
}
g_string_append_c (str, *parser->data);
parser->data++;
break;
case '\0':
/* FIXME: position */
_gtk_css_parser_error (parser, "Missing end quote in string.");
g_string_set_size (str, 0);
return NULL;
default:
_gtk_css_parser_error (parser,
"Invalid character in string. Must be escaped.");
g_string_set_size (str, 0);
return NULL;
}
}
g_assert_not_reached ();
return NULL;
}
gboolean
_gtk_css_parser_try_int (GtkCssParser *parser,
int *value)
{
gint64 result;
char *end;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (value != NULL, FALSE);
/* strtoll parses a plus, but we are not allowed to */
if (*parser->data == '+')
return FALSE;
errno = 0;
result = g_ascii_strtoll (parser->data, &end, 10);
if (errno)
return FALSE;
if (result > G_MAXINT || result < G_MININT)
return FALSE;
if (parser->data == end)
return FALSE;
parser->data = end;
*value = result;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
gboolean
_gtk_css_parser_try_uint (GtkCssParser *parser,
guint *value)
{
guint64 result;
char *end;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (value != NULL, FALSE);
errno = 0;
result = g_ascii_strtoull (parser->data, &end, 10);
if (errno)
return FALSE;
if (result > G_MAXUINT)
return FALSE;
if (parser->data == end)
return FALSE;
parser->data = end;
*value = result;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
gboolean
_gtk_css_parser_try_double (GtkCssParser *parser,
gdouble *value)
{
gdouble result;
char *end;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (value != NULL, FALSE);
errno = 0;
result = g_ascii_strtod (parser->data, &end);
if (errno)
return FALSE;
if (parser->data == end)
return FALSE;
parser->data = end;
*value = result;
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
gboolean
_gtk_css_parser_has_number (GtkCssParser *parser)
{
char c;
if (parser->data[0] == '-' || parser->data[0] == '+')
c = parser->data[1];
else
c = parser->data[0];
/* ahem */
return g_ascii_isdigit (c) || c == '.';
}
GtkCssValue *
gtk_css_dimension_value_parse (GtkCssParser *parser,
GtkCssNumberParseFlags flags)
{
static const struct {
const char *name;
GtkCssUnit unit;
GtkCssNumberParseFlags required_flags;
} units[] = {
{ "px", GTK_CSS_PX, GTK_CSS_PARSE_LENGTH },
{ "pt", GTK_CSS_PT, GTK_CSS_PARSE_LENGTH },
{ "em", GTK_CSS_EM, GTK_CSS_PARSE_LENGTH },
{ "ex", GTK_CSS_EX, GTK_CSS_PARSE_LENGTH },
{ "rem", GTK_CSS_REM, GTK_CSS_PARSE_LENGTH },
{ "pc", GTK_CSS_PC, GTK_CSS_PARSE_LENGTH },
{ "in", GTK_CSS_IN, GTK_CSS_PARSE_LENGTH },
{ "cm", GTK_CSS_CM, GTK_CSS_PARSE_LENGTH },
{ "mm", GTK_CSS_MM, GTK_CSS_PARSE_LENGTH },
{ "rad", GTK_CSS_RAD, GTK_CSS_PARSE_ANGLE },
{ "deg", GTK_CSS_DEG, GTK_CSS_PARSE_ANGLE },
{ "grad", GTK_CSS_GRAD, GTK_CSS_PARSE_ANGLE },
{ "turn", GTK_CSS_TURN, GTK_CSS_PARSE_ANGLE },
{ "s", GTK_CSS_S, GTK_CSS_PARSE_TIME },
{ "ms", GTK_CSS_MS, GTK_CSS_PARSE_TIME }
};
char *end, *unit_name;
double value;
GtkCssUnit unit;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
errno = 0;
value = g_ascii_strtod (parser->data, &end);
if (errno)
{
_gtk_css_parser_error (parser, "not a number: %s", g_strerror (errno));
return NULL;
}
if (parser->data == end)
{
_gtk_css_parser_error (parser, "not a number");
return NULL;
}
parser->data = end;
if (flags & GTK_CSS_POSITIVE_ONLY &&
value < 0)
{
_gtk_css_parser_error (parser, "negative values are not allowed.");
return NULL;
}
unit_name = _gtk_css_parser_try_ident (parser, FALSE);
if (unit_name)
{
guint i;
for (i = 0; i < G_N_ELEMENTS (units); i++)
{
if (flags & units[i].required_flags &&
g_ascii_strcasecmp (unit_name, units[i].name) == 0)
break;
}
if (i >= G_N_ELEMENTS (units))
{
_gtk_css_parser_error (parser, "'%s' is not a valid unit.", unit_name);
g_free (unit_name);
return NULL;
}
unit = units[i].unit;
g_free (unit_name);
}
else
{
if ((flags & GTK_CSS_PARSE_PERCENT) &&
_gtk_css_parser_try (parser, "%", FALSE))
{
unit = GTK_CSS_PERCENT;
}
else if (value == 0.0)
{
if (flags & GTK_CSS_PARSE_NUMBER)
unit = GTK_CSS_NUMBER;
else if (flags & GTK_CSS_PARSE_LENGTH)
unit = GTK_CSS_PX;
else if (flags & GTK_CSS_PARSE_ANGLE)
unit = GTK_CSS_DEG;
else if (flags & GTK_CSS_PARSE_TIME)
unit = GTK_CSS_S;
else
unit = GTK_CSS_PERCENT;
}
else if (flags & GTK_CSS_PARSE_NUMBER)
{
unit = GTK_CSS_NUMBER;
}
else
{
_gtk_css_parser_error (parser, "Unit is missing.");
return NULL;
}
}
_gtk_css_parser_skip_whitespace (parser);
return gtk_css_dimension_value_new (value, unit);
}
/* XXX: we should introduce GtkCssLenght that deals with
* different kind of units */
gboolean
_gtk_css_parser_try_length (GtkCssParser *parser,
int *value)
{
if (!_gtk_css_parser_try_int (parser, value))
return FALSE;
/* FIXME: _try_uint skips spaces while the
* spec forbids them
*/
_gtk_css_parser_try (parser, "px", TRUE);
return TRUE;
}
gboolean
_gtk_css_parser_try_enum (GtkCssParser *parser,
GType enum_type,
int *value)
{
GEnumClass *enum_class;
gboolean result;
const char *start;
char *str;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
g_return_val_if_fail (value != NULL, FALSE);
result = FALSE;
enum_class = g_type_class_ref (enum_type);
start = parser->data;
str = _gtk_css_parser_try_ident (parser, TRUE);
if (str == NULL)
return FALSE;
if (enum_class->n_values)
{
GEnumValue *enum_value;
for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
{
if (enum_value->value_nick &&
g_ascii_strcasecmp (str, enum_value->value_nick) == 0)
{
*value = enum_value->value;
result = TRUE;
break;
}
}
}
g_free (str);
g_type_class_unref (enum_class);
if (!result)
parser->data = start;
return result;
}
gboolean
_gtk_css_parser_try_hash_color (GtkCssParser *parser,
GdkRGBA *rgba)
{
if (parser->data[0] == '#' &&
g_ascii_isxdigit (parser->data[1]) &&
g_ascii_isxdigit (parser->data[2]) &&
g_ascii_isxdigit (parser->data[3]))
{
if (g_ascii_isxdigit (parser->data[4]) &&
g_ascii_isxdigit (parser->data[5]) &&
g_ascii_isxdigit (parser->data[6]))
{
rgba->red = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
rgba->green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
rgba->blue = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
rgba->alpha = 1.0;
parser->data += 7;
}
else
{
rgba->red = get_xdigit (parser->data[1]) / 15.0;
rgba->green = get_xdigit (parser->data[2]) / 15.0;
rgba->blue = get_xdigit (parser->data[3]) / 15.0;
rgba->alpha = 1.0;
parser->data += 4;
}
_gtk_css_parser_skip_whitespace (parser);
return TRUE;
}
return FALSE;
}
GFile *
_gtk_css_parser_read_url (GtkCssParser *parser)
{
gchar *path;
char *scheme;
GFile *file;
if (_gtk_css_parser_try (parser, "url", FALSE))
{
if (!_gtk_css_parser_try (parser, "(", TRUE))
{
_gtk_css_parser_error (parser, "Expected '(' after 'url'");
return NULL;
}
path = _gtk_css_parser_read_string (parser);
if (path == NULL)
return NULL;
if (!_gtk_css_parser_try (parser, ")", TRUE))
{
_gtk_css_parser_error (parser, "No closing ')' found for 'url'");
g_free (path);
return NULL;
}
scheme = g_uri_parse_scheme (path);
if (scheme != NULL)
{
file = g_file_new_for_uri (path);
g_free (path);
g_free (scheme);
return file;
}
}
else
{
path = _gtk_css_parser_try_name (parser, TRUE);
if (path == NULL)
{
_gtk_css_parser_error (parser, "Not a valid url");
return NULL;
}
}
file = _gtk_css_parser_get_file_for_path (parser, path);
g_free (path);
return file;
}
static void
gtk_css_parser_resync_internal (GtkCssParser *parser,
gboolean sync_at_semicolon,
gboolean read_sync_token,
char terminator)
{
gsize len;
do {
len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
parser->data += len;
if (gtk_css_parser_new_line (parser))
continue;
if (_gtk_css_parser_is_string (parser))
{
/* Hrm, this emits errors, and i suspect it shouldn't... */
char *free_me = _gtk_css_parser_read_string (parser);
g_free (free_me);
continue;
}
if (gtk_css_parser_skip_comment (parser))
continue;
switch (*parser->data)
{
case '\\':
{
GString *ignore = g_string_new (NULL);
_gtk_css_parser_unescape (parser, ignore);
g_string_free (ignore, TRUE);
}
break;
case ';':
if (sync_at_semicolon && !read_sync_token)
return;
parser->data++;
if (sync_at_semicolon)
{
_gtk_css_parser_skip_whitespace (parser);
return;
}
break;
case '(':
parser->data++;
_gtk_css_parser_resync (parser, FALSE, ')');
if (*parser->data)
parser->data++;
break;
case '[':
parser->data++;
_gtk_css_parser_resync (parser, FALSE, ']');
if (*parser->data)
parser->data++;
break;
case '{':
parser->data++;
_gtk_css_parser_resync (parser, FALSE, '}');
if (*parser->data)
parser->data++;
if (sync_at_semicolon || !terminator)
{
_gtk_css_parser_skip_whitespace (parser);
return;
}
break;
case '}':
case ')':
case ']':
if (terminator == *parser->data)
{
_gtk_css_parser_skip_whitespace (parser);
return;
}
parser->data++;
continue;
case '\0':
break;
case '/':
default:
parser->data++;
break;
}
} while (*parser->data);
}
char *
_gtk_css_parser_read_value (GtkCssParser *parser)
{
const char *start;
char *result;
g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
start = parser->data;
/* This needs to be done better */
gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
result = g_strndup (start, parser->data - start);
if (result)
{
g_strchomp (result);
if (result[0] == 0)
{
g_free (result);
result = NULL;
}
}
if (result == NULL)
_gtk_css_parser_error (parser, "Expected a property value");
return result;
}
void
_gtk_css_parser_resync (GtkCssParser *parser,
gboolean sync_at_semicolon,
char terminator)
{
g_return_if_fail (GTK_IS_CSS_PARSER (parser));
gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);
}
void
_gtk_css_print_string (GString *str,
const char *string)
{
gsize len;
g_return_if_fail (str != NULL);
g_return_if_fail (string != NULL);
g_string_append_c (str, '"');
do {
len = strcspn (string, "\\\"\n\r\f");
g_string_append_len (str, string, len);
string += len;
switch (*string)
{
case '\0':
goto out;
case '\n':
g_string_append (str, "\\A ");
break;
case '\r':
g_string_append (str, "\\D ");
break;
case '\f':
g_string_append (str, "\\C ");
break;
case '\"':
g_string_append (str, "\\\"");
break;
case '\\':
g_string_append (str, "\\\\");
break;
default:
g_assert_not_reached ();
break;
}
string++;
} while (*string);
out:
g_string_append_c (str, '"');
}