/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_xmp.cpp#1 $ */ 
/* $DateTime: 2012/05/30 13:28:51 $ */
/* $Change: 832332 $ */
/* $Author: tknoll $ */

/*****************************************************************************/
#if qDNGUseXMP

#include "dng_xmp.h"

#include "dng_assertions.h"
#include "dng_date_time.h"
#include "dng_exceptions.h"
#include "dng_exif.h"
#include "dng_image_writer.h"
#include "dng_iptc.h"
#include "dng_negative.h"
#include "dng_string.h"
#include "dng_string_list.h"
#include "dng_utils.h"
#include "dng_xmp_sdk.h"

/*****************************************************************************/

dng_xmp::dng_xmp (dng_memory_allocator &allocator)

	:	fAllocator (allocator)
	
	,	fSDK (NULL)
	
	{
	
	fSDK = new dng_xmp_sdk ();
	
	if (!fSDK)
		{
		ThrowMemoryFull ();
		}
			
	}

/*****************************************************************************/

dng_xmp::dng_xmp (const dng_xmp &xmp)

	:	fAllocator (xmp.fAllocator)
	
	,	fSDK (NULL)
	
	{
	
	fSDK = new dng_xmp_sdk (*xmp.fSDK);
	
	if (!fSDK)
		{
		ThrowMemoryFull ();
		}
			
	}

/*****************************************************************************/

dng_xmp::~dng_xmp ()
	{
	
	if (fSDK)
		{
		
		delete fSDK;
		
		}
	
	}

/*****************************************************************************/

dng_xmp * dng_xmp::Clone () const
	{
	
	dng_xmp *result = new dng_xmp (*this);
	
	if (!result)
		{
		ThrowMemoryFull ();
		}
	
	return result;
	
	}

/*****************************************************************************/

void dng_xmp::TrimDecimal (char *s)
	{
	
	uint32 len = (uint32) strlen (s);
	
	while (len > 0)
		{
		
		if (s [len - 1] == '0')
			s [--len] = 0;
			
		else
			break;
			
		}
	
	if (len > 0)
		{
		
		if (s [len - 1] == '.')
			s [--len] = 0;
			
		}

	}
	
/*****************************************************************************/

dng_string dng_xmp::EncodeFingerprint (const dng_fingerprint &f,
									   bool allowInvalid)
	{
	
	dng_string result;
	
	if (f.IsValid () || allowInvalid)
		{
		
		char s [dng_fingerprint::kDNGFingerprintSize * 2 + 1];
		
		f.ToUtf8HexString (s);
			
		result.Set (s);
		
		}
	
	return result;
	
	}

/*****************************************************************************/

dng_fingerprint dng_xmp::DecodeFingerprint (const dng_string &s)
	{
	
	dng_fingerprint result;
	
	if (s.Length () == 32)
		result.FromUtf8HexString (s.Get ());
	
	return result;
	
	}

/*****************************************************************************/

dng_string dng_xmp::EncodeGPSVersion (uint32 version)
	{
	
	dng_string result;
	
	if (version)
		{
		
		uint8 b0 = (uint8) (version >> 24);
		uint8 b1 = (uint8) (version >> 16);
		uint8 b2 = (uint8) (version >>  8);
		uint8 b3 = (uint8) (version      );
		
		if (b0 <= 9 && b1 <= 9 && b2 <= 9 && b3 <= 9)
			{
			
   			char s [32];

			sprintf (s,
					 "%u.%u.%u.%u",
					 (unsigned) b0,
					 (unsigned) b1,
					 (unsigned) b2, 
					 (unsigned) b3);
					 
			result.Set (s);
			
			}
		
		}
	
	return result;
	
	}
		
/*****************************************************************************/

uint32 dng_xmp::DecodeGPSVersion (const dng_string &s)
	{
	
	uint32 result = 0;
	
	if (s.Length () == 7)
		{
		
		unsigned b0 = 0;
		unsigned b1 = 0;
		unsigned b2 = 0;
		unsigned b3 = 0;
		
		if (sscanf (s.Get (),
					"%u.%u.%u.%u",
					&b0,
					&b1,
					&b2,
					&b3) == 4)
			{
			
			result = (b0 << 24) |
					 (b1 << 16) |
					 (b2 <<  8) |
					 (b3      );
			
			}
		
		}
		
	return result;
	
	}
							   				   
/*****************************************************************************/

dng_string dng_xmp::EncodeGPSCoordinate (const dng_string &ref,
							    		 const dng_urational *coord)
	{
	
	dng_string result;
	
	if (ref.Length () == 1 && coord [0].IsValid () &&
							  coord [1].IsValid ())
		{
		
		char refChar = ForceUppercase (ref.Get () [0]);
		
		if (refChar == 'N' ||
			refChar == 'S' ||
			refChar == 'E' ||
			refChar == 'W')
			{
			
			char s [256];
			
			// Use the seconds case if all three values are
			// integers.
			
			if (coord [0].d == 1 &&
				coord [1].d == 1 &&
				coord [2].d == 1)
				{
								
				sprintf (s,
						 "%u,%u,%u%c",
						 (unsigned) coord [0].n,
						 (unsigned) coord [1].n,
						 (unsigned) coord [2].n,
						 refChar);
			
				}
				
			// Else we need to use the fractional minutes case.
				
			else
				{
				
				// Find value minutes.
				
				real64 x = coord [0].As_real64 () * 60.0 +
						   coord [1].As_real64 () +
						   coord [2].As_real64 () * (1.0 / 60.0);
						   
				// Round to fractional four decimal places.
				
				uint32 y = Round_uint32 (x * 10000.0);
				
				// Split into degrees and minutes.
				
				uint32 d = y / (60 * 10000);
				uint32 m = y % (60 * 10000);
				
				char min [32];
				
				sprintf (min, "%.4f", m * (1.0 / 10000.0));

				TrimDecimal (min);
				
				sprintf (s,
						 "%u,%s%c",
						 (unsigned) d,
						 min,
						 refChar);

				}
				
			result.Set (s);
				
			}
				
		}
	
	return result;
	
	}
		
/*****************************************************************************/

void dng_xmp::DecodeGPSCoordinate (const dng_string &s,
								   dng_string &ref,
								   dng_urational *coord)
	{
	
	ref.Clear ();
	
	coord [0].Clear ();
	coord [1].Clear ();
	coord [2].Clear ();
	
	if (s.Length () > 1)
		{
		
		char refChar = ForceUppercase (s.Get () [s.Length () - 1]);
		
		if (refChar == 'N' ||
			refChar == 'S' ||
			refChar == 'E' ||
			refChar == 'W')
			{
			
			dng_string ss (s);
			
			ss.Truncate (ss.Length () - 1);
			
			ss.NormalizeAsCommaSeparatedNumbers();
				
			int degrees = 0;
			
			real64 minutes = 0.0;
			real64 seconds = 0.0;
			
			int count = sscanf (ss.Get (),
								"%d,%lf,%lf",
								&degrees,
								&minutes,
								&seconds);
								
			if (count < 1)
				{
				return;
				}
				
			// The degree, minute, second values should always be positive.
			
			if (degrees < 0 || minutes < 0 || seconds < 0)
				{
				return;
				}
				
			coord [0] = dng_urational ((uint32) degrees, 1);
			
			if (count <= 2)
				{
				coord [1].Set_real64 (minutes, 10000);
				coord [2] = dng_urational (0, 1);
				}
			else
				{
				coord [1].Set_real64 (minutes, 1);
				coord [2].Set_real64 (seconds, 100);
				}
				
			char r [2];
			
			r [0] = refChar;
			r [1] = 0;
			
			ref.Set (r);
				
			}
		
		}
	
	}
											 
/*****************************************************************************/

dng_string dng_xmp::EncodeGPSDateTime (const dng_string &dateStamp,
									   const dng_urational *timeStamp)
	{
	
	dng_string result;
	
	if (timeStamp [0].IsValid () &&
		timeStamp [1].IsValid () &&
		timeStamp [2].IsValid ())
		{
  
 		char s [256];
 		
		char sec [32];
		
		sprintf (sec,
				 "%09.6f",
				 timeStamp [2].As_real64 ());
		
		TrimDecimal (sec);
		
		int year  = 0;
		int month = 0;
		int day   = 0;
		
		if (dateStamp.NotEmpty ())
			{
			
			sscanf (dateStamp.Get (), 
				    "%d:%d:%d",
				    &year,
				    &month,
				    &day);
			
			}
			
		if (year  >= 1 && year  <= 9999 &&
			month >= 1 && month <=   12 &&
			day   >= 1 && day   <=   31)
			{
			
			sprintf (s,
					 "%04d-%02d-%02dT%02u:%02u:%sZ",
					 year,
					 month,
					 day,
					 (unsigned) Round_uint32 (timeStamp [0].As_real64 ()),
					 (unsigned) Round_uint32 (timeStamp [1].As_real64 ()),
					 sec);
					 
			}
			
		else
			{
			
			sprintf (s,
					 "%02u:%02u:%sZ",
					 (unsigned) Round_uint32 (timeStamp [0].As_real64 ()),
					 (unsigned) Round_uint32 (timeStamp [1].As_real64 ()),
					 sec);
					 
			}
		
		result.Set (s);
		
		}
	
	return result;
	
	}
		
/*****************************************************************************/

void dng_xmp::DecodeGPSDateTime (const dng_string &s,
								 dng_string &dateStamp,
								 dng_urational *timeStamp)
	{
	
	dateStamp.Clear ();
	
	timeStamp [0].Clear ();
	timeStamp [1].Clear ();
	timeStamp [2].Clear ();
	
	if (s.NotEmpty ())
		{
		
		unsigned year   = 0;
		unsigned month  = 0;
		unsigned day    = 0;
		unsigned hour   = 0;
		unsigned minute = 0;
		
		double second = 0.0;
		
		if (sscanf (s.Get (),
					"%u-%u-%uT%u:%u:%lf",
					&year,
					&month,
					&day,
					&hour,
					&minute,
					&second) == 6)
			{
			
			if (year  >= 1 && year  <= 9999 &&
				month >= 1 && month <= 12   &&
				day   >= 1 && day   <= 31   )
				{
				
				char ss [64];
				
				sprintf (ss,
						 "%04u:%02u:%02u",
						 year,
						 month,
						 day);
						 
				dateStamp.Set (ss);
				
				}
			
			}
			
		else if (sscanf (s.Get (),
						 "%u:%u:%lf",
						 &hour,
				 		 &minute,
				 		 &second) != 3)
			{
			
			return;
			
			}
			
		timeStamp [0] = dng_urational ((uint32) hour  , 1);
		timeStamp [1] = dng_urational ((uint32) minute, 1);
		
		timeStamp [2].Set_real64 (second, 1000);
		
		}
	
	}
		
/*****************************************************************************/

void dng_xmp::Parse (dng_host &host,
					 const void *buffer,
				     uint32 count)
	{
	
	fSDK->Parse (host,
				 (const char *) buffer,
				 count);
	
	}
	
/*****************************************************************************/

dng_memory_block * dng_xmp::Serialize (bool asPacket,
									   uint32 targetBytes,
									   uint32 padBytes,
									   bool forJPEG,
									   bool compact) const
	{
	
	return fSDK->Serialize (fAllocator,
							asPacket,
							targetBytes,
							padBytes,
							forJPEG,
							compact);
	
	}
	
