Logo Search packages:      
Sourcecode: gretl version File versions  Download package

gtksourcelanguage.c

/*  gtksourcelanguage.c
 *
 *  Copyright (C) 2003 - Paolo Maggi <paolo.maggi@polito.it>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Library 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library 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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <string.h>
#include <fcntl.h>

#include <libxml/xmlreader.h>

#include <glib/gutils.h>
#if GLIB_MINOR_VERSION >= 6
# include <glib/gstdio.h>
#endif
#if GLIB_MINOR_VERSION >= 8
# include <glib/gmappedfile.h>
#endif

#include "gtksourcelanguage-private.h"
#include "gtksourcelanguage.h"
#include "gtksourcetag.h"

#include "gtksourceview-marshal.h"

static void         gtk_source_language_class_init    (GtkSourceLanguageClass       *klass);
static void         gtk_source_language_init          (GtkSourceLanguage            *lang);
static void         gtk_source_language_finalize            (GObject                *object);

static GtkSourceLanguage *process_language_node             (xmlTextReaderPtr              reader, 
                                                 const gchar                  *filename);
static GSList            *get_mime_types_from_file          (GtkSourceLanguage            *language);

/* Signals */
enum {
      TAG_STYLE_CHANGED = 0,
      LAST_SIGNAL
};

static GtkSourceTagStyle normal_style = { TRUE, 0 };

static guint       signals[LAST_SIGNAL] = { 0 };

static GObjectClass      *parent_class  = NULL;

static void
slist_deep_free (GSList *list)
{
      g_slist_foreach (list, (GFunc) g_free, NULL);
      g_slist_free (list);
}

GtkSourceLanguage *
_gtk_source_language_new_from_file (const gchar             *filename,
                            GtkSourceLanguagesManager *lm)
{
      GtkSourceLanguage *lang = NULL;

      xmlTextReaderPtr reader = NULL;
      gint ret;

      g_return_val_if_fail (filename != NULL, NULL);
      g_return_val_if_fail (lm != NULL, NULL);

      reader = xmlNewTextReaderFilename (filename);

      if (reader != NULL) 
      {
            ret = xmlTextReaderRead (reader);
            
            while (ret == 1) 
            {
                  if (xmlTextReaderNodeType (reader) == 1)
                  {
                        xmlChar *name;

                        name = xmlTextReaderName (reader);

                        if (xmlStrcmp (name, BAD_CAST "language") == 0)
                        {

                              lang = process_language_node (reader, filename);
                              
                              ret = 0;
                        }

                        xmlFree (name);
                  }
                  
                  if (ret != 0)
                        ret = xmlTextReaderRead (reader);
                  
            }
      
            xmlFreeTextReader (reader);

            if (ret != 0) 
            {
                  g_warning("Failed to parse '%s'", filename);
                return NULL;
            }
        }
      else 
      {
            g_warning("Unable to open '%s'", filename);

      }
      
      return lang;
}

GType
gtk_source_language_get_type (void)
{
      static GType our_type = 0;

      if (our_type == 0) {
            static const GTypeInfo our_info = {
                  sizeof (GtkSourceLanguageClass),
                  (GBaseInitFunc) NULL,
                  (GBaseFinalizeFunc) NULL,
                  (GClassInitFunc) gtk_source_language_class_init,
                  NULL, /* class_finalize */
                  NULL, /* class_data */
                  sizeof (GtkSourceLanguage),
                  0,    /* n_preallocs */
                  (GInstanceInitFunc) gtk_source_language_init
            };

            our_type =
                g_type_register_static (G_TYPE_OBJECT,
                                  "GtkSourceLanguage", &our_info, 0);
      }

      return our_type;
}

