/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (c) 2002-2003 Mikael Hallendal * Copyright (c) 2002-2003 CodeFactory AB * Copyright (C) 2005 Imendio AB * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #ifdef HAVE_LIBZ #include #endif #include "dh-error.h" #include "dh-link.h" #include "dh-parser.h" #define d(x) #define DH_PARSER(o) ((DhParser *) o) #define BYTES_PER_READ 4096 typedef struct { GMarkupParser *m_parser; GMarkupParseContext *context; const gchar *path; gchar *base; /* Top node of book */ GNode *book_node; /* Current sub section node */ GNode *parent; gboolean parsing_chapters; gboolean parsing_functions; GNode *book_tree; GList **keywords; /* Version 2 uses instead of . */ gint version; } DhParser; static void parser_start_node_cb (GMarkupParseContext *context, const gchar *node_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error); static void parser_end_node_cb (GMarkupParseContext *context, const gchar *node_name, gpointer user_data, GError **error); static void parser_error_cb (GMarkupParseContext *context, GError *error, gpointer user_data); static gboolean parser_read_gz_file (const gchar *path, GNode *book_tree, GList **keywords, GError **error); static gchar *extract_page_name (const gchar *uri); static void parser_start_node_cb (GMarkupParseContext *context, const gchar *node_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { DhParser *parser; gint i, line, col; DhLink *dh_link; gchar *full_link, *page; parser = DH_PARSER (user_data); if (!parser->book_node) { const gchar *xmlns = NULL; const gchar *title = NULL; const gchar *base = NULL; const gchar *name = NULL; const gchar *link = NULL; if (g_ascii_strcasecmp (node_name, "book") != 0) { g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, DH_ERROR, DH_ERROR_MALFORMED_BOOK, _("Expected '%s' got '%s' at line %d, column %d"), "book", node_name, line, col); return; } for (i = 0; attribute_names[i]; ++i) { if (g_ascii_strcasecmp (attribute_names[i], "xmlns") == 0) { xmlns = attribute_values[i]; if (g_ascii_strcasecmp (xmlns, "http://www.devhelp.net/book") != 0) { /* Set error */ g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, DH_ERROR, DH_ERROR_MALFORMED_BOOK, _("Invalid namespace '%s' at" " line %d, column %d"), xmlns, line, col); return; } } else if (g_ascii_strcasecmp (attribute_names[i], "name") == 0) { name = attribute_values[i]; } else if (g_ascii_strcasecmp (attribute_names[i], "title") == 0) { title = attribute_values[i]; } else if (g_ascii_strcasecmp (attribute_names[i], "base") == 0) { base = attribute_values[i]; } else if (g_ascii_strcasecmp (attribute_names[i], "link") == 0) { link = attribute_values[i]; } } if (!title || !name || !link) { /* Required attributes */ g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, DH_ERROR, DH_ERROR_MALFORMED_BOOK, _("title, name, and link elements are " "required at line %d, column %d"), line, col); return; } if (base) { parser->base = g_strdup (base); } else { parser->base = g_path_get_dirname (parser->path); } full_link = g_strconcat (parser->base, "/", link, NULL); dh_link = dh_link_new (DH_LINK_TYPE_BOOK, title, name, NULL, full_link); g_free (full_link); *parser->keywords = g_list_prepend (*parser->keywords, dh_link); parser->book_node = g_node_new (dh_link); g_node_prepend (parser->book_tree, parser->book_node); parser->parent = parser->book_node; return; } if (parser->parsing_chapters) { const gchar *name = NULL; const gchar *link = NULL; GNode *node; if (g_ascii_strcasecmp (node_name, "sub") != 0) { g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, DH_ERROR, DH_ERROR_MALFORMED_BOOK, _("Expected '%s' got '%s' at line %d, column %d"), "sub", node_name, line, col); return; } for (i = 0; attribute_names[i]; ++i) { if (g_ascii_strcasecmp (attribute_names[i], "name") == 0) { name = attribute_values[i]; } else if (g_ascii_strcasecmp (attribute_names[i], "link") == 0) { link = attribute_values[i]; } } if (!name || !link) { g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, DH_ERROR, DH_ERROR_MALFORMED_BOOK, _("name and link elements are required " "inside on line %d, column %d"), line, col); return; } full_link = g_strconcat (parser->base, "/", link, NULL); page = extract_page_name (link); dh_link = dh_link_new (DH_LINK_TYPE_PAGE, name, DH_LINK(parser->book_node->data)->book, page, full_link); g_free (full_link); g_free (page); *parser->keywords = g_list_prepend (*parser->keywords, dh_link); node = g_node_new (dh_link); g_node_prepend (parser->parent, node); parser->parent = node; } else if (parser->parsing_functions) { gboolean ok = FALSE; const gchar *name = NULL; const gchar *link = NULL; const gchar *deprecated = NULL; if (g_ascii_strcasecmp (node_name, "function") == 0) { ok = TRUE; if (parser->version == 2) { /* Skip this keyword. */ return; } } else if (g_ascii_strcasecmp (node_name, "keyword") == 0) { ok = TRUE; /* Note: We have this hack since there are released * tarballs out there of GTK+ etc that have been built * with a non-released version of gtk-doc that didn't * have the proper versioning scheme. So when we find * this new tag, we force the version to the newer one. */ if (parser->version < 2) { parser->version = 2; } } if (!ok) { g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, DH_ERROR, DH_ERROR_MALFORMED_BOOK, _("Expected '%s' got '%s' at line %d, column %d"), "function or keyword", node_name, line, col); return; } for (i = 0; attribute_names[i]; ++i) { if (g_ascii_strcasecmp (attribute_names[i], "name") == 0) { name = attribute_values[i]; } else if (g_ascii_strcasecmp (attribute_names[i], "link") == 0) { link = attribute_values[i]; } else if (g_ascii_strcasecmp (attribute_names[i], "deprecated") == 0) { deprecated = attribute_values[i]; } } if (!name || !link) { /* Required */ g_markup_parse_context_get_position (context, &line, &col); g_set_error (error, DH_ERROR, DH_ERROR_MALFORMED_BOOK, _("name and link elements are required " "inside on line %d, column %d"), line, col); return; } /* Strip out these, they are only present for code that gtk-doc * couldn't parse properly. We'll get this information in a * better way soon from gtk-doc. */ if (g_str_has_prefix (name, "struct ")) { name = name + 7; } else if (g_str_has_prefix (name, "union ")) { name = name + 6; } else if (g_str_has_prefix (name, "enum ")) { name = name + 5; } full_link = g_strconcat (parser->base, "/", link, NULL); page = extract_page_name (link); if (g_str_has_suffix (name, " ()")) { gchar *tmp; tmp = g_strndup (name, strlen (name) - 3); dh_link = dh_link_new (DH_LINK_TYPE_KEYWORD, tmp, DH_LINK (parser->book_node->data)->book, page, full_link); g_free (tmp); } else { dh_link = dh_link_new (DH_LINK_TYPE_KEYWORD, name, DH_LINK (parser->book_node->data)->book, page, full_link); } g_free (full_link); g_free (page); if (deprecated) { dh_link_set_is_deprecated (dh_link, TRUE); } *parser->keywords = g_list_prepend (*parser->keywords, dh_link); } else if (g_ascii_strcasecmp (node_name, "chapters") == 0) { parser->parsing_chapters = TRUE; } else if (g_ascii_strcasecmp (node_name, "functions") == 0) { parser->parsing_functions = TRUE; } } static void parser_end_node_cb (GMarkupParseContext *context, const gchar *node_name, gpointer user_data, GError **error) { DhParser *parser; parser = DH_PARSER (user_data); if (parser->parsing_chapters) { g_node_reverse_children (parser->parent); if (g_ascii_strcasecmp (node_name, "sub") == 0) { parser->parent = parser->parent->parent; /* Move up in the tree */ } else if (g_ascii_strcasecmp (node_name, "chapters") == 0) { parser->parsing_chapters = FALSE; } } else if (parser->parsing_functions) { if (g_ascii_strcasecmp (node_name, "function") == 0) { /* Do nothing */ return; } else if (g_ascii_strcasecmp (node_name, "functions") == 0) { parser->parsing_functions = FALSE; } } /* else if (g_ascii_strcasecmp (node_name, "book") == 0) { */ /* } */ } static void parser_error_cb (GMarkupParseContext *context, GError *error, gpointer user_data) { DhParser *parser; parser = DH_PARSER (user_data); g_markup_parse_context_free (parser->context); parser->context = NULL; } gboolean dh_parser_read_file (const gchar *path, GNode *book_tree, GList **keywords, GError **error) { DhParser *parser; GIOChannel *io; gchar buf[BYTES_PER_READ]; gboolean result = TRUE; if (g_str_has_suffix (path, ".gz")) { return parser_read_gz_file (path, book_tree, keywords, error); } parser = g_new0 (DhParser, 1); if (!parser) { g_set_error (error, DH_ERROR, DH_ERROR_INTERNAL_ERROR, _("Could not create book parser")); return FALSE; } if (g_str_has_suffix (path, ".devhelp2")) { parser->version = 2; } else { parser->version = 1; } parser->m_parser = g_new0 (GMarkupParser, 1); if (!parser->m_parser) { g_free (parser); g_set_error (error, DH_ERROR, DH_ERROR_INTERNAL_ERROR, _("Could not create markup parser")); return FALSE; } parser->m_parser->start_element = parser_start_node_cb; parser->m_parser->end_element = parser_end_node_cb; parser->m_parser->error = parser_error_cb; parser->context = g_markup_parse_context_new (parser->m_parser, 0, parser, NULL); parser->parent = NULL; parser->parsing_functions = FALSE; parser->parsing_chapters = FALSE; parser->path = path; parser->book_tree = book_tree; parser->keywords = keywords; /* Parse the string */ io = g_io_channel_new_file (path, "r", error); if (!io) { result = FALSE; goto exit; } while (TRUE) { GIOStatus io_status; gsize bytes_read; io_status = g_io_channel_read_chars (io, buf, BYTES_PER_READ, &bytes_read, error); if (io_status == G_IO_STATUS_ERROR) { result = FALSE; goto exit; } if (io_status != G_IO_STATUS_NORMAL) { break; } g_markup_parse_context_parse (parser->context, buf, bytes_read, error); if (error != NULL && *error != NULL) { result = FALSE; goto exit; } if (bytes_read < BYTES_PER_READ) { break; } } exit: g_io_channel_unref (io); g_markup_parse_context_free (parser->context); g_free (parser->m_parser); g_free (parser); return result; } static gboolean parser_read_gz_file (const gchar *path, GNode *book_tree, GList **keywords, GError **error) { #ifdef HAVE_LIBZ DhParser *parser; gchar buf[BYTES_PER_READ]; gzFile file; parser = g_new0 (DhParser, 1); if (!parser) { g_set_error (error, DH_ERROR, DH_ERROR_INTERNAL_ERROR, _("Could not create book parser")); return FALSE; } parser->m_parser = g_new0 (GMarkupParser, 1); if (!parser->m_parser) { g_free (parser); g_set_error (error, DH_ERROR, DH_ERROR_INTERNAL_ERROR, _("Could not create markup parser")); return FALSE; } if (g_str_has_suffix (path, ".devhelp2")) { parser->version = 2; } else { parser->version = 1; } parser->m_parser->start_element = parser_start_node_cb; parser->m_parser->end_element = parser_end_node_cb; parser->m_parser->error = parser_error_cb; parser->context = g_markup_parse_context_new (parser->m_parser, 0, parser, NULL); parser->parent = NULL; parser->parsing_functions = FALSE; parser->parsing_chapters = FALSE; parser->path = path; parser->book_tree = book_tree; parser->keywords = keywords; /* Parse the string */ file = gzopen (path, "r"); if (!file) { g_markup_parse_context_free (parser->context); g_free (parser); g_set_error (error, DH_ERROR, DH_ERROR_FILE_NOT_FOUND, g_strerror (errno)); return FALSE; } while (TRUE) { gsize bytes_read; bytes_read = gzread (file, buf, BYTES_PER_READ); if (bytes_read == -1) { const gchar *message; gint err; g_markup_parse_context_free (parser->context); g_free (parser); message = gzerror (file, &err); g_set_error (error, DH_ERROR, DH_ERROR_INTERNAL_ERROR, _("Cannot uncompress book '%s': %s"), path, message); return FALSE; } g_markup_parse_context_parse (parser->context, buf, bytes_read, error); if (error != NULL && *error != NULL) { return FALSE; } if (bytes_read < BYTES_PER_READ) { break; } } gzclose (file); g_markup_parse_context_free (parser->context); g_free (parser); return TRUE; #else g_set_error (error, DH_ERROR, DH_ERROR_INTERNAL_ERROR, _("Devhelp is not built with zlib support")); return FALSE; #endif } static gchar * extract_page_name (const gchar *uri) { gchar *page = NULL; gchar **split; if ((split = g_strsplit (uri, ".", 2)) != NULL) { page = g_strdup (split[0]); g_strfreev (split); } return page; }