/*****************************************************************************/

void dng_xmp::PackageForJPEG (AutoPtr<dng_memory_block> &stdBlock,
							  AutoPtr<dng_memory_block> &extBlock,
							  dng_string &extDigest) const
	{
	
	fSDK->PackageForJPEG (fAllocator,
						  stdBlock,
						  extBlock,
						  extDigest);
	
	}
	
/*****************************************************************************/

void dng_xmp::MergeFromJPEG (const dng_xmp &xmp)
	{
	
	fSDK->MergeFromJPEG (xmp.fSDK);
	
	}

/*****************************************************************************/

bool dng_xmp::HasMeta () const
	{
	
	return fSDK->HasMeta ();
	
	}

/*****************************************************************************/

void * dng_xmp::GetPrivateMeta ()
	{
	
	return fSDK->GetPrivateMeta ();
	
	}

/*****************************************************************************/

bool dng_xmp::Exists (const char *ns,
					  const char *path) const
	{
 
	return fSDK->Exists (ns, path);
 
	}

/*****************************************************************************/

bool dng_xmp::HasNameSpace (const char *ns) const
	{
 
	return fSDK->HasNameSpace (ns);
 
	}

/*****************************************************************************/

bool dng_xmp::IteratePaths (IteratePathsCallback *callback,
						    void *callbackData,
							const char *ns,
							const char *path)
	{
	
	return fSDK->IteratePaths (callback, callbackData, ns, path);
	
	}
						   
/*****************************************************************************/

void dng_xmp::Remove (const char *ns,
				      const char *path)
	{
	
	fSDK->Remove (ns, path);
	
	}
	
/*****************************************************************************/

void dng_xmp::RemoveProperties (const char *ns)
	{
	
	fSDK->RemoveProperties (ns);
	
	}

/*****************************************************************************/

void dng_xmp::RemoveEmptyStringOrArray (const char *ns,
								        const char *path)
	{
	
	if (path == NULL || path [0] == 0)
		{
		return;
		}
	
	if (fSDK->IsEmptyString (ns, path) ||
		fSDK->IsEmptyArray  (ns, path))
		{
		
		Remove (ns, path);
		
		}
	
	}

/*****************************************************************************/

static bool RemoveEmptyStringsAndArraysCallback (const char *ns,
												 const char *path,
												 void *callbackData)
	{
	
	dng_xmp *xmp = (dng_xmp *) callbackData;
	
	xmp->RemoveEmptyStringOrArray (ns, path);
	
	return true;
	
	}

/*****************************************************************************/

void dng_xmp::RemoveEmptyStringsAndArrays (const char *ns)
	{
	
	IteratePaths (RemoveEmptyStringsAndArraysCallback,
				  (void *) this,
				  ns,
				  NULL);
	
	}
		
/*****************************************************************************/

void dng_xmp::Set (const char *ns,
				   const char *path,
				   const char *text)
	{
	
	fSDK->Set (ns, path, text);
	
	}
	
/*****************************************************************************/

bool dng_xmp::GetString (const char *ns,
						 const char *path,
						 dng_string &s) const
	{
	
	return fSDK->GetString (ns, path, s);
	
	}
		
/*****************************************************************************/

void dng_xmp::SetString (const char *ns,
						 const char *path,
						 const dng_string &s)
	{
	
	fSDK->SetString (ns, path, s);
	
	}
		
/*****************************************************************************/

bool dng_xmp::SyncString (const char *ns,
						  const char *path,
						  dng_string &s,
						  uint32 options)
	{
	
	bool isDefault = s.IsEmpty ();
	
	// Sync 1: Force XMP to match non-XMP.
	
	if (options & ignoreXMP)
		{
		
		if (isDefault || (options & removeXMP))
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
			
			SetString (ns, path, s);
			
			}
		
		return false;
		
		}
	
	// Sync 2: From non-XMP to XMP if non-XMP is prefered.
	
	if ((options & preferNonXMP) && !isDefault)
		{
		
		if (options & removeXMP)
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
		
			SetString (ns, path, s);
			
			}
				   
		return false;
		
		}
		
	// Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
		
	if ((options & preferXMP) || isDefault)
		{
		
		if (GetString (ns, path, s))
			{
			
			if (options & removeXMP)
				{
				
				Remove (ns, path);
				
				}

			return true;
						
			}
		
		}
		
	// Sync 4: From non-XMP to XMP.
	
	if (options & removeXMP)
		{
		
		Remove (ns, path);
		
		}
		
	else if (!isDefault)
		{
		
		SetString (ns, path, s);
		
		}
		
	return false;
	
	}

/*****************************************************************************/

bool dng_xmp::GetStringList (const char *ns,
						 	 const char *path,
						 	 dng_string_list &list) const
	{
	
	return fSDK->GetStringList (ns, path, list);
	
	}
		
/*****************************************************************************/

void dng_xmp::SetStringList (const char *ns,
						     const char *path,
						     const dng_string_list &list,
						     bool isBag)
	{
	
	fSDK->SetStringList (ns, path, list, isBag);
	
	}
		
/*****************************************************************************/

void dng_xmp::SyncStringList (const char *ns,
						      const char *path,
						      dng_string_list &list,
						      bool isBag,
						      uint32 options)
	{
	
	bool isDefault = (list.Count () == 0);
	
	// First make sure the XMP is not badly formatted, since
	// this breaks some Photoshop logic.
	
	ValidateStringList (ns, path);
	
	// Sync 1: Force XMP to match non-XMP.
	
	if (options & ignoreXMP)
		{
		
		if (isDefault)
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
			
			SetStringList (ns, path, list, isBag);
			
			}
		
		return;
		
		}
	
	// Sync 2: From non-XMP to XMP if non-XMP is prefered.
	
	if ((options & preferNonXMP) && !isDefault)
		{
		
		SetStringList (ns, path, list, isBag);
				   
		return;
		
		}
		
	// Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
		
	if ((options & preferXMP) || isDefault)
		{
		
		if (GetStringList (ns, path, list))
			{
			
			return;
						
			}
			
		}
		
	// Sync 4: From non-XMP to XMP.
	
	if (!isDefault)
		{
		
		SetStringList (ns, path, list, isBag);
		
		}
		
	}

/*****************************************************************************/

void dng_xmp::SetStructField (const char *ns,
							  const char *path,
							  const char *fieldNS,
							  const char *fieldName,
							  const dng_string &s)
	{

	dng_string ss (s);
	
	ss.SetLineEndings ('\n');
	
	ss.StripLowASCII ();
	
	fSDK->SetStructField (ns, path, fieldNS, fieldName, ss.Get ());

	}

/*****************************************************************************/

void dng_xmp::SetStructField (const char *ns,
							  const char *path,
							  const char *fieldNS,
							  const char *fieldName,
							  const char *s)
	{

	fSDK->SetStructField (ns, path, fieldNS, fieldName, s);

	}

/*****************************************************************************/

void dng_xmp::DeleteStructField (const char *ns,
								 const char *path,
								 const char *fieldNS,
								 const char *fieldName)
	{
	
	fSDK->DeleteStructField (ns, path, fieldNS, fieldName);
	
	}
								
/*****************************************************************************/

bool dng_xmp::GetStructField (const char *ns,
							  const char *path,
							  const char *fieldNS,
							  const char *fieldName,
							  dng_string &s) const
	{
		
	return fSDK->GetStructField (ns, path, fieldNS, fieldName, s);

	}

/*****************************************************************************/

void dng_xmp::SetAltLangDefault (const char *ns,
								 const char *path,
								 const dng_string &s)
	{
		
	fSDK->SetAltLangDefault (ns, path, s);

	}

/*****************************************************************************/

bool dng_xmp::GetAltLangDefault (const char *ns,
								 const char *path,
								 dng_string &s) const
	{
			
	return fSDK->GetAltLangDefault (ns, path, s);

	}

/*****************************************************************************/

bool dng_xmp::SyncAltLangDefault (const char *ns,
								  const char *path,
								  dng_string &s,
								  uint32 options)
	{
	
	bool isDefault = s.IsEmpty ();
	
	// Sync 1: Force XMP to match non-XMP.
	
	if (options & ignoreXMP)
		{
		
		if (isDefault)
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
			
			SetAltLangDefault (ns, path, s);
			
			}
		
		return false;
		
		}
	
	// Sync 2: From non-XMP to XMP if non-XMP is prefered.
	
	if ((options & preferNonXMP) && !isDefault)
		{
		
		SetAltLangDefault (ns, path, s);
				   
		return false;
		
		}
		
	// Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
		
	if ((options & preferXMP) || isDefault)
		{
		
		if (GetAltLangDefault (ns, path, s))
			{
							
			return true;
						
			}
		
		}
		
	// Sync 4: From non-XMP to XMP.
	
	if (!isDefault)
		{
		
		SetAltLangDefault (ns, path, s);
		
		}
		
	return false;
	
	}

/*****************************************************************************/

bool dng_xmp::GetBoolean (const char *ns,
					 	  const char *path,
					 	  bool &x) const
	{
	
	dng_string s;
	
	if (GetString (ns, path, s))
		{
	
		if (s.Matches ("True"))
			{
			
			x = true;
			
			return true;
			
			}
			
		if (s.Matches ("False"))
			{
			
			x = false;
			
			return true;
			
			}
			
		}
		
	return false;
	
	}
	
/*****************************************************************************/

void dng_xmp::SetBoolean (const char *ns,
					 	  const char *path,
					 	  bool x)
	{
	
	Set (ns, path, x ? "True" : "False");
	
	}
	
/*****************************************************************************/

bool dng_xmp::Get_int32 (const char *ns,
						 const char *path,
						 int32 &x) const
	{
	
	dng_string s;
	
	if (GetString (ns, path, s))
		{
	
		if (s.NotEmpty ())
			{
			
			int y = 0;
			
			if (sscanf (s.Get (), "%d", &y) == 1)
				{
				
				x = y;
				
				return true;
				
				}

			}
			
		}
		
	return false;
	
	}
	
/*****************************************************************************/

void dng_xmp::Set_int32 (const char *ns,
						 const char *path,
						 int32 x,
						 bool usePlus)
	{
	
	char s [64];
	
	if (x > 0 && usePlus)
		{
		sprintf (s, "+%d", (int) x);
		}
	else
		{
		sprintf (s, "%d", (int) x);
		}

	Set (ns, path, s);
	
	}
						 
/*****************************************************************************/

bool dng_xmp::Get_uint32 (const char *ns,
					 	  const char *path,
					 	  uint32 &x) const
	{
	
	dng_string s;
	
	if (GetString (ns, path, s))
		{
	
		if (s.NotEmpty ())
			{
			
			unsigned y = 0;
			
			if (sscanf (s.Get (), "%u", &y) == 1)
				{
				
				x = y;
				
				return true;
				
				}

			}
			
		}
		
	return false;
	
	}
	
/*****************************************************************************/

void dng_xmp::Set_uint32 (const char *ns,
						  const char *path,
						  uint32 x)
	{

	char s [64];
	
	sprintf (s,
			 "%u",
			 (unsigned) x);
	
	Set (ns, path, s);
		
	}
	
/*****************************************************************************/