static void
gtk_source_language_class_init (GtkSourceLanguageClass *klass)
{
      GObjectClass *object_class = G_OBJECT_CLASS (klass);

      parent_class            = g_type_class_peek_parent (klass);
      object_class->finalize  = gtk_source_language_finalize;

      signals[TAG_STYLE_CHANGED] = 
            g_signal_new ("tag_style_changed",
                    G_OBJECT_CLASS_TYPE (object_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (GtkSourceLanguageClass, tag_style_changed),
                    NULL, NULL,
                    gtksourceview_marshal_VOID__STRING,
                    G_TYPE_NONE, 
                    1, 
                    G_TYPE_STRING);
}

static void
gtk_source_language_init (GtkSourceLanguage *lang)
{
      lang->priv = g_new0 (GtkSourceLanguagePrivate, 1);

      lang->priv->style_scheme = gtk_source_style_scheme_get_default ();
}

static void
gtk_source_language_finalize (GObject *object)
{
      GtkSourceLanguage *lang;

      lang = GTK_SOURCE_LANGUAGE (object);
            
      if (lang->priv != NULL)
      {
            g_free (lang->priv->lang_file_name);

            xmlFree (lang->priv->name);
            xmlFree (lang->priv->section);
            g_free  (lang->priv->id);

            slist_deep_free (lang->priv->mime_types);

            if (lang->priv->tag_id_to_style_name != NULL)
                  g_hash_table_destroy (lang->priv->tag_id_to_style_name);

            if (lang->priv->tag_id_to_style != NULL)
                  g_hash_table_destroy (lang->priv->tag_id_to_style);

            g_object_unref (lang->priv->style_scheme);

            g_free (lang->priv); 
      }
      
      G_OBJECT_CLASS (parent_class)->finalize (object);
}

/*
 * This function has been taken from GConf.
 * Copyright (C) 1999, 2000 Red Hat Inc.
 */

static const gchar invalid_chars[] = " \t\r\n\"$&<>,+=#!()'|{}[]?~`;%\\";

/**
 * escape_id:
 * @arbitrary_text: some text in any encoding or format.
 * @len: length of @arbitrary_text in bytes, or -1 if @arbitrary_text is nul-terminated.
 * 
 * Return value: a null-terminated valid part of a GConf key.
 **/
static gchar*
escape_id (const gchar *arbitrary_text, gint len)
{
      const char *p;
      const char *end;
      GString *retval;

      g_return_val_if_fail (arbitrary_text != NULL, NULL);
  
      /* Nearly all characters we would normally use for escaping aren't allowed in key
       * names, so we use @ for that.
       *
       * Invalid chars and @ itself are escaped as @xxx@ where xxx is the
       * Latin-1 value in decimal
       */

      if (len < 0)
            len = strlen (arbitrary_text);

      retval = g_string_new ("");

      p = arbitrary_text;
      end = arbitrary_text + len;
      while (p != end)
      {
            if (*p == '/' || *p == '.' || *p == '@' || ((guchar) *p) > 127 ||
                  strchr (invalid_chars, *p))
            {
                  g_string_append_c (retval, '@');
                  g_string_append_printf (retval, "%u", (unsigned int) *p);
                  g_string_append_c (retval, '@');
            }
            else
                  g_string_append_c (retval, *p);
      
            ++p;
      }

      return g_string_free (retval, FALSE);
}

static GtkSourceLanguage *
process_language_node (xmlTextReaderPtr reader, const gchar *filename)
{
      xmlChar *version;
      xmlChar *mimetypes;
      gchar** mtl;
      int i;
      xmlChar *tmp;
      xmlChar *id_temp;
      GtkSourceLanguage *lang;
      
      lang= g_object_new (GTK_TYPE_SOURCE_LANGUAGE, NULL);

      lang->priv->lang_file_name = g_strdup (filename);
      
      tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "_name");
      if (tmp == NULL)
      {
            lang->priv->name = (gchar *)xmlTextReaderGetAttribute (reader, BAD_CAST "name");
            if (lang->priv->name == NULL)
            {
                  g_warning ("Impossible to get language name from file '%s'",
                           filename);

                  g_object_unref (lang);
                  return NULL;
            }

            id_temp = xmlStrdup (BAD_CAST lang->priv->name);
      }
      else
      {
            id_temp = xmlStrdup (tmp);
            /* if tmp is NULL then lang->priv->name is a xmlChar so it must
             * always be a xmlChar, this is why xmlStrdup() is used instead
             * of g_strdup() */
            lang->priv->name = (gchar *)xmlStrdup (BAD_CAST tmp);
            xmlFree (tmp);
      }

      g_return_val_if_fail (id_temp != NULL, NULL);
      lang->priv->id = escape_id ((gchar *)id_temp, -1);
      xmlFree (id_temp);

      tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "_section");
      if (tmp == NULL)
      {
            lang->priv->section = (gchar *)xmlTextReaderGetAttribute (reader, BAD_CAST "section");
            if (lang->priv->section == NULL)
            {
                  g_warning ("Impossible to get language section from file '%s'",
                           filename);
                  
                  g_object_unref (lang);
                  return NULL;
            }
      }
      else
      {
            /* if tmp is NULL then lang->priv->section is a xmlChar so it
             * must always be a xmlChar, this is why xmlStrdup() is used
             * instead of g_strdup() */
            lang->priv->section = (gchar *)xmlStrdup (BAD_CAST tmp);
            xmlFree (tmp);
      }
      
      version = xmlTextReaderGetAttribute (reader, BAD_CAST "version");
      if (version == NULL)
      {
            g_warning ("Impossible to get version number from file '%s'",
                     filename);

            g_object_unref (lang);
            return NULL;
      }
      else
      {
            if (xmlStrcmp (version , BAD_CAST "1.0") != 0)
            {
                  g_warning ("Usupported language spec version '%s' in file '%s'",
                           (gchar *)version, filename);

                  xmlFree (version);
                  
                  g_object_unref (lang);
                  return NULL;
            }

            xmlFree (version);
      }

      mimetypes = xmlTextReaderGetAttribute (reader, BAD_CAST "mimetypes");
      if (mimetypes == NULL)
      {
            g_warning ("Impossible to get mimetypes from file '%s'",
                     filename);

            g_object_unref (lang);
            return NULL;
      }

      mtl = g_strsplit ((gchar *)mimetypes, ";", 0);

      for (i = 0; mtl[i] != NULL; i++)
      {
            /* steal the strings from the array */
            lang->priv->mime_types = g_slist_prepend (lang->priv->mime_types,
                                            mtl[i]);
      }

      g_free (mtl);
      xmlFree (mimetypes);

      lang->priv->mime_types = g_slist_reverse (lang->priv->mime_types);

      return lang;
}

