medit/moo/mooedit/mooeditlang-xml.c

1324 lines
40 KiB
C

/*
* mooedit/mooeditlang-xml.c
*
* Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* 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.
*
* See COPYING file that comes with this distribution.
*/
/*
* Pretty big part of this file is taken from/based on gtksourceview/gtksourcelang.c
* Copyright (C) 2003 - Paolo Maggi <paolo.maggi@polito.it>
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define MOOEDIT_COMPILATION
#include "mooedit/mooeditlang-private.h"
#include "mooedit/mooeditprefs.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <string.h>
/****************************************************************************/
/*
* lang files are the same as gtksourceview's with some additions:
*
* lang file looks like the following:
<!-- header is ignored -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE language SYSTEM "language.dtd">
<!-- version is ignored -->
<!-- 'extensions' are really globs, but word 'extensions' sounds better -->
<!-- it accepts both 'name' and '_name'; and both 'section' and '_section' -->
<language name="name"
version="1.0"
section="section"
mimetypes="text/x-c;text/x-chdr;text/x-csrc"
extensions="*.c"
author="Copyright by Some Guy or whatever">
<brackets><!-- in some format --></brackets>
<word-chars><!-- in some format --></word-chars>
<comments><!-- in some format --></comments>
<!-- then the same stuff as in gtksourceview lang files -->
<styles>
<style name="The style name used in tag definition" default_style="Default style name"
bold="0" italic="TRUE" underline="FALSE" strikethrough="no"
foreground="blue" background="#000" />
<!-- etc. -->
</styles>
</language>
*/
/****************************************************************************/
#define MIME_TYPES_DELIMITER ";"
typedef enum {
BOLD = 1 << 0,
ITALIC = 1 << 1,
UNDERLINE = 1 << 2,
STRIKETHROUGH = 1 << 3
} StyleAttrMask;
typedef struct {
char *name;
char *default_style;
guint bold : 1;
guint italic : 1;
guint underline : 1;
guint strikethrough : 1;
StyleAttrMask attr_mask;
char *foreground;
char *background;
} Style;
typedef struct {
gboolean error;
const char *filename;
char *name;
char *section;
char *mimetypes;
char *extensions;
char *author;
GSList *style_names;
GPtrArray *styles; /* Style* */
} LangDescription;
static void free_string_list (GSList *list);
static void free_xmlstring_list(GSList *list);
static GSList *split_mime_types (const char *str);
static GSList *split_extensions (const char *str);
static Style *style_new (void);
static void style_free (Style *style);
static gboolean get_bool (const char *str);
static GtkSourceTagStyle *create_style (const char *lang_id,
const char *style_name,
GPtrArray *styles);
#define assign_string(var,string) \
g_free (var); \
var = g_strdup (string);
static LangDescription *lang_description_new (const char *filename);
static void lang_description_free (LangDescription *desc);
static void load_desc_start_element (LangDescription *desc,
const char *name,
const char **attrs);
gboolean moo_edit_lang_load_description (MooEditLang *lang)
{
xmlSAXHandler sax;
LangDescription *desc;
GSList *l;
if (lang->priv->description_loaded) return TRUE;
g_return_val_if_fail (lang->priv->filename != NULL, FALSE);
memset (&sax, 0, sizeof(sax));
sax.startElement = (startElementSAXFunc)load_desc_start_element;
desc = lang_description_new (lang->priv->filename);
if (xmlSAXUserParseFile (&sax, desc, lang->priv->filename)) {
g_critical ("%s: error in xmlSAXUserParseFile (%s)",
G_STRLOC, lang->priv->filename);
lang_description_free (desc);
return FALSE;
}
if (desc->error) {
g_critical ("%s: error in loading language description from file '%s'",
G_STRLOC, lang->priv->filename);
lang_description_free (desc);
return FALSE;
}
if (!desc->name) {
g_critical ("%s: language doesn't have 'name' attribute in file '%s'",
G_STRLOC, lang->priv->filename);
lang_description_free (desc);
return FALSE;
}
if (!desc->section) {
g_warning ("%s: language '%s' doesn't have 'section' attribute in file '%s'",
G_STRLOC, desc->name, lang->priv->filename);
desc->section = g_strdup (MOO_EDIT_LANG_SECTION_OTHERS);
}
free_string_list (lang->priv->mime_types);
lang->priv->mime_types = split_mime_types (desc->mimetypes);
free_string_list (lang->priv->extensions);
lang->priv->extensions = split_extensions (desc->extensions);
assign_string (lang->priv->id, desc->name);
assign_string (lang->priv->name, desc->name);
assign_string (lang->priv->section, desc->section);
assign_string (lang->priv->author, desc->author);
for (l = desc->style_names; l; l = l->next) {
const char *style_name;
GtkSourceTagStyle *style;
style_name = (const char*)l->data;
style = create_style (lang->priv->id, style_name, desc->styles);
g_hash_table_insert (lang->priv->style_id_to_style,
g_strdup (style_name), style);
g_hash_table_insert (lang->priv->style_id_to_style_name,
g_strdup (style_name), g_strdup (style_name));
}
lang_description_free (desc);
lang->priv->description_loaded = TRUE;
return TRUE;
}
static LangDescription *lang_description_new (const char *filename)
{
LangDescription *desc = g_new0 (LangDescription, 1);
desc->styles = g_ptr_array_new ();
desc->filename = filename;
return desc;
}
static void lang_description_free (LangDescription *desc)
{
guint i;
g_return_if_fail (desc != NULL);
g_free (desc->name);
g_free (desc->section);
g_free (desc->mimetypes);
g_free (desc->extensions);
g_free (desc->author);
free_string_list (desc->style_names);
for (i = 0; i < desc->styles->len; ++i)
style_free ((Style*) desc->styles->pdata[i]);
g_ptr_array_free (desc->styles, TRUE);
g_free (desc);
}
static void load_desc_start_element (LangDescription *desc,
const char *elm_name,
const char **attrs)
{
const char **attr;
if (desc->error) return;
g_return_if_fail (elm_name != NULL);
if (!strcmp (elm_name, "line-comment") ||
!strcmp (elm_name, "string") ||
!strcmp (elm_name, "keyword-list") ||
!strcmp (elm_name, "pattern-item") ||
!strcmp (elm_name, "syntax-item") ||
!strcmp (elm_name, "block-comment"))
{
const char *tag_name = NULL;
const char *style = NULL;
for (attr = attrs; *attr != NULL; ++attr) {
if (!strcmp (*attr, "name") || !strcmp (*attr, "_name"))
{
++attr;
tag_name = *attr;
}
else if (!strcmp (*attr, "style") || !strcmp (*attr, "_style"))
{
++attr;
style = *attr;
break;
}
else
++attr;
}
if (style)
{
desc->style_names =
g_slist_prepend (desc->style_names, g_strdup (style));
}
else if (tag_name)
{
desc->style_names =
g_slist_prepend (desc->style_names, g_strdup (tag_name));
}
else
{
g_critical ("%s: syntax tag without name or style in file '%s'",
G_STRLOC, desc->filename ? desc->filename : "???");
desc->error = FALSE;
}
return;
}
if (!strcmp (elm_name, "styles"))
return;
if (!strcmp (elm_name, "style"))
{
Style *style = style_new ();
g_ptr_array_add (desc->styles, style);
for (attr = attrs; *attr != NULL; ++attr) {
if (!strcmp (*attr, "name"))
{
++attr;
style->name = g_strdup (*attr);
}
else if (!strcmp (*attr, "default_style"))
{
++attr;
style->default_style = g_strdup (*attr);
}
else if (!strcmp (*attr, "foreground")) {
++attr;
style->foreground = g_strdup (*attr);
}
else if (!strcmp (*attr, "background")) {
++attr;
style->background = g_strdup (*attr);
}
else if (!strcmp (*attr, "bold")) {
++attr;
style->bold = get_bool (*attr);
style->attr_mask |= BOLD;
}
else if (!strcmp (*attr, "italic")) {
++attr;
style->italic = get_bool (*attr);
style->attr_mask |= ITALIC;
}
else if (!strcmp (*attr, "underline")) {
++attr;
style->underline = get_bool (*attr);
style->attr_mask |= UNDERLINE;
}
else if (!strcmp (*attr, "strikethrough")) {
++attr;
style->strikethrough = get_bool (*attr);
style->attr_mask |= STRIKETHROUGH;
}
else {
g_critical ("%s: unknown style attribute '%s' in file '%s'",
G_STRLOC, *attr,
desc->filename ? desc->filename : "???");
++attr;
}
}
return;
}
if (!strcmp (elm_name, "brackets") ||
!strcmp (elm_name, "word-chars") ||
!strcmp (elm_name, "comments"))
return;
if (!strcmp (elm_name, "language"))
{
g_return_if_fail (attrs != NULL);
for (attr = attrs; *attr != NULL; ++attr) {
if (!strcmp (*attr, "_name") ||
!strcmp (*attr, "name"))
{
++attr;
if (desc->name) {
g_warning ("%s: both 'name' and '_name' attributes specified", G_STRLOC);
g_free (desc->name);
}
desc->name = g_strdup (*attr);
}
else if (!strcmp (*attr, "_section") ||
!strcmp (*attr, "section"))
{
++attr;
if (desc->section) {
g_warning ("%s: both 'section' and '_section' attributes specified", G_STRLOC);
g_free (desc->section);
}
desc->section = g_strdup (*attr);
}
else if (!strcmp (*attr, "mimetypes")) {
++attr;
desc->mimetypes = g_strdup (*attr);
}
else if (!strcmp (*attr, "extensions")) {
++attr;
desc->extensions = g_strdup (*attr);
}
else if (!strcmp (*attr, "version")) {
++attr;
}
else {
g_warning ("%s: unknown language attribute %s", G_STRLOC, *attr);
++attr;
}
}
}
}
static GtkSourceTagStyle *create_style (const char *lang_id,
const char *style_id,
GPtrArray *styles)
{
Style *s = NULL;
GtkSourceTagStyle *style;
guint i;
g_return_val_if_fail (style_id != NULL,
gtk_source_tag_style_new());
style = moo_edit_lang_get_default_style (style_id);
if (!style) style = gtk_source_tag_style_new();
for (i = 0; i < styles->len; ++i) {
Style *s = g_ptr_array_index (styles, i);
if (s->name && !strcmp (style_id, s->name))
break;
}
if (i < styles->len) s = g_ptr_array_index (styles, i);
if (s) {
GtkSourceTagStyle *default_style =
moo_edit_lang_get_default_style (s->default_style);
if (default_style) {
*style = *default_style;
gtk_source_tag_style_free (default_style);
}
}
if (s)
{
GdkColor color;
if (s->foreground) {
if (gdk_color_parse (s->foreground, &color)) {
style->foreground = color;
style->mask |= GTK_SOURCE_TAG_STYLE_USE_FOREGROUND;
}
else {
g_warning ("could not parse color '%s' for tag style '%s'",
s->foreground, style_id);
}
}
if (s->background) {
if (gdk_color_parse (s->background, &color)) {
style->background = color;
style->mask |= GTK_SOURCE_TAG_STYLE_USE_BACKGROUND;
}
else {
g_warning ("could not parse color '%s' for tag style '%s'",
s->background, style_id);
}
}
if (s->attr_mask & BOLD)
style->bold = s->bold;
if (s->attr_mask & ITALIC)
style->italic = s->italic;
if (s->attr_mask & UNDERLINE)
style->underline = s->underline;
if (s->attr_mask & STRIKETHROUGH)
style->strikethrough = s->strikethrough;
}
moo_edit_style_load (lang_id, style_id, style);
return style;
}
/****************************************************************************/
/* Full loading */
/***/
#define KEYWORDS_NUM_LIMIT 1000
static gchar *strconvescape (gchar *source);
static gboolean parse_brackets (const char *string,
gunichar **left,
gunichar **right,
guint *num);
static gboolean parse_word_chars (const char *string,
gunichar **chars,
guint *num);
static void add_tag (MooEditLang *lang,
GtkSourceTag *tag,
const char *style_id);
static gboolean process_lang_element_node (MooEditLang *lang,
xmlNode *node);
static gboolean process_brackets_node (MooEditLang *lang,
xmlNode *node);
static gboolean process_word_chars_node (MooEditLang *lang,
xmlNode *node);
static gboolean process_comments_node (MooEditLang *lang,
xmlNode *node);
static gboolean process_escape_char_node (MooEditLang *lang,
xmlNode *node);
static gboolean process_line_comment_node (MooEditLang *lang,
xmlNode *node);
static gboolean process_syntax_item_node (MooEditLang *lang,
xmlNode *node);
static gboolean process_string_node (MooEditLang *lang,
xmlNode *node);
static gboolean process_pattern_item_node (MooEditLang *lang,
xmlNode *node);
static gboolean process_keyword_list_node (MooEditLang *lang,
xmlNode *node);
gboolean moo_edit_lang_load_full (MooEditLang *lang)
{
xmlDoc *doc;
xmlNode *root, *elm;
g_return_val_if_fail (MOO_IS_EDIT_LANG (lang) &&
lang->priv->filename != NULL, FALSE);
if (lang->priv->loaded) return TRUE;
if (!moo_edit_lang_load_description (lang)) return FALSE;
lang->priv->loaded = TRUE;
LIBXML_TEST_VERSION
#ifdef HAVE_XMLREADFILE
doc = xmlReadFile (lang->priv->filename, NULL, 0);
#else /* ! HAVE_XMLREADFILE */
doc = xmlParseFile (lang->priv->filename);
#endif /* ! HAVE_XMLREADFILE */
if (doc == NULL) {
g_critical ("%s: could not parse file '%s'",
G_STRLOC, lang->priv->filename);
xmlCleanupParser();
lang->priv->loaded = FALSE;
return FALSE;
}
root = xmlDocGetRootElement (doc);
if (xmlStrcmp (root->name, (const xmlChar*)"language")) {
g_critical ("%s: root element in file '%s'"
"is not 'language'", G_STRLOC,
lang->priv->filename);
xmlFreeDoc (doc);
xmlCleanupParser();
lang->priv->loaded = FALSE;
return FALSE;
}
for (elm = root->children; elm != NULL; elm = elm->next)
process_lang_element_node (lang, elm);
lang->priv->tags = g_slist_reverse (lang->priv->tags);
xmlFreeDoc (doc);
xmlCleanupParser();
return TRUE;
}
static void add_tag (MooEditLang *lang,
GtkSourceTag *tag,
const char *style_id)
{
char *tag_id;
GtkSourceTagStyle *style;
g_return_if_fail (tag != NULL);
lang->priv->tags = g_slist_prepend (lang->priv->tags, tag);
tag_id = gtk_source_tag_get_id (tag);
if (!style_id)
style_id = tag_id;
g_hash_table_insert (lang->priv->tag_id_to_style_id,
g_strdup (tag_id), g_strdup (style_id));
style = g_hash_table_lookup (lang->priv->style_id_to_style, style_id);
if (!style) {
g_warning ("%s: no style with id '%s'", G_STRLOC, style_id);
style = gtk_source_tag_style_new ();
g_hash_table_insert (lang->priv->style_id_to_style,
g_strdup (style_id), style);
g_hash_table_insert (lang->priv->style_id_to_style_name,
g_strdup (style_id), g_strdup (style_id));
}
gtk_source_tag_set_style (tag, style);
g_free (tag_id);
}
static gchar *strconvescape (gchar *source)
{
gchar cur_char;
gchar last_char = '\0';
gint iterations = 0;
gint max_chars;
gchar *dest;
if (source == NULL)
return NULL;
max_chars = strlen (source);
dest = source;
for (iterations = 0; iterations < max_chars; iterations++) {
cur_char = source[iterations];
*dest = cur_char;
if (last_char == '\\' && cur_char == 'n') {
dest--;
*dest = '\n';
} else if (last_char == '\\' && cur_char == 't') {
dest--;
*dest = '\t';
}
last_char = cur_char;
dest++;
}
*dest = '\0';
return source;
}
static gboolean process_lang_element_node (MooEditLang *lang,
xmlNode *node)
{
if (node->type == XML_ELEMENT_NODE)
{
if (!xmlStrcmp (node->name, (const xmlChar*)"escape-char"))
return process_escape_char_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"brackets"))
return process_brackets_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"comments"))
return process_comments_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"word-chars"))
return process_word_chars_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"line-comment"))
return process_line_comment_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"block-comment"))
return process_syntax_item_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"string"))
return process_string_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"syntax-item"))
return process_syntax_item_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"keyword-list"))
return process_keyword_list_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"pattern-item"))
return process_pattern_item_node (lang, node);
else if (!xmlStrcmp (node->name, (const xmlChar*)"styles"))
return TRUE;
else {
#ifdef HAVE_XMLNODE_LINE
g_critical ("%s: unknown tag '%s' at line %d in file '%s'",
G_STRLOC, node->name, node->line,
lang->priv->filename ? lang->priv->filename : "???");
#else /* !HAVE_XMLNODE_LINE */
g_critical ("%s: unknown tag '%s' in file '%s'",
G_STRLOC, node->name,
lang->priv->filename ? lang->priv->filename : "???");
#endif /* !HAVE_XMLNODE_LINE */
return TRUE;
}
}
else return TRUE;
}
static gboolean process_brackets_node (MooEditLang *lang,
xmlNode *node)
{
xmlChar *val;
val = xmlNodeGetContent (node);
if (!val) {
g_critical ("%s: no content", G_STRLOC);
return FALSE;
}
if (!parse_brackets ((char*) val, NULL, NULL, NULL)) {
g_critical ("%s: could not parse brackets in '%s'",
G_STRLOC, (char*) val);
xmlFree (val);
return FALSE;
}
g_free (lang->priv->brackets);
lang->priv->brackets = g_strdup ((char*)val);
xmlFree (val);
return TRUE;
}
static gboolean process_word_chars_node (MooEditLang *lang,
xmlNode *node)
{
xmlChar *val;
gunichar *chars = NULL;
guint n = 0;
val = xmlNodeGetContent (node);
if (!val) {
g_critical ("%s: no content", G_STRLOC);
return FALSE;
}
if (!parse_word_chars ((char*) val, &chars, &n)) {
g_critical ("%s: could not parse word_chars in '%s'",
G_STRLOC, (char*) val);
xmlFree (val);
return FALSE;
}
g_free (lang->priv->word_chars);
lang->priv->word_chars = chars;
lang->priv->num_word_chars = n;
xmlFree (val);
return TRUE;
}
static gboolean process_comments_node (G_GNUC_UNUSED MooEditLang *lang,
G_GNUC_UNUSED xmlNode *node)
{
g_warning ("%s: implement me", G_STRLOC);
return FALSE;
}
static gboolean process_escape_char_node (MooEditLang *lang,
xmlNode *node)
{
xmlChar *val = xmlNodeGetContent ((xmlNode*)node);
gunichar esc_char;
if (!val) {
g_critical ("%s: no content", G_STRLOC);
return FALSE;
}
esc_char = g_utf8_get_char_validated ((const char*)val, -1);
xmlFree (val);
if (esc_char == (gunichar)-1 || esc_char == (gunichar)-2)
{
g_critical ("%s: invalid unicode char read", G_STRLOC);
return FALSE;
}
lang->priv->escape_char = esc_char;
return TRUE;
}
static gboolean process_line_comment_node (MooEditLang *lang,
xmlNode *node)
{
xmlChar *name = NULL, *style = NULL;
xmlChar *start_regex = NULL;
xmlNode *n;
GtkTextTag *tag = NULL;
gboolean success = TRUE;
name = xmlGetProp (node, (const xmlChar*)"_name");
if (!name) name = xmlGetProp (node, (const xmlChar*)"name");
style = xmlGetProp (node, (const xmlChar*)"_style");
if (!style) style = xmlGetProp (node, (const xmlChar*)"style");
for (n = node->children; n; n = n->next) {
if (n->type == XML_ELEMENT_NODE) {
if (!xmlStrcmp (n->name, (const xmlChar*)"start-regex"))
start_regex = xmlNodeGetContent (n);
else
g_critical ("%s: unexpected element '%s'", G_STRLOC,
n->name);
break;
}
}
tag = NULL;
if (name && start_regex) {
tag = gtk_line_comment_tag_new ((char*)name, (char*)name,
strconvescape((char*)start_regex));
if (tag) {
add_tag (lang, GTK_SOURCE_TAG (tag), style);
}
else {
g_critical ("%s: could not create tag", G_STRLOC);
success = FALSE;
}
}
else {
if (!name)
g_critical ("%s: no name attribute", G_STRLOC);
if (!start_regex)
g_critical ("%s: no start_regex attribute", G_STRLOC);
success = FALSE;
}
if (name) xmlFree (name);
if (style) xmlFree (style);
if (start_regex) xmlFree (start_regex);
return success;
}
static gboolean process_syntax_item_node (MooEditLang *lang,
xmlNode *node)
{
xmlChar *name, *style;
xmlChar *start_regex = NULL;
xmlChar *end_regex = NULL;
GtkTextTag *tag = NULL;
gboolean success = TRUE;
xmlNode *n;
name = xmlGetProp (node, (const xmlChar*)"_name");
if (!name) name = xmlGetProp (node, (const xmlChar*)"name");
style = xmlGetProp (node, (const xmlChar*)"_style");
if (!style) style = xmlGetProp (node, (const xmlChar*)"style");
for (n = node->children; n != NULL; n = n->next)
{
if (n->type == XML_ELEMENT_NODE)
{
if (!start_regex && !xmlStrcmp (n->name, (const xmlChar*)"start-regex"))
{
start_regex = xmlNodeGetContent (n);
}
else if (!end_regex && !xmlStrcmp (n->name, (const xmlChar*)"end-regex"))
{
end_regex = xmlNodeGetContent (n);
}
else
{
g_critical ("%s: pattern node has wrong name '%s'",
G_STRLOC, n->name);
success = FALSE;
break;
}
}
}
if (success && name && start_regex && end_regex)
{
tag = gtk_block_comment_tag_new ((char*)name, (char*)name,
strconvescape((char*)start_regex),
strconvescape((char*)end_regex));
if (tag)
{
add_tag (lang, GTK_SOURCE_TAG (tag), style);
}
else
{
g_critical ("%s: could not create tag '%s'", G_STRLOC, (char*)name);
success = FALSE;
}
}
else
{
if (!name)
g_critical ("%s: no name attribute", G_STRLOC);
if (!start_regex)
g_critical ("%s: no start_regex attribute", G_STRLOC);
if (!end_regex)
g_critical ("%s: no end_regex attribute", G_STRLOC);
success = FALSE;
}
if (name) xmlFree (name);
if (style) xmlFree (style);
if (start_regex) xmlFree (start_regex);
if (end_regex) xmlFree (end_regex);
return success;
}
static gboolean process_string_node (MooEditLang *lang,
xmlNode *node)
{
xmlChar *name, *style;
xmlChar *start_regex = NULL;
xmlChar *end_regex = NULL;
gboolean end_at_line_end = TRUE;
xmlChar *prop = NULL;
gboolean success = TRUE;
GtkTextTag *tag = NULL;
xmlNode *n;
name = xmlGetProp (node, (const xmlChar*)"_name");
if (!name) name = xmlGetProp (node, (const xmlChar*)"name");
style = xmlGetProp (node, (const xmlChar*)"_style");
if (!style) style = xmlGetProp (node, (const xmlChar*)"style");
prop = xmlGetProp (node, (const xmlChar*)"end-at-line-end");
if (!prop)
g_critical ("%s: no 'end-at-line-end' property", G_STRLOC);
else {
end_at_line_end = get_bool (prop);
xmlFree (prop);
}
for (n = node->children; n != NULL; n = n->next)
{
if (n->type == XML_ELEMENT_NODE) {
if (!xmlStrcmp (n->name, (const xmlChar*)"start-regex"))
start_regex = xmlNodeGetContent (n);
else if (!xmlStrcmp (n->name, (const xmlChar*)"end-regex"))
end_regex = xmlNodeGetContent (n);
else {
g_critical ("%s: pattern node has wrong name '%s'", G_STRLOC, n->name);
success = FALSE;
break;
}
}
}
if (success && name && start_regex && end_regex) {
tag = gtk_string_tag_new ((char*)name, (char*)name,
strconvescape((char*)start_regex),
strconvescape((char*)end_regex),
end_at_line_end);
if (tag) {
add_tag (lang, GTK_SOURCE_TAG (tag), style);
}
else {
g_critical ("%s: could not create tag", G_STRLOC);
success = FALSE;
}
}
else {
if (!name)
g_critical ("%s: no name attribute", G_STRLOC);
if (!style)
g_critical ("%s: no style attribute", G_STRLOC);
if (!start_regex)
g_critical ("%s: no start_regex attribute", G_STRLOC);
if (!end_regex)
g_critical ("%s: no end_regex attribute", G_STRLOC);
success = FALSE;
}
if (name) xmlFree (name);
if (style) xmlFree (style);
if (start_regex) xmlFree (start_regex);
if (end_regex) xmlFree (end_regex);
return success;
}
static gboolean process_pattern_item_node (MooEditLang *lang,
xmlNode *node)
{
gboolean success = TRUE;
xmlChar *name, *style;
xmlChar *pattern = NULL;
xmlNode *n;
name = xmlGetProp (node, (const xmlChar*)"_name");
if (!name) name = xmlGetProp (node, (const xmlChar*)"name");
style = xmlGetProp (node, (const xmlChar*)"_style");
if (!style) style = xmlGetProp (node, (const xmlChar*)"style");
for (n = node->children; n; n = n->next) {
if (n->type == XML_ELEMENT_NODE) {
if (!xmlStrcmp (n->name, (const xmlChar*)"regex")) {
pattern = xmlNodeGetContent (n);
break;
}
else {
g_critical ("%s: unexpected element %s", G_STRLOC, n->name);
break;
}
}
}
if (name && pattern) {
GtkTextTag *tag = gtk_pattern_tag_new ((char*)name, (char*)name,
strconvescape((char*)pattern));
if (tag) {
add_tag (lang, GTK_SOURCE_TAG (tag), style);
}
else {
g_warning ("%s: could not create tag", G_STRLOC);
success = FALSE;
}
}
else {
if (!name)
g_critical ("%s: no name attribute", G_STRLOC);
if (!pattern)
g_critical ("%s: no pattern attribute", G_STRLOC);
success = FALSE;
}
if (name) xmlFree (name);
if (style) xmlFree (style);
if (pattern) xmlFree (pattern);
return success;
}
static gboolean process_keyword_list_node (MooEditLang *lang,
xmlNode *node)
{
gboolean success = TRUE;
gboolean case_sensitive = TRUE;
gboolean match_empty_string_at_beginning = TRUE;
gboolean match_empty_string_at_end = TRUE;
xmlChar *prop = NULL;
xmlChar *name, *style;
xmlChar *start_regex, *end_regex;
guint keywords_num = 0;
GSList *keywords_list = NULL;
guint keywords_group = 0;
xmlNode *kw_node;
GtkTextTag *tag = NULL;
name = xmlGetProp (node, (const xmlChar*)"_name");
if (!name) name = xmlGetProp (node, (const xmlChar*)"name");
if (!name)
{
g_critical ("%s: no 'name' atribute in file '%s'",
G_STRLOC, lang->priv->filename ? lang->priv->filename : "???");
success = FALSE;
}
style = xmlGetProp (node, (const xmlChar*)"_style");
if (!style) style = xmlGetProp (node, (const xmlChar*)"style");
if (!style)
{
g_critical ("%s: no 'style' atribute in file '%s'",
G_STRLOC, lang->priv->filename ? lang->priv->filename : "???");
success = FALSE;
}
prop = xmlGetProp (node, (const xmlChar*)"case-sensitive");
if (!prop)
{
/* not a warning because it happens in gtksourceview files */
g_message ("%s: no 'case-sensitive' atribute in file '%s'",
G_STRLOC, lang->priv->filename ? lang->priv->filename : "???");
}
else
{
case_sensitive = get_bool (prop);
xmlFree (prop);
}
prop = xmlGetProp (node, (const xmlChar*)"match-empty-string-at-beginning");
if (prop)
{
match_empty_string_at_beginning = get_bool (prop);
xmlFree (prop);
}
prop = xmlGetProp (node, (const xmlChar*)"match-empty-string-at-end");
if (prop) {
match_empty_string_at_end = get_bool (prop);
xmlFree (prop);
}
start_regex = xmlGetProp (node, (const xmlChar*)"beginning-regex");
end_regex = xmlGetProp (node, (const xmlChar*)"end-regex");
for (kw_node = node->children; success && kw_node != NULL; kw_node = kw_node->next)
{
if (kw_node->type == XML_ELEMENT_NODE) {
xmlChar *keyword;
if (xmlStrcmp (kw_node->name, (const xmlChar*)"keyword")) {
g_critical ("%s: unexpected node '%s' in file '%s'",
G_STRLOC, kw_node->name,
lang->priv->filename ? lang->priv->filename : "???");
success = FALSE;
free_xmlstring_list (keywords_list);
break;
}
keyword = xmlNodeGetContent (kw_node);
if (!keyword) {
g_critical ("%s: empty node in file '%s'",
G_STRLOC, lang->priv->filename ? lang->priv->filename : "???");
success = FALSE;
free_xmlstring_list (keywords_list);
break;
}
keywords_list = g_slist_prepend (keywords_list,
strconvescape((char*)keyword));
++keywords_num;
if (keywords_num == KEYWORDS_NUM_LIMIT - 1)
{
char *group_name = g_strdup_printf ("%s%d", name, keywords_group);
keywords_list = g_slist_reverse (keywords_list);
tag = gtk_keyword_list_tag_new (
(char*) name,
group_name,
keywords_list,
case_sensitive,
match_empty_string_at_beginning,
match_empty_string_at_end,
strconvescape((char*)start_regex),
strconvescape((char*)end_regex));
g_free (group_name);
if (tag) {
add_tag (lang, GTK_SOURCE_TAG (tag), style);
}
else {
g_critical ("%s: could not create tag in file '%s'",
G_STRLOC, lang->priv->filename ? lang->priv->filename : "???");
success = FALSE;
}
free_xmlstring_list (keywords_list);
keywords_list = NULL;
keywords_num = 0;
++keywords_group;
}
}
}
if (keywords_list) {
char *group_name = g_strdup_printf ("%s%d", name, keywords_group);
keywords_list = g_slist_reverse (keywords_list);
tag = gtk_keyword_list_tag_new (
(char*) name,
group_name,
keywords_list,
case_sensitive,
match_empty_string_at_beginning,
match_empty_string_at_end,
strconvescape((char*)start_regex),
strconvescape((char*)end_regex));
g_free (group_name);
if (tag)
{
add_tag (lang, GTK_SOURCE_TAG (tag), style);
}
else
{
g_critical ("%s: could not create tag in file '%s'",
G_STRLOC, lang->priv->filename ? lang->priv->filename : "???");
success = FALSE;
}
free_xmlstring_list (keywords_list);
keywords_list = NULL;
keywords_num = 0;
++keywords_group;
}
if (name) xmlFree (name);
if (style) xmlFree (style);
if (start_regex) xmlFree (start_regex);
if (end_regex) xmlFree (end_regex);
return success;
}
#define utf8_next_char(p) (const char *)((p) + g_utf8_skip[*(const guchar *)(p)])
static gboolean parse_brackets (const char *string,
gunichar **left_brackets,
gunichar **right_brackets,
guint *num)
{
long len, i;
const char *p;
gunichar *left, *right;
g_return_val_if_fail (g_utf8_validate (string, -1, NULL), FALSE);
len = g_utf8_strlen (string, -1);
g_return_val_if_fail (len > 0 && (len / 2) * 2 == len, FALSE);
len /= 2;
p = string;
left = g_new (gunichar, len);
right = g_new (gunichar, len);
for (i = 0; i < len; ++i) {
left[i] = g_utf8_get_char (p);
p = utf8_next_char (p);
right[i] = g_utf8_get_char (p);
p = utf8_next_char (p);
}
if (left_brackets)
*left_brackets = left;
else
g_free (left);
if (right_brackets)
*right_brackets = right;
else
g_free (right);
if (num)
*num = len;
return TRUE;
}
static gboolean parse_word_chars (const char *string,
gunichar **ch,
guint *num)
{
long len, i;
const char *p;
gunichar *chars;
g_return_val_if_fail (g_utf8_validate (string, -1, NULL), FALSE);
len = g_utf8_strlen (string, -1);
g_return_val_if_fail (len > 0, FALSE);
p = string;
chars = g_new (gunichar, len);
for (i = 0; i < len; ++i) {
chars[i] = g_utf8_get_char (p);
p = utf8_next_char (p);
}
*ch = chars;
*num = len;
return TRUE;
}
/****************************************************************************/
/* Aux functions */
/***/
static void free_string_list (GSList *list)
{
if (!list) return;
g_slist_foreach (list, (GFunc) g_free, NULL);
g_slist_free (list);
}
static void free_xmlstring_list (GSList *list)
{
if (!list) return;
g_slist_foreach (list, (GFunc) xmlFree, NULL);
g_slist_free (list);
}
static GSList *split_mime_types (const char *str)
{
GSList *result;
char **mime_types, **s;
if (!str) return NULL;
mime_types = g_strsplit (str, MIME_TYPES_DELIMITER, 0);
if (!mime_types) return NULL;
result = NULL;
for (s = mime_types; *s; ++s)
result = g_slist_prepend (result, g_strdup (*s));
g_strfreev (mime_types);
result = g_slist_reverse (result);
return result;
}
static GSList *split_extensions (const char *str)
{
return split_mime_types (str);
}
static Style *style_new (void)
{
return g_new0 (Style, 1);
}
static void style_free (Style *style)
{
if (!style) return;
g_free (style->name);
g_free (style->default_style);
g_free (style->foreground);
g_free (style->background);
g_free (style);
}
static gboolean get_bool (const char *str)
{
g_return_val_if_fail (str != NULL, FALSE);
if (!g_ascii_strcasecmp (str, "TRUE") ||
!g_ascii_strcasecmp (str, "yes") ||
!g_ascii_strcasecmp (str, "1"))
return TRUE;
else
return FALSE;
}