void dng_xmp::Sync_uint32 (const char *ns,
						   const char *path,
						   uint32 &x,
						   bool isDefault,
						   uint32 options)
	{
	
	// Sync 1: Force XMP to match non-XMP.
	
	if (options & ignoreXMP)
		{
		
		if (isDefault || (options & removeXMP))
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
			
			Set_uint32 (ns, path, x);
			
			}
		
		return;
		
		}
	
	// Sync 2: From non-XMP to XMP if non-XMP is prefered.
	
	if ((options & preferNonXMP) && !isDefault)
		{
		
		if (options & removeXMP)
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
			
			Set_uint32 (ns, path, x);
			
			}
				   
		return;
		
		}
		
	// Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
		
	if ((options & preferXMP) || isDefault)
		{
		
		if (Get_uint32 (ns, path, x))
			{
							
			if (options & removeXMP)
				{
				
				Remove (ns, path);
				
				}

			return;
						
			}
		
		}
		
	// Sync 4: From non-XMP to XMP.
	
	if (options & removeXMP)
		{
		
		Remove (ns, path);
		
		}
		
	else if (!isDefault)
		{
		
		Set_uint32 (ns, path, x);
		
		}
	
	}
						 
/*****************************************************************************/

void dng_xmp::Sync_uint32_array (const char *ns,
						   		 const char *path,
						   		 uint32 *data,
						   		 uint32 &count,
						   		 uint32 maxCount,
						   		 uint32 options)
	{
	
	dng_string_list list;
	
	for (uint32 j = 0; j < count; j++)
		{
		
		char s [32];
		
		sprintf (s, "%u", (unsigned) data [j]);
		
		dng_string ss;
		
		ss.Set (s);
		
		list.Append (ss);
		
		}
		
	SyncStringList (ns,
					path,
					list,
					false,
					options);
					
	count = 0;
	
	for (uint32 k = 0; k < maxCount; k++)
		{
		
		data [k] = 0;
		
		if (k < list.Count ())
			{
			
			unsigned x = 0;
			
			if (sscanf (list [k].Get (), "%u", &x) == 1)
				{
				
				data [count++] = x;
				
				}
			
			}
		
		}
	
	}

/*****************************************************************************/

bool dng_xmp::Get_real64 (const char *ns,
					  	  const char *path,
					  	  real64 &x) const
	{
	
	dng_string s;
	
	if (GetString (ns, path, s))
		{
	
		if (s.NotEmpty ())
			{
			
			double y = 0;
			
			if (sscanf (s.Get (), "%lf", &y) == 1)
				{
				
				x = y;
				
				return true;
				
				}

			}
			
		}
		
	return false;
	
	}
	
/*****************************************************************************/

void dng_xmp::Set_real64 (const char *ns,
					  	  const char *path,
					  	  real64 x,
					      uint32 places,
					      bool trim,
					      bool usePlus)
	{
	
	char s [64];
	
	if (x > 0.0 && usePlus)
		{
		sprintf (s, "+%0.*f", (unsigned) places, (double) x);
		}
	else
		{
		sprintf (s, "%0.*f", (unsigned) places, (double) x);
		}
	
	if (trim)
		{
		
		while (s [strlen (s) - 1] == '0')
			{
			s [strlen (s) - 1] = 0;
			}
			
		if (s [strlen (s) - 1] == '.')
			{
			s [strlen (s) - 1] = 0;
			}
			
		}
	
	Set (ns, path, s);
	
	}

/*****************************************************************************/

bool dng_xmp::Get_urational (const char *ns,
							 const char *path,
							 dng_urational &r) const
	{
	
	dng_string s;
	
	if (GetString (ns, path, s))
		{
	
		if (s.NotEmpty ())
			{
			
			unsigned n = 0;
			unsigned d = 0;
			
			if (sscanf (s.Get (), "%u/%u", &n, &d) == 2)
				{
				
				if (d != 0)
					{
				
					r = dng_urational (n, d);
					
					return true;
					
					}
				
				}
					
			}
			
		}

	return false;

	}

/*****************************************************************************/

void dng_xmp::Set_urational (const char *ns,
							 const char *path,
							 const dng_urational &r)
	{

	char s [64];
	
	sprintf (s,
			 "%u/%u",
			 (unsigned) r.n,
			 (unsigned) r.d);
	
	Set (ns, path, s);
		
	}

/*****************************************************************************/

void dng_xmp::Sync_urational (const char *ns,
							  const char *path,
							  dng_urational &r,
							  uint32 options)
	{
	
	bool isDefault = r.NotValid ();
	
	// Sync 1: Force XMP to match non-XMP.
	
	if (options & ignoreXMP)
		{
		
		if (isDefault || (options & removeXMP))
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
			
			Set_urational (ns, path, r);
			
			}
		
		return;
		
		}
	
	// Sync 2: From non-XMP to XMP if non-XMP is prefered.
	
	if ((options & preferNonXMP) && !isDefault)
		{
		
		if (options & removeXMP)
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
			
			Set_urational (ns, path, r);
			
			}
				   
		return;
		
		}
		
	// Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
		
	if ((options & preferXMP) || isDefault)
		{
		
		if (Get_urational (ns, path, r))
			{
							
			if (options & removeXMP)
				{
				
				Remove (ns, path);
				
				}

			return;
						
			}
		
		}
		
	// Sync 4: From non-XMP to XMP.
	
	if (options & removeXMP)
		{
		
		Remove (ns, path);
		
		}
		
	else if (!isDefault)
		{
		
		Set_urational (ns, path, r);
		
		}
	
	}
		
/*****************************************************************************/

bool dng_xmp::Get_srational (const char *ns,
							 const char *path,
							 dng_srational &r) const
	{
	
	dng_string s;
	
	if (GetString (ns, path, s))
		{
	
		if (s.NotEmpty ())
			{
			
			int n = 0;
			int d = 0;
			
			if (sscanf (s.Get (), "%d/%d", &n, &d) == 2)
				{
				
				if (d != 0)
					{
				
					r = dng_srational (n, d);
					
					return true;
					
					}
				
				}
					
			}
			
		}

	return false;

	}

/*****************************************************************************/

void dng_xmp::Set_srational (const char *ns,
							 const char *path,
							 const dng_srational &r)
	{

	char s [64];
	
	sprintf (s,
			 "%d/%d",
			 (int) r.n,
			 (int) r.d);
	
	Set (ns, path, s);
		
	}

/*****************************************************************************/

void dng_xmp::Sync_srational (const char *ns,
							  const char *path,
							  dng_srational &r,
							  uint32 options)
	{
	
	bool isDefault = r.NotValid ();
	
	// Sync 1: Force XMP to match non-XMP.
	
	if (options & ignoreXMP)
		{
		
		if (isDefault || (options & removeXMP))
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
			
			Set_srational (ns, path, r);
			
			}
		
		return;
		
		}
	
	// Sync 2: From non-XMP to XMP if non-XMP is prefered.
	
	if ((options & preferNonXMP) && !isDefault)
		{
		
		if (options & removeXMP)
			{
			
			Remove (ns, path);
			
			}
			
		else
			{
			
			Set_srational (ns, path, r);
			
			}
				   
		return;
		
		}
		
	// Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
		
	if ((options & preferXMP) || isDefault)
		{
		
		if (Get_srational (ns, path, r))
			{
							
			if (options & removeXMP)
				{
				
				Remove (ns, path);
				
				}

			return;
						
			}
		
		}
		
	// Sync 4: From non-XMP to XMP.
	
	if (options & removeXMP)
		{
		
		Remove (ns, path);
		
		}
		
	else if (!isDefault)
		{
		
		Set_srational (ns, path, r);
		
		}
	
	}
		
/*****************************************************************************/

bool dng_xmp::GetFingerprint (const char *ns,
					 		  const char *path,
					    	  dng_fingerprint &print) const
	{
	
	dng_string s;
	
	if (GetString (ns, path, s))
		{
		
		dng_fingerprint temp = DecodeFingerprint (s);
		
		if (temp.IsValid ())
			{
			
			print = temp;
			
			return true;
			
			}
		
		}
		
	return false;
	
	}
	
/******************************************************************************/

void dng_xmp::SetFingerprint (const char *ns,
							  const char *tag,
							  const dng_fingerprint &print,
							  bool allowInvalid)
	{
	
	dng_string s = EncodeFingerprint (print, allowInvalid);
	
	if (s.IsEmpty ())
		{
		
		Remove (ns, tag);
		
		}
		
	else
		{
		
		SetString (ns, tag, s);
		
		}
	
	}

/******************************************************************************/

void dng_xmp::SetVersion2to4 (const char *ns,
							  const char *path,
							  uint32 version)
	{
	
	char buf [32];

	if (version & 0x000000ff)
		{
	
		// x.x.x.x
	
		sprintf (buf,
				 "%u.%u.%u.%u",
				 (unsigned) ((version >> 24) & 0xff),
				 (unsigned) ((version >> 16) & 0xff),
				 (unsigned) ((version >>  8) & 0xff),
				 (unsigned) ((version	   ) & 0xff));
	
		}

	else if (version & 0x0000ff00)
		{
	
		// x.x.x
	
		sprintf (buf,
				 "%u.%u.%u",
				 (unsigned) ((version >> 24) & 0xff),
				 (unsigned) ((version >> 16) & 0xff),
				 (unsigned) ((version >>  8) & 0xff));
	
		}

	else
		{
	
		// x.x
	
		sprintf (buf,
				 "%u.%u",
				 (unsigned) ((version >> 24) & 0xff),
				 (unsigned) ((version >> 16) & 0xff));
	
		}
		
	Set (ns, path, buf);
	
	}

/******************************************************************************/

dng_fingerprint dng_xmp::GetIPTCDigest () const
	{
	
	dng_fingerprint digest;
	
	if (GetFingerprint (XMP_NS_PHOTOSHOP,
						"LegacyIPTCDigest",
						digest))
		{
		
		return digest;
		
		}
		
	return dng_fingerprint ();
	
	}

/******************************************************************************/

void dng_xmp::SetIPTCDigest (dng_fingerprint &digest)
	{
	
	SetFingerprint (XMP_NS_PHOTOSHOP,
					"LegacyIPTCDigest",
					digest);
		
	}
		
/******************************************************************************/

void dng_xmp::ClearIPTCDigest ()
	{
	
	Remove (XMP_NS_PHOTOSHOP, "LegacyIPTCDigest");

	}
		
/*****************************************************************************/