/**
 * gtk_source_language_get_id:
 * @language: a #GtkSourceLanguage.
 *
 * Returns the ID of the language. The ID is not locale-dependent.
 *
 * Return value: the ID of @language.
 **/
gchar *
gtk_source_language_get_id (GtkSourceLanguage *language)
{
      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
      g_return_val_if_fail (language->priv->id != NULL, NULL);
            
      return g_strdup (language->priv->id);
}

/**
 * gtk_source_language_get_name:
 * @language: a #GtkSourceLanguage.
 *
 * Returns the localized name of the language.
 *
 * Return value: the name of @language.
 **/
gchar *
gtk_source_language_get_name (GtkSourceLanguage *language)
{
      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
      g_return_val_if_fail (language->priv->name != NULL, NULL);

      return g_strdup (language->priv->name);
}

/**
 * gtk_source_language_get_section:
 * @language: a #GtkSourceLanguage.
 *
 * Returns the localized section of the language.
 * Each language belong to a section (ex. HTML belogs to the
 * Markup section).
 *
 * Return value: the section of @language.
 **/
gchar *
gtk_source_language_get_section     (GtkSourceLanguage *language)
{
      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
      g_return_val_if_fail (language->priv->section != NULL, NULL);

      return g_strdup (language->priv->section);
}

/**
 * gtk_source_language_get_mime_types:
 * @language: a #GtkSourceLanguage.
 *
 * Returns a list of mime types for the given @language.  After usage you should
 * free each element of the list as well as the list itself.
 *
 * Return value: a list of mime types (strings). 
 **/
GSList *
gtk_source_language_get_mime_types (GtkSourceLanguage *language)
{
      const GSList *l;
      GSList *mime_types = NULL;

      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
      g_return_val_if_fail (language->priv->mime_types != NULL, NULL);

      /* Dup mime_types */
      
      l = language->priv->mime_types;
      
      while (l != NULL)
      {
            mime_types = g_slist_prepend (mime_types, g_strdup ((const gchar*)l->data));
            l = g_slist_next (l);
      }
      
      mime_types = g_slist_reverse (mime_types);

      return mime_types;
}

static GSList *
get_mime_types_from_file (GtkSourceLanguage *language)
{
      xmlTextReaderPtr reader = NULL;
      gint ret;
      GSList *mime_types = NULL;

      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
      g_return_val_if_fail (language->priv->lang_file_name != NULL, NULL);

      reader = xmlNewTextReaderFilename (language->priv->lang_file_name);           

      if (reader != NULL) 
      {
            ret = xmlTextReaderRead (reader);
            
            while (ret == 1) 
            {
                  if (xmlTextReaderNodeType (reader) == 1)
                  {
                        xmlChar *name;

                        name = xmlTextReaderName (reader);

                        if (xmlStrcmp (name, BAD_CAST "language") == 0)
                        {
                              gchar *mimetypes;

                              mimetypes = (gchar *)xmlTextReaderGetAttribute (reader,
                                          BAD_CAST "mimetypes");
                              
                              if (mimetypes == NULL)
                              {
                                    g_warning ("Impossible to get mimetypes from file '%s'",
                                             language->priv->lang_file_name);

                                    ret = 0;
                              }
                              else
                              {
                                    gchar **mtl;
                                    gint i;

                                    mtl = g_strsplit (mimetypes, ";", 0);

                                    for (i = 0; mtl[i] != NULL; i++)
                                    {
                                          /* steal the strings from the array */
                                          mime_types = g_slist_prepend (mime_types,
                                                                  mtl[i]);
                                    }

                                    g_free (mtl);
                                    xmlFree (mimetypes);

                                    ret = 0;
                              }
                        }

                        xmlFree (name);
                  }
                  
                  if (ret != 0)
                        ret = xmlTextReaderRead (reader);
            }
      
            xmlFreeTextReader (reader);

            if (ret != 0) 
            {
                  g_warning("Failed to parse '%s'", language->priv->lang_file_name);
                return NULL;
            }
        }
      else 
      {
            g_warning("Unable to open '%s'", language->priv->lang_file_name);

      }

      return mime_types;
}

/**
 * gtk_source_language_set_mime_types:
 * @language: a #GtkSourceLanguage
 * @mime_types: a list of mime types (strings).
 *
 * Sets a list of @mime_types for the given @language.
 * If @mime_types is %NULL this function will use the default mime
 * types from the language file.
 **/
