482 lines
13 KiB
C
482 lines
13 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* tips-parser.c - Parse the pika-tips.xml file.
|
|
* Copyright (C) 2002, 2008 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 3 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, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <gio/gio.h>
|
|
|
|
#include "config/config-types.h"
|
|
#include "config/pikaxmlparser.h"
|
|
|
|
#include "tips-parser.h"
|
|
|
|
#include "pika-intl.h"
|
|
|
|
|
|
typedef enum
|
|
{
|
|
TIPS_START,
|
|
TIPS_IN_TIPS,
|
|
TIPS_IN_TIP,
|
|
TIPS_IN_THETIP,
|
|
TIPS_IN_UNKNOWN
|
|
} TipsParserState;
|
|
|
|
typedef enum
|
|
{
|
|
TIPS_LOCALE_NONE,
|
|
TIPS_LOCALE_MATCH,
|
|
TIPS_LOCALE_MISMATCH
|
|
} TipsParserLocaleState;
|
|
|
|
typedef struct
|
|
{
|
|
TipsParserState state;
|
|
TipsParserState last_known_state;
|
|
const gchar *locale;
|
|
const gchar *help_id;
|
|
TipsParserLocaleState locale_state;
|
|
gint markup_depth;
|
|
gint unknown_depth;
|
|
GString *value;
|
|
PikaTip *current_tip;
|
|
GList *tips;
|
|
} TipsParser;
|
|
|
|
|
|
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 gchar * tips_parser_parse_help_id (TipsParser *parser,
|
|
const gchar **names,
|
|
const gchar **values);
|
|
|
|
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 */
|
|
};
|
|
|
|
|
|
PikaTip *
|
|
pika_tip_new (const gchar *title,
|
|
const gchar *format,
|
|
...)
|
|
{
|
|
PikaTip *tip = g_slice_new0 (PikaTip);
|
|
GString *str = g_string_new (NULL);
|
|
|
|
if (title)
|
|
{
|
|
g_string_append (str, "<b>");
|
|
g_string_append (str, title);
|
|
g_string_append (str, "</b>");
|
|
|
|
if (format)
|
|
g_string_append (str, "\n\n");
|
|
}
|
|
|
|
if (format)
|
|
{
|
|
va_list args;
|
|
|
|
va_start (args, format);
|
|
g_string_append_vprintf (str, format, args);
|
|
va_end (args);
|
|
}
|
|
|
|
tip->text = g_string_free (str, FALSE);
|
|
|
|
return tip;
|
|
}
|
|
|
|
void
|
|
pika_tip_free (PikaTip *tip)
|
|
{
|
|
if (! tip)
|
|
return;
|
|
|
|
g_free (tip->text);
|
|
g_free (tip->help_id);
|
|
|
|
g_slice_free (PikaTip, tip);
|
|
}
|
|
|
|
/**
|
|
* pika_tips_from_file:
|
|
* @file: the tips file to parse
|
|
* @error: return location for a #GError
|
|
*
|
|
* Reads a pika-tips XML file, creates a new #PikaTip 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.
|
|
*
|
|
* Returns: a #Glist of #PikaTips.
|
|
**/
|
|
GList *
|
|
pika_tips_from_file (GFile *file,
|
|
GError **error)
|
|
{
|
|
PikaXmlParser *xml_parser;
|
|
TipsParser parser = { 0, };
|
|
const gchar *tips_locale;
|
|
GList *tips = NULL;
|
|
|
|
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
parser.value = g_string_new (NULL);
|
|
|
|
/* This is a special string to specify the language identifier to
|
|
look for in the pika-tips.xml file. Please translate the C in it
|
|
according to the name of the po file used for pika-tips.xml.
|
|
E.g. for the german translation, that would be "tips-locale:de".
|
|
*/
|
|
tips_locale = _("tips-locale:C");
|
|
|
|
if (g_str_has_prefix (tips_locale, "tips-locale:"))
|
|
{
|
|
tips_locale += strlen ("tips-locale:");
|
|
|
|
if (*tips_locale && *tips_locale != 'C')
|
|
parser.locale = tips_locale;
|
|
}
|
|
else
|
|
{
|
|
g_warning ("Wrong translation for 'tips-locale:', fix the translation!");
|
|
}
|
|
|
|
xml_parser = pika_xml_parser_new (&markup_parser, &parser);
|
|
|
|
pika_xml_parser_parse_gfile (xml_parser, file, error);
|
|
|
|
pika_xml_parser_free (xml_parser);
|
|
|
|
tips = g_list_reverse (parser.tips);
|
|
|
|
pika_tip_free (parser.current_tip);
|
|
g_string_free (parser.value, TRUE);
|
|
|
|
return tips;
|
|
}
|
|
|
|
void
|
|
pika_tips_free (GList *tips)
|
|
{
|
|
GList *list;
|
|
|
|
for (list = tips; list; list = list->next)
|
|
pika_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 = user_data;
|
|
|
|
switch (parser->state)
|
|
{
|
|
case TIPS_START:
|
|
if (strcmp (element_name, "pika-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_slice_new0 (PikaTip);
|
|
parser->current_tip->help_id = tips_parser_parse_help_id (parser,
|
|
attribute_names,
|
|
attribute_values);
|
|
}
|
|
else
|
|
{
|
|
tips_parser_start_unknown (parser);
|
|
}
|
|
break;
|
|
|
|
case TIPS_IN_TIP:
|
|
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_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 = user_data;
|
|
|
|
switch (parser->state)
|
|
{
|
|
case TIPS_START:
|
|
g_warning ("%s: shouldn't get here", G_STRLOC);
|
|
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_THETIP:
|
|
if (parser->markup_depth == 0)
|
|
{
|
|
tips_parser_set_by_locale (parser, &parser->current_tip->text);
|
|
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 = user_data;
|
|
|
|
switch (parser->state)
|
|
{
|
|
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)
|
|
{
|
|
pika_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)
|
|
{
|
|
pika_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 gchar *
|
|
tips_parser_parse_help_id (TipsParser *parser,
|
|
const gchar **names,
|
|
const gchar **values)
|
|
{
|
|
while (*names && *values)
|
|
{
|
|
if (strcmp (*names, "help") == 0 && **values)
|
|
return g_strdup (*values);
|
|
|
|
names++;
|
|
values++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|