620 lines
13 KiB
C
620 lines
13 KiB
C
/*
|
|
* xed-taglist-plugin-parser.c
|
|
* This file is part of xed
|
|
*
|
|
* Copyright (C) 2002-2005 - Paolo Maggi
|
|
*
|
|
* 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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/*
|
|
* Modified by the xed Team, 2002-2005. See the AUTHORS file for a
|
|
* list of people on the xed Team.
|
|
* See the ChangeLog files for a list of changes.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
/* FIXME: we should rewrite the parser to avoid using DOM */
|
|
|
|
#include <config.h>
|
|
#include <string.h>
|
|
#include <libxml/parser.h>
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <xed/xed-debug.h>
|
|
|
|
#include "xed-taglist-plugin-parser.h"
|
|
|
|
/* we screwed up so we still look here for compatibility */
|
|
#define USER_XED_TAGLIST_PLUGIN_LOCATION "xed/taglist/"
|
|
|
|
TagList* taglist = NULL;
|
|
static gint taglist_ref_count = 0;
|
|
|
|
static gboolean parse_tag (Tag *tag, xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur);
|
|
static gboolean parse_tag_group (TagGroup *tg, const gchar *fn,
|
|
xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur,
|
|
gboolean sort);
|
|
static TagGroup* get_tag_group (const gchar* filename, xmlDocPtr doc,
|
|
xmlNsPtr ns, xmlNodePtr cur);
|
|
static TagList* lookup_best_lang (TagList *taglist, const gchar *filename,
|
|
xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur);
|
|
static TagList *parse_taglist_file (const gchar* filename);
|
|
static TagList *parse_taglist_dir (const gchar *dir);
|
|
|
|
static void free_tag (Tag *tag);
|
|
static void free_tag_group (TagGroup *tag_group);
|
|
|
|
static gboolean
|
|
parse_tag (Tag *tag, xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur)
|
|
{
|
|
/*
|
|
xed_debug_message (DEBUG_PLUGINS, " Tag name: %s", tag->name);
|
|
*/
|
|
/* We don't care what the top level element name is */
|
|
cur = cur->xmlChildrenNode;
|
|
|
|
while (cur != NULL)
|
|
{
|
|
if ((!xmlStrcmp (cur->name, (const xmlChar *)"Begin")) &&
|
|
(cur->ns == ns))
|
|
{
|
|
tag->begin = xmlNodeListGetString (doc, cur->xmlChildrenNode, 1);
|
|
/*
|
|
xed_debug_message (DEBUG_PLUGINS, " - Begin: %s", tag->begin);
|
|
*/
|
|
}
|
|
|
|
if ((!xmlStrcmp (cur->name, (const xmlChar *)"End")) &&
|
|
(cur->ns == ns))
|
|
{
|
|
tag->end = xmlNodeListGetString (doc, cur->xmlChildrenNode, 1);
|
|
/*
|
|
xed_debug_message (DEBUG_PLUGINS, " - End: %s", tag->end);
|
|
*/
|
|
}
|
|
|
|
cur = cur->next;
|
|
}
|
|
|
|
if ((tag->begin == NULL) && (tag->end == NULL))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
tags_cmp (gconstpointer a, gconstpointer b)
|
|
{
|
|
gchar *tag_a = (gchar*)((Tag *)a)->name;
|
|
gchar *tag_b = (gchar*)((Tag *)b)->name;
|
|
|
|
return g_utf8_collate (tag_a, tag_b);
|
|
}
|
|
|
|
static gboolean
|
|
parse_tag_group (TagGroup *tg, const gchar* fn, xmlDocPtr doc,
|
|
xmlNsPtr ns, xmlNodePtr cur, gboolean sort)
|
|
{
|
|
xed_debug_message (DEBUG_PLUGINS, "Parse TagGroup: %s", tg->name);
|
|
|
|
/* We don't care what the top level element name is */
|
|
cur = cur->xmlChildrenNode;
|
|
|
|
while (cur != NULL)
|
|
{
|
|
if ((xmlStrcmp (cur->name, (const xmlChar *) "Tag")) || (cur->ns != ns))
|
|
{
|
|
g_warning ("The tag list file '%s' is of the wrong type, "
|
|
"was '%s', 'Tag' expected.", fn, cur->name);
|
|
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
Tag *tag;
|
|
|
|
tag = g_new0 (Tag, 1);
|
|
|
|
/* Get Tag name */
|
|
tag->name = xmlGetProp (cur, (const xmlChar *) "name");
|
|
|
|
if (tag->name == NULL)
|
|
{
|
|
/* Error: No name */
|
|
g_warning ("The tag list file '%s' is of the wrong type, "
|
|
"Tag without name.", fn);
|
|
|
|
g_free (tag);
|
|
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
/* Parse Tag */
|
|
if (parse_tag (tag, doc, ns, cur))
|
|
{
|
|
/* Prepend Tag to TagGroup */
|
|
tg->tags = g_list_prepend (tg->tags, tag);
|
|
}
|
|
else
|
|
{
|
|
/* Error parsing Tag */
|
|
g_warning ("The tag list file '%s' is of the wrong type, "
|
|
"error parsing Tag '%s' in TagGroup '%s'.",
|
|
fn, tag->name, tg->name);
|
|
|
|
free_tag (tag);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
cur = cur->next;
|
|
}
|
|
|
|
if (sort)
|
|
tg->tags = g_list_sort (tg->tags, tags_cmp);
|
|
else
|
|
tg->tags = g_list_reverse (tg->tags);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static TagGroup*
|
|
get_tag_group (const gchar* filename, xmlDocPtr doc,
|
|
xmlNsPtr ns, xmlNodePtr cur)
|
|
{
|
|
TagGroup *tag_group;
|
|
xmlChar *sort_str;
|
|
gboolean sort = FALSE;
|
|
|
|
tag_group = g_new0 (TagGroup, 1);
|
|
|
|
/* Get TagGroup name */
|
|
tag_group->name = xmlGetProp (cur, (const xmlChar *) "name");
|
|
|
|
sort_str = xmlGetProp (cur, (const xmlChar *) "sort");
|
|
|
|
if ((sort_str != NULL) &&
|
|
((xmlStrcasecmp (sort_str, (const xmlChar *) "yes") == 0) ||
|
|
(xmlStrcasecmp (sort_str, (const xmlChar *) "true") == 0) ||
|
|
(xmlStrcasecmp (sort_str, (const xmlChar *) "1") == 0)))
|
|
{
|
|
sort = TRUE;
|
|
}
|
|
|
|
xmlFree(sort_str);
|
|
|
|
if (tag_group->name == NULL)
|
|
{
|
|
/* Error: No name */
|
|
g_warning ("The tag list file '%s' is of the wrong type, "
|
|
"TagGroup without name.", filename);
|
|
|
|
g_free (tag_group);
|
|
}
|
|
else
|
|
{
|
|
/* Name found */
|
|
gboolean exists = FALSE;
|
|
GList *t = taglist->tag_groups;
|
|
|
|
/* Check if the tag group already exists */
|
|
while (t && !exists)
|
|
{
|
|
gchar *tgn = (gchar*)((TagGroup*)(t->data))->name;
|
|
|
|
if (strcmp (tgn, (gchar*)tag_group->name) == 0)
|
|
{
|
|
xed_debug_message (DEBUG_PLUGINS,
|
|
"Tag group '%s' already exists.", tgn);
|
|
|
|
exists = TRUE;
|
|
|
|
free_tag_group (tag_group);
|
|
}
|
|
|
|
t = g_list_next (t);
|
|
}
|
|
|
|
if (!exists)
|
|
{
|
|
/* Parse tag group */
|
|
if (parse_tag_group (tag_group, filename, doc, ns, cur, sort))
|
|
{
|
|
return tag_group;
|
|
}
|
|
else
|
|
{
|
|
/* Error parsing TagGroup */
|
|
g_warning ("The tag list file '%s' is of the wrong type, "
|
|
"error parsing TagGroup '%s'.",
|
|
filename, tag_group->name);
|
|
|
|
free_tag_group (tag_group);
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static gint
|
|
groups_cmp (gconstpointer a, gconstpointer b)
|
|
{
|
|
gchar *g_a = (gchar *)((TagGroup *)a)->name;
|
|
gchar *g_b = (gchar *)((TagGroup *)b)->name;
|
|
|
|
return g_utf8_collate (g_a, g_b);
|
|
}
|
|
|
|
/*
|
|
* tags file is localized by intltool-merge below.
|
|
*
|
|
* <xed:TagGroup name="XSLT - Elements">
|
|
* </xed:TagGroup>
|
|
* <xed:TagGroup xml:lang="am" name="LOCALIZED TEXT">
|
|
* </xed:TagGroup>
|
|
* <xed:TagGroup xml:lang="ar" name="LOCALIZED TEXT">
|
|
* </xed:TagGroup>
|
|
* .....
|
|
* <xed:TagGroup name="XSLT - Functions">
|
|
* </xed:TagGroup>
|
|
* .....
|
|
* Therefore need to pick up the best lang on the current locale.
|
|
*/
|
|
static TagList*
|
|
lookup_best_lang (TagList *taglist, const gchar *filename,
|
|
xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur)
|
|
{
|
|
|
|
TagGroup *best_tag_group = NULL;
|
|
TagGroup *tag_group;
|
|
gint best_lanking = -1;
|
|
|
|
/*
|
|
* Walk the tree.
|
|
*
|
|
* First level we expect a list TagGroup
|
|
*/
|
|
cur = cur->xmlChildrenNode;
|
|
|
|
while (cur != NULL)
|
|
{
|
|
if ((xmlStrcmp (cur->name, (const xmlChar *) "TagGroup")) || (cur->ns != ns))
|
|
{
|
|
g_warning ("The tag list file '%s' is of the wrong type, "
|
|
"was '%s', 'TagGroup' expected.", filename, cur->name);
|
|
xmlFreeDoc (doc);
|
|
|
|
return taglist;
|
|
}
|
|
else
|
|
{
|
|
const char * const *langs_pointer;
|
|
gchar *lang;
|
|
gint cur_lanking;
|
|
gint i;
|
|
|
|
langs_pointer = g_get_language_names ();
|
|
|
|
lang = (gchar*) xmlGetProp (cur, (const xmlChar*) "lang");
|
|
cur_lanking = 1;
|
|
|
|
/*
|
|
* When found a new TagGroup, prepend the best
|
|
* tag_group to taglist. In the current intltool-merge,
|
|
* the first section is the default lang NULL.
|
|
*/
|
|
if (lang == NULL) {
|
|
if (best_tag_group != NULL) {
|
|
taglist->tag_groups =
|
|
g_list_prepend (taglist->tag_groups, best_tag_group);
|
|
}
|
|
|
|
best_tag_group = NULL;
|
|
best_lanking = -1;
|
|
}
|
|
|
|
/*
|
|
* If already find the best TagGroup on the current
|
|
* locale, ignore the logic.
|
|
*/
|
|
if (best_lanking != -1 && best_lanking <= cur_lanking) {
|
|
cur = cur->next;
|
|
continue;
|
|
}
|
|
|
|
/* try to find the best lang */
|
|
for (i = 0; langs_pointer[i] != NULL; i++)
|
|
{
|
|
const gchar *best_lang = langs_pointer[i];
|
|
|
|
/*
|
|
* if launch on C, POSIX locale or does
|
|
* not find the best lang on the current locale,
|
|
* this is called.
|
|
* g_get_language_names returns lang
|
|
* lists with C locale.
|
|
*/
|
|
if (lang == NULL &&
|
|
(!g_ascii_strcasecmp (best_lang, "C") ||
|
|
!g_ascii_strcasecmp (best_lang, "POSIX")))
|
|
{
|
|
tag_group = get_tag_group (filename, doc, ns, cur);
|
|
if (tag_group != NULL)
|
|
{
|
|
if (best_tag_group !=NULL)
|
|
free_tag_group (best_tag_group);
|
|
best_lanking = cur_lanking;
|
|
best_tag_group = tag_group;
|
|
}
|
|
}
|
|
|
|
/* if it is possible the best lang is not C */
|
|
else if (lang == NULL)
|
|
{
|
|
cur_lanking++;
|
|
continue;
|
|
}
|
|
|
|
/* if the best lang is found */
|
|
else if (!g_ascii_strcasecmp (best_lang, lang))
|
|
{
|
|
tag_group = get_tag_group (filename, doc, ns, cur);
|
|
if (tag_group != NULL)
|
|
{
|
|
if (best_tag_group !=NULL)
|
|
free_tag_group (best_tag_group);
|
|
best_lanking = cur_lanking;
|
|
best_tag_group = tag_group;
|
|
}
|
|
}
|
|
|
|
cur_lanking++;
|
|
}
|
|
|
|
if (lang) g_free (lang);
|
|
} /* End of else */
|
|
|
|
cur = cur->next;
|
|
} /* End of while (cur != NULL) */
|
|
|
|
/* Prepend TagGroup to TagList */
|
|
if (best_tag_group != NULL) {
|
|
taglist->tag_groups =
|
|
g_list_prepend (taglist->tag_groups, best_tag_group);
|
|
}
|
|
|
|
taglist->tag_groups = g_list_sort (taglist->tag_groups, groups_cmp);
|
|
|
|
return taglist;
|
|
}
|
|
|
|
static TagList *
|
|
parse_taglist_file (const gchar* filename)
|
|
{
|
|
xmlDocPtr doc;
|
|
|
|
xmlNsPtr ns;
|
|
xmlNodePtr cur;
|
|
|
|
xed_debug_message (DEBUG_PLUGINS, "Parse file: %s", filename);
|
|
|
|
xmlKeepBlanksDefault (0);
|
|
|
|
/*
|
|
* build an XML tree from a the file;
|
|
*/
|
|
doc = xmlParseFile (filename);
|
|
if (doc == NULL)
|
|
{
|
|
g_warning ("The tag list file '%s' is empty.", filename);
|
|
|
|
return taglist;
|
|
}
|
|
|
|
/*
|
|
* Check the document is of the right kind
|
|
*/
|
|
|
|
cur = xmlDocGetRootElement (doc);
|
|
|
|
if (cur == NULL)
|
|
{
|
|
g_warning ("The tag list file '%s' is empty.", filename);
|
|
xmlFreeDoc(doc);
|
|
return taglist;
|
|
}
|
|
|
|
ns = xmlSearchNsByHref (doc, cur,
|
|
(const xmlChar *) "http://xed.sourceforge.net/some-location");
|
|
|
|
if (ns == NULL)
|
|
{
|
|
g_warning ("The tag list file '%s' is of the wrong type, "
|
|
"xed namespace not found.", filename);
|
|
xmlFreeDoc (doc);
|
|
|
|
return taglist;
|
|
}
|
|
|
|
if (xmlStrcmp(cur->name, (const xmlChar *) "TagList"))
|
|
{
|
|
g_warning ("The tag list file '%s' is of the wrong type, "
|
|
"root node != TagList.", filename);
|
|
xmlFreeDoc (doc);
|
|
|
|
return taglist;
|
|
}
|
|
|
|
/*
|
|
* If needed, allocate taglist
|
|
*/
|
|
|
|
if (taglist == NULL)
|
|
taglist = g_new0 (TagList, 1);
|
|
|
|
taglist = lookup_best_lang (taglist, filename, doc, ns, cur);
|
|
|
|
xmlFreeDoc (doc);
|
|
|
|
xed_debug_message (DEBUG_PLUGINS, "END");
|
|
|
|
return taglist;
|
|
}
|
|
|
|
static void
|
|
free_tag (Tag *tag)
|
|
{
|
|
/*
|
|
xed_debug_message (DEBUG_PLUGINS, "Tag: %s", tag->name);
|
|
*/
|
|
g_return_if_fail (tag != NULL);
|
|
|
|
free (tag->name);
|
|
|
|
if (tag->begin != NULL)
|
|
free (tag->begin);
|
|
|
|
if (tag->end != NULL)
|
|
free (tag->end);
|
|
|
|
g_free (tag);
|
|
}
|
|
|
|
static void
|
|
free_tag_group (TagGroup *tag_group)
|
|
{
|
|
GList *l;
|
|
|
|
xed_debug_message (DEBUG_PLUGINS, "Tag group: %s", tag_group->name);
|
|
|
|
g_return_if_fail (tag_group != NULL);
|
|
|
|
free (tag_group->name);
|
|
|
|
for (l = tag_group->tags; l != NULL; l = g_list_next (l))
|
|
{
|
|
free_tag ((Tag *) l->data);
|
|
}
|
|
|
|
g_list_free (tag_group->tags);
|
|
g_free (tag_group);
|
|
|
|
xed_debug_message (DEBUG_PLUGINS, "END");
|
|
}
|
|
|
|
void free_taglist(void)
|
|
{
|
|
GList* l;
|
|
|
|
xed_debug_message(DEBUG_PLUGINS, "ref_count: %d", taglist_ref_count);
|
|
|
|
if (taglist == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_return_if_fail(taglist_ref_count > 0);
|
|
|
|
--taglist_ref_count;
|
|
|
|
if (taglist_ref_count > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (l = taglist->tag_groups; l != NULL; l = g_list_next (l))
|
|
{
|
|
free_tag_group ((TagGroup*) l->data);
|
|
}
|
|
|
|
g_list_free (taglist->tag_groups);
|
|
g_free (taglist);
|
|
taglist = NULL;
|
|
|
|
xed_debug_message (DEBUG_PLUGINS, "Really freed");
|
|
}
|
|
|
|
static TagList* parse_taglist_dir(const gchar* dir)
|
|
{
|
|
GError* error = NULL;
|
|
GDir* d;
|
|
const gchar* dirent;
|
|
|
|
xed_debug_message(DEBUG_PLUGINS, "DIR: %s", dir);
|
|
|
|
d = g_dir_open(dir, 0, &error);
|
|
|
|
if (!d)
|
|
{
|
|
xed_debug_message(DEBUG_PLUGINS, "%s", error->message);
|
|
g_error_free (error);
|
|
return taglist;
|
|
}
|
|
|
|
while ((dirent = g_dir_read_name(d)))
|
|
{
|
|
if (g_str_has_suffix(dirent, ".tags") || g_str_has_suffix(dirent, ".tags.gz"))
|
|
{
|
|
gchar* tags_file = g_build_filename(dir, dirent, NULL);
|
|
parse_taglist_file(tags_file);
|
|
g_free (tags_file);
|
|
}
|
|
}
|
|
|
|
g_dir_close (d);
|
|
|
|
return taglist;
|
|
}
|
|
|
|
TagList* create_taglist(const gchar* data_dir)
|
|
{
|
|
gchar* pdir;
|
|
|
|
xed_debug_message(DEBUG_PLUGINS, "ref_count: %d", taglist_ref_count);
|
|
|
|
if (taglist_ref_count > 0)
|
|
{
|
|
++taglist_ref_count;
|
|
|
|
return taglist;
|
|
}
|
|
|
|
const gchar* home;
|
|
|
|
/* load user's taglists */
|
|
|
|
home = g_get_home_dir ();
|
|
if (home != NULL)
|
|
{
|
|
pdir = g_build_filename(home, ".config", USER_XED_TAGLIST_PLUGIN_LOCATION, NULL);
|
|
parse_taglist_dir(pdir);
|
|
g_free (pdir);
|
|
}
|
|
|
|
/* load system's taglists */
|
|
parse_taglist_dir(data_dir);
|
|
|
|
++taglist_ref_count;
|
|
g_return_val_if_fail(taglist_ref_count == 1, taglist);
|
|
|
|
return taglist;
|
|
}
|