void 
gtk_source_language_set_mime_types (GtkSourceLanguage *language,
                            const GSList  *mime_types)
{
      g_return_if_fail (GTK_IS_SOURCE_LANGUAGE (language));
      g_return_if_fail (language->priv->mime_types != NULL);
            
      slist_deep_free (language->priv->mime_types);
      language->priv->mime_types = NULL;

      if (mime_types != NULL)
      {
            const GSList *l;

            /* Dup mime_types */
            l = mime_types;
            while (l != NULL)
            {
                  language->priv->mime_types = g_slist_prepend (language->priv->mime_types,
                                                g_strdup ((const gchar*)l->data));
                  l = g_slist_next (l);
            }
      
            language->priv->mime_types = g_slist_reverse (language->priv->mime_types);
      }
      else
            language->priv->mime_types = get_mime_types_from_file (language);
}

/* FIXME: is this function UTF-8 aware? - Paolo */
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 GtkTextTag *
parseLineComment (xmlDocPtr doc, xmlNodePtr cur, gchar* id, xmlChar *name)
{
      GtkTextTag *tag = NULL;
      
      xmlNodePtr child;

      child = cur->xmlChildrenNode;
            
      if ((child != NULL) && !xmlStrcmp (child->name, (const xmlChar *)"start-regex"))
      {
            xmlChar *start_regex;
                  
            start_regex = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
                  
            tag = gtk_line_comment_tag_new (id, (gchar *)name, strconvescape ((gchar *)start_regex));

            xmlFree (start_regex);
      }
      else
      {
            g_warning ("Missing start-regex in tag 'line-comment' (%s, line %ld)", 
                     doc->name, xmlGetLineNo (child));
      }

      return tag;
}

static GtkTextTag *
parseBlockComment (xmlDocPtr doc, xmlNodePtr cur, gchar* id, xmlChar *name)
{
      GtkTextTag *tag = NULL;

      xmlChar *start_regex = NULL;
      xmlChar *end_regex = NULL;

      xmlNodePtr child;

      child = cur->xmlChildrenNode;
      
      while (child != NULL)
      {     
            if (!xmlStrcmp (child->name, (const xmlChar *)"start-regex"))
            {
                  start_regex = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
            }
            else
            if (!xmlStrcmp (child->name, (const xmlChar *)"end-regex"))
            {
                  end_regex = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
            }

            child = child->next;
      }

      if (start_regex == NULL)
      {
            g_warning ("Missing start-regex in tag 'block-comment' (%s, line %ld)", 
                     doc->name, xmlGetLineNo (cur));

            return NULL;
      }

      if (end_regex == NULL)
      {
            xmlFree (start_regex);

            g_warning ("Missing end-regex in tag 'block-comment' (%s, line %ld)", 
                     doc->name, xmlGetLineNo (cur));

            return NULL;
      }

      tag = gtk_block_comment_tag_new (id,
                               (gchar *)name, 
                               strconvescape ((gchar *)start_regex), 
                               strconvescape ((gchar *)end_regex));

      xmlFree (start_regex);
      xmlFree (end_regex);

      return tag;
}

static GtkTextTag *
parseString (xmlDocPtr doc, xmlNodePtr cur, gchar* id, xmlChar *name)
{
      GtkTextTag *tag = NULL;

      xmlChar *start_regex = NULL;
      xmlChar *end_regex = NULL;

      xmlChar *prop = NULL;
      gboolean end_at_line_end = TRUE;

      xmlNodePtr child;

      prop = xmlGetProp (cur, BAD_CAST "end-at-line-end");
      if (prop != NULL)
      {
            if (!xmlStrcasecmp (prop, BAD_CAST "TRUE") ||
                !xmlStrcmp (prop, BAD_CAST "1"))

                        end_at_line_end = TRUE;
                  else
                        end_at_line_end = FALSE;

            xmlFree (prop);   
      }
      
      child = cur->xmlChildrenNode;
      
      while (child != NULL)
      {     
            if (!xmlStrcmp (child->name, (const xmlChar *)"start-regex"))
            {
                  start_regex = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
            }
            else
            if (!xmlStrcmp (child->name, (const xmlChar *)"end-regex"))
            {
                  end_regex = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
            }
      
            child = child->next;
      }

      if (start_regex == NULL)
      {
            g_warning ("Missing start-regex in tag 'string' (%s, line %ld)", 
                     doc->name, xmlGetLineNo (cur));

            return NULL;
      }

      if (end_regex == NULL)
      {
            xmlFree (start_regex);

            g_warning ("Missing end-regex in tag 'string' (%s, line %ld)", 
                     doc->name, xmlGetLineNo (cur));

            return NULL;
      }

      tag = gtk_string_tag_new (id,
                          (gchar *)name, 
                          strconvescape ((gchar *)start_regex), 
                          strconvescape ((gchar *)end_regex), 
                          end_at_line_end);

      xmlFree (start_regex);
      xmlFree (end_regex);

      return tag;
}

