/* Cataract - Static web photo gallery generator * Copyright (C) 2009 Tomas Bzatek * * 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 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include "block-parser.h" #include "gallery-utils.h" #include "replace-table.h" struct BlockParser { GHashTable *table; GQueue *active_tree; gchar *current_line; GHashTable *conditionals; GHashTable *functions; guint ignore_level; }; typedef struct { gchar *replace_key; gchar *data; gboolean used; gboolean finished; gboolean is_conditional; gboolean is_conditional_function; gchar *conditional_key; gboolean should_ignore; } BlockData; typedef struct { BlockParserConditionalFunction callback; gpointer user_data; } BlockParserFuncData; static void block_parser_destroy_notify (gpointer data) { BlockData *d = data; if (data == NULL) return; g_free (d->replace_key); g_free (d->data); g_free (d->conditional_key); g_free (d); } BlockParser * block_parser_new () { BlockParser *parser; parser = g_new0 (BlockParser, 1); parser->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, block_parser_destroy_notify); parser->functions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); parser->active_tree = g_queue_new (); return parser; } void block_parser_free (BlockParser *parser) { g_return_if_fail (parser != NULL); g_hash_table_destroy (parser->functions); g_hash_table_destroy (parser->table); g_queue_free (parser->active_tree); g_free (parser); } /* * block_parser_set_conditionals: set a hash table to be used for conditionals lookup * */ void block_parser_set_conditionals (BlockParser *parser, GHashTable *conditionals) { g_return_if_fail (parser != NULL); parser->conditionals = conditionals; } /* * block_parser_register_key: tell parser to expect the key to catch * key: part of the BEGIN_xxx placeholder, matched BEGIN_xxx and END_xxx as one block * replace_key: placeholder to replace finished block with, for later use in replace_table, * placeholder will be surrounded by "" * */ void block_parser_register_key (BlockParser *parser, const gchar *key, const gchar *replace_key) { BlockData *data; g_return_if_fail (parser != NULL); data = g_new0 (BlockData, 1); if (replace_key) data->replace_key = g_strdup_printf ("", replace_key); data->used = TRUE; /* buffer is empty, mask it for block_parser_has_unused_data() */ data->is_conditional = FALSE; g_hash_table_replace (parser->table, g_strdup (key), data); } /* * block_parser_register_function: function called to determine whether a block should be ignored or not * */ void block_parser_register_function (BlockParser *parser, const gchar *conditional_name, BlockParserConditionalFunction callback, gpointer user_data) { BlockParserFuncData *func_data; g_return_if_fail (parser != NULL); func_data = g_malloc0 (sizeof (BlockParserFuncData)); func_data->callback = callback; func_data->user_data = user_data; g_hash_table_insert (parser->functions, g_strdup (conditional_name), func_data); } /* * block_parser_get_data: return retrieved data or NULL if none read yet * returns newly allocated string, caller is responsible for freeing * */ gchar * block_parser_get_data (BlockParser *parser, const gchar *key) { BlockData *data = NULL; g_return_val_if_fail (parser != NULL, NULL); data = g_hash_table_lookup (parser->table, key); if (data != NULL) { data->used = TRUE; return g_strdup (data->data); } return NULL; } /* * block_parser_has_unused_data: indicates whether the data have already been read and used (by calling block_parser_get_data) * */ gboolean block_parser_has_unused_data (BlockParser *parser, const gchar *key) { BlockData *data = NULL; g_return_val_if_fail (parser != NULL, FALSE); data = g_hash_table_lookup (parser->table, key); if (data != NULL) { return (data->used == FALSE) && (data->finished == TRUE); } return FALSE; } /* * block_parser_set_as_used: manually set data as used - sometimes blocks can act as containers for other nested blocks and * cannot be used itself * */ void block_parser_set_as_used (BlockParser *parser, const gchar *key) { BlockData *data = NULL; g_assert (parser != NULL); data = g_hash_table_lookup (parser->table, key); if (data != NULL && data->finished == TRUE) { data->used = TRUE; } } /* -------------------------------------------------------------------------------------------------------- */ static void push_string (BlockParser *parser, const gchar *piece) { BlockData *data; gchar *s; guint i; /* Test if the data should be ignored */ if (parser->ignore_level > 0) return; /* Not ignoring, find first non-conditional record */ for (i = 0; (data = g_queue_peek_nth (parser->active_tree, i)) != NULL; i++) if (! data->is_conditional) break; /* Actually push the string */ if (data) { if (data->data) s = g_strconcat (data->data, piece, NULL); else s = g_strdup (piece); g_free (data->data); data->data = s; data->used = FALSE; } else { s = g_strconcat (parser->current_line, piece, NULL); g_free (parser->current_line); parser->current_line = s; } } static gboolean check_conditional_tree_for_token (const gchar *token, BlockData *data) { gchar *func1, *func2; gboolean ret; if (data == NULL || ! data->is_conditional) return FALSE; ret = FALSE; if (! data->is_conditional_function) { ret = g_strcmp0 (data->conditional_key, token) == 0; } if (data->is_conditional_function) { func1 = parse_function (token, NULL, NULL); func2 = parse_function (data->conditional_key, NULL, NULL); ret = (func1 != NULL && func2 != NULL && g_str_equal (func1, func2)); g_free (func1); g_free (func2); } return ret; } /* returns TRUE when the parser is in block retrieval state (i.e. between BEGIN_xxx and END_xxx tags) */ static gboolean in_block_retrieval (BlockParser *parser) { BlockData *data; if (parser->active_tree == NULL) return FALSE; data = g_queue_peek_head (parser->active_tree); if (data != NULL && ! data->is_conditional) return TRUE; return FALSE; } /* * block_parser_process: parse input data * - if there's a multiline block, string before the opening placeholder and * string after closing placeholder are returned on one line, * separated by "replace_key" placeholder as specified in registered blocks * - only the outmost "replace_key" placeholder will be used, * no matter how many nested blocks were inside * - returns newly allocated string, caller is responsible for freeing * */ gchar * block_parser_process (BlockParser *parser, const gchar *buffer) { gchar *token; gchar *start, *end; gchar *s; GHashTableIter iter; gchar *key; BlockData *data; gboolean handled; gchar **args; gboolean negation; BlockParserFuncData *func_data; g_return_val_if_fail (parser != NULL, NULL); g_return_val_if_fail (buffer != NULL, NULL); parser->current_line = g_strdup (""); /* find tokens */ while ((token = get_next_token (buffer, &start, &end, NULL))) { handled = FALSE; /* push the string before the token */ s = g_strndup (buffer, start - buffer); push_string (parser, s); g_free (s); /* match conditionals */ if (!in_block_retrieval (parser) && ((parser->conditionals && (token_has_prefix (token, "ifdef") || token_has_prefix (token, "ifndef"))) || token_has_prefix (token, "if"))) { data = g_new0 (BlockData, 1); data->is_conditional = TRUE; data->is_conditional_function = token_has_prefix (token, "if"); data->conditional_key = extract_token_arg (token); if (! data->is_conditional_function) { data->should_ignore = ((token_has_prefix (token, "ifdef") && g_hash_table_lookup (parser->conditionals, data->conditional_key) == NULL) || (token_has_prefix (token, "ifndef") && g_hash_table_lookup (parser->conditionals, data->conditional_key) != NULL)); } else { s = parse_function (data->conditional_key, &args, &negation); data->should_ignore = TRUE; if (s == NULL) { log_error ("block_parser_read_and_parse: invalid conditional function specified: '%s'\n", data->conditional_key); } else { func_data = g_hash_table_lookup (parser->functions, s); if (func_data) { /* when not found, ignore the block as the function cannot be called */ data->should_ignore = ! func_data->callback (args, func_data->user_data); if (negation) data->should_ignore = ! data->should_ignore; } g_strfreev (args); g_free (s); } } if (data->should_ignore && parser->ignore_level == 0) parser->ignore_level = 1; else if (parser->ignore_level > 0) parser->ignore_level++; g_queue_push_head (parser->active_tree, data); handled = TRUE; } if (parser->conditionals && !in_block_retrieval (parser) && token_has_prefix (token, "endif")) { s = extract_token_arg (token); data = g_queue_peek_head (parser->active_tree); if (! check_conditional_tree_for_token (s, data)) { log_error ("block_parser_read_and_parse: something is wrong with the parser table: expected '%s' but got '%s'\n", data->conditional_key, s); } else { g_queue_pop_head (parser->active_tree); if (parser->ignore_level > 0) parser->ignore_level--; block_parser_destroy_notify (data); handled = TRUE; } g_free (s); } if (parser->conditionals && !in_block_retrieval (parser) && token_has_prefix (token, "else")) { s = extract_token_arg (token); data = g_queue_peek_head (parser->active_tree); if (! check_conditional_tree_for_token (s, data)) { log_error ("block_parser_read_and_parse: something is wrong with the parser table: expected '%s' but got '%s'\n", data->conditional_key, s); } else { data->should_ignore = ! data->should_ignore; /* if we're not ignoring, level is zero, let's start ignoring by setting to one */ if (data->should_ignore && parser->ignore_level == 0) parser->ignore_level = 1; else /* if we're ignoring but should not, let's go down to zero */ if (! data->should_ignore && parser->ignore_level == 1) parser->ignore_level = 0; /* in all other cases (level > 1) we're ignoring already and we're not the topmost block, so let's keep being ignored */ handled = TRUE; } g_free (s); } /* match known tags */ g_hash_table_iter_init (&iter, parser->table); while (!handled && g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &data)) { /* test BEGIN_ tokens */ s = g_strdup_printf ("BEGIN_%s", key); if (strcmp (s, token) == 0 && data) { g_queue_push_head (parser->active_tree, data); handled = TRUE; } g_free (s); if (handled) break; /* test END_ tokens */ s = g_strdup_printf ("END_%s", key); if (strcmp (s, token) == 0) { if (data == NULL || data != g_queue_peek_head (parser->active_tree)) { log_error ("block_parser_read_and_parse: something is wrong with the parser table: expected '%s'\n", key); } else { g_queue_pop_head (parser->active_tree); /* push the replacement placeholder */ if (data->replace_key) push_string (parser, data->replace_key); data->finished = TRUE; handled = TRUE; } } g_free (s); } /* push the tag if not matched above */ if (! handled) { s = g_strndup (start, end - start + 1); push_string (parser, s); g_free (s); } g_free (token); buffer = end + 1; } /* push rest of the buffer till the end of the line */ push_string (parser, buffer); return parser->current_line; }