/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikatag.c * Copyright (C) 2008 Aurimas Juška * * 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 3 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, see . */ #include "config.h" #include #include #include "core-types.h" #include "pikatag.h" #define PIKA_TAG_INTERNAL_PREFIX "pika:" G_DEFINE_TYPE (PikaTag, pika_tag, G_TYPE_OBJECT) #define parent_class pika_tag_parent_class static void pika_tag_class_init (PikaTagClass *klass) { } static void pika_tag_init (PikaTag *tag) { tag->tag = 0; tag->collate_key = 0; tag->internal = FALSE; } /** * pika_tag_new: * @tag_string: a tag name. * * If given tag name is not valid, an attempt will be made to fix it. * * Returns: (nullable) (transfer full): a new #PikaTag object, * or %NULL if tag string is invalid and cannot be fixed. **/ PikaTag * pika_tag_new (const char *tag_string) { PikaTag *tag; gchar *tag_name; gchar *case_folded; gchar *collate_key; g_return_val_if_fail (tag_string != NULL, NULL); tag_name = pika_tag_string_make_valid (tag_string); if (! tag_name) return NULL; tag = g_object_new (PIKA_TYPE_TAG, NULL); tag->tag = g_quark_from_string (tag_name); case_folded = g_utf8_casefold (tag_name, -1); collate_key = g_utf8_collate_key (case_folded, -1); tag->collate_key = g_quark_from_string (collate_key); g_free (collate_key); g_free (case_folded); g_free (tag_name); return tag; } /** * pika_tag_try_new: * @tag_string: a tag name. * * Similar to pika_tag_new(), but returns %NULL if tag is surely not equal * to any of currently created tags. It is useful for tag querying to avoid * unneeded comparisons. If tag is created, however, it does not mean that * it would necessarily match with some other tag. * * Returns: (nullable) (transfer full): new #PikaTag object, * or %NULL if tag will not match with any other #PikaTag. **/ PikaTag * pika_tag_try_new (const char *tag_string) { PikaTag *tag; gchar *tag_name; gchar *case_folded; gchar *collate_key; GQuark tag_quark; GQuark collate_key_quark; tag_name = pika_tag_string_make_valid (tag_string); if (! tag_name) return NULL; case_folded = g_utf8_casefold (tag_name, -1); collate_key = g_utf8_collate_key (case_folded, -1); collate_key_quark = g_quark_try_string (collate_key); g_free (collate_key); g_free (case_folded); if (! collate_key_quark) { g_free (tag_name); return NULL; } tag_quark = g_quark_from_string (tag_name); g_free (tag_name); if (! tag_quark) return NULL; tag = g_object_new (PIKA_TYPE_TAG, NULL); tag->tag = tag_quark; tag->collate_key = collate_key_quark; return tag; } /** * pika_tag_get_internal: * @tag: a pika tag. * * Retrieve internal status of the tag. * * Returns: internal status of tag. Internal tags are not saved. **/ gboolean pika_tag_get_internal (PikaTag *tag) { g_return_val_if_fail (PIKA_IS_TAG (tag), FALSE); return tag->internal; } /** * pika_tag_set_internal: * @tag: a pika tag. * @internal: desired tag internal status * * Set internal status of the tag. Internal tags are usually automatically * generated and will not be saved into users tag cache. * **/ void pika_tag_set_internal (PikaTag *tag, gboolean internal) { g_return_if_fail (PIKA_IS_TAG (tag)); tag->internal = internal; } /** * pika_tag_get_name: * @tag: a pika tag. * * Retrieve name of the tag. * * Returns: name of tag. **/ const gchar * pika_tag_get_name (PikaTag *tag) { g_return_val_if_fail (PIKA_IS_TAG (tag), NULL); return g_quark_to_string (tag->tag); } /** * pika_tag_get_hash: * @tag: a pika tag. * * Hashing function which is useful, for example, to store #PikaTag in * a #GHashTable. * * Returns: hash value for tag. **/ guint pika_tag_get_hash (PikaTag *tag) { g_return_val_if_fail (PIKA_IS_TAG (tag), -1); return tag->collate_key; } /** * pika_tag_equals: * @tag: a pika tag. * @other: another pika tag to compare with. * * Compares tags for equality according to tag comparison rules. * * Returns: TRUE if tags are equal, FALSE otherwise. **/ gboolean pika_tag_equals (PikaTag *tag, PikaTag *other) { g_return_val_if_fail (PIKA_IS_TAG (tag), FALSE); g_return_val_if_fail (PIKA_IS_TAG (other), FALSE); return tag->collate_key == other->collate_key; } /** * pika_tag_compare_func: * @p1: pointer to left-hand #PikaTag object. * @p2: pointer to right-hand #PikaTag object. * * Compares tags according to tag comparison rules. Useful for sorting * functions. * * Returns: meaning of return value is the same as in strcmp(). **/ int pika_tag_compare_func (const void *p1, const void *p2) { PikaTag *t1 = PIKA_TAG (p1); PikaTag *t2 = PIKA_TAG (p2); return g_strcmp0 (g_quark_to_string (t1->collate_key), g_quark_to_string (t2->collate_key)); } /** * pika_tag_compare_with_string: * @tag: a #PikaTag object. * @tag_string: the string to compare to. * * Compares tag and a string according to tag comparison rules. Similar to * pika_tag_compare_func(), but can be used without creating temporary tag * object. * * Returns: meaning of return value is the same as in strcmp(). **/ gint pika_tag_compare_with_string (PikaTag *tag, const gchar *tag_string) { gchar *case_folded; const gchar *collate_key; gchar *collate_key2; gint result; g_return_val_if_fail (PIKA_IS_TAG (tag), 0); g_return_val_if_fail (tag_string != NULL, 0); collate_key = g_quark_to_string (tag->collate_key); case_folded = g_utf8_casefold (tag_string, -1); collate_key2 = g_utf8_collate_key (case_folded, -1); result = g_strcmp0 (collate_key, collate_key2); g_free (collate_key2); g_free (case_folded); return result; } /** * pika_tag_has_prefix: * @tag: a #PikaTag object. * @prefix_string: the prefix to compare to. * * Compares tag and a prefix according to tag comparison rules. Similar to * pika_tag_compare_with_string(), but does not work on the collate key * because that can't be matched partially. * * Returns: wheher #tag starts with @prefix_string. **/ gboolean pika_tag_has_prefix (PikaTag *tag, const gchar *prefix_string) { gchar *case_folded1; gchar *case_folded2; gboolean has_prefix; g_return_val_if_fail (PIKA_IS_TAG (tag), FALSE); g_return_val_if_fail (prefix_string != NULL, FALSE); case_folded1 = g_utf8_casefold (g_quark_to_string (tag->tag), -1); case_folded2 = g_utf8_casefold (prefix_string, -1); has_prefix = g_str_has_prefix (case_folded1, case_folded2); g_free (case_folded1); g_free (case_folded2); g_printerr ("'%s' has prefix '%s': %d\n", g_quark_to_string (tag->tag), prefix_string, has_prefix); return has_prefix; } /** * pika_tag_string_make_valid: * @tag_string: a text string. * * Tries to create a valid tag string from given @tag_string. * * Returns: (transfer full) (nullable): a newly allocated tag string in case * given @tag_string was valid or could be fixed, otherwise %NULL. Allocated * value should be freed using g_free(). **/ gchar * pika_tag_string_make_valid (const gchar *tag_string) { gchar *tag; GString *buffer; gchar *tag_cursor; gunichar c; g_return_val_if_fail (tag_string, NULL); tag = g_utf8_normalize (tag_string, -1, G_NORMALIZE_ALL); if (! tag) return NULL; tag = g_strstrip (tag); if (! *tag) { g_free (tag); return NULL; } buffer = g_string_new (""); tag_cursor = tag; if (g_str_has_prefix (tag_cursor, PIKA_TAG_INTERNAL_PREFIX)) { tag_cursor += strlen (PIKA_TAG_INTERNAL_PREFIX); } do { c = g_utf8_get_char (tag_cursor); tag_cursor = g_utf8_next_char (tag_cursor); if (g_unichar_isprint (c) && ! pika_tag_is_tag_separator (c)) { g_string_append_unichar (buffer, c); } } while (c); g_free (tag); tag = g_string_free (buffer, FALSE); tag = g_strstrip (tag); if (! *tag) { g_free (tag); return NULL; } return tag; } /** * pika_tag_is_tag_separator: * @c: Unicode character. * * Defines a set of characters that are considered tag separators. The * tag separators are hand-picked from the set of characters with the * Terminal_Punctuation property as specified in the version 5.1.0 of * the Unicode Standard. * * Returns: %TRUE if the character is a tag separator. */ gboolean pika_tag_is_tag_separator (gunichar c) { switch (c) { case 0x002C: /* COMMA */ case 0x060C: /* ARABIC COMMA */ case 0x07F8: /* NKO COMMA */ case 0x1363: /* ETHIOPIC COMMA */ case 0x1802: /* MONGOLIAN COMMA */ case 0x1808: /* MONGOLIAN MANCHU COMMA */ case 0x3001: /* IDEOGRAPHIC COMMA */ case 0xA60D: /* VAI COMMA */ case 0xFE50: /* SMALL COMMA */ case 0xFF0C: /* FULLWIDTH COMMA */ case 0xFF64: /* HALFWIDTH IDEOGRAPHIC COMMA */ return TRUE; default: return FALSE; } } /** * pika_tag_or_null_ref: * @tag: a #PikaTag * * A simple wrapper around g_object_ref() that silently accepts %NULL. **/ void pika_tag_or_null_ref (PikaTag *tag_or_null) { if (tag_or_null) { g_return_if_fail (PIKA_IS_TAG (tag_or_null)); g_object_ref (tag_or_null); } } /** * pika_tag_or_null_unref: * @tag: a #PikaTag * * A simple wrapper around g_object_unref() that silently accepts %NULL. **/ void pika_tag_or_null_unref (PikaTag *tag_or_null) { if (tag_or_null) { g_return_if_fail (PIKA_IS_TAG (tag_or_null)); g_object_unref (tag_or_null); } }