static GtkTextTag *
parseKeywordList (xmlDocPtr doc, xmlNodePtr cur, gchar* id, xmlChar *name)
{
      GtkTextTag *tag = NULL;

      gboolean case_sensitive = TRUE;
      gboolean match_empty_string_at_beginning = TRUE;
      gboolean match_empty_string_at_end = TRUE;
      gchar  *beginning_regex = NULL;
      gchar  *end_regex = NULL;

      GSList *list = NULL;

      xmlChar *prop;

      xmlNodePtr child;

      prop = xmlGetProp (cur, BAD_CAST "case-sensitive");
      if (prop != NULL)
      {
            if (!xmlStrcasecmp (prop, (const xmlChar *)"TRUE") ||
                !xmlStrcmp (prop, (const xmlChar *)"1"))

                        case_sensitive = TRUE;
                  else
                        case_sensitive = FALSE;

            xmlFree (prop);   
      }

      prop = xmlGetProp (cur, BAD_CAST "match-empty-string-at-beginning");
      if (prop != NULL)
      {
            if (!xmlStrcasecmp (prop, (const xmlChar *)"TRUE") ||
                !xmlStrcmp (prop, (const xmlChar *)"1"))

                        match_empty_string_at_beginning = TRUE;
                  else
                        match_empty_string_at_beginning = FALSE;

            xmlFree (prop);   
      }

      prop = xmlGetProp (cur, BAD_CAST "match-empty-string-at-end");
      if (prop != NULL)
      {
            if (!xmlStrcasecmp (prop, (const xmlChar *)"TRUE") ||
                !xmlStrcmp (prop, (const xmlChar *)"1"))

                        match_empty_string_at_end = TRUE;
                  else
                        match_empty_string_at_end = FALSE;

            xmlFree (prop);   
      }

      prop = xmlGetProp (cur, BAD_CAST "beginning-regex");
      if (prop != NULL)
      {
            beginning_regex = g_strdup ((gchar *)prop);
            
            xmlFree (prop);   
      }

      prop = xmlGetProp (cur, BAD_CAST "end-regex");
      if (prop != NULL)
      {
            end_regex = g_strdup ((gchar *)prop);
            
            xmlFree (prop);   
      }

      child = cur->xmlChildrenNode;
      
      while (child != NULL)
      {
            if (!xmlStrcmp (child->name, (const xmlChar *)"keyword"))
            {
                  xmlChar *keyword;
                  keyword = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
                  
                  list = g_slist_prepend (list, strconvescape ((gchar *)keyword));
            }

            child = child->next;
      }

      list = g_slist_reverse (list);

      if (list == NULL)
      {
            g_warning ("No keywords in tag 'keyword-list' (%s, line %ld)", 
                     doc->name, xmlGetLineNo (cur));

            g_free (beginning_regex),
            g_free (end_regex);
            
            return NULL;
      }

      tag = gtk_keyword_list_tag_new (id,
                              (gchar *)name, 
                              list,
                              case_sensitive,
                              match_empty_string_at_beginning,
                              match_empty_string_at_end,
                              strconvescape (beginning_regex),
                              strconvescape (end_regex));

      g_free (beginning_regex),
      g_free (end_regex);

      g_slist_foreach (list, (GFunc) xmlFree, NULL);
      g_slist_free (list);

      return tag;
}

static GtkTextTag *
parsePatternItem (xmlDocPtr doc, xmlNodePtr cur, gchar* id, xmlChar *name)
{
      GtkTextTag *tag = NULL;
      
      xmlNodePtr child;

      child = cur->xmlChildrenNode;
            
      if ((child != NULL) && !xmlStrcmp (child->name, (const xmlChar *)"regex"))
      {
            xmlChar *regex;
                  
            regex = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
                  
            tag = gtk_pattern_tag_new (id, (gchar *)name,
                        strconvescape ((gchar *)regex));

            xmlFree (regex);
      }
      else
      {
            g_warning ("Missing regex in tag 'pattern-item' (%s, line %ld)", 
                     doc->name, xmlGetLineNo (child));
      }

      return tag;
}

static GtkTextTag *
parseSyntaxItem (xmlDocPtr doc, xmlNodePtr cur, gchar* id, xmlChar *name)
{
      GtkTextTag *tag = NULL;

      xmlChar *start_regex = NULL;
      xmlChar *end_regex = NULL;

      xmlNodePtr child;

      child = cur->xmlChildrenNode;
      
      while (child != NULL)
      {     
            if (!xmlStrcmp (child->name, (const xmlChar *)"start-regex"))
            {
                  start_regex = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
            }
            else
            if (!xmlStrcmp (child->name, (const xmlChar *)"end-regex"))
            {
                  end_regex = xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
            }

            child = child->next;
      }

      if (start_regex == NULL)
      {
            g_warning ("Missing start-regex in tag 'syntax-item' (%s, line %ld)", 
                     doc->name, xmlGetLineNo (cur));

            return NULL;
      }

      if (end_regex == NULL)
      {
            xmlFree (start_regex);

            g_warning ("Missing end-regex in tag 'syntax-item' (%s, line %ld)", 
                     doc->name, xmlGetLineNo (cur));

            return NULL;
      }

      tag = gtk_syntax_tag_new (id,
                          (gchar *)name, 
                          strconvescape ((gchar *)start_regex),
                          strconvescape ((gchar *)end_regex));

      xmlFree (start_regex);
      xmlFree (end_regex);

      return tag;
}

