/* 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" #define BUFFER_SIZE 65536 /* line cannot be longer than this */ struct BlockParser { GHashTable *table; GQueue *active_tree; gchar *current_line; GHashTable *conditionals; guint ignore_level; }; typedef struct { gchar *replace_key; gchar *data; gboolean used; gboolean finished; gboolean is_conditional; gchar *conditional_key; gboolean should_ignore; } BlockData; 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->active_tree = g_queue_new (); return parser; } void block_parser_free (BlockParser *parser) { g_return_if_fail (parser != NULL); 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_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_strdup_printf ("%s%s", data->data, piece); else s = g_strdup (piece); g_free (data->data); data->data = s; data->used = FALSE; } else { s = g_strdup_printf ("%s%s", parser->current_line, piece); g_free (parser->current_line); parser->current_line = s; } } /* * block_parser_read_and_parse: reads input from the file and returns parsed line * - 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_read_and_parse (BlockParser *parser, FILE *stream) { gchar *buffer; gchar *b; gchar *token; gchar *start, *end; gchar *s; GHashTableIter iter; gchar *key; BlockData *data; gboolean handled; g_return_val_if_fail (parser != NULL, NULL); buffer = g_malloc0 (BUFFER_SIZE); if (! fgets (buffer, BUFFER_SIZE, stream) || strlen (buffer) == 0) { g_free (buffer); return NULL; } parser->current_line = g_strdup (""); /* find tokens */ b = buffer; while ((token = get_next_token (b, &start, &end, NULL))) { handled = FALSE; /* push the string before the token */ s = g_strndup (b, start - b); push_string (parser, s); g_free (s); /* match conditionals */ if (parser->conditionals && (token_has_prefix (token, "ifdef") || token_has_prefix (token, "ifndef"))) { data = g_new0 (BlockData, 1); data->is_conditional = TRUE; data->conditional_key = extract_token_arg (token); 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)); 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 && token_has_prefix (token, "endif")) { s = extract_token_arg (token); data = g_queue_peek_head (parser->active_tree); if (data == NULL || !data->is_conditional || strcmp (data->conditional_key, s) != 0) { log_error ("block_parser_read_and_parse: something is wrong with the parser table: expected '%s' but got '%s'\n", s, data->conditional_key); } 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 && token_has_prefix (token, "else")) { s = extract_token_arg (token); data = g_queue_peek_head (parser->active_tree); if (data == NULL || !data->is_conditional || strcmp (data->conditional_key, s) != 0) { log_error ("block_parser_read_and_parse: something is wrong with the parser table: expected '%s' but got '%s'\n", s, data->conditional_key); } 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); b = end + 1; } /* push rest of the buffer till the end of the line */ push_string (parser, b); g_free (buffer); return parser->current_line; }