[gexiv2] Add function: if tag supports multiple values



commit 2f78f647b5f0d3f0b9ae11bebdf2acab26518fc6
Author: postscript-dev <43813-postscript-dev users noreply gitlab gnome org>
Date:   Wed Mar 17 14:53:24 2021 +0000

    Add function: if tag supports multiple values
    
    + The function works on all tags. For Iptc tags, this is the only way
      to find out if it is Repeatable or Non-repeatable.
    + Only added `_try_` public version
    + Added Python tests

 gexiv2/gexiv2-metadata-exif.cpp  | 14 ++++++++++++++
 gexiv2/gexiv2-metadata-iptc.cpp  | 14 ++++++++++++++
 gexiv2/gexiv2-metadata-private.h |  5 +++++
 gexiv2/gexiv2-metadata-xmp.cpp   | 41 ++++++++++++++++++++++++++++++++++++++++
 gexiv2/gexiv2-metadata.cpp       | 23 ++++++++++++++++++++++
 gexiv2/gexiv2-metadata.h         | 15 +++++++++++++++
 test/python/test_metadata.py     | 38 ++++++++++++++++++++++++++++++++++++-
 7 files changed, 149 insertions(+), 1 deletion(-)
---
diff --git a/gexiv2/gexiv2-metadata-exif.cpp b/gexiv2/gexiv2-metadata-exif.cpp
index 7b800fc..89749bf 100644
--- a/gexiv2/gexiv2-metadata-exif.cpp
+++ b/gexiv2/gexiv2-metadata-exif.cpp
@@ -432,6 +432,20 @@ const gchar* gexiv2_metadata_get_exif_tag_type (const gchar* tag, GError **error
     return NULL;
 }
 