static void 
tag_style_changed_cb (GtkSourceLanguage *language,
                  const gchar       *id,
                  GtkSourceTag      *tag)
{
      GtkSourceTagStyle *ts;
      gchar *tag_id;

      tag_id = gtk_source_tag_get_id (tag);

      if (strcmp (tag_id, id) != 0)
      {
            g_free (tag_id);
            return;
      }

      g_free (tag_id);

      ts = gtk_source_language_get_tag_style (language, id);

      if (ts != NULL)
      {
            gtk_source_tag_set_style (GTK_SOURCE_TAG (tag), ts);
            gtk_source_tag_style_free (ts);
      }
}

static GSList *
parseTag (GtkSourceLanguage *language, 
        xmlDocPtr          doc, 
        xmlNodePtr         cur, 
        GSList            *tag_list, 
        gboolean           populate_styles_table)
{
      GtkTextTag *tag = NULL;
      xmlChar *name;
      xmlChar *style;
      gchar   *id_temp;
      gchar   *id;
      
      name = xmlGetProp (cur, BAD_CAST "_name");
      if (name == NULL)
      {
            name = xmlGetProp (cur, BAD_CAST "name");
            id_temp = g_strdup ((gchar *)name);
      }
      else
      {
            xmlChar *tmp = xmlStrdup (BAD_CAST name);
            id_temp = g_strdup ((gchar *)name);
            xmlFree (name);
            name = tmp;
      }
      
      style = xmlGetProp (cur, BAD_CAST "style");

      if (name == NULL)
      {
            return tag_list;  
      }

      g_return_val_if_fail (id_temp != NULL, tag_list);
      id = escape_id (id_temp, -1);
      g_free (id_temp);

      if (style == NULL)
      {
            /* FIXME */
            style = xmlStrdup (BAD_CAST "Normal");
      }
      
      if (!xmlStrcmp (cur->name, (const xmlChar *)"line-comment"))
      {
            tag = parseLineComment (doc, cur, id, name);          
      }
      else if (!xmlStrcmp (cur->name, (const xmlChar *)"block-comment"))
      {
            tag = parseBlockComment (doc, cur, id, name);         
      }
      else if (!xmlStrcmp (cur->name, (const xmlChar *)"string"))
      {
            tag = parseString (doc, cur, id, name);         
      }
      else if (!xmlStrcmp (cur->name, (const xmlChar *)"keyword-list"))
      {
            tag = parseKeywordList (doc, cur, id, name);          
      }
      else if (!xmlStrcmp (cur->name, (const xmlChar *)"pattern-item"))
      {
            tag = parsePatternItem (doc, cur, id, name);          
      }
      else if (!xmlStrcmp (cur->name, (const xmlChar *)"syntax-item"))
      {
            tag = parseSyntaxItem (doc, cur, id, name);           
      }
      else
      {
            g_print ("Unknown tag: %s\n", cur->name);
      }

      if (tag != NULL)
      {
            GtkSourceTagStyle *ts;
            
            tag_list = g_slist_prepend (tag_list, tag);

            if (populate_styles_table)
                  g_hash_table_insert (language->priv->tag_id_to_style_name, 
                                   g_strdup (id), 
                                   g_strdup ((gchar *)style));

            ts = gtk_source_language_get_tag_style (language, id);

            if (ts != NULL)
            {
                  gtk_source_tag_set_style (GTK_SOURCE_TAG (tag), ts);
                  gtk_source_tag_style_free (ts);
            }
            
            g_signal_connect_object (language, 
                               "tag_style_changed",
                               G_CALLBACK (tag_style_changed_cb),
                               tag,
                               0);
      }
            
      xmlFree (name);
      xmlFree (style);
      g_free (id);

      return tag_list;
}