void dng_xmp::SyncIPTC (dng_iptc &iptc,
					    uint32 options)
	{
	
	SyncAltLangDefault (XMP_NS_DC,
						"title",
						iptc.fTitle,
						options);

	SyncString (XMP_NS_PHOTOSHOP,
				"Category",
				iptc.fCategory,
				options);
				
		{
		
		uint32 x = 0xFFFFFFFF;
		
		if (iptc.fUrgency >= 0)
			{
			
			x = (uint32) iptc.fUrgency;
			
			}
			
		Sync_uint32 (XMP_NS_PHOTOSHOP,
					 "Urgency",
					 x,
					 x == 0xFFFFFFFF,
					 options);
					 
		if (x <= 9)
			{
			
			iptc.fUrgency = (int32) x;
			
			}
		
		}
		
	SyncStringList (XMP_NS_PHOTOSHOP,
					"SupplementalCategories",
					iptc.fSupplementalCategories,
					true,
					options);
				
	SyncStringList (XMP_NS_PHOTOSHOP,
					"Keywords",
					iptc.fKeywords,
					true,
					options);
				
	SyncString (XMP_NS_PHOTOSHOP,
			    "Instructions",
			    iptc.fInstructions,
			    options);
			    
		{
		
		dng_string s = iptc.fDateTimeCreated.Encode_ISO_8601 ();
		
		if (SyncString (XMP_NS_PHOTOSHOP,
						"DateCreated",
						s,
						options))
			{
			
			iptc.fDateTimeCreated.Decode_ISO_8601 (s.Get ());
			
			}
		
		}
		
		{
		
		dng_string s = iptc.fDigitalCreationDateTime.Encode_ISO_8601 ();
		
		if (SyncString (XMP_NS_EXIF,
						"DateTimeDigitized",
						s,
						options))
			{
			
			iptc.fDigitalCreationDateTime.Decode_ISO_8601 (s.Get ());
			
			}
					
		}
		
	SyncStringList (XMP_NS_DC,
			        "creator",
			        iptc.fAuthors,
					false,
					options);
			
	SyncString (XMP_NS_PHOTOSHOP,
			    "AuthorsPosition",
			    iptc.fAuthorsPosition,
			    options);
			
	SyncString (XMP_NS_PHOTOSHOP,
			    "City",
			    iptc.fCity,
			    options);
			
	SyncString (XMP_NS_PHOTOSHOP,
			    "State",
			    iptc.fState,
			    options);
			
	SyncString (XMP_NS_PHOTOSHOP,
			    "Country",
			    iptc.fCountry,
			    options);
			
	SyncString (XMP_NS_IPTC,
			    "CountryCode",
			    iptc.fCountryCode,
			    options);
			
	SyncString (XMP_NS_IPTC,
			    "Location",
			    iptc.fLocation,
			    options);
			
	SyncString (XMP_NS_PHOTOSHOP,
			    "TransmissionReference",
			    iptc.fTransmissionReference,
			    options);
			
	SyncString (XMP_NS_PHOTOSHOP,
			    "Headline",
			    iptc.fHeadline,
			    options);

	SyncString (XMP_NS_PHOTOSHOP,
			    "Credit",
			    iptc.fCredit,
			    options);

	SyncString (XMP_NS_PHOTOSHOP,
			    "Source",
			    iptc.fSource,
			    options);

	SyncAltLangDefault (XMP_NS_DC,
						"rights",
						iptc.fCopyrightNotice,
						options);
				
	SyncAltLangDefault (XMP_NS_DC,
						"description",
						iptc.fDescription,
						options);
				
	SyncString (XMP_NS_PHOTOSHOP,
			    "CaptionWriter",
			    iptc.fDescriptionWriter,
			    options);
			   
	}
		
/*****************************************************************************/

void dng_xmp::IngestIPTC (dng_metadata &metadata,
					      bool xmpIsNewer)
	{
	
	if (metadata.IPTCLength ())
		{
		
		// Parse the IPTC block.
	
		dng_iptc iptc;
		
		iptc.Parse (metadata.IPTCData   (),
					metadata.IPTCLength (),
					metadata.IPTCOffset ());
					
		// Compute fingerprint of IPTC data both ways, including and
		// excluding the padding data.
		
		dng_fingerprint iptcDigest1 = metadata.IPTCDigest (true );
		dng_fingerprint iptcDigest2 = metadata.IPTCDigest (false);
		
		// See if there is an IPTC fingerprint stored in the XMP.
			
		dng_fingerprint xmpDigest = GetIPTCDigest ();
		
		if (xmpDigest.IsValid ())
			{
			
			// If they match, the XMP was already synced with this
			// IPTC block, and we should not resync since it might
			// overwrite changes in the XMP data.
			
			if (iptcDigest1 == xmpDigest)
				{
				
				return;
				
				}
				
			// If it matches the incorrectly computed digest, skip
			// the sync, but fix the digest in the XMP.
			
			if (iptcDigest2 == xmpDigest)
				{
				
				SetIPTCDigest (iptcDigest1);
				
				return;
				
				}
				
			// Else the IPTC has changed, so force an update.
				
			xmpIsNewer = false;
			
			}
			
		else
			{
			
			// There is no IPTC digest.  Previously we would
			// prefer the IPTC in this case, but the MWG suggests
			// that we prefer the XMP in this case.
			
			xmpIsNewer = true;
			
			}
			
		// Remember the fingerprint of the IPTC we are syncing with.
			
		SetIPTCDigest (iptcDigest1);
					    
		// Find the sync options.
		
		uint32 options = xmpIsNewer ? preferXMP
									: preferNonXMP;
					
		// Synchronize the fields.
		
		SyncIPTC (iptc, options);
		
		}

	// After the IPTC data is moved to XMP, we don't need it anymore.
	
	metadata.ClearIPTC ();

	}
		
/*****************************************************************************/

void dng_xmp::RebuildIPTC (dng_metadata &metadata,
						   dng_memory_allocator &allocator,
						   bool padForTIFF)
	{
	
	// If there is no XMP, then there is no IPTC.
	
	if (!fSDK->HasMeta ())
		{
		return;
		}
		
	// Extract the legacy IPTC fields from the XMP data.
	
	dng_iptc iptc;
	
	SyncIPTC (iptc, preferXMP);
		
	// Build legacy IPTC record
	
	if (iptc.NotEmpty ())
		{
		
		AutoPtr<dng_memory_block> block (iptc.Spool (allocator,
													 padForTIFF));
		
		metadata.SetIPTC (block);
		
		}

	}
		
/*****************************************************************************/

void dng_xmp::SyncFlash (uint32 &flashState,
						 uint32 &flashMask,
						 uint32 options)
	{
	
	bool isDefault = (flashState == 0xFFFFFFFF);
	
	if ((options & ignoreXMP) || !isDefault)
		{
		
		Remove (XMP_NS_EXIF, "Flash");
		
		}
	
	if (!isDefault)
		{
		  
		fSDK->SetStructField (XMP_NS_EXIF,
							  "Flash",
							  XMP_NS_EXIF,
							  "Fired",
							  (flashState & 0x1) ? "True" : "False");
			
		if (((flashMask >> 1) & 3) == 3)
			{
			
			char s [8];
		
			sprintf (s, "%u", (unsigned) ((flashState >> 1) & 3));
			
			fSDK->SetStructField (XMP_NS_EXIF,
								  "Flash",
								  XMP_NS_EXIF,
								  "Return",
								  s);
			
			}
			
		if (((flashMask >> 3) & 3) == 3)
			{
		
			char s [8];
		
			sprintf (s, "%u", (unsigned) ((flashState >> 3) & 3));
			
			fSDK->SetStructField (XMP_NS_EXIF,
								  "Flash",
								  XMP_NS_EXIF,
								  "Mode",
								  s);
			
			}
			
		if ((flashMask & (1 << 5)) != 0)
			{
			
			fSDK->SetStructField (XMP_NS_EXIF,
								  "Flash",
								  XMP_NS_EXIF,
								  "Function",
								  (flashState & (1 << 5)) ? "True" : "False");
			
			}
			
		if ((flashMask & (1 << 6)) != 0)
			{
			
			fSDK->SetStructField (XMP_NS_EXIF,
								  "Flash",
								  XMP_NS_EXIF,
								  "RedEyeMode",
								  (flashState & (1 << 6)) ? "True" : "False");
			
			}
			
		}
		
	else if (fSDK->Exists (XMP_NS_EXIF, "Flash"))
		{
		
		dng_string s;
		
		if (fSDK->GetStructField (XMP_NS_EXIF,
								  "Flash",
								  XMP_NS_EXIF,
								  "Fired",
								  s))
			{
			
			flashState = 0;
			flashMask  = 1;
			
			if (s.Matches ("True"))
				{
				flashState |= 1;
				}
				
			if (fSDK->GetStructField (XMP_NS_EXIF,
									  "Flash",
									  XMP_NS_EXIF,
									  "Return",
									  s))
				{
				
				unsigned x = 0;
				
				if (sscanf (s.Get (), "%u", &x) == 1 && x <= 3)
					{
					
					flashState |= x << 1;
					flashMask  |= 3 << 1;
					
					}
				
				}
			
			if (fSDK->GetStructField (XMP_NS_EXIF,
									  "Flash",
									  XMP_NS_EXIF,
									  "Mode",
									  s))
				{
				
				unsigned x = 0;
				
				if (sscanf (s.Get (), "%u", &x) == 1 && x <= 3)
					{
					
					flashState |= x << 3;
					flashMask  |= 3 << 3;
					
					}
				
				}
			
			if (fSDK->GetStructField (XMP_NS_EXIF,
									  "Flash",
									  XMP_NS_EXIF,
									  "Function",
									  s))
				{
				
				flashMask |= 1 << 5;
				
				if (s.Matches ("True"))
					{
					flashState |= 1 << 5;
					}
					
				}
			
			if (fSDK->GetStructField (XMP_NS_EXIF,
									  "Flash",
									  XMP_NS_EXIF,
									  "RedEyeMode",
									  s))
				{
				
				flashMask |= 1 << 6;
				
				if (s.Matches ("True"))
					{
					flashState |= 1 << 6;
					}
					
				}
			
			}
		
		}
		
	}

/*****************************************************************************/

