plug-ins: issue #7658 add support for unregistered xmp namespaces

Our metadata library exiv2 only registers the most commonly used xmp
namespaces. Other namespaces need to be explicitly registered. We did not
read or try to store these namespaces, which caused a lot of warnings about
"No namespace info available for XMP prefix '...' and then we could not
process that metadata or save/export those tags.

We had to wait for gexiv2 support for registering/reading namespaces, but
that was added in gexiv2 version 0.12.2 (and the "try" version in 0.14.0).

When reading xmp metadata we process all namespaces and add them to our
metadata xml when we haven't seen them before in the same image.
A GHashTable is used to keep track of the prefixes we have seen before.
The new namespace xml tag is skipped in older GIMP versions, but will be
used now to add the namespaces when exporting images with xmp metadata.
This commit is contained in:
Jacob Boerema 2023-05-22 11:54:58 -04:00
parent 3cd896c983
commit 4789a31a15
1 changed files with 153 additions and 9 deletions

View File

@ -82,15 +82,21 @@ struct _GimpMetadataClass
#define GIMP_METADATA_ERROR gimp_metadata_error_quark ()
static GQuark gimp_metadata_error_quark (void);
static void gimp_metadata_copy_tag (GExiv2Metadata *src,
GExiv2Metadata *dest,
const gchar *tag);
static void gimp_metadata_copy_tags (GExiv2Metadata *src,
GExiv2Metadata *dest,
const gchar **tags);
static void gimp_metadata_add (GimpMetadata *src,
GimpMetadata *dest);
static GQuark gimp_metadata_error_quark (void);
static void gimp_metadata_copy_tag (GExiv2Metadata *src,
GExiv2Metadata *dest,
const gchar *tag);
static void gimp_metadata_copy_tags (GExiv2Metadata *src,
GExiv2Metadata *dest,
const gchar **tags);
static void gimp_metadata_add (GimpMetadata *src,
GimpMetadata *dest);
static void gimp_metadata_add_namespace (GHashTable *namespaces,
GString *xml,
gchar *prefix);
static void gimp_metadata_add_xmp_namespaces (GHashTable *namespaces,
GString *xml,
const gchar *tag);
static const gchar *tiff_tags[] =
@ -588,6 +594,7 @@ gimp_metadata_duplicate (GimpMetadata *metadata)
typedef struct
{
gchar name[1024];
gchar prefix[256];
gboolean base64;
gboolean excessive_message_shown;
GimpMetadata *metadata;
@ -645,6 +652,33 @@ gimp_metadata_deserialize_start_element (GMarkupParseContext *context,
parse_data->base64 = (encoding && ! strcmp (encoding, "base64"));
}
else if (! strcmp (element_name, "namespace"))
{
const gchar *url;
const gchar *prefix;
prefix = gimp_metadata_attribute_name_to_value (attribute_names,
attribute_values,
"prefix");
url = gimp_metadata_attribute_name_to_value (attribute_names,
attribute_values,
"url");
if (! prefix)
{
g_set_error (error, GIMP_METADATA_ERROR, 1002,
"Element 'namespace' does not contain required attribute 'prefix'.");
return;
}
if (! url)
{
g_set_error (error, GIMP_METADATA_ERROR, 1003,
"Element 'namespace' does not contain required attribute 'url'.");
return;
}
g_strlcpy (parse_data->prefix, prefix, sizeof (parse_data->prefix));
g_strlcpy (parse_data->name, url, sizeof (parse_data->name));
}
}
static void
@ -760,6 +794,20 @@ gimp_metadata_deserialize_text (GMarkupParseContext *context,
g_free (value);
}
}
else if (! g_strcmp0 (current_element, "namespace"))
{
GError *error = NULL;
gexiv2_metadata_try_register_xmp_namespace (parse_data->name,
parse_data->prefix,
&error);
if (error) {
g_warning ("%s: failed to register namespace %s (url: '%s'): %s\n",
G_STRFUNC, parse_data->prefix, parse_data->name,
error->message);
g_clear_error(&error);
}
}
}
static void
@ -860,6 +908,97 @@ gimp_metadata_append_tag (GString *string,
}
}
static void
gimp_metadata_add_namespace (GHashTable *namespaces,
GString *xml,
gchar *prefix)
{
if (! g_hash_table_lookup (namespaces, prefix))
{
gchar *namespace_url;
namespace_url = gexiv2_metadata_try_get_xmp_namespace_for_tag (prefix, NULL);
if (namespace_url)
{
g_debug ("Adding namespace %s, url: %s", prefix, namespace_url);
if (! g_hash_table_insert (namespaces, prefix, namespace_url))
g_warning ("Namespace already present: %s!", prefix);
g_string_append_printf (xml,
" <namespace prefix=\"%s\" url=\"%s\"></namespace>\n",
prefix, namespace_url);
/* namespace_url and prefix are added to hashtable, so we don't free here */
}
else
{
g_free (prefix);
}
}
else
{
g_free (prefix);
}
}
/* Register a namespace in our xml metadata for each XMP namespace.
* We use the following XML format:
* <namespace prefix="namespace-prefix" url="namespace-url"></namespace>
*
* There are two types of namespace prefixes:
* - Xmp.prefix.whatever, and
* - /prefix:something, which is prefixed by the above
*
* We use a hashtable to keep track of which namespaces we have already
* seen in the current run.
*/
static void
gimp_metadata_add_xmp_namespaces (GHashTable *namespaces,
GString *xml,
const gchar *tag)
{
gchar *tag_ptr = (gchar *) tag;
gchar *prefix;
gchar **substrings;
/* Find word between the first and second '.' */
substrings = g_strsplit ((gchar *) tag_ptr, ".", 3);
if (substrings && substrings[1])
{
prefix = g_strdup (substrings[1]);
gimp_metadata_add_namespace (namespaces, xml, prefix);
}
g_strfreev (substrings);
/* Multiple namespaces in the form /prefix:value are possible in one tag. */
while (tag_ptr)
{
gchar *tag_next = NULL;
tag_ptr = strstr (tag_ptr, "/");
if (! tag_ptr || strlen (tag_ptr) <= 1)
break;
tag_ptr++;
tag_next = strstr (tag_ptr, ":");
if (tag_next)
{
gsize prefix_len = (gsize) tag_next - (gsize) tag_ptr + 1;
prefix = g_new (gchar, prefix_len);
g_strlcpy (prefix, tag_ptr, prefix_len);
gimp_metadata_add_namespace (namespaces, xml, prefix);
tag_ptr = tag_next;
}
}
}
/**
* gimp_metadata_serialize:
* @metadata: A #GimpMetadata instance.
@ -921,8 +1060,12 @@ gimp_metadata_serialize (GimpMetadata *metadata)
if (xmp_data)
{
GHashTable *namespaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
for (i = 0; xmp_data[i] != NULL; i++)
{
gimp_metadata_add_xmp_namespaces (namespaces, string, xmp_data[i]);
/* XmpText is always a single value, but structures like
* XmpBag and XmpSeq can have multiple values that need to be
* treated separately or else saving will do things wrong. */
@ -990,6 +1133,7 @@ gimp_metadata_serialize (GimpMetadata *metadata)
}
}
g_strfreev (xmp_data);
g_hash_table_destroy (namespaces);
}
iptc_data = gexiv2_metadata_get_iptc_tags (GEXIV2_METADATA (metadata));