gimp/app/gui/tips-parser.c

435 lines
12 KiB
C

/* The GIMP -- an image manipulation program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* tips-parser.c -- Parse the gimp-tips.xml file.
* Copyright (C) 2002 Sven Neumann <sven@gimp.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <string.h>
#include <glib.h>
#include "config/config-types.h"
#include "config/gimpxmlparser.h"
#include "tips-parser.h"
#include "gimp-intl.h"
typedef enum
{
TIPS_START,
TIPS_IN_TIPS,
TIPS_IN_TIP,
TIPS_IN_WELCOME,
TIPS_IN_THETIP,
TIPS_IN_UNKNOWN
} TipsParserState;
typedef enum
{
TIPS_LOCALE_NONE,
TIPS_LOCALE_MATCH,
TIPS_LOCALE_MISMATCH,
} TipsParserLocaleState;
typedef struct _TipsParser TipsParser;
struct _TipsParser
{
TipsParserState state;
TipsParserState last_known_state;
const gchar *locale;
TipsParserLocaleState locale_state;
gint markup_depth;
gint unknown_depth;
GString *value;
GimpTip *current_tip;
GList *tips;
};
static void tips_parser_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error);
static void tips_parser_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error);
static void tips_parser_characters (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error);
static void tips_parser_start_markup (TipsParser *parser,
const gchar *markup_name);
static void tips_parser_end_markup (TipsParser *parser,
const gchar *markup_name);
static void tips_parser_start_unknown (TipsParser *parser);
static void tips_parser_end_unknown (TipsParser *parser);
static void tips_parser_parse_locale (TipsParser *parser,
const gchar **names,
const gchar **values);
static void tips_parser_set_by_locale (TipsParser *parser,
gchar **dest);
static const GMarkupParser markup_parser =
{
tips_parser_start_element,
tips_parser_end_element,
tips_parser_characters,
NULL, /* passthrough */
NULL /* error */
};
GimpTip *
gimp_tip_new (const gchar *welcome,
const gchar *thetip)
{
GimpTip *tip = g_new (GimpTip, 1);
tip->welcome = welcome ? g_strdup (welcome) : NULL;
tip->thetip = thetip ? g_strdup (thetip) : NULL;
return tip;
}
void
gimp_tip_free (GimpTip *tip)
{
if (!tip)
return;
g_free (tip->welcome);
g_free (tip->thetip);
g_free (tip);
}
/**
* gimp_tips_from_file:
* @filename: the name of the tips file to parse
* @error: return location for a #GError
*
* Reads a gimp-tips XML file, creates a new #GimpTip for
* each tip entry and returns a #GList of them. If a parser
* error occurs at some point, the uncompleted list is
* returned and @error is set (unless @error is %NULL).
* The message set in @error contains a detailed description
* of the problem.
*
* Return value: a #Glist of #GimpTips.
**/
GList *
gimp_tips_from_file (const gchar *filename,
GError **error)
{
GimpXmlParser *xml_parser;
TipsParser *parser;
const gchar *tips_locale;
GList *tips = NULL;
g_return_val_if_fail (filename != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
parser = g_new0 (TipsParser, 1);
parser->value = g_string_new (NULL);
/* This is a special string to specify the language identifier to
look for in the gimp-tips.xml file. Please translate the C in it
according to the name of the po file used for gimp-tips.xml.
E.g. for the german translation, that would be "tips-locale:de".
*/
tips_locale = _("tips-locale:C");
if (strncmp (tips_locale, "tips-locale:", 12) == 0)
{
tips_locale += 12;
if (*tips_locale && *tips_locale != 'C')
parser->locale = tips_locale;
}
else
g_warning ("Wrong translation for 'tips-locale:', fix the translation!");
xml_parser = gimp_xml_parser_new (&markup_parser, parser);
gimp_xml_parser_parse_file (xml_parser, filename, error);
gimp_xml_parser_free (xml_parser);
tips = g_list_reverse (parser->tips);
gimp_tip_free (parser->current_tip);
g_string_free (parser->value, TRUE);
g_free (parser);
return tips;
}
void
gimp_tips_free (GList *tips)
{
GList *list;
for (list = tips; list; list = list->next)
gimp_tip_free (list->data);
g_list_free (tips);
}
static void
tips_parser_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error)
{
TipsParser *parser = (TipsParser *) user_data;
switch (parser->state)
{
case TIPS_START:
if (strcmp (element_name, "gimp-tips") == 0)
parser->state = TIPS_IN_TIPS;
else
tips_parser_start_unknown (parser);
break;
case TIPS_IN_TIPS:
if (strcmp (element_name, "tip") == 0)
{
parser->state = TIPS_IN_TIP;
parser->current_tip = g_new0 (GimpTip, 1);
}
else
tips_parser_start_unknown (parser);
break;
case TIPS_IN_TIP:
if (strcmp (element_name, "welcome") == 0)
{
parser->state = TIPS_IN_WELCOME;
tips_parser_parse_locale (parser, attribute_names, attribute_values);
}
else if (strcmp (element_name, "thetip") == 0)
{
parser->state = TIPS_IN_THETIP;
tips_parser_parse_locale (parser, attribute_names, attribute_values);
}
else
tips_parser_start_unknown (parser);
break;
case TIPS_IN_WELCOME:
case TIPS_IN_THETIP:
if (strcmp (element_name, "b" ) == 0 ||
strcmp (element_name, "big") == 0 ||
strcmp (element_name, "tt" ) == 0)
tips_parser_start_markup (parser, element_name);
else
tips_parser_start_unknown (parser);
break;
case TIPS_IN_UNKNOWN:
tips_parser_start_unknown (parser);
break;
}
}
static void
tips_parser_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error)
{
TipsParser *parser = (TipsParser *) user_data;
switch (parser->state)
{
case TIPS_START:
g_warning ("tips_parser: This shouldn't happen.\n");
break;
case TIPS_IN_TIPS:
parser->state = TIPS_START;
break;
case TIPS_IN_TIP:
parser->tips = g_list_prepend (parser->tips, parser->current_tip);
parser->current_tip = NULL;
parser->state = TIPS_IN_TIPS;
break;
case TIPS_IN_WELCOME:
if (parser->markup_depth == 0)
{
tips_parser_set_by_locale (parser, &parser->current_tip->welcome);
g_string_truncate (parser->value, 0);
parser->state = TIPS_IN_TIP;
}
else
tips_parser_end_markup (parser, element_name);
break;
case TIPS_IN_THETIP:
if (parser->markup_depth == 0)
{
tips_parser_set_by_locale (parser, &parser->current_tip->thetip);
g_string_truncate (parser->value, 0);
parser->state = TIPS_IN_TIP;
}
else
tips_parser_end_markup (parser, element_name);
break;
case TIPS_IN_UNKNOWN:
tips_parser_end_unknown (parser);
break;
}
}
static void
tips_parser_characters (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
TipsParser *parser = (TipsParser *) user_data;
switch (parser->state)
{
case TIPS_IN_WELCOME:
case TIPS_IN_THETIP:
if (parser->locale_state != TIPS_LOCALE_MISMATCH)
{
gint i;
/* strip tabs, newlines and adjacent whitespace */
for (i = 0; i < text_len; i++)
{
if (text[i] != ' ' &&
text[i] != '\t' && text[i] != '\n' && text[i] != '\r')
g_string_append_c (parser->value, text[i]);
else if (parser->value->len > 0 &&
parser->value->str[parser->value->len - 1] != ' ')
g_string_append_c (parser->value, ' ');
}
}
break;
default:
break;
}
}
static void
tips_parser_start_markup (TipsParser *parser,
const gchar *markup_name)
{
parser->markup_depth++;
g_string_append_printf (parser->value, "<%s>", markup_name);
}
static void
tips_parser_end_markup (TipsParser *parser,
const gchar *markup_name)
{
g_assert (parser->markup_depth > 0);
parser->markup_depth--;
g_string_append_printf (parser->value, "</%s>", markup_name);
}
static void
tips_parser_start_unknown (TipsParser *parser)
{
if (parser->unknown_depth == 0)
parser->last_known_state = parser->state;
parser->state = TIPS_IN_UNKNOWN;
parser->unknown_depth++;
}
static void
tips_parser_end_unknown (TipsParser *parser)
{
g_assert (parser->unknown_depth > 0 && parser->state == TIPS_IN_UNKNOWN);
parser->unknown_depth--;
if (parser->unknown_depth == 0)
parser->state = parser->last_known_state;
}
static void
tips_parser_parse_locale (TipsParser *parser,
const gchar **names,
const gchar **values)
{
parser->locale_state = TIPS_LOCALE_NONE;
while (*names && *values)
{
if (strcmp (*names, "xml:lang") == 0 && **values)
{
parser->locale_state = (parser->locale &&
strcmp (*values, parser->locale) == 0 ?
TIPS_LOCALE_MATCH : TIPS_LOCALE_MISMATCH);
}
names++;
values++;
}
}
static void
tips_parser_set_by_locale (TipsParser *parser,
gchar **dest)
{
switch (parser->locale_state)
{
case TIPS_LOCALE_NONE:
if (!parser->locale)
{
g_free (*dest);
*dest = g_strdup (parser->value->str);
}
else if (*dest == NULL)
{
*dest = g_strdup (parser->value->str);
}
break;
case TIPS_LOCALE_MATCH:
g_free (*dest);
*dest = g_strdup (parser->value->str);
break;
case TIPS_LOCALE_MISMATCH:
break;
}
}