void dng_xmp::SyncExif (dng_exif &exif,
						const dng_exif *originalExif,
						bool doingUpdateFromXMP,
						bool removeFromXMP)
	{
	
	DNG_ASSERT (!doingUpdateFromXMP || originalExif,
				"Must have original EXIF if doingUpdateFromXMP");
	
	// Default synchronization options for the read-only fields.
			
	uint32 readOnly = doingUpdateFromXMP ? ignoreXMP
								         : preferNonXMP;
										 
	// Option for removable fields.
	
	uint32 removable = removeFromXMP ? removeXMP
									 : 0;
	
	// Make:
	
	SyncString (XMP_NS_TIFF,
				"Make",
				exif.fMake,
				readOnly + removable);
			
	// Model:
	
	SyncString (XMP_NS_TIFF,
			    "Model",
			    exif.fModel,
			    readOnly + removable);
			    
	// Exif version number:
	
		{
		
		dng_string exifVersion;
		
		if (exif.fExifVersion)
			{
			
			unsigned b0 = ((exif.fExifVersion >> 24) & 0x0FF) - '0';
			unsigned b1 = ((exif.fExifVersion >> 16) & 0x0FF) - '0';
			unsigned b2 = ((exif.fExifVersion >>  8) & 0x0FF) - '0';
			unsigned b3 = ((exif.fExifVersion      ) & 0x0FF) - '0';
			
			if (b0 <= 9 && b1 <= 9 && b2 <= 9 && b3 <= 9)
				{
				
				char s [5];
	
				sprintf (s,
						 "%1u%1u%1u%1u",
						 b0,
						 b1,
						 b2,
						 b3);
				
				exifVersion.Set (s);
				
				}
				
			}
			
		SyncString (XMP_NS_EXIF,
					"ExifVersion",
					exifVersion,
					readOnly);
					
		if (exifVersion.NotEmpty ())
			{
			
			unsigned b0;
			unsigned b1;
			unsigned b2;
			unsigned b3;
			
			if (sscanf (exifVersion.Get (),
						"%1u%1u%1u%1u",
						&b0,
						&b1,
						&b2,
						&b3) == 4)
				{
				
				if (b0 <= 9 && b1 <= 9 && b2 <= 9 && b3 <= 9)
					{
					
					b0 += '0';
					b1 += '0';
					b2 += '0';
					b3 += '0';
					
					exif.fExifVersion = (b0 << 24) |
										(b1 << 16) |
										(b2 <<  8) |
										(b3      );
					
					}
				
				}
			
			}
			
		// Provide default value for ExifVersion.
		
		if (!exif.fExifVersion)
			{
			
			exif.fExifVersion = DNG_CHAR4 ('0','2','2','1');
		
			Set (XMP_NS_EXIF,
				 "ExifVersion",
				 "0221");
					
			}
			
		if (removeFromXMP)
			{
			
			Remove (XMP_NS_EXIF, "ExifVersion");
			
			}
		
		}
		
	// ExposureTime / ShutterSpeedValue:
	
		{
		
		// Process twice in case XMP contains only one of the
		// two fields.
	
		for (uint32 pass = 0; pass < 2; pass++)
			{
			
			dng_urational et = exif.fExposureTime;
		
			Sync_urational (XMP_NS_EXIF,
							"ExposureTime",
							et,
							readOnly);
							
			if (et.IsValid ())
				{
				
				exif.SetExposureTime (et.As_real64 (), false);
				
				}
				
			dng_srational ss = exif.fShutterSpeedValue;
							
			Sync_srational (XMP_NS_EXIF,
						    "ShutterSpeedValue",
						    ss,
						    readOnly);
						    
			if (ss.IsValid ())
				{
				
				exif.SetShutterSpeedValue (ss.As_real64 ());
				
				}
						    
			}
			
		if (removeFromXMP)
			{
			
			Remove (XMP_NS_EXIF, "ExposureTime");
			
			Remove (XMP_NS_EXIF, "ShutterSpeedValue");
			
			}
			
		}
				    
	// FNumber / ApertureValue:
		
		{
		
		for (uint32 pass = 0; pass < 2; pass++)
			{
			
			dng_urational fs = exif.fFNumber;
			
			Sync_urational (XMP_NS_EXIF,
							"FNumber",
							fs,
							readOnly);
							
			if (fs.IsValid ())
				{
							
				exif.SetFNumber (fs.As_real64 ());
				
				}
			
			dng_urational av = exif.fApertureValue;
			
			Sync_urational (XMP_NS_EXIF,
							"ApertureValue",
							av,
							readOnly);
							
			if (av.IsValid ())
				{
							
				exif.SetApertureValue (av.As_real64 ());
				
				}
				
			}
			
		if (removeFromXMP)
			{
			
			Remove (XMP_NS_EXIF, "FNumber");
			
			Remove (XMP_NS_EXIF, "ApertureValue");
			
			}
			
		}
			
	// Exposure program:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "ExposureProgram",
				 exif.fExposureProgram,
				 exif.fExposureProgram == 0xFFFFFFFF,
				 readOnly + removable);
	
	// ISO Speed Ratings:
	
		{
		
		uint32 isoSpeedRatingsCount = 0;

		uint32 isoSpeedRatingsOptions = readOnly;
		
		uint32 oldISOSpeedRatings [3];

		memcpy (oldISOSpeedRatings,
				exif.fISOSpeedRatings,
				sizeof (oldISOSpeedRatings));

		bool checkXMPForHigherISO = false;
		
		for (uint32 j = 0; j < 3; j++)
			{

			// Special case: the EXIF 2.2x standard represents ISO speed ratings with
			// 2 bytes, which cannot hold ISO speed ratings above 65535 (e.g.,
			// 102400). If the EXIF ISO speed rating value is 65535, prefer the XMP
			// ISOSpeedRatings tag value.

			if (exif.fISOSpeedRatings [j] == 65535)
				{

				isoSpeedRatingsOptions = preferXMP;

				checkXMPForHigherISO = true;

				isoSpeedRatingsCount = 0;

				break;

				}
			
			else if (exif.fISOSpeedRatings [j] == 0)
				{
				break;
				}
				
			isoSpeedRatingsCount++;
			
			}
			
		Sync_uint32_array (XMP_NS_EXIF,
						   "ISOSpeedRatings",
						   exif.fISOSpeedRatings,
						   isoSpeedRatingsCount,
						   3,
						   isoSpeedRatingsOptions);

		// If the EXIF ISO was 65535 and we failed to find anything meaningful in the
		// XMP, then we fall back to the EXIF ISO.

		if (checkXMPForHigherISO && (isoSpeedRatingsCount == 0))
			{
				
			memcpy (exif.fISOSpeedRatings,
					oldISOSpeedRatings,
					sizeof (oldISOSpeedRatings));
			
			}
			
		// Only remove the ISO tag if there are not ratings over 65535.
		
		if (removeFromXMP)
			{
			
			bool hasHighISO = false;
			
			for (uint32 j = 0; j < 3; j++)
				{
				
				if (exif.fISOSpeedRatings [j] == 0)
					{
					break;
					}

				hasHighISO = hasHighISO || (exif.fISOSpeedRatings [j] > 65535);
				
				}
				
			if (!hasHighISO)
				{
				
				Remove (XMP_NS_EXIF, "ISOSpeedRatings");
				
				}
			
			}
		
		}

	// SensitivityType:

	Sync_uint32 (XMP_NS_EXIF,
				 "SensitivityType",
				 exif.fSensitivityType,
				 exif.fSensitivityType == stUnknown,
				 readOnly + removable);
		
	// StandardOutputSensitivity:

	Sync_uint32 (XMP_NS_EXIF,
				 "StandardOutputSensitivity",
				 exif.fStandardOutputSensitivity,
				 exif.fStandardOutputSensitivity == 0,
				 readOnly + removable);
		
	// RecommendedExposureIndex:

	Sync_uint32 (XMP_NS_EXIF,
				 "RecommendedExposureIndex",
				 exif.fRecommendedExposureIndex,
				 exif.fRecommendedExposureIndex == 0,
				 readOnly + removable);
		
	// ISOSpeed:

	Sync_uint32 (XMP_NS_EXIF,
				 "ISOSpeed",
				 exif.fISOSpeed,
				 exif.fISOSpeed == 0,
				 readOnly + removable);
		
	// ISOSpeedLatitudeyyy:

	Sync_uint32 (XMP_NS_EXIF,
				 "ISOSpeedLatitudeyyy",
				 exif.fISOSpeedLatitudeyyy,
				 exif.fISOSpeedLatitudeyyy == 0,
				 readOnly + removable);
		
	// ISOSpeedLatitudezzz:

	Sync_uint32 (XMP_NS_EXIF,
				 "ISOSpeedLatitudezzz",
				 exif.fISOSpeedLatitudezzz,
				 exif.fISOSpeedLatitudezzz == 0,
				 readOnly + removable);
		
	// ExposureIndex:
	
	Sync_urational (XMP_NS_EXIF,
				    "ExposureIndex",
				    exif.fExposureIndex,
				    readOnly + removable);
				    
	// Brightness Value:
	
	Sync_srational (XMP_NS_EXIF,
				    "BrightnessValue",
				    exif.fBrightnessValue,
				    readOnly + removable);
				    
	// Exposure Bias:
	
	Sync_srational (XMP_NS_EXIF,
				    "ExposureBiasValue",
				    exif.fExposureBiasValue,
				    readOnly + removable);
				    
	// Max Aperture:
	
	Sync_urational (XMP_NS_EXIF,
				    "MaxApertureValue",
				    exif.fMaxApertureValue,
				    readOnly + removable);

	// Subject Distance:
	
	Sync_urational (XMP_NS_EXIF,
				    "SubjectDistance",
				    exif.fSubjectDistance,
				    readOnly + removable);
	
	// Metering Mode:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "MeteringMode",
				 exif.fMeteringMode,
				 exif.fMeteringMode == 0xFFFFFFFF,
				 readOnly + removable);
	
	// Light Source:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "LightSource",
				 exif.fLightSource,
				 exif.fLightSource > 0x0FFFF,
				 readOnly + removable);
	
	// Flash State:
	
	SyncFlash (exif.fFlash,
			   exif.fFlashMask,
			   readOnly);
			   
	if (removeFromXMP)
		{
		Remove (XMP_NS_EXIF, "Flash");
		}

	// Focal Length:
	
	Sync_urational (XMP_NS_EXIF,
					"FocalLength",
					exif.fFocalLength,
					readOnly + removable);
	
	// Sensing Method.
	
	Sync_uint32 (XMP_NS_EXIF,
				 "SensingMethod",
				 exif.fSensingMethod,
				 exif.fSensingMethod > 0x0FFFF,
				 readOnly + removable);
	
	// File Source.
	
	Sync_uint32 (XMP_NS_EXIF,
				 "FileSource",
				 exif.fFileSource,
				 exif.fFileSource > 0x0FF,
				 readOnly + removable);
	
	// Scene Type.
	
	Sync_uint32 (XMP_NS_EXIF,
				 "SceneType",
				 exif.fSceneType,
				 exif.fSceneType > 0x0FF,
				 readOnly + removable);
	
	// Focal Length in 35mm Film:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "FocalLengthIn35mmFilm",
				 exif.fFocalLengthIn35mmFilm,
				 exif.fFocalLengthIn35mmFilm == 0,
				 readOnly + removable);
	
	// Custom Rendered:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "CustomRendered",
				 exif.fCustomRendered,
				 exif.fCustomRendered > 0x0FFFF,
				 readOnly + removable);
	
	// Exposure Mode:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "ExposureMode",
				 exif.fExposureMode,
				 exif.fExposureMode > 0x0FFFF,
				 readOnly + removable);
	
	// White Balance:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "WhiteBalance",
				 exif.fWhiteBalance,
				 exif.fWhiteBalance > 0x0FFFF,
				 readOnly + removable);
	
	// Scene Capture Type:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "SceneCaptureType",
				 exif.fSceneCaptureType,
				 exif.fSceneCaptureType > 0x0FFFF,
				 readOnly + removable);
	
	// Gain Control:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "GainControl",
				 exif.fGainControl,
				 exif.fGainControl > 0x0FFFF,
				 readOnly + removable);
	
	// Contrast:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "Contrast",
				 exif.fContrast,
				 exif.fContrast > 0x0FFFF,
				 readOnly + removable);
	
	// Saturation:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "Saturation",
				 exif.fSaturation,
				 exif.fSaturation > 0x0FFFF,
				 readOnly + removable);
	
	// Sharpness:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "Sharpness",
				 exif.fSharpness,
				 exif.fSharpness > 0x0FFFF,
				 readOnly + removable);
	
	// Subject Distance Range:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "SubjectDistanceRange",
				 exif.fSubjectDistanceRange,
				 exif.fSubjectDistanceRange > 0x0FFFF,
				 readOnly + removable);
		
	// Subject Area:
	
	Sync_uint32_array (XMP_NS_EXIF,
					   "SubjectArea",
					   exif.fSubjectArea,
					   exif.fSubjectAreaCount,
					   sizeof (exif.fSubjectArea    ) /
					   sizeof (exif.fSubjectArea [0]),
					   readOnly);
					   
	if (removeFromXMP)
		{
		Remove (XMP_NS_EXIF, "SubjectArea");
		}
		
	// Digital Zoom Ratio:
	
	Sync_urational (XMP_NS_EXIF,
					"DigitalZoomRatio",
					exif.fDigitalZoomRatio,
					readOnly + removable);
	
	// Focal Plane Resolution:
	
	Sync_urational (XMP_NS_EXIF,
					"FocalPlaneXResolution",
					exif.fFocalPlaneXResolution,
					readOnly + removable);
	
	Sync_urational (XMP_NS_EXIF,
					"FocalPlaneYResolution",
					exif.fFocalPlaneYResolution,
					readOnly + removable);
	
	Sync_uint32 (XMP_NS_EXIF,
				 "FocalPlaneResolutionUnit",
				 exif.fFocalPlaneResolutionUnit,
				 exif.fFocalPlaneResolutionUnit > 0x0FFFF,
				 readOnly + removable);
		
	// ImageDescription:  (XMP is is always preferred)
	
	if (fSDK->GetAltLangDefault (XMP_NS_DC,
								 "description",
								 exif.fImageDescription))
		
		{
		
		}
		
	else if (doingUpdateFromXMP)
		{
		
		exif.fImageDescription.Clear ();
		
		if (originalExif->fImageDescription.NotEmpty ())
			{
			
			fSDK->SetAltLangDefault (XMP_NS_DC,
									 "description",
									 dng_string ());
									 
			}
		
		}
	
	else if (exif.fImageDescription.NotEmpty ())
		{
		
		fSDK->SetAltLangDefault (XMP_NS_DC,
								 "description",
								 exif.fImageDescription);
									 
		}
		
	// Artist:  (XMP is is always preferred)
	
		{
		
		dng_string_list xmpList;
		
		if (fSDK->GetStringList (XMP_NS_DC,
								 "creator",
								 xmpList))
			{
			
			exif.fArtist.Clear ();
							
			if (xmpList.Count () > 0)
				{
				
				uint32 j;
				
				uint32 bufferSize = xmpList.Count () * 4 + 1;
				
				for (j = 0; j < xmpList.Count (); j++)
					{
					
					bufferSize += xmpList [j].Length () * 2;
					
					}
					
				dng_memory_data temp (bufferSize);
				
				char *t = temp.Buffer_char ();
				
				for (j = 0; j < xmpList.Count (); j++)
					{
					
					const char *s = xmpList [j].Get ();
					
					bool needQuotes = xmpList [j].Contains ("; ") ||
									  s [0] == '\"';
									   
					if (needQuotes)
						{
						*(t++) = '\"';
						}
						
					while (s [0] != 0)
						{
						
						if (s [0] == '\"' && needQuotes)
							{
							*(t++) = '\"';
							}
							
						*(t++) = *(s++);
						
						}
						
					if (needQuotes)
						{
						*(t++) = '\"';
						}
						
					if (j != xmpList.Count () - 1)
						{
						*(t++) = ';';
						*(t++) = ' ';
						}
					else
						{
						*t = 0;
						}

					}
				
				exif.fArtist.Set (temp.Buffer_char ());
				
				}

			}
			
		else if (doingUpdateFromXMP)
			{
			
			exif.fArtist.Clear ();
			
			if (originalExif->fArtist.NotEmpty ())
				{
				
				dng_string_list fakeList;
				
				fakeList.Append (dng_string ());
				
				SetStringList (XMP_NS_DC,
							   "creator",
							   fakeList,
							   false);
										 
				}
			
			}
			
		else if (exif.fArtist.NotEmpty ())
			{
			
			dng_string_list newList;
		
			dng_memory_data temp (exif.fArtist.Length () + 1);
					
			const char *s = exif.fArtist.Get ();
			
			char *t = temp.Buffer_char ();
			
			bool first = true;
			
			bool quoted = false;
			
			bool valid = true;
				
			while (s [0] != 0 && valid)
				{
				
				if (first)
					{
					
					if (s [0] == '\"')
						{
						
						quoted = true;
						
						s++;
						
						}
					
					}
					
				first = false;
				
				if (quoted)
					{
					
					if (s [0] == '\"' &&
						s [1] == '\"')
						{
						
						s+= 2;
						
						*(t++) = '\"';
						
						}
							
					else if (s [0] == '\"')
						{
						
						s++;
						
						quoted = false;
						
						valid = valid && ((s [0] == 0) || ((s [0] == ';' && s [1] == ' ')));
				
						}
						
					else
						{
						
						*(t++) = *(s++);
						
						}
							
					}
						
				else if (s [0] == ';' &&
						 s [1] == ' ')
					{
					
					s += 2;
					
					t [0] = 0;
						
					dng_string ss;
					
					ss.Set (temp.Buffer_char ());
					
					newList.Append (ss);
					
					t = temp.Buffer_char ();
					
					first = true;
						
					}
					
				else
					{
					
					*(t++) = *(s++);
						
					}
					
				}
				
			if (quoted)
				{
				
				valid = false;
				
				}
				
			if (valid)
				{
				
				if (t != temp.Buffer_char ())
					{
					
					t [0] = 0;
						
					dng_string ss;
					
					ss.Set (temp.Buffer_char ());
					
					newList.Append (ss);
					
					}
					
				}
				
			else
				{
				
				newList.Clear ();
				
				newList.Append (exif.fArtist);
				
				}
				
			SetStringList (XMP_NS_DC,
						   "creator",
						   newList,
						   false);
										 
			}
			
		}
		
	// Software:  (XMP is is always preferred)
	
	if (fSDK->GetString (XMP_NS_XAP,
						 "CreatorTool",
						 exif.fSoftware))
		
		{
		
		}
		
	else if (doingUpdateFromXMP)
		{
		
		exif.fSoftware.Clear ();
		
		if (originalExif->fSoftware.NotEmpty ())
			{
			
			fSDK->SetString (XMP_NS_XAP,
							 "CreatorTool",
							 dng_string ());
									 
			}
		
		}
	
	else if (exif.fSoftware.NotEmpty ())
		{
		
		fSDK->SetString (XMP_NS_XAP,
						 "CreatorTool",
						 exif.fSoftware);
									 
		}
		
	// Copyright:  (XMP is is always preferred)
	
	if (fSDK->GetAltLangDefault (XMP_NS_DC,
								 "rights",
								 exif.fCopyright))
		
		{
		
		}
	
	else if (doingUpdateFromXMP)
		{
		
		exif.fCopyright.Clear ();
		
		if (originalExif->fCopyright.NotEmpty ())
			{
			
			fSDK->SetAltLangDefault (XMP_NS_DC,
									 "rights",
									 dng_string ());
									 
			}
		
		}
	
	else if (exif.fCopyright.NotEmpty ())
		{
		
		fSDK->SetAltLangDefault (XMP_NS_DC,
								 "rights",
								 exif.fCopyright);
				
		}
					
	// Camera serial number private tag:

	SyncString (XMP_NS_AUX,
				"SerialNumber",
				exif.fCameraSerialNumber,
				readOnly);
			
	// Lens Info:
	
		{
		
		dng_string s;
		
		if (exif.fLensInfo [0].IsValid ())
			{
			
			char ss [256];
			
			sprintf (ss,
					 "%u/%u %u/%u %u/%u %u/%u",
					 (unsigned) exif.fLensInfo [0].n,
					 (unsigned) exif.fLensInfo [0].d,
					 (unsigned) exif.fLensInfo [1].n,
					 (unsigned) exif.fLensInfo [1].d,
					 (unsigned) exif.fLensInfo [2].n,
					 (unsigned) exif.fLensInfo [2].d,
					 (unsigned) exif.fLensInfo [3].n,
					 (unsigned) exif.fLensInfo [3].d);
					 
			s.Set (ss);
			
			}
			
		SyncString (XMP_NS_AUX,
					"LensInfo",
				    s,
				    readOnly);
				    
		if (s.NotEmpty ())
			{
			
			unsigned n [4];
			unsigned d [4];
			
			if (sscanf (s.Get (),
						"%u/%u %u/%u %u/%u %u/%u",
						&n [0],
						&d [0],
						&n [1],
						&d [1],
						&n [2],
						&d [2],
						&n [3],
						&d [3]) == 8)
				{
				
				for (uint32 j = 0; j < 4; j++)
					{
					
					exif.fLensInfo [j] = dng_urational (n [j], d [j]);
					
					}
				
				}
						
			
			}

		}
		
	// Lens name:
	
		{
		
		// EXIF lens names are sometimes missing or wrong (esp. when non-OEM lenses
		// are used). So prefer the value from XMP.
		
		SyncString (XMP_NS_AUX,
					"Lens",
					exif.fLensName,
					preferXMP);

		// Generate default lens name from lens info if required.
		// Ignore names names that end in "f/0.0" due to third party bug.
		
		if ((exif.fLensName.IsEmpty () ||
			 exif.fLensName.EndsWith ("f/0.0")) && exif.fLensInfo [0].IsValid ())
			{
			
			char s [256];
							
			real64 minFL = exif.fLensInfo [0].As_real64 ();
			real64 maxFL = exif.fLensInfo [1].As_real64 ();
			
			// The f-stop numbers are optional.
			
			if (exif.fLensInfo [2].IsValid ())
				{
				
				real64 minFS = exif.fLensInfo [2].As_real64 ();
				real64 maxFS = exif.fLensInfo [3].As_real64 ();
				
				if (minFL == maxFL)
					sprintf (s, "%.1f mm f/%.1f", minFL, minFS);
					
				else if (minFS == maxFS)
					sprintf (s, "%.1f-%.1f mm f/%.1f", minFL, maxFL, minFS);
					
				else
					sprintf (s, "%.1f-%.1f mm f/%.1f-%.1f", minFL, maxFL, minFS, maxFS);
				
				}
				
			else
				{
				
				if (minFL == maxFL)
					sprintf (s, "%.1f mm", minFL);
					
				else
					sprintf (s, "%.1f-%.1f mm", minFL, maxFL);
				
				}
				
			exif.fLensName.Set (s);
				
			SetString (XMP_NS_AUX,
					   "Lens",
					   exif.fLensName);
			
			}

		}

	// Lens ID:
	
	SyncString (XMP_NS_AUX,
				"LensID",
				exif.fLensID,
				readOnly);
	
	// Lens Make:
	
	SyncString (XMP_NS_EXIF,
				"LensMake",
				exif.fLensMake,
				readOnly + removable);
		
	// Lens Serial Number:
	
	SyncString (XMP_NS_AUX,
				"LensSerialNumber",
				exif.fLensSerialNumber,
				readOnly);
		
	// Image Number:
	
	Sync_uint32 (XMP_NS_AUX, 
				 "ImageNumber",
				 exif.fImageNumber,
				 exif.fImageNumber == 0xFFFFFFFF,
				 readOnly);
		
	// User Comment:
	
	if (exif.fUserComment.NotEmpty ())
		{

		fSDK->SetAltLangDefault (XMP_NS_EXIF,
								 "UserComment",
								 exif.fUserComment);
		
		}
			
	else
		{
	
		(void) fSDK->GetAltLangDefault (XMP_NS_EXIF,
									  	"UserComment",
									  	exif.fUserComment);

		}
		
	if (removeFromXMP)
		{
		Remove (XMP_NS_EXIF, "UserComment");
		}
		
	// Approximate focus distance:

	SyncApproximateFocusDistance (exif,
								  readOnly);
	
	// Flash Compensation:
	
	Sync_srational (XMP_NS_AUX,
				    "FlashCompensation",
				    exif.fFlashCompensation,
				    readOnly);

	// Owner Name: (allow XMP updates)
	
	SyncString (XMP_NS_AUX,
				"OwnerName",
				exif.fOwnerName,
				preferXMP);
		
	// Firmware:
	
	SyncString (XMP_NS_AUX,
				"Firmware",
				exif.fFirmware,
				readOnly);
		
	// Image Unique ID:
	
		{
		
		dng_string s = EncodeFingerprint (exif.fImageUniqueID);
		
		SyncString (XMP_NS_EXIF,
				    "ImageUniqueID",
				    s,
				    readOnly + removable);
				    
		exif.fImageUniqueID = DecodeFingerprint (s);
		
		}
		
	// Allow EXIF GPS to be updated via updates from XMP.
	
	if (doingUpdateFromXMP)
		{
		
		// Require that at least one basic GPS field exist in the
		// XMP before overrriding the EXIF GPS fields.
		
		if (Exists (XMP_NS_EXIF, "GPSVersionID"       ) ||
			Exists (XMP_NS_EXIF, "GPSLatitude"        ) ||
			Exists (XMP_NS_EXIF, "GPSLongitude"       ) ||
			Exists (XMP_NS_EXIF, "GPSAltitude"        ) ||
			Exists (XMP_NS_EXIF, "GPSTimeStamp"       ) ||
			Exists (XMP_NS_EXIF, "GPSProcessingMethod"))
			{
			
			// Clear out the GPS info from the EXIF so it will
			// replaced by the GPS info from the XMP.
			
			dng_exif blankExif;
			
			exif.CopyGPSFrom (blankExif);
			
			}
		
		}
			
	// GPS Version ID:
	
		{
		
		dng_string s = EncodeGPSVersion (exif.fGPSVersionID);
		
		if (SyncString (XMP_NS_EXIF,
						"GPSVersionID",
						s,
						preferNonXMP + removable))
			{
					
			exif.fGPSVersionID = DecodeGPSVersion (s);
			
			}
	
		}
		
	// GPS Latitude:
	
		{
		
		dng_string s = EncodeGPSCoordinate (exif.fGPSLatitudeRef,
					  						exif.fGPSLatitude);
					  						
		if (SyncString (XMP_NS_EXIF,
						"GPSLatitude",
						s,
						preferNonXMP + removable))
			{
			
			DecodeGPSCoordinate (s,
								 exif.fGPSLatitudeRef,
								 exif.fGPSLatitude);
			
			}
					
		}
					  
	// GPS Longitude:
	
		{
	
		dng_string s = EncodeGPSCoordinate (exif.fGPSLongitudeRef,
					  						exif.fGPSLongitude);
					  						
		if (SyncString (XMP_NS_EXIF,
						"GPSLongitude",
						s,
						preferNonXMP + removable))
			{
			
			DecodeGPSCoordinate (s,
								 exif.fGPSLongitudeRef,
								 exif.fGPSLongitude);
			
			}
					
		}
					
	// Handle simple case of incorrectly written GPS altitude where someone didn't understand the GPSAltitudeRef and assumed the GPSAltitude RATIONAL is signed.
	// Only handle this case as we do not want to misinterpret e.g. a fixed point representation of very high GPS altitudes.
	
	uint32 &altitudeRef = exif.fGPSAltitudeRef;
	dng_urational &altitude = exif.fGPSAltitude;

	if (altitude.IsValid () &&
		(altitudeRef == 0 || altitudeRef == 0xFFFFFFFF))  // If the file contains a "below sea level" altitudeRef, assume the writing software is working according to the spec.
		{

		if ((altitude.n & (1U << 31)) &&
			altitude.d < 7) // As the denominator increases, large numerator values become possibly valid distances. Pick a limit on the conservative side (approx 33e6m) to prevent misinterpretation.
							// Noting that the normal case for this mistake has a denominator of 1
			{

			altitude.n = ~altitude.n + 1;
			altitudeRef = 1;

			}

		}

	// GPS Altitude Reference:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "GPSAltitudeRef",
				 altitudeRef,
				 altitudeRef == 0xFFFFFFFF,
				 preferNonXMP + removable);
	
	// GPS Altitude:
	
	Sync_urational (XMP_NS_EXIF,
					"GPSAltitude",
					altitude,
					preferNonXMP + removable);
	
	// GPS Date/Time:
	
		{
		
		dng_string s = EncodeGPSDateTime (exif.fGPSDateStamp,
										  exif.fGPSTimeStamp);
										  
		if (SyncString (XMP_NS_EXIF,
						"GPSTimeStamp",
						s,
						preferNonXMP + removable))
			{
			
			DecodeGPSDateTime (s,
							   exif.fGPSDateStamp,
							   exif.fGPSTimeStamp);
			
			}
	
		}
		
	// GPS Satellites:
	
	SyncString (XMP_NS_EXIF,
				"GPSSatellites",
				exif.fGPSSatellites,
				preferNonXMP + removable);
	
	// GPS Status:

	SyncString (XMP_NS_EXIF,
				"GPSStatus",
				exif.fGPSStatus,
				preferNonXMP + removable);
	
	// GPS Measure Mode:
	
	SyncString (XMP_NS_EXIF,
				"GPSMeasureMode",
				exif.fGPSMeasureMode,
				preferNonXMP + removable);
	
	// GPS DOP:
	
	Sync_urational (XMP_NS_EXIF,
					"GPSDOP",
					exif.fGPSDOP,
					preferNonXMP + removable);
		
	// GPS Speed Reference:
	
	SyncString (XMP_NS_EXIF,
				"GPSSpeedRef",
				exif.fGPSSpeedRef,
				preferNonXMP + removable);
	
	// GPS Speed:
		
	Sync_urational (XMP_NS_EXIF,
					"GPSSpeed",
					exif.fGPSSpeed,
					preferNonXMP + removable);
		
	// GPS Track Reference:
	
	SyncString (XMP_NS_EXIF,
				"GPSTrackRef",
				exif.fGPSTrackRef,
				preferNonXMP + removable);
	
	// GPS Track:
		
	Sync_urational (XMP_NS_EXIF,
					"GPSTrack",
					exif.fGPSTrack,
					preferNonXMP + removable);
		
	// GPS Image Direction Reference:
	
	SyncString (XMP_NS_EXIF,
				"GPSImgDirectionRef",
				exif.fGPSImgDirectionRef,
				preferNonXMP + removable);
	
	// GPS Image Direction:
		
	Sync_urational (XMP_NS_EXIF,
					"GPSImgDirection",
					exif.fGPSImgDirection,
					preferNonXMP + removable);
		
	// GPS Map Datum:
	
	SyncString (XMP_NS_EXIF,
				"GPSMapDatum",
				exif.fGPSMapDatum,
				preferNonXMP + removable);
	
	// GPS Destination Latitude:
	
		{
		
		dng_string s = EncodeGPSCoordinate (exif.fGPSDestLatitudeRef,
					  						exif.fGPSDestLatitude);
					  						
		if (SyncString (XMP_NS_EXIF,
						"GPSDestLatitude",
						s,
						preferNonXMP + removable))
			{
			
			DecodeGPSCoordinate (s,
								 exif.fGPSDestLatitudeRef,
								 exif.fGPSDestLatitude);
			
			}
					
		}

	// GPS Destination Longitude:
	
		{
		
		dng_string s = EncodeGPSCoordinate (exif.fGPSDestLongitudeRef,
					  						exif.fGPSDestLongitude);
					  						
		if (SyncString (XMP_NS_EXIF,
						"GPSDestLongitude",
						s,
						preferNonXMP + removable))
			{
			
			DecodeGPSCoordinate (s,
								 exif.fGPSDestLongitudeRef,
								 exif.fGPSDestLongitude);
			
			}
					
		}

	// GPS Destination Bearing Reference:
	
	SyncString (XMP_NS_EXIF,
				"GPSDestBearingRef",
				exif.fGPSDestBearingRef,
				preferNonXMP + removable);
	
	// GPS Destination Bearing:
		
	Sync_urational (XMP_NS_EXIF,
					"GPSDestBearing",
					exif.fGPSDestBearing,
					preferNonXMP + removable);
		
	// GPS Destination Distance Reference:
	
	SyncString (XMP_NS_EXIF,
				"GPSDestDistanceRef",
				exif.fGPSDestDistanceRef,
				preferNonXMP + removable);
	
	// GPS Destination Distance:
		
	Sync_urational (XMP_NS_EXIF,
					"GPSDestDistance",
					exif.fGPSDestDistance,
					preferNonXMP + removable);
		
	// GPS Processing Method:
	
	SyncString (XMP_NS_EXIF,
				"GPSProcessingMethod",
				exif.fGPSProcessingMethod,
				preferNonXMP + removable);
	
	// GPS Area Information:
	
	SyncString (XMP_NS_EXIF,
				"GPSAreaInformation",
				exif.fGPSAreaInformation,
				preferNonXMP + removable);
	
	// GPS Differential:
	
	Sync_uint32 (XMP_NS_EXIF,
				 "GPSDifferential",
				 exif.fGPSDifferential,
				 exif.fGPSDifferential == 0xFFFFFFFF,
				 preferNonXMP + removable);
				 
	// GPS Horizontal Positioning Error:
	
	Sync_urational (XMP_NS_EXIF,
					"GPSHPositioningError",
					exif.fGPSHPositioningError,
					preferNonXMP + removable);
					
	// Sync date/times.
		
	UpdateExifDates (exif, removeFromXMP);
		
	// We are syncing EXIF and XMP, but we are not updating the
	// NativeDigest tags.  It is better to just delete them than leave
	// the stale values around.
	
	Remove (XMP_NS_EXIF, "NativeDigest");
	Remove (XMP_NS_TIFF, "NativeDigest");
	
	}