+gboolean gexiv2_metadata_exif_tag_supports_multiple_values (const gchar* tag, GError **error) {
+    g_return_val_if_fail(tag != nullptr, FALSE);
+    g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
+
+    try {
+       // Exif does not support multiple values, but still check if @tag is valid
+        const Exiv2::ExifKey key(tag);
+    } catch (Exiv2::Error& e) {
+        g_set_error_literal(error, g_quark_from_string("GExiv2"), e.code(), e.what());
+    }
+
+    return FALSE;
+}
+
 GBytes* gexiv2_metadata_get_exif_tag_raw (GExiv2Metadata *self, const gchar* tag, GError **error) {
     g_return_val_if_fail(GEXIV2_IS_METADATA (self), NULL);
     g_return_val_if_fail(tag != NULL, NULL);
diff --git a/gexiv2/gexiv2-metadata-iptc.cpp b/gexiv2/gexiv2-metadata-iptc.cpp
index 165d1e6..afeec29 100644
--- a/gexiv2/gexiv2-metadata-iptc.cpp
+++ b/gexiv2/gexiv2-metadata-iptc.cpp
@@ -288,6 +288,20 @@ const gchar* gexiv2_metadata_get_iptc_tag_type (const gchar* tag, GError **error
     return NULL;
 }
 
+gboolean gexiv2_metadata_iptc_tag_supports_multiple_values(const gchar* tag, GError** error) {
+    g_return_val_if_fail(tag != nullptr, FALSE);
+    g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
+
+    try {
+        const Exiv2::IptcKey key(tag); // Check to see if @tag is valid
+        return (Exiv2::IptcDataSets::dataSetRepeatable(key.tag(), key.record()) ? TRUE : FALSE);
+    } catch (Exiv2::Error& e) {
+        g_set_error_literal(error, g_quark_from_string("GExiv2"), e.code(), e.what());
+    }
+
+    return FALSE;
+}
+
 GBytes* gexiv2_metadata_get_iptc_tag_raw (GExiv2Metadata *self, const gchar* tag, GError **error) {
     g_return_val_if_fail(GEXIV2_IS_METADATA (self), NULL);
     g_return_val_if_fail(tag != NULL, NULL);
diff --git a/gexiv2/gexiv2-metadata-private.h b/gexiv2/gexiv2-metadata-private.h
index b5482ed..d36ac50 100644
--- a/gexiv2/gexiv2-metadata-private.h
+++ b/gexiv2/gexiv2-metadata-private.h
@@ -63,9 +63,12 @@ G_GNUC_INTERNAL gchar*                       
gexiv2_metadata_get_exif_tag_interpreted_string (GExiv2
 G_GNUC_INTERNAL glong                  gexiv2_metadata_get_exif_tag_long       (GExiv2Metadata *self, const 
gchar* tag, GError **error);
 G_GNUC_INTERNAL gboolean               gexiv2_metadata_set_exif_tag_long       (GExiv2Metadata *self, const 
gchar* tag, glong value, GError **error);
 G_GNUC_INTERNAL gdouble                        gexiv2_metadata_get_exif_tag_rational_as_double 
(GExiv2Metadata *self, const gchar* tag, gdouble def);
+
 G_GNUC_INTERNAL const gchar*   gexiv2_metadata_get_exif_tag_label      (const gchar* tag, GError **error);
 G_GNUC_INTERNAL const gchar*   gexiv2_metadata_get_exif_tag_description (const gchar* tag, GError **error);
 G_GNUC_INTERNAL const gchar*   gexiv2_metadata_get_exif_tag_type (const gchar* tag, GError **error);
+G_GNUC_INTERNAL gboolean        gexiv2_metadata_exif_tag_supports_multiple_values(const gchar* tag, GError** 
error);
+
 G_GNUC_INTERNAL GBytes*                        gexiv2_metadata_get_exif_tag_raw        (GExiv2Metadata 
*self, const gchar* tag, GError **error);
 
 /* private XMP functions */
@@ -84,6 +87,7 @@ G_GNUC_INTERNAL gboolean              gexiv2_metadata_set_xmp_tag_multiple (GExiv2Metadata *
 G_GNUC_INTERNAL const gchar*   gexiv2_metadata_get_xmp_tag_label               (const gchar* tag, GError 
**error);
 G_GNUC_INTERNAL const gchar*   gexiv2_metadata_get_xmp_tag_description (const gchar* tag, GError **error);
 G_GNUC_INTERNAL const gchar*   gexiv2_metadata_get_xmp_tag_type        (const gchar* tag, GError **error);
+G_GNUC_INTERNAL gboolean        gexiv2_metadata_xmp_tag_supports_multiple_values(GExiv2Metadata* self, const 
gchar* tag, GError** error);
 
 G_GNUC_INTERNAL GBytes*                        gexiv2_metadata_get_xmp_tag_raw         (GExiv2Metadata 
*self, const gchar* tag, GError **error);
 
@@ -100,6 +104,7 @@ G_GNUC_INTERNAL gboolean            gexiv2_metadata_set_iptc_tag_multiple   
(GExiv2Metadata
 G_GNUC_INTERNAL const gchar*   gexiv2_metadata_get_iptc_tag_label      (const gchar* tag, GError **error);
 G_GNUC_INTERNAL const gchar*   gexiv2_metadata_get_iptc_tag_description        (const gchar* tag, GError 
**error);
 G_GNUC_INTERNAL const gchar*   gexiv2_metadata_get_iptc_tag_type       (const gchar* tag, GError **error);
+G_GNUC_INTERNAL gboolean        gexiv2_metadata_iptc_tag_supports_multiple_values(const gchar* tag, GError** 
error);
 
 G_GNUC_INTERNAL GBytes*                        gexiv2_metadata_get_iptc_tag_raw        (GExiv2Metadata 
*self, const gchar* tag, GError **error);
 
diff --git a/gexiv2/gexiv2-metadata-xmp.cpp b/gexiv2/gexiv2-metadata-xmp.cpp
index f0de00f..777d624 100644
--- a/gexiv2/gexiv2-metadata-xmp.cpp
+++ b/gexiv2/gexiv2-metadata-xmp.cpp
@@ -525,6 +525,47 @@ const gchar* gexiv2_metadata_get_xmp_tag_type (const gchar* tag, GError **error)
     return NULL;
 }
 
+gboolean gexiv2_metadata_xmp_tag_supports_multiple_values(GExiv2Metadata* self, const gchar* tag, GError** 
error) {
+    g_return_val_if_fail(GEXIV2_IS_METADATA(self), FALSE);
+    g_return_val_if_fail(self->priv != nullptr, FALSE);
+    g_return_val_if_fail(self->priv->image.get() != nullptr, FALSE);
+    g_return_val_if_fail(tag != nullptr, FALSE);
+    g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
+
+    try {
+        const Exiv2::XmpKey key(tag); // Check tag is in correct format
+        const gchar* type = gexiv2_metadata_get_xmp_tag_type(tag, error);
+
+        if (error != nullptr && *error != nullptr) {
+            g_set_error_literal(error, g_quark_from_string("GExiv2"), (*error)->code, (*error)->message);
+            return FALSE;
+        }
+
+        if (type == nullptr)
+            throw Exiv2::Error(Exiv2::ErrorCode::kerInvalidKey, tag);
+
+        // If @tag has a valid familyName and groupName, Exiv2 will return
+        // "XmpText" even if the tagName has never been added (e.g.
+        // "Xmp.dc.TagDoesNotExist").
+        // For consistency with the `_supports_multiple_values` Exif and Iptc functions,
+        // check if @tag exists - Note: all built-in tags have a label.
+        const auto& xmp_data = self->priv->image->xmpData();
+
+        if (g_ascii_strcasecmp(type, "XmpText") == 0 && gexiv2_metadata_get_xmp_tag_label(tag, error) == 
nullptr &&
+            xmp_data.findKey(key) == xmp_data.end()) {
+            throw Exiv2::Error(Exiv2::ErrorCode::kerInvalidKey, tag);
+        }
+
+        if (g_ascii_strcasecmp(type, "XmpAlt") == 0 || g_ascii_strcasecmp(type, "XmpBag") == 0 ||
+            g_ascii_strcasecmp(type, "XmpSeq") == 0 || g_ascii_strcasecmp(type, "LangAlt") == 0) {
+            return TRUE;
+        }
+    } catch (Exiv2::Error& e) {
+        g_set_error_literal(error, g_quark_from_string("GExiv2"), e.code(), e.what());
+    }
+    return FALSE;
+}
+
 GBytes* gexiv2_metadata_get_xmp_tag_raw (GExiv2Metadata *self, const gchar* tag, GError **error) {
     g_return_val_if_fail(GEXIV2_IS_METADATA (self), NULL);
     g_return_val_if_fail(tag != NULL, NULL);
diff --git a/gexiv2/gexiv2-metadata.cpp b/gexiv2/gexiv2-metadata.cpp
index 6448c2b..d51977b 100644
--- a/gexiv2/gexiv2-metadata.cpp
+++ b/gexiv2/gexiv2-metadata.cpp
@@ -1438,6 +1438,29 @@ const gchar* gexiv2_metadata_get_tag_type (const gchar *tag) {
     return value;
 }
 
+gboolean gexiv2_metadata_try_tag_supports_multiple_values(GExiv2Metadata* self, const gchar* tag, GError** 
error) {
+    g_return_val_if_fail(GEXIV2_IS_METADATA(self), FALSE);
+    g_return_val_if_fail(self->priv != nullptr, FALSE);
+    g_return_val_if_fail(self->priv->image.get() != nullptr, FALSE);
+    g_return_val_if_fail(tag != nullptr, FALSE);
+    g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
+
+    if (gexiv2_metadata_is_iptc_tag(tag) == TRUE)
+        return gexiv2_metadata_iptc_tag_supports_multiple_values(tag, error);
+
+    if (gexiv2_metadata_is_xmp_tag(tag) == TRUE)
+        return gexiv2_metadata_xmp_tag_supports_multiple_values(self, tag, error);
+
+    if (gexiv2_metadata_is_exif_tag(tag) == TRUE)
+        return gexiv2_metadata_exif_tag_supports_multiple_values(tag, error);
+
+    // Invalid tag (Family name)
+    Exiv2::Error e(Exiv2::ErrorCode::kerInvalidKey, tag);
+    g_set_error_literal(error, g_quark_from_string("GExiv2"), e.code(), e.what());
+
+    return FALSE;
+}
+
 GBytes* gexiv2_metadata_try_get_tag_raw(GExiv2Metadata *self, const gchar* tag, GError **error) {
     g_return_val_if_fail(GEXIV2_IS_METADATA (self), NULL);
     g_return_val_if_fail(tag != NULL, NULL);
diff --git a/gexiv2/gexiv2-metadata.h b/gexiv2/gexiv2-metadata.h
index 6b413de..a247ec4 100644
--- a/gexiv2/gexiv2-metadata.h
+++ b/gexiv2/gexiv2-metadata.h
@@ -535,6 +535,21 @@ const gchar*       gexiv2_metadata_try_get_tag_type        (const gchar *tag, GError 
**error)
 G_DEPRECATED_FOR(gexiv2_metadata_try_get_tag_type)
 const gchar*   gexiv2_metadata_get_tag_type    (const gchar *tag);
 
+/**
+ * gexiv2_metadata_try_tag_supports_multiple_values:
+ * @self: An instance of #GExiv2Metadata
+ * @tag: An Exiv2 tag
+ * @error: (allow-none): A return location for a #GError or %NULL
+ *
+ * The Exiv2 Tag Reference can be found at <ulink url="https://www.exiv2.org/metadata.html";></ulink>
+ *
+ * Returns: Whether @tag is capable of storing multiple values or not. If @tag is undefined
+ * (i.e. not built-in and not added to @self), then @error is set and %FALSE is returned.
+ *
+ * Since: 0.12.2
+ */
+gboolean gexiv2_metadata_try_tag_supports_multiple_values(GExiv2Metadata* self, const gchar* tag, GError** 
error);
+
 /**
  * gexiv2_metadata_get_supports_exif:
  * @self: An instance of #GExiv2Metadata
diff --git a/test/python/test_metadata.py b/test/python/test_metadata.py
index 4361dc5..9d325df 100644
--- a/test/python/test_metadata.py
+++ b/test/python/test_metadata.py
@@ -32,7 +32,7 @@ PY3K = sys.version_info[0] == 3
 
 import gi
 gi.require_version('GExiv2', '0.10')
-from gi.repository import GExiv2
+from gi.repository import GExiv2, GLib
 from fractions import Fraction
 
 
@@ -185,6 +185,7 @@ class TestMetadata(unittest.TestCase):
                        'stop_emission',
                        'stop_emission_by_name',
                        'thaw_notify',
+                       'try_tag_supports_multiple_values',
                        'try_delete_gps_info',
                        'try_generate_xmp_packet',
                        'try_get_exif_tag_rational',
@@ -722,6 +723,41 @@ generated the image. When the field is left blank, it is treated as unknown.""")
         self.assertEqual(
             self.metadata.get_iptc_tags(), ['Iptc.Application2.City'])
 
+    def test_try_tag_supports_multiple_values(self):
+        # Test different types of built-in/added tags for Exif/Iptc/Xmp
+         
+        # Add a new xmpText string
+        self.metadata.set_tag_string('Xmp.dc.NewXmpTextValue', 'New Value')
+        
+        ### Test Exif tags
+        self.assertFalse(self.metadata.try_tag_supports_multiple_values('Exif.Image.Model'))
+        
self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Exif.Madeup.Orientation')
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Exif.Image.MadeUp')
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Exif.MadeUp.MadeUp')
+
+        ### Test Iptc tags
+        self.assertTrue(self.metadata.try_tag_supports_multiple_values('Iptc.Application2.Keywords'))
+        self.assertFalse(self.metadata.try_tag_supports_multiple_values('Iptc.Envelope.ServiceId'))
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Iptc.Madeup.Keywords')
+        
self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Iptc.Application2.Madeup')
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Iptc.Madeup.MadeUp')
+
+        ### Test Xmp tags
+        self.assertFalse(self.metadata.try_tag_supports_multiple_values('Xmp.xmpMM.InstanceID'))
+        self.assertTrue(self.metadata.try_tag_supports_multiple_values('Xmp.dc.subject'))
+        self.assertTrue(self.metadata.try_tag_supports_multiple_values('Xmp.photoshop.TextLayers'))
+        self.assertTrue(self.metadata.try_tag_supports_multiple_values('Xmp.xmpRights.UsageTerms'))
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Xmp.MadeUp.subject')
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Xmp.dc.MadeUp')
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Xmp.MadeUp.MadeUp')
+        self.assertFalse(self.metadata.try_tag_supports_multiple_values('Xmp.dc.NewXmpTextValue'))
+
+        ### Test Other tags
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values, '')
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'MadeUp.dc.subject')
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'MadeUp.MadeUp.subject')
+        self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'MadeUp.MadeUp.MadeUp')
+
     def test_delete_gps_info(self):
         # Longitude, latitude, altitude
         self.metadata.set_gps_info(-123.35, 48.43, 10)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]