/* 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 #include "replace-table.h" #include "gallery-utils.h" struct ReplaceTable { GHashTable *table; GHashTable *defines; GHashTable *functions; }; typedef struct { ReplaceTableFunction callback; gpointer user_data; } RegisteredFuncData; ReplaceTable * replace_table_new () { ReplaceTable *table; table = g_new0 (ReplaceTable, 1); table->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); table->functions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); return table; } void replace_table_free (ReplaceTable *table) { g_return_if_fail (table != NULL); g_hash_table_destroy (table->table); g_hash_table_destroy (table->functions); g_free (table); } /* * replace_table_add_key: add tag/value pair to replace * * tag, value will be referenced inside * */ void replace_table_add_key (ReplaceTable *table, const gchar *tag, const gchar *value) { g_return_if_fail (table != NULL); if (tag != NULL && value != NULL) g_hash_table_replace (table->table, g_strdup (tag), g_strdup (value)); } void replace_table_add_key_int (ReplaceTable *table, const gchar *tag, gint value) { g_return_if_fail (table != NULL); if (tag != NULL) g_hash_table_replace (table->table, g_strdup (tag), g_strdup_printf ("%d", value)); } void replace_table_add_key_printf (ReplaceTable *table, const gchar *tag, const gchar *format, ...) { va_list args; gchar *s = NULL; g_return_if_fail (table != NULL); if (tag != NULL && format != NULL) { va_start (args, format); g_vasprintf (&s, format, args); g_hash_table_replace (table->table, g_strdup (tag), s); va_end (args); } } /* * replace_table_set_defines: set a hash table to be used for defines lookup * */ void replace_table_set_defines (ReplaceTable *table, GHashTable *defines) { g_return_if_fail (table != NULL); table->defines = defines; } /* * replace_table_register_function: register a method callback for specified function name * */ void replace_table_register_function (ReplaceTable *table, const gchar *function_name, ReplaceTableFunction callback, gpointer user_data) { RegisteredFuncData *func_data; g_return_if_fail (table != NULL); func_data = g_malloc0 (sizeof (RegisteredFuncData)); func_data->callback = callback; func_data->user_data = user_data; g_hash_table_insert (table->functions, g_strdup (function_name), func_data); } static void add_arg (GPtrArray *table, const char *str, gsize n) { char *s; char *s2; if (n > 0) { s = g_strndup (str, n); s2 = g_strstrip (s); if (strlen (s2) > 0) g_ptr_array_add (table, g_strdup (s2)); g_free (s); } } static void parse_function_args (const char *str, GPtrArray *a) { gboolean in_quotes; gboolean in_apos; int i; const char *str_start; in_quotes = FALSE; in_apos = FALSE; str_start = str; for (i = 0; i < strlen (str); i++) { switch (str[i]) { case '"': if (in_apos || (i > 0 && str[i - 1] == '\\')) continue; in_quotes = ! in_quotes; if (in_quotes) { str_start = str + i + 1; } else { add_arg (a, str_start, str + i - str_start); str_start = NULL; } break; case '\'': if (in_quotes || (i > 0 && str[i - 1] == '\\')) continue; in_apos = ! in_apos; if (in_apos) { str_start = str + i + 1; } else { add_arg (a, str_start, str + i - str_start); str_start = NULL; } break; case ',': if (! in_quotes && ! in_apos && str_start) { add_arg (a, str_start, str + i - str_start); str_start = str + i + 1; } break; } } if (in_quotes) g_print ("Error: argument string \"%s\" is missing a closing quote\n", str); else if (in_apos) g_print ("Error: argument string \"%s\" is missing a closing apostrophe\n", str); else if (str_start != NULL) add_arg (a, str_start, str + i - str_start); } /* * parse_function: returns name of the function or NULL if token is invalid. Parsed arguments are stored in *args. * - in case an exclamation mark is present before the function name, the *negation argument is set to TRUE * */ gchar * parse_function (const char *token, gchar ***args, gboolean *negation) { char *opening_p, *closing_p; GPtrArray *a; gchar *tok; gchar *s; gchar *fname; if (args) *args = NULL; if (negation) *negation = FALSE; g_return_val_if_fail (token != NULL, NULL); tok = g_strdup (token); g_strstrip (tok); opening_p = strchr (tok, '('); closing_p = strrchr (tok, ')'); /* every function call should contain an opening and closing parenthesis */ if (opening_p == NULL || closing_p == NULL || closing_p <= opening_p) { g_free (tok); return NULL; } /* find a possible exclamation mark */ if (*tok == '!') { if (negation) *negation = TRUE; *tok = ' '; } /* find function name */ s = g_strndup (tok, opening_p - tok); fname = g_strdup (g_strstrip (s)); g_free (s); /* parse arguments */ if (args) { a = g_ptr_array_new (); s = g_strndup (opening_p + 1, closing_p - opening_p - 1); parse_function_args (s, a); g_free (s); g_ptr_array_add (a, NULL); *args = (gchar **) g_ptr_array_free (a, FALSE); } g_free (tok); return fname; } /* * replace_table_process: process buffer and replace all tags filled in the replace table * * - reallocates source buffer * */ void replace_table_process (gchar **buffer, ReplaceTable *table) { gchar *token; gchar *start, *end; gchar *b; gchar *replace_value; gchar *s; GString *dst; gboolean tag_parameter; gboolean handled; gchar **args; RegisteredFuncData *func_data; g_return_if_fail (table != NULL); g_return_if_fail (buffer != NULL); if (*buffer == NULL || strlen (*buffer) < 1) return; dst = g_string_new (NULL); b = *buffer; while ((token = get_next_token (b, &start, &end, &tag_parameter))) { handled = FALSE; /* push the string before the token */ g_string_append_len (dst, b, start - b); replace_value = NULL; /* match defines */ if (table->defines && token_has_prefix (token, "value")) { s = extract_token_arg (token); replace_value = g_strdup (g_hash_table_lookup (table->defines, s)); /* fall back to empty string if not defined */ if (replace_value == NULL) replace_value = g_strdup (""); g_free (s); } /* check registered functions */ if (! replace_value) { s = parse_function (token, &args, NULL); if (s) { func_data = g_hash_table_lookup (table->functions, s); if (func_data) { replace_value = func_data->callback (args, func_data->user_data); if (replace_value == NULL) replace_value = g_strdup (""); } g_strfreev (args); g_free (s); } } /* lookup in the replace table */ if (! replace_value) replace_value = g_strdup (g_hash_table_lookup (table->table, token)); /* process the data */ if (replace_value) { if (! tag_parameter) adjust_tags_normal (&replace_value); else adjust_tags_parameter (&replace_value); g_string_append (dst, replace_value); g_free (replace_value); handled = TRUE; } /* push the tag if not matched above */ if (! handled) g_string_append_len (dst, start, end - start + 1); g_free (token); b = end + 1; } /* push rest of the buffer till the end of the line */ g_string_append (dst, b); g_free (*buffer); *buffer = g_string_free (dst, FALSE); } /* * adjust_tags_normal: adjust string for normal HTML use * adjust_tags_parameter: adjust string for use as tag parameter value * - both funtions return newly allocated string */ void adjust_tags_normal (gchar **str) { fix_entities (str); } void adjust_tags_parameter (gchar **str) { /* TODO: replace line endings with a single space? */ remove_tags (str, ""); /* comments */ remove_tags (str, "<", ">"); /* tags */ fix_entities (str); /* entities */ str_replace (str, "\"", """); /* " */ str_replace (str, "'", "'"); /* ' */ } /* -------------------------------------------------------------------------------------------------------- */ /* * get_next_token: retrieves first token ( or $(TOKEN)) in the string * - returns newly allocated token name, caller is responsible for freeing * - start and end are positions of token in the source string 's' * - tag_parameter indicates where the token stands - as a tag itself ( ) * or as a parameter value of some tag ( $(TOKEN) ) * */ gchar * get_next_token (const gchar *s, gchar **start, gchar **end, gboolean *tag_parameter) { gchar *dollar; gchar *end_paren; gchar *b; int num_paren; *start = NULL; *end = NULL; if (tag_parameter) *tag_parameter = TRUE; dollar = strstr (s, "$("); if (dollar == NULL) return NULL; end_paren = strchr (dollar + 2, ')'); if (end_paren == NULL) return NULL; /* Count opening parentheses and include nested parentheses in the token */ num_paren = 0; for (b = dollar; b < end_paren; b++) if (*b == '(') num_paren++; while (num_paren > 1) { end_paren = strchr (end_paren + 1, ')'); if (end_paren == NULL) return NULL; num_paren--; } g_assert (num_paren <= 1); /* something really bad happened */ /* Go back and try to find placeholder beginning */ for (b = dollar - 1; b >= s + 3; b--) { if (*b == '-' && *(b-1) == '-' && *(b-2) == '!' && *(b-3) == '<') { *start = b - 3; if (tag_parameter) *tag_parameter = FALSE; break; } if (*b != ' ') break; } if (*start == NULL) *start = dollar; /* Go forth and try to find placeholder end */ for (b = end_paren + 1; b <= end_paren + strlen (end_paren) - 3; b++) { if (*b == '-' && *(b+1) == '-' && *(b+2) == '>') { *end = b + 2; break; } if (*b != ' ') break; } if (*end == NULL) *end = end_paren; return g_strndup (dollar + 2, end_paren - dollar - 2); } /* * extract_token_arg: retrieves token argument "xxx(value)" * - returns newly allocated string, caller is responsible for freeing * */ gchar * extract_token_arg (const gchar *str) { const gchar *start; const gchar *end; start = strchr (str, '('); if (start == NULL) return NULL; start++; end = strrchr (str, ')'); if (end == NULL) return NULL; end++; return g_strndup (start, end - start - 1); } /* * token_has_prefix: returns TRUE if 'prefix' matches the token syntax (i.e. "prefix (") with unlimited number of spaces before the opening bracket * */ gboolean token_has_prefix (const gchar *token, const gchar *prefix) { const gchar *s; if (! g_str_has_prefix (token, prefix)) return FALSE; s = token + strlen (prefix); while (*s != '\0' && (*s == ' ' || *s == '\t')) s++; return (*s == '('); }