/*****************************************************************************/

void dng_xmp::SyncApproximateFocusDistance (dng_exif &exif,
											const uint32 readOnly)
	{
	
	Sync_urational (XMP_NS_AUX,
					"ApproximateFocusDistance",
					exif.fApproxFocusDistance,
					readOnly);	
	
	}
							   
/******************************************************************************/
	
void dng_xmp::ValidateStringList (const char *ns,
							      const char *path)
	{
	
	fSDK->ValidateStringList (ns, path);

	}
							   
/******************************************************************************/
	
void dng_xmp::ValidateMetadata ()
	{
	
	// The following values should be arrays, but are not always.  So
	// fix them up because Photoshop sometimes has problems parsing invalid
	// tags.
	
	ValidateStringList (XMP_NS_DC, "creator");
							   
	ValidateStringList (XMP_NS_PHOTOSHOP, "Keywords");
	ValidateStringList (XMP_NS_PHOTOSHOP, "SupplementalCategories");
	
	}
		
/******************************************************************************/
	
bool dng_xmp::DateTimeIsDateOnly (const char *ns,
							      const char *path)
	{
	
	dng_string s;
	
	if (GetString (ns, path, s))
		{
		
		uint32 len = s.Length ();
		
		if (len)
			{
		
			for (uint32 j = 0; j < len; j++)
				{
				
				if (s.Get () [j] == 'T')
					{
					
					return false;
					
					}
					
				}
				
			return true;
			
			}
		
		}
	
	return false;
	
	}

