tracker-0.16.2/src/libtracker-extract/tracker-xmp.c

No issues found

   1 /*
   2  * Copyright (C) 2006, Jamie McCracken <jamiemcc@gnome.org>
   3  *
   4  * This library is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2.1 of the License, or (at your option) any later version.
   8  *
   9  * This library is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with this library; if not, write to the
  16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  17  * Boston, MA  02110-1301, USA.
  18  */
  19 
  20 #include "config.h"
  21 
  22 #include <locale.h>
  23 
  24 #include <libtracker-common/tracker-utils.h>
  25 
  26 #include "tracker-xmp.h"
  27 #include "tracker-utils.h"
  28 
  29 #ifdef HAVE_EXEMPI
  30 
  31 #define NS_XMP_REGIONS "http://www.metadataworkinggroup.com/schemas/regions/"
  32 #define NS_ST_DIM "http://ns.adobe.com/xap/1.0/sType/Dimensions#"
  33 #define NS_ST_AREA "http://ns.adobe.com/xmp/sType/Area#"
  34 
  35 #define REGION_LIST_REGEX "^mwg-rs:Regions/mwg-rs:RegionList\\[(\\d+)\\]"
  36 
  37 #include <exempi/xmp.h>
  38 #include <exempi/xmpconsts.h>
  39 
  40 /**
  41  * SECTION:tracker-xmp
  42  * @title: XMP
  43  * @short_description: Extensible Metadata Platform (XMP)
  44  * @stability: Stable
  45  * @include: libtracker-extract/tracker-extract.h
  46  *
  47  * The Adobe Extensible Metadata Platform (XMP) is a standard, created
  48  * by Adobe Systems Inc., for processing and storing standardized and
  49  * proprietary information relating to the contents of a file.
  50  *
  51  * XMP standardizes the definition, creation, and processing of
  52  * extensible metadata. Serialized XMP can be embedded into a
  53  * significant number of popular file formats, without breaking their
  54  * readability by non-XMP-aware applications. Embedding metadata ("the
  55  * truth is in the file") avoids many problems that occur when
  56  * metadata is stored separately. XMP is used in PDF, photography and
  57  * photo editing applications.
  58  *
  59  * This API is provided to remove code duplication between extractors
  60  * using these standards.
  61  **/
  62 
  63 static void iterate        (XmpPtr                xmp,
  64                             XmpIteratorPtr        iter,
  65                             const gchar          *uri,
  66                             TrackerXmpData       *data,
  67                             gboolean              append);
  68 static void iterate_simple (const gchar          *uri,
  69                             TrackerXmpData       *data,
  70                             const gchar          *schema,
  71                             const gchar          *path,
  72                             const gchar          *value,
  73                             gboolean              append);
  74 
  75 static const gchar *
  76 fix_metering_mode (const gchar *mode)
  77 {
  78 	gint value;
  79 	value = atoi(mode);
  80 
  81 	switch (value) {
  82 	case 0:
  83 		return "nmm:metering-mode-other";
  84 	case 1:
  85 		return "nmm:metering-mode-average";
  86 	case 2:
  87 		return "nmm:metering-mode-center-weighted-average";
  88 	case 3:
  89 		return "nmm:metering-mode-spot";
  90 	case 4:
  91 		return "nmm:metering-mode-multispot";
  92 	case 5:
  93 		return "nmm:metering-mode-pattern";
  94 	case 6:
  95 		return "nmm:metering-mode-partial";
  96 	}
  97 
  98 	return "nmm:metering-mode-other";
  99 }
 100 
 101 static const gchar *
 102 fix_flash (const gchar *flash)
 103 {
 104 	static const gint fired_mask = 0x1;
 105 	gint value;
 106 
 107 	value = atoi (flash);
 108 
 109 	if (value & fired_mask) {
 110 		return "nmm:flash-on";
 111 	} else {
 112 		return "nmm:flash-off";
 113 	}
 114 }
 115 
 116 static const gchar *
 117 fix_white_balance (const gchar *wb)
 118 {
 119 	if (g_strcmp0 (wb, "1") == 0) {
 120 		return "nmm:white-balance-manual";
 121 	} else {
 122 		return "nmm:white-balance-auto";
 123 	}
 124 }
 125 
 126 static gchar *
 127 gps_coordinate_dup (const gchar *coordinates)
 128 {
 129 	static GRegex *reg = NULL;
 130 	GMatchInfo *info = NULL;
 131 
 132 	if (!reg) {
 133 		reg = g_regex_new ("([0-9]+),([0-9]+.[0-9]+)([A-Z])", 0, 0, NULL);
 134 	}
 135 
 136 	if (g_regex_match (reg, coordinates, 0, &info)) {
 137 		gchar *deg,*min,*ref;
 138 		gdouble r,d,m;
 139 
 140 		deg = g_match_info_fetch (info, 1);
 141 		min = g_match_info_fetch (info, 2);
 142 		ref = g_match_info_fetch (info, 3);
 143 
 144 		d = atof (deg);
 145 		m = atof (min);
 146 
 147 		r = d + m/60;
 148 
 149 		if ( (ref[0] == 'S') || (ref[0] == 'W')) {
 150 			r = r * -1;
 151 		}
 152 
 153 		g_free (deg);
 154 		g_free (min);
 155 		g_free (ref);
 156                 g_match_info_free (info);
 157 
 158 		return g_strdup_printf ("%f", r);
 159 	} else {
 160                 g_match_info_free (info);
 161 		return NULL;
 162 	}
 163 }
 164 
 165 /* We have an array, now recursively iterate over it's children.  Set
 166  * 'append' to true so that all values of the array are added under
 167  * one entry.
 168  */
 169 static void
 170 iterate_array (XmpPtr          xmp,
 171                const gchar    *uri,
 172                TrackerXmpData *data,
 173                const gchar    *schema,
 174                const gchar    *path)
 175 {
 176 	XmpIteratorPtr iter;
 177 
 178 	iter = xmp_iterator_new (xmp, schema, path, XMP_ITER_JUSTCHILDREN);
 179 	iterate (xmp, iter, uri, data, TRUE);
 180 	xmp_iterator_free (iter);
 181 }
 182 
 183 /* We have an array, now recursively iterate over it's children.  Set
 184  * 'append' to false so that only one item is used.
 185  */
 186 static void
 187 iterate_alt_text (XmpPtr          xmp,
 188                   const gchar    *uri,
 189                   TrackerXmpData *data,
 190                   const gchar    *schema,
 191                   const gchar    *path)
 192 {
 193 	XmpIteratorPtr iter;
 194 
 195 	iter = xmp_iterator_new (xmp, schema, path, XMP_ITER_JUSTCHILDREN);
 196 	iterate (xmp, iter, uri, data, FALSE);
 197 	xmp_iterator_free (iter);
 198 }
 199 
 200 static gchar *
 201 div_str_dup (const gchar *value)
 202 {
 203 	gchar *ret;
 204 	gchar *ptr = strchr (value, '/');
 205 
 206 	if (ptr) {
 207 		gchar *cpy = g_strdup (value);
 208 		gint a, b;
 209 
 210 		cpy [ptr - value] = '\0';
 211 		a = atoi (cpy);
 212 		b = atoi (cpy + (ptr - value) + 1);
 213 
 214 		if (b != 0) {
 215 			ret = g_strdup_printf ("%G", ((gdouble)((gdouble) a / (gdouble) b)));
 216 		} else {
 217 			ret = NULL;
 218 		}
 219 
 220 		g_free (cpy);
 221 	} else {
 222 		ret = g_strdup (value);
 223 	}
 224 
 225 	return ret;
 226 }
 227 
 228 /* We have a simple element, but need to iterate over the qualifiers */
 229 static void
 230 iterate_simple_qual (XmpPtr          xmp,
 231                      const gchar    *uri,
 232                      TrackerXmpData *data,
 233                      const gchar    *schema,
 234                      const gchar    *path,
 235                      const gchar    *value,
 236                      gboolean        append)
 237 {
 238 	XmpIteratorPtr iter;
 239 	XmpStringPtr the_path;
 240 	XmpStringPtr the_prop;
 241 	static gchar *locale = NULL;
 242 	gboolean ignore_element = FALSE;
 243 
 244 	iter = xmp_iterator_new (xmp, schema, path, XMP_ITER_JUSTCHILDREN | XMP_ITER_JUSTLEAFNAME);
 245 
 246 	the_path = xmp_string_new ();
 247 	the_prop = xmp_string_new ();
 248 
 249 	if (G_UNLIKELY (!locale)) {
 250 		locale = g_strdup (setlocale (LC_ALL, NULL));
 251 
 252 		if (!locale) {
 253 			locale = g_strdup ("C");
 254 		} else {
 255 			gchar *sep;
 256 
 257 			sep = strchr (locale, '.');
 258 
 259 			if (sep) {
 260 				locale[sep - locale] = '\0';
 261 			}
 262 
 263 			sep = strchr (locale, '_');
 264 
 265 			if (sep) {
 266 				locale[sep - locale] = '-';
 267 			}
 268 		}
 269 	}
 270 
 271 	while (xmp_iterator_next (iter, NULL, the_path, the_prop, NULL)) {
 272 		const gchar *qual_path = xmp_string_cstr (the_path);
 273 		const gchar *qual_value = xmp_string_cstr (the_prop);
 274 
 275 		if (g_ascii_strcasecmp (qual_path, "xml:lang") == 0) {
 276 			/* Is this a language we should ignore? */
 277 			if (g_ascii_strcasecmp (qual_value, "x-default") != 0 &&
 278 			    g_ascii_strcasecmp (qual_value, "x-repair") != 0 &&
 279 			    g_ascii_strcasecmp (qual_value, locale) != 0) {
 280 				ignore_element = TRUE;
 281 				break;
 282 			}
 283 		}
 284 	}
 285 
 286 	if (!ignore_element) {
 287 		iterate_simple (uri, data, schema, path, value, append);
 288 	}
 289 
 290 	xmp_string_free (the_prop);
 291 	xmp_string_free (the_path);
 292 
 293 	xmp_iterator_free (iter);
 294 }
 295 
 296 static const gchar *
 297 fix_orientation (const gchar *orientation)
 298 {
 299 	if (orientation && g_ascii_strcasecmp (orientation, "1") == 0) {
 300 		return "nfo:orientation-top";
 301 	} else if (orientation && g_ascii_strcasecmp (orientation, "2") == 0) {
 302 		return  "nfo:orientation-top-mirror";
 303 	} else if (orientation && g_ascii_strcasecmp (orientation, "3") == 0) {
 304 		return "nfo:orientation-bottom-mirror";
 305 	} else if (orientation && g_ascii_strcasecmp (orientation, "4") == 0) {
 306 		return  "nfo:orientation-bottom";
 307 	} else if (orientation && g_ascii_strcasecmp (orientation, "5") == 0) {
 308 		return  "nfo:orientation-left-mirror";
 309 	} else if (orientation && g_ascii_strcasecmp (orientation, "6") == 0) {
 310 		return  "nfo:orientation-right";
 311 	} else if (orientation && g_ascii_strcasecmp (orientation, "7") == 0) {
 312 		return "nfo:orientation-right-mirror";
 313 	} else if (orientation && g_ascii_strcasecmp (orientation, "8") == 0) {
 314 		return  "nfo:orientation-left";
 315 	}
 316 
 317 	return  "nfo:orientation-top";
 318 }
 319 
 320 /*
 321  * In a path like: mwg-rs:Regions/mwg-rs:RegionList[2]/mwg-rs:Area/stArea:x
 322  * this function returns the "2" from RegionsList[2] 
 323  *  Note: The first element from a list is 1
 324  */
 325 static gint
 326 get_region_counter (const gchar *path) 
 327 {
 328         static GRegex *regex = NULL;
 329         GMatchInfo    *match_info = NULL;
 330         gchar         *match;
 331         gint           result;
 332         
 333         if (!regex) {
 334                 regex = g_regex_new (REGION_LIST_REGEX, 0, 0, NULL);
 335         }
 336 
 337         if (!g_regex_match (regex, path, 0, &match_info)) {
 338                 g_match_info_free (match_info);
 339                 return -1;
 340         }
 341 
 342         match = g_match_info_fetch (match_info, 1);
 343         result =  g_strtod (match, NULL);
 344 
 345         g_free (match);
 346         g_match_info_free (match_info);
 347 
 348         return result;
 349 }
 350 
 351 
 352 
 353 /* We have a simple element. Add any data we know about to the
 354  * hash table.
 355  */
 356 static void
 357 iterate_simple (const gchar    *uri,
 358                 TrackerXmpData *data,
 359                 const gchar    *schema,
 360                 const gchar    *path,
 361                 const gchar    *value,
 362                 gboolean        append)
 363 {
 364 	gchar *name;
 365 	const gchar *p;
 366 	gchar *propname;
 367 
 368 	p = strchr (path, ':');
 369 	if (!p) {
 370 		return;
 371 	}
 372 
 373 	name = g_strdup (p + 1);
 374 
 375 	/* For 'dc:subject[1]' the name will be 'subject'.
 376 	 * This rule doesn't work for RegionLists
 377 	 */
 378 	p = strrchr (name, '[');
 379 	if (p) {
 380 		name[p - name] = '\0';
 381 	}
 382 
 383 	/* Exif basic scheme */
 384 	if (g_ascii_strcasecmp (schema, NS_EXIF) == 0) {
 385 		if (!data->title2 && g_ascii_strcasecmp (name, "Title") == 0) {
 386 			data->title2 = g_strdup (value);
 387 		} else if (g_ascii_strcasecmp (name, "DateTimeOriginal") == 0 && !data->time_original) {
 388 			data->time_original = tracker_date_guess (value);
 389 		} else if (!data->artist && g_ascii_strcasecmp (name, "Artist") == 0) {
 390 			data->artist = g_strdup (value);
 391 		/* } else if (g_ascii_strcasecmp (name, "Software") == 0) {
 392 			   tracker_statement_list_insert (metadata, uri,
 393 			   "Image:Software", value);*/
 394 		} else if (!data->make && g_ascii_strcasecmp (name, "Make") == 0) {
 395 			data->make = g_strdup (value);
 396 		} else if (!data->model && g_ascii_strcasecmp (name, "Model") == 0) {
 397 			data->model = g_strdup (value);
 398 		} else if (!data->flash && g_ascii_strcasecmp (name, "Flash") == 0) {
 399 			data->flash = g_strdup (fix_flash (value));
 400 		} else if (!data->metering_mode && g_ascii_strcasecmp (name, "MeteringMode") == 0) {
 401 			data->metering_mode = g_strdup (fix_metering_mode (value));
 402 		/* } else if (g_ascii_strcasecmp (name, "ExposureProgram") == 0) {
 403 			   tracker_statement_list_insert (metadata, uri,
 404 			   "Image:ExposureProgram", value);*/
 405 		} else if (!data->exposure_time && g_ascii_strcasecmp (name, "ExposureTime") == 0) {
 406 			data->exposure_time = div_str_dup (value);
 407 		} else if (!data->fnumber && g_ascii_strcasecmp (name, "FNumber") == 0) {
 408 			data->fnumber = div_str_dup (value);
 409 		} else if (!data->focal_length && g_ascii_strcasecmp (name, "FocalLength") == 0) {
 410 			data->focal_length = div_str_dup (value);
 411 		} else if (!data->iso_speed_ratings && g_ascii_strcasecmp (name, "ISOSpeedRatings") == 0) {
 412 			data->iso_speed_ratings = div_str_dup (value);
 413 		} else if (!data->white_balance && g_ascii_strcasecmp (name, "WhiteBalance") == 0) {
 414 			data->white_balance = g_strdup (fix_white_balance (value));
 415 		} else if (!data->copyright && g_ascii_strcasecmp (name, "Copyright") == 0) {
 416 			data->copyright = g_strdup (value);
 417 		} else if (!data->gps_altitude && g_ascii_strcasecmp (name, "GPSAltitude") == 0) {
 418 			data->gps_altitude = div_str_dup (value);
 419 		} else if (!data->gps_altitude_ref && g_ascii_strcasecmp (name, "GPSAltitudeRef") == 0) {
 420 			data->gps_altitude_ref = g_strdup (value);
 421 		} else if (!data->gps_latitude && g_ascii_strcasecmp (name, "GPSLatitude") == 0) {
 422 			data->gps_latitude = gps_coordinate_dup (value);
 423 		} else if (!data->gps_longitude && g_ascii_strcasecmp (name, "GPSLongitude") == 0) {
 424 			data->gps_longitude = gps_coordinate_dup (value);
 425 		} else if (!data->gps_direction && g_ascii_strcasecmp (name, "GPSImgDirection") == 0) {
 426 			data->gps_direction = div_str_dup (value);
 427 		}
 428 		/* TIFF */
 429 	} else if (g_ascii_strcasecmp (schema, NS_TIFF) == 0) {
 430 		if (!data->orientation && g_ascii_strcasecmp (name, "Orientation") == 0) {
 431 			data->orientation = g_strdup (fix_orientation (value));
 432 		}
 433 		/* PDF*/
 434 	} else if (g_ascii_strcasecmp (schema, NS_PDF) == 0) {
 435 		if (g_ascii_strcasecmp (name, "keywords") == 0) {
 436 			if (data->pdf_keywords) {
 437 				gchar *temp = g_strdup_printf ("%s, %s", value, data->pdf_keywords);
 438 				g_free (data->pdf_keywords);
 439 				data->pdf_keywords = temp;
 440 			} else {
 441 				data->pdf_keywords = g_strdup (value);
 442 			}
 443 		} else
 444 			if (!data->pdf_title && g_ascii_strcasecmp (name, "title") == 0) {
 445 				data->pdf_title = g_strdup (value);
 446 			}
 447 		/* Dublin Core */
 448 	} else if (g_ascii_strcasecmp (schema, NS_DC) == 0) {
 449 		if (!data->title && g_ascii_strcasecmp (name, "title") == 0) {
 450 			data->title = g_strdup (value);
 451 		} else if (!data->rights && g_ascii_strcasecmp (name, "rights") == 0) {
 452 			data->rights = g_strdup (value);
 453 		} else if (!data->creator && g_ascii_strcasecmp (name, "creator") == 0) {
 454 			data->creator = g_strdup (value);
 455 		} else if (!data->description && g_ascii_strcasecmp (name, "description") == 0) {
 456 			data->description = g_strdup (value);
 457 		} else if (!data->date && g_ascii_strcasecmp (name, "date") == 0) {
 458 			data->date = tracker_date_guess (value);
 459 		} else if (g_ascii_strcasecmp (name, "keywords") == 0) {
 460 			if (data->keywords) {
 461 				gchar *temp = g_strdup_printf ("%s, %s", value, data->keywords);
 462 				g_free (data->keywords);
 463 				data->keywords = temp;
 464 			} else {
 465 				data->keywords = g_strdup (value);
 466 			}
 467 		} else if (g_ascii_strcasecmp (name, "subject") == 0) {
 468 			if (data->subject) {
 469 				gchar *temp = g_strdup_printf ("%s, %s", value, data->subject);
 470 				g_free (data->subject);
 471 				data->subject = temp;
 472 			} else {
 473 				data->subject = g_strdup (value);
 474 			}
 475 		} else if (!data->publisher && g_ascii_strcasecmp (name, "publisher") == 0) {
 476 			data->publisher = g_strdup (value);
 477 		} else if (!data->contributor && g_ascii_strcasecmp (name, "contributor") == 0) {
 478 			data->contributor = g_strdup (value);
 479 		} else if (!data->type && g_ascii_strcasecmp (name, "type") == 0) {
 480 			data->type = g_strdup (value);
 481 		} else if (!data->format && g_ascii_strcasecmp (name, "format") == 0) {
 482 			data->format = g_strdup (value);
 483 		} else if (!data->identifier && g_ascii_strcasecmp (name, "identifier") == 0) {
 484 			data->identifier = g_strdup (value);
 485 		} else if (!data->source && g_ascii_strcasecmp (name, "source") == 0) {
 486 			data->source = g_strdup (value);
 487 		} else if (!data->language && g_ascii_strcasecmp (name, "language") == 0) {
 488 			data->language = g_strdup (value);
 489 		} else if (!data->relation && g_ascii_strcasecmp (name, "relation") == 0) {
 490 			data->relation = g_strdup (value);
 491 		} else if (!data->coverage && g_ascii_strcasecmp (name, "coverage") == 0) {
 492 			data->coverage = g_strdup (value);
 493 		}
 494 		/* Creative Commons */
 495 	} else if (g_ascii_strcasecmp (schema, NS_CC) == 0) {
 496 		if (!data->license && g_ascii_strcasecmp (name, "license") == 0) {
 497 			data->license = g_strdup (value);
 498 		}
 499 		/* TODO: A lot of these location fields are pretty vague and ambigious.
 500 		 * We should go through them one by one and ensure that all of them are
 501 		 * used sanely */
 502 
 503 		/* Photoshop TODO: is this needed anyway? */
 504 	} else if (g_ascii_strcasecmp (schema, NS_PHOTOSHOP) == 0) {
 505 		if (!data->city && g_ascii_strcasecmp (name, "City") == 0) {
 506 			data->city = g_strdup (value);
 507 		} else if (!data->country && g_ascii_strcasecmp (name, "Country") == 0) {
 508 			data->country = g_strdup (value);
 509 		} else if (!data->state && g_ascii_strcasecmp (name, "State") == 0) {
 510 			data->state = g_strdup (value);
 511 		} else if (!data->address && g_ascii_strcasecmp (name, "Location") == 0) {
 512 			data->address = g_strdup (value);
 513 		}
 514 		/* IPTC4XMP scheme - GeoClue / location stuff, TODO */
 515 	} else if (g_ascii_strcasecmp (schema, NS_IPTC4XMP) == 0) {
 516 		if (!data->city && g_ascii_strcasecmp (name, "City") == 0) {
 517 			data->city = g_strdup (value);
 518 		} else if (!data->country && g_ascii_strcasecmp (name, "Country") == 0) {
 519 			data->country = g_strdup (value);
 520 		} else if (!data->country && g_ascii_strcasecmp (name, "CountryName") == 0) {
 521 			data->country = g_strdup (value);
 522 		} else if (!data->country && g_ascii_strcasecmp (name, "PrimaryLocationName") == 0) {
 523 			data->country = g_strdup (value);
 524 		} else if (!data->state && g_ascii_strcasecmp (name, "State") == 0) {
 525 			data->state = g_strdup (value);
 526 		} else if (!data->state && g_ascii_strcasecmp (name, "Province") == 0) {
 527 			data->state = g_strdup (value);
 528 		} else if (!data->address && g_ascii_strcasecmp (name, "Sublocation") == 0) {
 529 			data->address = g_strdup (value);
 530 		}
 531 	} else if (g_ascii_strcasecmp (schema, NS_XAP) == 0) {
 532 		if (!data->rating && g_ascii_strcasecmp (name, "Rating") == 0) {
 533 			data->rating = g_strdup (value);
 534 		}
 535 	} else if (g_ascii_strcasecmp (schema, NS_XMP_REGIONS) == 0) {
 536                 if (g_str_has_prefix (path, "mwg-rs:Regions/mwg-rs:RegionList")) {
 537 	                TrackerXmpRegion *current_region;
 538                         gint              position = get_region_counter (path);
 539 
 540                         if (position == -1) {
 541                                 g_free (name);
 542                                 return;
 543                         }
 544 
 545                         /* First time a property appear for a region, we create the region */
 546                         current_region = g_slist_nth_data (data->regions, position-1);
 547                         if (current_region == NULL) {
 548                                 current_region = g_slice_new0 (TrackerXmpRegion);
 549                                 data->regions = g_slist_append (data->regions, current_region);
 550                         }
 551 
 552                         propname = g_strdup (strrchr (path, '/') + 1);
 553 
 554                         if (!current_region->title && g_ascii_strcasecmp (propname, "mwg-rs:Name") == 0) {
 555                                 current_region->title = g_strdup (value);
 556                         } else if (!current_region->description && g_ascii_strcasecmp (propname, "mwg-rs:Description") == 0) {
 557                                 current_region->description = g_strdup (value);
 558                         } else if (!current_region->x && g_ascii_strcasecmp (propname, "stArea:x") == 0) {
 559                                 current_region->x = g_strdup (value);
 560                         } else if (!current_region->y && g_ascii_strcasecmp (propname, "stArea:y") == 0) {
 561                                 current_region->y = g_strdup (value);
 562                         } else if (!current_region->width && g_ascii_strcasecmp (propname, "stArea:w") == 0) {
 563                                 current_region->width = g_strdup (value);
 564                         } else if (!current_region->height && g_ascii_strcasecmp (propname, "stArea:h") == 0) {
 565                                 current_region->height = g_strdup (value);
 566 
 567                                 /* Spec not clear about units
 568                                  *  } else if (!current_region->unit
 569                                  *             && g_ascii_strcasecmp (propname, "stArea:unit") == 0) {
 570                                  *      current_region->unit = g_strdup (value);
 571                                  *
 572                                  *  we consider it always comes normalized
 573                                 */
 574                         } else if (!current_region->type && g_ascii_strcasecmp (propname, "mwg-rs:Type") == 0) {
 575                                 current_region->type = g_strdup (value);
 576                         } else if (g_str_has_prefix (strrchr (path, ']') + 2, "mwg-rs:Extensions")) {
 577                                 current_region->link_class = g_strdup (propname);
 578                                 current_region->link_uri = g_strdup (value);
 579                         }
 580 
 581                         g_free (propname);
 582                 }
 583         }
 584 
 585 	g_free (name);
 586 }
 587 
 588 
 589 /* Iterate over the XMP, dispatching to the appropriate element type
 590  * (simple, simple w/qualifiers, or an array) handler.
 591  */
 592 static void
 593 iterate (XmpPtr          xmp,
 594          XmpIteratorPtr  iter,
 595          const gchar    *uri,
 596          TrackerXmpData *data,
 597          gboolean        append)
 598 {
 599 	XmpStringPtr the_schema = xmp_string_new ();
 600 	XmpStringPtr the_path = xmp_string_new ();
 601 	XmpStringPtr the_prop = xmp_string_new ();
 602 
 603 	uint32_t opt;
 604 
 605 	while (xmp_iterator_next (iter, the_schema, the_path, the_prop, &opt)) {
 606 		const gchar *schema = xmp_string_cstr (the_schema);
 607 		const gchar *path = xmp_string_cstr (the_path);
 608 		const gchar *value = xmp_string_cstr (the_prop);
 609 
 610 		if (XMP_IS_PROP_SIMPLE (opt)) {
 611 			if (!tracker_is_empty_string (path)) {
 612 				if (XMP_HAS_PROP_QUALIFIERS (opt)) {
 613 					iterate_simple_qual (xmp, uri, data, schema, path, value, append);
 614 				} else {
 615 					iterate_simple (uri, data, schema, path, value, append);
 616 				}
 617 			}
 618 		} else if (XMP_IS_PROP_ARRAY (opt)) {
 619 			if (XMP_IS_ARRAY_ALTTEXT (opt)) {
 620 				iterate_alt_text (xmp, uri, data, schema, path);
 621 				xmp_iterator_skip (iter, XMP_ITER_SKIPSUBTREE);
 622 			} else {
 623 				iterate_array (xmp, uri, data, schema, path);
 624 
 625                                 /* Some dc: elements are handled as arrays by exempi.
 626                                  * In those cases, to avoid duplicated values, is easier
 627                                  * to skip the subtree.
 628                                  */
 629                                 if (g_ascii_strcasecmp (schema, NS_DC) == 0) {
 630                                         xmp_iterator_skip (iter, XMP_ITER_SKIPSUBTREE);
 631                                 }
 632 			}
 633 		}
 634 	}
 635 
 636 	xmp_string_free (the_prop);
 637 	xmp_string_free (the_path);
 638 	xmp_string_free (the_schema);
 639 }
 640 
 641 static void
 642 register_namespace (const gchar *ns_uri,
 643                     const gchar *suggested_prefix)
 644 {
 645         if (!xmp_namespace_prefix (ns_uri, NULL)) {
 646                 xmp_register_namespace (ns_uri, suggested_prefix, NULL);
 647         }
 648 }
 649 
 650 #endif /* HAVE_EXEMPI */
 651 
 652 static gboolean
 653 parse_xmp (const gchar    *buffer,
 654            size_t          len,
 655            const gchar    *uri,
 656            TrackerXmpData *data)
 657 {
 658 #ifdef HAVE_EXEMPI
 659 	XmpPtr xmp;
 660 #endif /* HAVE_EXEMPI */
 661 
 662 	memset (data, 0, sizeof (TrackerXmpData));
 663 
 664 #ifdef HAVE_EXEMPI
 665 
 666 	xmp_init ();
 667 
 668         register_namespace (NS_XMP_REGIONS, "mwg-rs");
 669         register_namespace (NS_ST_DIM, "stDim");
 670         register_namespace (NS_ST_AREA, "stArea");
 671 
 672 	xmp = xmp_new_empty ();
 673 	xmp_parse (xmp, buffer, len);
 674 
 675 	if (xmp != NULL) {
 676 		XmpIteratorPtr iter;
 677 
 678 		iter = xmp_iterator_new (xmp, NULL, NULL, XMP_ITER_PROPERTIES);
 679 		iterate (xmp, iter, uri, data, FALSE);
 680 		xmp_iterator_free (iter);
 681 		xmp_free (xmp);
 682 	}
 683 
 684 	xmp_terminate ();
 685 #endif /* HAVE_EXEMPI */
 686 
 687 	return TRUE;
 688 }
 689 
 690 #ifndef TRACKER_DISABLE_DEPRECATED
 691 
 692 // LCOV_EXCL_START
 693 
 694 /**
 695  * tracker_xmp_read:
 696  * @buffer: a chunk of data with xmp data in it.
 697  * @len: the size of @buffer.
 698  * @uri: the URI this is related to.
 699  * @data: a pointer to a TrackerXmpData structure to populate.
 700  *
 701  * This function takes @len bytes of @buffer and runs it through the
 702  * XMP library. The result is that @data is populated with the XMP
 703  * data found in @uri.
 704  *
 705  * Returns: %TRUE if the @data was populated successfully, otherwise
 706  * %FALSE is returned.
 707  *
 708  * Since: 0.8
 709  *
 710  * Deprecated: 0.9. Use tracker_xmp_new() instead.
 711  **/
 712 gboolean
 713 tracker_xmp_read (const gchar    *buffer,
 714                   size_t          len,
 715                   const gchar    *uri,
 716                   TrackerXmpData *data)
 717 {
 718 	g_return_val_if_fail (buffer != NULL, FALSE);
 719 	g_return_val_if_fail (len > 0, FALSE);
 720 	g_return_val_if_fail (uri != NULL, FALSE);
 721 	g_return_val_if_fail (data != NULL, FALSE);
 722 
 723 	return parse_xmp (buffer, len, uri, data);
 724 }
 725 
 726 // LCOV_EXCL_STOP
 727 
 728 #endif /* TRACKER_DISABLE_DEPRECATED */
 729 
 730 static void
 731 xmp_region_free (gpointer data)
 732 {
 733         TrackerXmpRegion *region = (TrackerXmpRegion *) data;
 734 
 735         g_free (region->title);
 736         g_free (region->description);
 737         g_free (region->type);
 738         g_free (region->x);
 739         g_free (region->y);
 740         g_free (region->width);
 741         g_free (region->height);
 742         g_free (region->link_class);
 743         g_free (region->link_uri);
 744 
 745         g_slice_free (TrackerXmpRegion, region);
 746 }
 747 
 748 
 749 /**
 750  * tracker_xmp_new:
 751  * @buffer: a chunk of data with xmp data in it.
 752  * @len: the size of @buffer.
 753  * @uri: the URI this is related to.
 754  *
 755  * This function takes @len bytes of @buffer and runs it through the
 756  * XMP library.
 757  *
 758  * Returns: a newly allocated #TrackerXmpData struct if XMP data was
 759  * found, %NULL otherwise. Free the returned struct with tracker_xmp_free().
 760  *
 761  * Since: 0.10
 762  **/
 763 TrackerXmpData *
 764 tracker_xmp_new (const gchar *buffer,
 765                  gsize        len,
 766                  const gchar *uri)
 767 {
 768 	TrackerXmpData *data;
 769 
 770 	g_return_val_if_fail (buffer != NULL, NULL);
 771 	g_return_val_if_fail (len > 0, NULL);
 772 	g_return_val_if_fail (uri != NULL, NULL);
 773 
 774 	data = g_new0 (TrackerXmpData, 1);
 775 
 776 	if (!parse_xmp (buffer, len, uri, data)) {
 777 		tracker_xmp_free (data);
 778 		return NULL;
 779 	}
 780 
 781 	return data;
 782 }
 783 
 784 /**
 785  * tracker_xmp_free:
 786  * @data: a #TrackerXmpData struct
 787  *
 788  * Frees @data and all #TrackerXmpData members. %NULL will produce a
 789  * a warning.
 790  *
 791  * Since: 0.10
 792  **/
 793 void
 794 tracker_xmp_free (TrackerXmpData *data)
 795 {
 796 	g_return_if_fail (data != NULL);
 797 
 798 	g_free (data->title);
 799 	g_free (data->rights);
 800 	g_free (data->creator);
 801 	g_free (data->description);
 802 	g_free (data->date);
 803 	g_free (data->keywords);
 804 	g_free (data->subject);
 805 	g_free (data->publisher);
 806 	g_free (data->contributor);
 807 	g_free (data->type);
 808 	g_free (data->format);
 809 	g_free (data->identifier);
 810 	g_free (data->source);
 811 	g_free (data->language);
 812 	g_free (data->relation);
 813 	g_free (data->coverage);
 814 	g_free (data->license);
 815 	g_free (data->pdf_title);
 816 	g_free (data->pdf_keywords);
 817 	g_free (data->title2);
 818 	g_free (data->time_original);
 819 	g_free (data->artist);
 820 	g_free (data->make);
 821 	g_free (data->model);
 822 	g_free (data->orientation);
 823 	g_free (data->flash);
 824 	g_free (data->metering_mode);
 825 	g_free (data->exposure_time);
 826 	g_free (data->fnumber);
 827 	g_free (data->focal_length);
 828 	g_free (data->iso_speed_ratings);
 829 	g_free (data->white_balance);
 830 	g_free (data->copyright);
 831 	g_free (data->rating);
 832 	g_free (data->address);
 833 	g_free (data->country);
 834 	g_free (data->state);
 835 	g_free (data->city);
 836 	g_free (data->gps_altitude);
 837 	g_free (data->gps_altitude_ref);
 838 	g_free (data->gps_latitude);
 839 	g_free (data->gps_longitude);
 840 	g_free (data->gps_direction);
 841 
 842         g_slist_free_full (data->regions, xmp_region_free);
 843 	g_free (data);
 844 }
 845 
 846 
 847 static const gchar *
 848 fix_region_type (const gchar *region_type)
 849 {
 850         if (region_type == NULL) {
 851                 return "nfo:region-content-undefined";
 852         }
 853 
 854         if (g_ascii_strncasecmp (region_type, "Face", 4) == 0) {
 855                 return "nfo:roi-content-face";
 856         } else if (g_ascii_strncasecmp (region_type, "Pet", 3) == 0) {
 857                 return "nfo:roi-content-pet";
 858         } else if (g_ascii_strncasecmp (region_type, "Focus", 5) == 0) {
 859                 return "nfo:roi-content-focus";
 860         } else if (g_ascii_strncasecmp (region_type, "BarCode", 7) == 0) {
 861                 return "nfo:roi-content-barcode";
 862         }
 863 
 864         return "nfo:roi-content-undefined";
 865 }
 866 
 867 
 868 /**
 869  * tracker_xmp_apply:
 870  * @preupdate: the preupdate object to apply XMP data to.
 871  * @metadata: the metadata object to apply XMP data to.
 872  * @graph: the graph to apply XMP data to.
 873  * @where: the where object.
 874  * @uri: the URI this is related to.
 875  * @data: the data to push into @metadata.
 876  *
 877  * This function applies all data in @data to @metadata.
 878  *
 879  * The @graph parameter was added in 0.12.
 880  *
 881  * This function also calls tracker_xmp_apply_regions(), so there is
 882  * no need to call both functions.
 883  *
 884  * Returns: %TRUE if the @data was applied to @metadata successfully,
 885  * otherwise %FALSE is returned.
 886  *
 887  * Since: 0.8
 888  **/
 889 gboolean
 890 tracker_xmp_apply (TrackerSparqlBuilder *preupdate,
 891                    TrackerSparqlBuilder *metadata,
 892                    const gchar          *graph,
 893                    GString              *where,
 894                    const gchar          *uri,
 895                    TrackerXmpData       *data)
 896 {
 897 	GPtrArray *keywords;
 898 	guint i;
 899 
 900 	g_return_val_if_fail (TRACKER_SPARQL_IS_BUILDER (preupdate), FALSE);
 901 	g_return_val_if_fail (TRACKER_SPARQL_IS_BUILDER (metadata), FALSE);
 902 	g_return_val_if_fail (uri != NULL, FALSE);
 903 	g_return_val_if_fail (data != NULL, FALSE);
 904 
 905 	keywords = g_ptr_array_new ();
 906 
 907 	if (data->keywords) {
 908 		tracker_keywords_parse (keywords, data->keywords);
 909 	}
 910 
 911 	if (data->subject) {
 912 		tracker_keywords_parse (keywords, data->subject);
 913 	}
 914 
 915 	if (data->pdf_keywords) {
 916 		tracker_keywords_parse (keywords, data->pdf_keywords);
 917 	}
 918 
 919 	for (i = 0; i < keywords->len; i++) {
 920 		gchar *p, *escaped, *var;
 921 
 922 		p = g_ptr_array_index (keywords, i);
 923 		escaped = tracker_sparql_escape_string (p);
 924 		var = g_strdup_printf ("tag%d", i + 1);
 925 
 926 		/* ensure tag with specified label exists */
 927 		tracker_sparql_builder_append (preupdate,
 928 		                               "INSERT { ");
 929 
 930 		if (graph) {
 931 			tracker_sparql_builder_append (preupdate, "GRAPH <");
 932 			tracker_sparql_builder_append (preupdate, graph);
 933 			tracker_sparql_builder_append (preupdate, "> { ");
 934 		}
 935 
 936 		tracker_sparql_builder_append (preupdate,"_:tag a nao:Tag ; nao:prefLabel \"");
 937 		tracker_sparql_builder_append (preupdate, escaped);
 938 
 939 		if (graph) {
 940 			tracker_sparql_builder_append (preupdate, " } ");
 941 		}
 942 
 943 		tracker_sparql_builder_append (preupdate,
 944 		                               "\" }\nWHERE { FILTER (NOT EXISTS { "
 945 		                               "?tag a nao:Tag ; nao:prefLabel \"");
 946 		tracker_sparql_builder_append (preupdate, escaped);
 947 		tracker_sparql_builder_append (preupdate,
 948 		                               "\" }) }\n");
 949 
 950 		/* associate file with tag */
 951 		tracker_sparql_builder_predicate (metadata, "nao:hasTag");
 952 		tracker_sparql_builder_object_variable (metadata, var);
 953 
 954 		g_string_append_printf (where, "?%s a nao:Tag ; nao:prefLabel \"%s\" .\n", var, escaped);
 955 
 956 		g_free (var);
 957 		g_free (escaped);
 958 		g_free (p);
 959 	}
 960 	g_ptr_array_free (keywords, TRUE);
 961 
 962 	if (data->publisher) {
 963 		tracker_sparql_builder_predicate (metadata, "nco:publisher");
 964 
 965 		tracker_sparql_builder_object_blank_open (metadata);
 966 		tracker_sparql_builder_predicate (metadata, "a");
 967 		tracker_sparql_builder_object (metadata, "nco:Contact");
 968 
 969 		tracker_sparql_builder_predicate (metadata, "nco:fullname");
 970 		tracker_sparql_builder_object_unvalidated (metadata, data->publisher);
 971 		tracker_sparql_builder_object_blank_close (metadata);
 972 	}
 973 
 974 	if (data->type) {
 975 		tracker_sparql_builder_predicate (metadata, "dc:type");
 976 		tracker_sparql_builder_object_unvalidated (metadata, data->type);
 977 	}
 978 
 979 	if (data->format) {
 980 		tracker_sparql_builder_predicate (metadata, "dc:format");
 981 		tracker_sparql_builder_object_unvalidated (metadata, data->format);
 982 	}
 983 
 984 	if (data->identifier) {
 985 		tracker_sparql_builder_predicate (metadata, "dc:identifier");
 986 		tracker_sparql_builder_object_unvalidated (metadata, data->identifier);
 987 	}
 988 
 989 	if (data->source) {
 990 		tracker_sparql_builder_predicate (metadata, "dc:source");
 991 		tracker_sparql_builder_object_unvalidated (metadata, data->source);
 992 	}
 993 
 994 	if (data->language) {
 995 		tracker_sparql_builder_predicate (metadata, "dc:language");
 996 		tracker_sparql_builder_object_unvalidated (metadata, data->language);
 997 	}
 998 
 999 	if (data->relation) {
1000 		tracker_sparql_builder_predicate (metadata, "dc:relation");
1001 		tracker_sparql_builder_object_unvalidated (metadata, data->relation);
1002 	}
1003 
1004 	if (data->coverage) {
1005 		tracker_sparql_builder_predicate (metadata, "dc:coverage");
1006 		tracker_sparql_builder_object_unvalidated (metadata, data->coverage);
1007 	}
1008 
1009 	if (data->license) {
1010 		tracker_sparql_builder_predicate (metadata, "dc:license");
1011 		tracker_sparql_builder_object_unvalidated (metadata, data->license);
1012 	}
1013 
1014 	if (data->make || data->model) {
1015 		gchar *equip_uri;
1016 
1017 		equip_uri = tracker_sparql_escape_uri_printf ("urn:equipment:%s:%s:",
1018 		                                              data->make ? data->make : "",
1019 		                                              data->model ? data->model : "");
1020 
1021 		tracker_sparql_builder_insert_open (preupdate, NULL);
1022 		if (graph) {
1023 			tracker_sparql_builder_graph_open (preupdate, graph);
1024 		}
1025 
1026 		tracker_sparql_builder_subject_iri (preupdate, equip_uri);
1027 		tracker_sparql_builder_predicate (preupdate, "a");
1028 		tracker_sparql_builder_object (preupdate, "nfo:Equipment");
1029 
1030 		if (data->make) {
1031 			tracker_sparql_builder_predicate (preupdate, "nfo:manufacturer");
1032 			tracker_sparql_builder_object_unvalidated (preupdate, data->make);
1033 		}
1034 		if (data->model) {
1035 			tracker_sparql_builder_predicate (preupdate, "nfo:model");
1036 			tracker_sparql_builder_object_unvalidated (preupdate, data->model);
1037 		}
1038 
1039 		if (graph) {
1040 			tracker_sparql_builder_graph_close (preupdate);
1041 		}
1042 		tracker_sparql_builder_insert_close (preupdate);
1043 
1044 		tracker_sparql_builder_predicate (metadata, "nfo:equipment");
1045 		tracker_sparql_builder_object_iri (metadata, equip_uri);
1046 		g_free (equip_uri);
1047 	}
1048 
1049 	if (data->title || data->title2 || data->pdf_title) {
1050 		const gchar *final_title = tracker_coalesce_strip (3, data->title,
1051 		                                                   data->title2,
1052 		                                                   data->pdf_title);
1053 
1054 		tracker_sparql_builder_predicate (metadata, "nie:title");
1055 		tracker_sparql_builder_object_unvalidated (metadata, final_title);
1056 	}
1057 
1058 	if (data->orientation) {
1059 		tracker_sparql_builder_predicate (metadata, "nfo:orientation");
1060 		tracker_sparql_builder_object_unvalidated (metadata, data->orientation);
1061 	}
1062 
1063 	if (data->rights || data->copyright) {
1064 		const gchar *final_rights = tracker_coalesce_strip (2, data->copyright, data->rights);
1065 
1066 		tracker_sparql_builder_predicate (metadata, "nie:copyright");
1067 		tracker_sparql_builder_object_unvalidated (metadata, final_rights);
1068 	}
1069 
1070 	if (data->white_balance) {
1071 		tracker_sparql_builder_predicate (metadata, "nmm:whiteBalance");
1072 		tracker_sparql_builder_object_unvalidated (metadata, data->white_balance);
1073 	}
1074 
1075 	if (data->fnumber) {
1076 		tracker_sparql_builder_predicate (metadata, "nmm:fnumber");
1077 		tracker_sparql_builder_object_unvalidated (metadata, data->fnumber);
1078 	}
1079 
1080 	if (data->flash) {
1081 		tracker_sparql_builder_predicate (metadata, "nmm:flash");
1082 		tracker_sparql_builder_object_unvalidated (metadata, data->flash);
1083 	}
1084 
1085 	if (data->focal_length) {
1086 		tracker_sparql_builder_predicate (metadata, "nmm:focalLength");
1087 		tracker_sparql_builder_object_unvalidated (metadata, data->focal_length);
1088 	}
1089 
1090 	if (data->artist || data->contributor) {
1091 		const gchar *final_artist = tracker_coalesce_strip (2, data->artist, data->contributor);
1092 
1093 		tracker_sparql_builder_predicate (metadata, "nco:contributor");
1094 
1095 		tracker_sparql_builder_object_blank_open (metadata);
1096 		tracker_sparql_builder_predicate (metadata, "a");
1097 		tracker_sparql_builder_object (metadata, "nco:Contact");
1098 
1099 		tracker_sparql_builder_predicate (metadata, "nco:fullname");
1100 		tracker_sparql_builder_object_unvalidated (metadata, final_artist);
1101 		tracker_sparql_builder_object_blank_close (metadata);
1102 	}
1103 
1104 	if (data->exposure_time) {
1105 		tracker_sparql_builder_predicate (metadata, "nmm:exposureTime");
1106 		tracker_sparql_builder_object_unvalidated (metadata, data->exposure_time);
1107 	}
1108 
1109 	if (data->iso_speed_ratings) {
1110 		tracker_sparql_builder_predicate (metadata, "nmm:isoSpeed");
1111 		tracker_sparql_builder_object_unvalidated (metadata, data->iso_speed_ratings);
1112 	}
1113 
1114 	if (data->date || data->time_original) {
1115 		const gchar *final_date = tracker_coalesce_strip (2, data->date,
1116 		                                                  data->time_original);
1117 
1118 		tracker_sparql_builder_predicate (metadata, "nie:contentCreated");
1119 		tracker_sparql_builder_object_unvalidated (metadata, final_date);
1120 	}
1121 
1122 	if (data->description) {
1123 		tracker_sparql_builder_predicate (metadata, "nie:description");
1124 		tracker_sparql_builder_object_unvalidated (metadata, data->description);
1125 	}
1126 
1127 	if (data->metering_mode) {
1128 		tracker_sparql_builder_predicate (metadata, "nmm:meteringMode");
1129 		tracker_sparql_builder_object_unvalidated (metadata, data->metering_mode);
1130 	}
1131 
1132 	if (data->creator) {
1133 		tracker_sparql_builder_predicate (metadata, "nco:creator");
1134 
1135 		tracker_sparql_builder_object_blank_open (metadata);
1136 		tracker_sparql_builder_predicate (metadata, "a");
1137 		tracker_sparql_builder_object (metadata, "nco:Contact");
1138 
1139 		tracker_sparql_builder_predicate (metadata, "nco:fullname");
1140 		tracker_sparql_builder_object_unvalidated (metadata, data->creator);
1141 		tracker_sparql_builder_object_blank_close (metadata);
1142 	}
1143 
1144 	if (data->address || data->state || data->country || data->city ||
1145 	    data->gps_altitude || data->gps_latitude || data->gps_longitude) {
1146 
1147 		tracker_sparql_builder_predicate (metadata, "slo:location");
1148 
1149 		tracker_sparql_builder_object_blank_open (metadata); /* GeoPoint */
1150 		tracker_sparql_builder_predicate (metadata, "a");
1151 		tracker_sparql_builder_object (metadata, "slo:GeoLocation");
1152 
1153 		if (data->address || data->state || data->country || data->city) {
1154 			gchar *addruri;
1155 
1156 			addruri = tracker_sparql_get_uuid_urn ();
1157 
1158 			tracker_sparql_builder_predicate (metadata, "slo:postalAddress");
1159 			tracker_sparql_builder_object_iri (metadata, addruri);
1160 
1161 			tracker_sparql_builder_insert_open (preupdate, NULL);
1162 			tracker_sparql_builder_subject_iri (preupdate, addruri);
1163 
1164 			g_free (addruri);
1165 
1166 			tracker_sparql_builder_predicate (preupdate, "a");
1167 			tracker_sparql_builder_object (preupdate, "nco:PostalAddress");
1168 
1169 			if (data->address) {
1170 				tracker_sparql_builder_predicate (preupdate, "nco:streetAddress");
1171 				tracker_sparql_builder_object_unvalidated (preupdate, data->address);
1172 			}
1173 
1174 			if (data->state) {
1175 				tracker_sparql_builder_predicate (preupdate, "nco:region");
1176 				tracker_sparql_builder_object_unvalidated (preupdate, data->state);
1177 			}
1178 
1179 			if (data->city) {
1180 				tracker_sparql_builder_predicate (preupdate, "nco:locality");
1181 				tracker_sparql_builder_object_unvalidated (preupdate, data->city);
1182 			}
1183 
1184 			if (data->country) {
1185 				tracker_sparql_builder_predicate (preupdate, "nco:country");
1186 				tracker_sparql_builder_object_unvalidated (preupdate, data->country);
1187 			}
1188 
1189 			tracker_sparql_builder_insert_close (preupdate);
1190 		}
1191 
1192 		/* FIXME We are not handling the altitude ref here */
1193 
1194 		if (data->gps_altitude) {
1195 			tracker_sparql_builder_predicate (metadata, "slo:altitude");
1196 			tracker_sparql_builder_object_unvalidated (metadata, data->gps_altitude);
1197 		}
1198 
1199 		if (data->gps_latitude) {
1200 			tracker_sparql_builder_predicate (metadata, "slo:latitude");
1201 			tracker_sparql_builder_object_unvalidated (metadata, data->gps_latitude);
1202 		}
1203 
1204 		if (data->gps_longitude) {
1205 			tracker_sparql_builder_predicate (metadata, "slo:longitude");
1206 			tracker_sparql_builder_object_unvalidated (metadata, data->gps_longitude);
1207 		}
1208 
1209 		tracker_sparql_builder_object_blank_close (metadata); /* GeoLocation */
1210 	}
1211 
1212 	if (data->gps_direction) {
1213 		tracker_sparql_builder_predicate (metadata, "nfo:heading");
1214 		tracker_sparql_builder_object_unvalidated (metadata, data->gps_direction);
1215 	}
1216 
1217 
1218         if (data->regions) {
1219 	        tracker_xmp_apply_regions (preupdate, metadata, graph, data);
1220         }
1221 
1222 	return TRUE;
1223 }
1224 
1225 /**
1226  * tracker_xmp_apply_regions:
1227  * @preupdate: the preupdate object to apply XMP data to.
1228  * @metadata: the metadata object to apply XMP data to.
1229  * @graph: the graph to apply XMP data to.
1230  * @data: the data to push into @preupdate and @metadata.
1231  *
1232  * This function applies all regional @data to @preupdate and
1233  * @metadata. Regional data exists for image formats like JPEG, PNG,
1234  * etc. where parts of the image refer to areas of interest. This can
1235  * be people's faces, places to focus, barcodes, etc. The regional
1236  * data describes the title, height, width, X, Y and can occur
1237  * multiple times in a given file.
1238  *
1239  * This data usually is standardized between image formats and that's
1240  * what makes this function different to tracker_xmp_apply() which is
1241  * useful for XMP files only.
1242  *
1243  * Returns: %TRUE if the @data was applied to @preupdate and @metadata
1244  * successfully, otherwise %FALSE is returned.
1245  *
1246  * Since: 0.12
1247  **/
1248 gboolean
1249 tracker_xmp_apply_regions (TrackerSparqlBuilder *preupdate,
1250                            TrackerSparqlBuilder *metadata,
1251                            const gchar          *graph,
1252                            TrackerXmpData       *data)
1253 {
1254         GSList *iter;
1255 
1256         g_return_val_if_fail (TRACKER_SPARQL_IS_BUILDER (preupdate), FALSE);
1257         g_return_val_if_fail (TRACKER_SPARQL_IS_BUILDER (metadata), FALSE);
1258         g_return_val_if_fail (data != NULL, FALSE);
1259 
1260         if (!data->regions) {
1261                 return TRUE;
1262         }
1263 
1264         for (iter = data->regions; iter != NULL; iter = iter->next) {
1265 	        TrackerXmpRegion *region;
1266 	        gchar *uuid;
1267 
1268                 region = (TrackerXmpRegion *) iter->data;
1269                 uuid = tracker_sparql_get_uuid_urn ();
1270 
1271 		tracker_sparql_builder_insert_open (preupdate, NULL);
1272 		if (graph) {
1273 			tracker_sparql_builder_graph_open (preupdate, graph);
1274 		}
1275 
1276 		tracker_sparql_builder_subject_iri (preupdate, uuid);
1277 		tracker_sparql_builder_predicate (preupdate, "a");
1278 		tracker_sparql_builder_object (preupdate, "nfo:RegionOfInterest");
1279 
1280                 if (region->title) {
1281                         tracker_sparql_builder_predicate (preupdate, "nie:title");
1282                         tracker_sparql_builder_object_string (preupdate, region->title);
1283                 }
1284 
1285                 if (region->description) {
1286                         tracker_sparql_builder_predicate (preupdate, "nie:description");
1287                         tracker_sparql_builder_object_string (preupdate, region->description);
1288                 }
1289 
1290                 if (region->type) {
1291                         tracker_sparql_builder_predicate (preupdate, "nfo:regionOfInterestType");
1292                         tracker_sparql_builder_object (preupdate, fix_region_type (region->type));
1293                 }
1294 
1295                 if (region->x) {
1296                         tracker_sparql_builder_predicate (preupdate, "nfo:regionOfInterestX");
1297                         tracker_sparql_builder_object_unvalidated (preupdate, region->x);
1298                 }
1299 
1300                 if (region->y) {
1301                         tracker_sparql_builder_predicate (preupdate, "nfo:regionOfInterestY");
1302                         tracker_sparql_builder_object_unvalidated (preupdate, region->y);
1303                 }
1304 
1305                 if (region->width) {
1306                         tracker_sparql_builder_predicate (preupdate, "nfo:regionOfInterestWidth");
1307                         tracker_sparql_builder_object_unvalidated (preupdate, region->width);
1308                 }
1309 
1310                 if (region->height) {
1311                         tracker_sparql_builder_predicate (preupdate, "nfo:regionOfInterestHeight");
1312                         tracker_sparql_builder_object_unvalidated (preupdate, region->height);
1313                 }
1314 
1315                 if (region->link_uri && region->link_class) {
1316                         tracker_sparql_builder_predicate (preupdate, "nfo:roiRefersTo");
1317                         tracker_sparql_builder_object_iri (preupdate, region->link_uri);
1318                 }
1319 
1320 		if (graph) {
1321 			tracker_sparql_builder_graph_close (preupdate);
1322 		}
1323 		tracker_sparql_builder_insert_close (preupdate);
1324 
1325                 /* Handle non-preupdate metadata */
1326                 tracker_sparql_builder_predicate (metadata, "nfo:hasRegionOfInterest");
1327                 tracker_sparql_builder_object_iri (metadata, uuid);
1328 
1329                 g_free (uuid);
1330         }
1331 
1332         return TRUE;
1333 }