static GSList *
language_file_parse (GtkSourceLanguage *language,
                 gboolean           get_tags,
                 gboolean           populate_styles_table)
{
      GSList *tag_list = NULL;
      xmlDocPtr doc;
      xmlNodePtr cur;
#if GLIB_MINOR_VERSION >= 8   
      GMappedFile *mf;
#endif      
      
      xmlKeepBlanksDefault (0);

#if GLIB_MINOR_VERSION >= 8
      mf = g_mapped_file_new (language->priv->lang_file_name, FALSE, NULL);
      
      if (mf == NULL)
          doc = NULL;
      else
          {
            doc = xmlParseMemory (g_mapped_file_get_contents (mf), 
                              g_mapped_file_get_length (mf));

            g_mapped_file_free (mf);
          }
#else
      if (1) {
          gchar *content = NULL;
          gsize length = 0;
            
          g_file_get_contents (language->priv->lang_file_name, &content, &length, NULL);
          if (content != NULL) {
            doc = xmlParseMemory (content, length);
            g_free (content);
          } else {
            doc = NULL;
          }
      }     

#endif      
      
      if (doc == NULL)
      {
            g_warning ("Impossible to parse file '%s'",
                     language->priv->lang_file_name);
            return NULL;
      }

      cur = xmlDocGetRootElement (doc);
      
      if (cur == NULL) 
      {
            g_warning ("The lang file '%s' is empty",
                     language->priv->lang_file_name);

            xmlFreeDoc (doc);
            return NULL;
      }

      if (xmlStrcmp (cur->name, (const xmlChar *) "language")) {
            g_warning ("File '%s' is of the wrong type",
                     language->priv->lang_file_name);
            
            xmlFreeDoc (doc);
            return NULL;
      }

      /* FIXME: check that the language name, version, etc. are the 
       * right ones - Paolo */

      cur = xmlDocGetRootElement (doc);
      cur = cur->xmlChildrenNode;
      g_return_val_if_fail (cur != NULL, NULL);
      
      while (cur != NULL)
      {
            if (!xmlStrcmp (cur->name, (const xmlChar *)"escape-char"))
            {
                  xmlChar *escape;
                  gunichar esc_char;
                  
                  escape = xmlNodeListGetString (doc, cur->xmlChildrenNode, 1);
                  esc_char = g_utf8_get_char_validated ((gchar *)escape, -1);
                  if (esc_char == (gunichar) -1)
                  {
                        g_warning ("Invalid (non UTF8) escape character in file '%s'",
                                 language->priv->lang_file_name);
                        esc_char = 0;
                  }
                  xmlFree (escape);
                  language->priv->escape_char = esc_char;
                  
                  if (!get_tags)
                        break;
            }
            else if (get_tags)
            {
                  tag_list = parseTag (language,
                                   doc, 
                                   cur, 
                                   tag_list, 
                                   populate_styles_table);
            }
            
            cur = cur->next;
      }
      language->priv->escape_char_valid = TRUE;

      tag_list = g_slist_reverse (tag_list);
      
      xmlFreeDoc (doc);

      return tag_list;
}

/**
 * gtk_source_language_get_tags:
 * @language: a #GtkSourceLanguage.
 *
 * Returns a list of tags for the given @language.  You should unref the tags
 * and free the list after usage.
 *
 * Return value: a list of #GtkSourceTag objects.
 **/
GSList *
gtk_source_language_get_tags (GtkSourceLanguage *language)
{
      GSList *tag_list = NULL;
      gboolean populate_styles_table = FALSE;

      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
      
      if (language->priv->tag_id_to_style_name == NULL)
      {
            g_return_val_if_fail (language->priv->tag_id_to_style == NULL, NULL);

            language->priv->tag_id_to_style_name = g_hash_table_new_full ((GHashFunc)g_str_hash,
                                                            (GEqualFunc)g_str_equal,
                                                            (GDestroyNotify)g_free,
                                                            (GDestroyNotify)g_free);

            language->priv->tag_id_to_style = g_hash_table_new_full ((GHashFunc)g_str_hash,
                                                         (GEqualFunc)g_str_equal,
                                                         (GDestroyNotify)g_free,
                                                         (GDestroyNotify)gtk_source_tag_style_free);

            populate_styles_table = TRUE;
      }

      tag_list = language_file_parse (language, TRUE, populate_styles_table);

      return tag_list;
}

static gboolean
gtk_source_language_lazy_init_hash_tables (GtkSourceLanguage *language)
{
      if (language->priv->tag_id_to_style_name == NULL)
      {
            GSList *list;

            g_return_val_if_fail (language->priv->tag_id_to_style == NULL, FALSE);

            list = gtk_source_language_get_tags (language);

            g_slist_foreach (list, (GFunc)g_object_unref, NULL);
            g_slist_free (list);
      
            g_return_val_if_fail (language->priv->tag_id_to_style_name != NULL, FALSE);
            g_return_val_if_fail (language->priv->tag_id_to_style != NULL, FALSE);
      }

      return TRUE;
}

/**
 * gtk_source_language_get_tag_style:
 * @language: a #GtkSourceLanguage.
 * @tag_id: the ID of a #GtkSourceTag.
 *
 * Gets the style of the tag whose ID is @tag_id. If the style is 
 * not defined then returns the default style.
 *
 * Return value: a #GtkSourceTagStyle.
 **/
GtkSourceTagStyle *
gtk_source_language_get_tag_style (GtkSourceLanguage *language, 
                           const gchar       *tag_id)
{
      const GtkSourceTagStyle *style;

      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
      g_return_val_if_fail (tag_id != NULL, NULL);

      if (!gtk_source_language_lazy_init_hash_tables (language))
            return NULL;

      style = (const GtkSourceTagStyle*)g_hash_table_lookup (language->priv->tag_id_to_style,
                                                 tag_id);

      if (style == NULL)
      {
            return gtk_source_language_get_tag_default_style (language, tag_id);
      }
      else
      {
            return gtk_source_tag_style_copy (style);
      }
}

/**
 * gtk_source_language_get_tag_default_style:
 * @language: a #GtkSourceLanguage.
 * @tag_id: the ID of a #GtkSourceTag.
 *
 * Gets the default style of the tag whose ID is @tag_id. 
 *
 * Return value: a #GtkSourceTagStyle.
 **/