/******************************************************************************/
	
void dng_xmp::UpdateExifDates (dng_exif &exif,
							   bool removeFromXMP)
	{
	
	// For the following three date/time fields, we always prefer XMP to
	// the EXIF values.  This is to allow the user to correct the date/times
	// via changes in a sidecar XMP file, without modifying the original
	// raw file.
	
	// Kludge: The Nikon D4 is writing date only date/times into XMP, so
	// prefer the EXIF values if the XMP only contains a date.
	
	// Modification Date/Time:
	// exif.fDateTime
	// kXMP_NS_XMP:"ModifyDate" & kXMP_NS_TIFF:"DateTime" are aliased
		
		{
			
		dng_string s = exif.fDateTime.Encode_ISO_8601 ();
		
		bool dateOnly = DateTimeIsDateOnly (XMP_NS_TIFF, "DateTime");
		
		SyncString (XMP_NS_TIFF,
					"DateTime",
					s,
					dateOnly ? preferNonXMP : preferXMP);
		
		if (s.NotEmpty ())
			{
			
			exif.fDateTime.Decode_ISO_8601 (s.Get ());
			
			// Round trip again in case we need to add a fake time zone.
			
			s = exif.fDateTime.Encode_ISO_8601 ();
			
			SetString (XMP_NS_TIFF,
					   "DateTime",
					   s);
			
			}
			
		}
		
	// Original Date/Time:
	// exif.fDateTimeOriginal
	// IPTC: DateCreated
	// XMP_NS_EXIF:"DateTimeOriginal" & XMP_NS_PHOTOSHOP:"DateCreated"
	// Adobe has decided to keep the two XMP fields separate.
	
		{
			
		dng_string s = exif.fDateTimeOriginal.Encode_ISO_8601 ();
		
		bool dateOnly = DateTimeIsDateOnly (XMP_NS_EXIF, "DateTimeOriginal");
		
		SyncString (XMP_NS_EXIF,
					"DateTimeOriginal",
					s,
					dateOnly ? preferNonXMP : preferXMP);
		
		if (s.NotEmpty ())
			{
			
			exif.fDateTimeOriginal.Decode_ISO_8601 (s.Get ());
			
			// Round trip again in case we need to add a fake time zone.
			
			s = exif.fDateTimeOriginal.Encode_ISO_8601 ();
			
			SetString (XMP_NS_EXIF,
					   "DateTimeOriginal",
					   s);
			
			}
			
		// Sync the IPTC value to the EXIF value if only the EXIF
		// value exists.
		
		if (s.NotEmpty () && !Exists (XMP_NS_PHOTOSHOP, "DateCreated"))
			{
			
			SetString (XMP_NS_PHOTOSHOP, "DateCreated", s);
			
			}
			
		if (removeFromXMP)
			{
			
			Remove (XMP_NS_EXIF, "DateTimeOriginal");
			
			}
			
		}
		
	// Date Time Digitized:
	// XMP_NS_EXIF:"DateTimeDigitized" & kXMP_NS_XMP:"CreateDate" are aliased
	
		{
			
		dng_string s = exif.fDateTimeDigitized.Encode_ISO_8601 ();
		
		bool dateOnly = DateTimeIsDateOnly (XMP_NS_EXIF, "DateTimeDigitized");
		
		SyncString (XMP_NS_EXIF,
					"DateTimeDigitized",
					s,
					dateOnly ? preferNonXMP : preferXMP);
			
		if (s.NotEmpty ())
			{
				
			exif.fDateTimeDigitized.Decode_ISO_8601 (s.Get ());
							
			// Round trip again in case we need to add a fake time zone.
			
			s = exif.fDateTimeDigitized.Encode_ISO_8601 ();
			
			SetString (XMP_NS_EXIF,
					   "DateTimeDigitized",
					   s);
			
			}
			
		}
		
	}