GtkSourceTagStyle*
gtk_source_language_get_tag_default_style (GtkSourceLanguage *language,
                                 const gchar       *tag_id)
{
      const gchar *style_name;
      
      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
      g_return_val_if_fail (tag_id != NULL, NULL);

      if (!gtk_source_language_lazy_init_hash_tables (language))
                  return NULL;

      style_name = (const gchar*)g_hash_table_lookup (language->priv->tag_id_to_style_name,
                                          tag_id);

      if (style_name != NULL)
      {
            GtkSourceTagStyle *tmp;

            g_return_val_if_fail (language->priv->style_scheme != NULL, NULL);

            tmp = gtk_source_style_scheme_get_tag_style (language->priv->style_scheme,
                                               style_name);
            if (tmp == NULL)
                  return gtk_source_tag_style_copy (&normal_style);
            else
                  return tmp;
      }
      else
            return gtk_source_tag_style_copy (&normal_style);
}

/**
 * gtk_source_language_set_tag_style:
 * @language: a #GtkSourceLanguage.
 * @tag_id: the ID of a #GtkSourceTag.
 * @style: a #GtkSourceTagStyle.
 *
 * Sets the @style of the tag whose ID is @tag_id. If @style is %NULL
 * restore the default style.
 **/
void 
gtk_source_language_set_tag_style (GtkSourceLanguage       *language,
                           const gchar             *tag_id,
                           const GtkSourceTagStyle *style)
{
      g_return_if_fail (GTK_SOURCE_LANGUAGE (language));
      g_return_if_fail (tag_id != NULL);

      if (!gtk_source_language_lazy_init_hash_tables (language))
                  return;     
      
      if (style != NULL)
      {
            GtkSourceTagStyle *ts;
            
            ts = gtk_source_tag_style_copy (style);

            g_hash_table_insert (language->priv->tag_id_to_style,
                             g_strdup (tag_id),
                             ts);
      }
      else
      {
            g_hash_table_remove (language->priv->tag_id_to_style,
                             tag_id);
      }

      g_signal_emit (G_OBJECT (language),
                   signals[TAG_STYLE_CHANGED], 
                   0, 
                   tag_id);
}

/**
 * gtk_source_language_get_style_scheme:
 * @language: a #GtkSourceLanguage.
 * 
 * Gets the style scheme associated with the given @language.
 *
 * Return value: a #GtkSourceStyleScheme.
 **/
GtkSourceStyleScheme *
gtk_source_language_get_style_scheme (GtkSourceLanguage *language)
{
      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);

      return language->priv->style_scheme;

}

static gboolean
emit_tag_style_changed_signal (gpointer  key, 
                         gpointer  value,
                         gpointer  user_data)
{
      GtkSourceLanguage *language = GTK_SOURCE_LANGUAGE (user_data);

      g_signal_emit (G_OBJECT (language),
                   signals[TAG_STYLE_CHANGED], 
                   0, 
                   (gchar*)key);

      return TRUE;
}

static void
style_changed_cb (GtkSourceStyleScheme *scheme,
              const gchar          *tag_id,
              gpointer              user_data)
{
      GtkSourceLanguage *language = GTK_SOURCE_LANGUAGE (user_data);

      g_signal_emit (G_OBJECT (language),
                   signals[TAG_STYLE_CHANGED], 
                   0, 
                   tag_id);   
}

/**
 * gtk_source_language_set_style_scheme:
 * @language: a #GtkSourceLanguage.
 * @scheme: a #GtkSourceStyleScheme.
 * 
 * Sets the style scheme of the given @language.
 **/
void 
gtk_source_language_set_style_scheme (GtkSourceLanguage    *language,
                              GtkSourceStyleScheme *scheme)
{     
      g_return_if_fail (GTK_IS_SOURCE_LANGUAGE (language));
      g_return_if_fail (GTK_IS_SOURCE_STYLE_SCHEME (scheme));
      g_return_if_fail (language->priv->style_scheme != NULL);

      if (language->priv->style_scheme == scheme)
            return;

      g_object_unref (language->priv->style_scheme);
      
      language->priv->style_scheme = scheme;
      g_object_ref (language->priv->style_scheme);

      if (!gtk_source_language_lazy_init_hash_tables (language))
            return;

      g_hash_table_foreach (language->priv->tag_id_to_style_name,
                        (GHFunc) emit_tag_style_changed_signal,
                        (gpointer) language);

      g_signal_connect (G_OBJECT (scheme), "style_changed",
                    G_CALLBACK (style_changed_cb), language);
}

/**
 * gtk_source_language_get_escape_char:
 * @language: a #GtkSourceLanguage.
 * 
 * Gets the value of the ESC character in the given @language.
 * 
 * Return value: the value of the ESC character.
 **/
gunichar 
gtk_source_language_get_escape_char (GtkSourceLanguage *language)
{
      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), 0);

      if (!language->priv->escape_char_valid)
            language_file_parse (language, FALSE, FALSE);
            
      return language->priv->escape_char;
}

Generated by  Doxygen 1.6.0   Back to index