/******************************************************************************/

void dng_xmp::UpdateDateTime (const dng_date_time_info &dt)
	{
	
	dng_string s = dt.Encode_ISO_8601 ();
								   
	SetString (XMP_NS_TIFF,
			   "DateTime",
			   s);
					
	}

/******************************************************************************/

void dng_xmp::UpdateMetadataDate (const dng_date_time_info &dt)
	{
	
	dng_string s = dt.Encode_ISO_8601 ();
								   
	SetString (XMP_NS_XAP,
			   "MetadataDate",
			   s);
					
	}

/*****************************************************************************/

bool dng_xmp::HasOrientation () const
	{
	
	uint32 x = 0;
	
	if (Get_uint32 (XMP_NS_TIFF,
					"Orientation",
					x))
		{
		
		return (x >= 1) && (x <= 8);
		
		}
		
	return false;
		
	}
		
/*****************************************************************************/

dng_orientation dng_xmp::GetOrientation () const
	{
	
	dng_orientation result;
	
	uint32 x = 0;
	
	if (Get_uint32 (XMP_NS_TIFF,
					"Orientation",
					x))
		{
		
		if ((x >= 1) && (x <= 8))
			{
			
			result.SetTIFF (x);
			
			}
						
		}
	
	return result;
	
	}
		
/******************************************************************************/

void dng_xmp::ClearOrientation ()
	{
	
	fSDK->Remove (XMP_NS_TIFF, "Orientation");
	
	}
				  
/******************************************************************************/

void dng_xmp::SetOrientation (const dng_orientation &orientation)
	{
	
	Set_uint32 (XMP_NS_TIFF,
			    "Orientation",
				orientation.GetTIFF ());
		
	}
		
/*****************************************************************************/

void dng_xmp::SyncOrientation (dng_negative &negative,
					   		   bool xmpIsMaster)
	{
	
	SyncOrientation (negative.Metadata (), xmpIsMaster);
	
	}
	
/*****************************************************************************/

void dng_xmp::SyncOrientation (dng_metadata &metadata,
					   		   bool xmpIsMaster)
	{
			
	// See if XMP contains the orientation.
	
	bool xmpHasOrientation = HasOrientation ();

	// See if XMP is the master value.
	
	if (xmpHasOrientation && (xmpIsMaster || !metadata.HasBaseOrientation ()))
		{
		
		metadata.SetBaseOrientation (GetOrientation ());
		
		}
		
	else
		{
		
		SetOrientation (metadata.BaseOrientation ());
		
		}

	}
	
/******************************************************************************/

void dng_xmp::ClearImageInfo ()
	{
	
	Remove (XMP_NS_TIFF, "ImageWidth" );
	Remove (XMP_NS_TIFF, "ImageLength");
	
	Remove (XMP_NS_EXIF, "PixelXDimension");
	Remove (XMP_NS_EXIF, "PixelYDimension");
		
	Remove (XMP_NS_TIFF, "BitsPerSample");
	
	Remove (XMP_NS_TIFF, "Compression");
		
	Remove (XMP_NS_TIFF, "PhotometricInterpretation");
	
	// "Orientation" is handled separately.
	
	Remove (XMP_NS_TIFF, "SamplesPerPixel");
	
	Remove (XMP_NS_TIFF, "PlanarConfiguration");
	
	Remove (XMP_NS_TIFF, "XResolution");
	Remove (XMP_NS_TIFF, "YResolution");
		
	Remove (XMP_NS_TIFF, "ResolutionUnit");
	
	Remove (XMP_NS_PHOTOSHOP, "ColorMode" );
	Remove (XMP_NS_PHOTOSHOP, "ICCProfile");
	
	}
	
/******************************************************************************/

void dng_xmp::SetImageSize (const dng_point &size)
	{
	
	Set_uint32 (XMP_NS_TIFF, "ImageWidth" , size.h);
	Set_uint32 (XMP_NS_TIFF, "ImageLength", size.v);
	
	// Mirror these values to the EXIF tags.
	
	Set_uint32 (XMP_NS_EXIF, "PixelXDimension" , size.h);
	Set_uint32 (XMP_NS_EXIF, "PixelYDimension" , size.v);
	
	}
	
/******************************************************************************/

void dng_xmp::SetSampleInfo (uint32 samplesPerPixel,
							 uint32 bitsPerSample)
	{
	
	Set_uint32 (XMP_NS_TIFF, "SamplesPerPixel", samplesPerPixel);
	
	char s [32];
	
	sprintf (s, "%u", (unsigned) bitsPerSample);
	
	dng_string ss;
	
	ss.Set (s);
	
	dng_string_list list;
	
	for (uint32 j = 0; j < samplesPerPixel; j++)
		{
		list.Append (ss);
		}
	
	SetStringList (XMP_NS_TIFF, "BitsPerSample", list, false);
	
	}
	
/******************************************************************************/

void dng_xmp::SetPhotometricInterpretation (uint32 pi)
	{
	
	Set_uint32 (XMP_NS_TIFF, "PhotometricInterpretation", pi);
	
	}
		
/******************************************************************************/

void dng_xmp::SetResolution (const dng_resolution &res)
	{
	
 	Set_urational (XMP_NS_TIFF, "XResolution", res.fXResolution);
	Set_urational (XMP_NS_TIFF, "YResolution", res.fYResolution);
		
    Set_uint32 (XMP_NS_TIFF, "ResolutionUnit", res.fResolutionUnit);
	
	}

/*****************************************************************************/

void dng_xmp::ComposeArrayItemPath (const char *ns,
									const char *arrayName,
									int32 itemNumber,
									dng_string &s) const
	{
	
	fSDK->ComposeArrayItemPath (ns, arrayName, itemNumber, s);

	}
		
/*****************************************************************************/

void dng_xmp::ComposeStructFieldPath (const char *ns,
									  const char *structName,
									  const char *fieldNS,
									  const char *fieldName,
									  dng_string &s) const
	{
	
	fSDK->ComposeStructFieldPath (ns, structName, fieldNS, fieldName, s);

	}

/*****************************************************************************/

int32 dng_xmp::CountArrayItems (const char *ns,
							    const char *path) const
	{
	
	return fSDK->CountArrayItems (ns, path);

	}

/*****************************************************************************/

void dng_xmp::AppendArrayItem (const char *ns,
							   const char *arrayName,
							   const char *itemValue,
							   bool isBag,
							   bool propIsStruct)
	{

	fSDK->AppendArrayItem (ns,
						   arrayName,
						   itemValue,
						   isBag,
						   propIsStruct);
	}

/*****************************************************************************/

#if qDNGXMPDocOps

/*****************************************************************************/

void dng_xmp::DocOpsOpenXMP (const char *srcMIMI)
	{
	
	fSDK->DocOpsOpenXMP (srcMIMI);
	
	}

/*****************************************************************************/

void dng_xmp::DocOpsPrepareForSave (const char *srcMIMI,
									const char *dstMIMI,
									bool newPath)
	{
	
	fSDK->DocOpsPrepareForSave (srcMIMI,
								dstMIMI,
								newPath);
	
	}

/*****************************************************************************/

void dng_xmp::DocOpsUpdateMetadata (const char *srcMIMI)
	{
	
	fSDK->DocOpsUpdateMetadata (srcMIMI);
	
	}

/*****************************************************************************/

#endif

#endif
/*****************************************************************************/