   <!-- Special treatment of PHOTO data: keep a local file if it has the
        same content as the binary data in the winning item.
        Use in combination with a PHOTO field of type="string" (not blob,
        because we need to be able to compare the field in the MERGEFIELDS()
        call to detect when the data really changes) and compare="conflict"
        (not used to find matches, changes are relevant).
   -->
   <macro name="VCARD_MERGESCRIPT"><![CDATA[
     integer mode;
     mode = MERGEMODE();

     INTEGER winningchanged;
     INTEGER loosingchanged;
     winningchanged = 0;
     loosingchanged = 0;

     STRING ignorefields;
     ignorefields = "";

     if (SESSIONVAR("keepPhotoData") &&
         WINNING.PHOTO == EMPTY &&
         LOOSING.PHOTO != EMPTY) {
       // The client's item is always the "winning" one in PBAP syncing,
       // but it might not have the photo data that is already on the
       // server. Therefore keep the server's photo, if there is
       // one and we are not in the phase where the client sends
       // its photos.
       WINNING.PHOTO = LOOSING.PHOTO;
       WINNING.PHOTO_TYPE = LOOSING.PHOTO_TYPE;
       WINNING.PHOTO_VALUE = LOOSING.PHOTO_VALUE;
       winningchanged = 1;
     } else if (mode == 1 && WINNING.PHOTO == EMPTY) {
       // Removing photo from loosing item.
       if (LOOSING.PHOTO != EMPTY) {
         loosingchanged = 1;
       }
       LOOSING.PHOTO = EMPTY;
       LOOSING.PHOTO_TYPE = "";
       LOOSING.PHOTO_VALUE = "";
     } else if (LOOSING.PHOTO != EMPTY) {
       // Updating photo. Might actually be the same!
       if (LOOSING.PHOTO_VALUE == "uri" && (WINNING.PHOTO_VALUE == EMPTY || WINNING.PHOTO_VALUE == "binary")) {
         string path;
         path = URITOPATH(LOOSING.PHOTO);
         if (path) {
           if (READ(path) == WINNING.PHOTO) {
             // Reuse photo file from loosing item.
             // If we need to write back for some other reason, we'll inline the data again.
             WINNING.PHOTO = LOOSING.PHOTO;
             WINNING.PHOTO_TYPE = LOOSING.PHOTO_TYPE;
             WINNING.PHOTO_VALUE = LOOSING.PHOTO_VALUE;
           }
         }
       }
     } else {
       LOOSING.PHOTO_VALUE = EMPTY;
       LOOSING.PHOTO_TYPE = EMPTY;
       LOOSING.PHOTO = EMPTY;
     }

     // In cache mode, we don't care about the order of entries, so
     // we do our own relaxed comparison and tell the engine to ignore
     // fields if the only change is in the ordering.
     if (CACHEDATA()) {
       ignorefields = ignorefields + RELAXEDCOMPARE("TEL", "", "TEL_FLAGS", "TEL_SLOT", "LABEL");
       ignorefields = ignorefields + RELAXEDCOMPARE("XDATE", "", "LABEL");
       ignorefields = ignorefields + RELAXEDCOMPARE("EMAIL", "", "EMAIL_FLAGS", "EMAIL_SLOT", "LABEL");
       ignorefields = ignorefields + RELAXEDCOMPARE("WEB", "", "WEB_FLAGS", "LABEL");
       ignorefields = ignorefields + RELAXEDCOMPARE("CALURI");
       ignorefields = ignorefields + RELAXEDCOMPARE("FBURL");
       ignorefields = ignorefields + RELAXEDCOMPARE("BLOGURL");
       ignorefields = ignorefields + RELAXEDCOMPARE("VIDEOURL");
       ignorefields = ignorefields + RELAXEDCOMPARE("RELATEDNAMES", "", "LABEL");
       ignorefields = ignorefields + RELAXEDCOMPARE("IMPP", "", "IMPP_SERVICE", "IMPP_SLOT", "LABEL");
       ignorefields = ignorefields + RELAXEDCOMPARE("AIM_HANDLE", "", "AIM_SLOT");
       ignorefields = ignorefields + RELAXEDCOMPARE("GADUGADU_HANDLE", "", "GADUGADU_SLOT");
       ignorefields = ignorefields + RELAXEDCOMPARE("GROUPWISE_HANDLE", "", "GROUPWISE_SLOT");
       ignorefields = ignorefields + RELAXEDCOMPARE("ICQ_HANDLE", "", "ICQ_SLOT");
       ignorefields = ignorefields + RELAXEDCOMPARE("JABBER_HANDLE", "", "JABBER_SLOT");
       ignorefields = ignorefields + RELAXEDCOMPARE("MSN_HANDLE", "", "MSN_SLOT");
       ignorefields = ignorefields + RELAXEDCOMPARE("YAHOO_HANDLE", "", "YAHOO_SLOT");
       ignorefields = ignorefields + RELAXEDCOMPARE("SKYPE_HANDLE", "", "SKYPE_SLOT");
       ignorefields = ignorefields + RELAXEDCOMPARE("SIP_HANDLE", "", "SIP_SLOT");
       ignorefields = ignorefields + RELAXEDCOMPARE("EMAIL", "", "EMAIL_FLAGS", "EMAIL_SLOT", "LABEL");
       ignorefields = ignorefields + RELAXEDCOMPARE("ADR", "ADR_STREET", "ADR_ADDTL", "ADR_STREET_FLAGS", "ADR_POBOX", "ADR_CITY", "ADR_REG", "ADR_ZIP", "ADR_COUNTRY", "", "LABEL");

       // Always ignore the label. Otherwise reordering will cause unnecessary
       // writes. We check for reordering more selectively above, but because
       // the LABEL field is shared, we cannot do the same for it.
       // The downside is that changes in just the label of an entry will not
       // cause an update of the cache. Some other value must be modified, too,
       // to trigger a write.
       ignorefields = ignorefields + " LABEL";
     }

     // When running in caching mode, we end up executing the merge script,
     // but in that case we want to remove data from the loosing item and
     // thus need to skip the array merging.
     if (!CACHEDATA()) {
       // Merge arrays such that the superset remains in the winning item.
       // The arrays then get copied wholesale to the loosing item.
       // Items which share the label array must avoid conflicts in the label
       // array, the others don't.
       // See "[SyncEvolution] merging of winning and loosing items"
       INTEGER i;
       INTEGER num;
       TELEPHONE tel;
       DATE xdate;
       STRING val;

       i = SIZE(LOOSING.TEL) - 1;
       while (i >= 0) {
          tel = LOOSING.TEL[i];
          if (tel != EMPTY &&
              !CONTAINS(WINNING.TEL, tel)) {
             num = SIZE(WINNING.LABEL);
             WINNING.TEL[num] = tel;
             WINNING.TEL_FLAGS[num] = LOOSING.TEL_FLAGS[i];
             WINNING.TEL_SLOT[num] = LOOSING.TEL_SLOT[i];
             WINNING.LABEL[num] = LOOSING.LABEL[i];
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.XDATE) - 1;
       while (i >= 0) {
          xdate = LOOSING.XDATE[i];
          if (xdate != EMPTY &&
              !CONTAINS(WINNING.XDATE, xdate)) {
             num = SIZE(WINNING.LABEL);
             WINNING.XDATE[num] = xdate;
             WINNING.LABEL[num] = LOOSING.LABEL[i];
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.EMAIL) - 1;
       while (i >= 0) {
          val = LOOSING.EMAIL[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.EMAIL, val)) {
             num = SIZE(WINNING.LABEL);
             WINNING.EMAIL[num] = val;
             WINNING.EMAIL_FLAGS[num] = LOOSING.EMAIL_FLAGS[i];
             WINNING.EMAIL_SLOT[num] = LOOSING.EMAIL_SLOT[i];
             WINNING.LABEL[num] = LOOSING.LABEL[i];
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.WEB) - 1;
       while (i >= 0) {
          val = LOOSING.WEB[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.WEB, val)) {
             num = SIZE(WINNING.LABEL);
             WINNING.WEB[num] = val;
             WINNING.WEB_FLAGS[num] = LOOSING.WEB_FLAGS[i];
             WINNING.LABEL[num] = LOOSING.LABEL[i];
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.CALURI) - 1;
       while (i >= 0) {
          val = LOOSING.CALURI[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.CALURI, val)) {
             num = SIZE(WINNING.CALURI);
             WINNING.CALURI[num] = val;
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.FBURL) - 1;
       while (i >= 0) {
          val = LOOSING.FBURL[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.FBURL, val)) {
             num = SIZE(WINNING.FBURL);
             WINNING.FBURL[num] = val;
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.BLOGURL) - 1;
       while (i >= 0) {
          val = LOOSING.BLOGURL[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.BLOGURL, val)) {
             num = SIZE(WINNING.BLOGURL);
             WINNING.BLOGURL[num] = val;
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.VIDEOURL) - 1;
       while (i >= 0) {
          val = LOOSING.VIDEOURL[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.VIDEOURL, val)) {
             num = SIZE(WINNING.VIDEOURL);
             WINNING.VIDEOURL[num] = val;
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.RELATEDNAMES) - 1;
       while (i >= 0) {
          val = LOOSING.RELATEDNAMES[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.RELATEDNAMES, val)) {
             num = SIZE(WINNING.LABEL);
             WINNING.RELATEDNAMES[num] = val;
             WINNING.LABEL[num] = LOOSING.LABEL[i];
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.IMPP) - 1;
       while (i >= 0) {
          val = LOOSING.IMPP[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.IMPP, val)) {
             num = SIZE(WINNING.LABEL);
             WINNING.IMPP[num] = val;
             WINNING.IMPP_SERVICE[num] = LOOSING.IMPP_SERVICE[i];
             WINNING.IMPP_SLOT[num] = LOOSING.IMPP_SLOT[i];
             WINNING.LABEL[num] = LOOSING.LABEL[i];
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.AIM_HANDLE) - 1;
       while (i >= 0) {
          val = LOOSING.AIM_HANDLE[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.AIM_HANDLE, val)) {
             num = SIZE(WINNING.AIM_HANDLE);
             WINNING.AIM_HANDLE[num] = val;
             WINNING.AIM_SLOT[num] = LOOSING.AIM_SLOT[i];
             winningchanged = 1;
          }
          i = i - 1;
       }
       i = SIZE(LOOSING.GADUGADU_HANDLE) - 1;
       while (i >= 0) {
          val = LOOSING.GADUGADU_HANDLE[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.GADUGADU_HANDLE, val)) {
             num = SIZE(WINNING.GADUGADU_HANDLE);
             WINNING.GADUGADU_HANDLE[num] = val;
             WINNING.GADUGADU_SLOT[num] = LOOSING.GADUGADU_SLOT[i];
             winningchanged = 1;
          }
          i = i - 1;
       }
       i = SIZE(LOOSING.GROUPWISE_HANDLE) - 1;
       while (i >= 0) {
          val = LOOSING.GROUPWISE_HANDLE[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.GROUPWISE_HANDLE, val)) {
             num = SIZE(WINNING.GROUPWISE_HANDLE);
             WINNING.GROUPWISE_HANDLE[num] = val;
             WINNING.GROUPWISE_SLOT[num] = LOOSING.GROUPWISE_SLOT[i];
             winningchanged = 1;
          }
          i = i - 1;
       }
       i = SIZE(LOOSING.ICQ_HANDLE) - 1;
       while (i >= 0) {
          val = LOOSING.ICQ_HANDLE[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.ICQ_HANDLE, val)) {
             num = SIZE(WINNING.ICQ_HANDLE);
             WINNING.ICQ_HANDLE[num] = val;
             WINNING.ICQ_SLOT[num] = LOOSING.ICQ_SLOT[i];
             winningchanged = 1;
          }
          i = i - 1;
       }
       i = SIZE(LOOSING.JABBER_HANDLE) - 1;
       while (i >= 0) {
          val = LOOSING.JABBER_HANDLE[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.JABBER_HANDLE, val)) {
             num = SIZE(WINNING.JABBER_HANDLE);
             WINNING.JABBER_HANDLE[num] = val;
             WINNING.JABBER_SLOT[num] = LOOSING.JABBER_SLOT[i];
             winningchanged = 1;
          }
          i = i - 1;
       }
       i = SIZE(LOOSING.MSN_HANDLE) - 1;
       while (i >= 0) {
          val = LOOSING.MSN_HANDLE[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.MSN_HANDLE, val)) {
             num = SIZE(WINNING.MSN_HANDLE);
             WINNING.MSN_HANDLE[num] = val;
             WINNING.MSN_SLOT[num] = LOOSING.MSN_SLOT[i];
             winningchanged = 1;
          }
          i = i - 1;
       }
       i = SIZE(LOOSING.YAHOO_HANDLE) - 1;
       while (i >= 0) {
          val = LOOSING.YAHOO_HANDLE[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.YAHOO_HANDLE, val)) {
             num = SIZE(WINNING.YAHOO_HANDLE);
             WINNING.YAHOO_HANDLE[num] = val;
             WINNING.YAHOO_SLOT[num] = LOOSING.YAHOO_SLOT[i];
             winningchanged = 1;
          }
          i = i - 1;
       }
       i = SIZE(LOOSING.SKYPE_HANDLE) - 1;
       while (i >= 0) {
          val = LOOSING.SKYPE_HANDLE[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.SKYPE_HANDLE, val)) {
             num = SIZE(WINNING.SKYPE_HANDLE);
             WINNING.SKYPE_HANDLE[num] = val;
             WINNING.SKYPE_SLOT[num] = LOOSING.SKYPE_SLOT[i];
             winningchanged = 1;
          }
          i = i - 1;
       }
       i = SIZE(LOOSING.SIP_HANDLE) - 1;
       while (i >= 0) {
          val = LOOSING.SIP_HANDLE[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.SIP_HANDLE, val)) {
             num = SIZE(WINNING.SIP_HANDLE);
             WINNING.SIP_HANDLE[num] = val;
             WINNING.SIP_SLOT[num] = LOOSING.SIP_SLOT[i];
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.EMAIL) - 1;
       while (i >= 0) {
          val = LOOSING.EMAIL[i];
          if (val != EMPTY &&
              !CONTAINS(WINNING.EMAIL, val)) {
             num = SIZE(WINNING.LABEL);
             WINNING.EMAIL[num] = val;
             WINNING.EMAIL_FLAGS[num] = LOOSING.EMAIL_FLAGS[i];
             WINNING.EMAIL_SLOT[num] = LOOSING.EMAIL_SLOT[i];
             WINNING.LABEL[num] = LOOSING.LABEL[i];
             winningchanged = 1;
          }
          i = i - 1;
       }

       i = SIZE(LOOSING.ADR_STREET) - 1;
       INTEGER e;
       INTEGER contained;
       while (i >= 0) {
          if (LOOSING.ADR_STREET[i] != EMPTY ||
              LOOSING.ADR_ADDTL[i] != EMPTY ||
              LOOSING.ADR_STREET_FLAGS[i] != EMPTY ||
              LOOSING.ADR_POBOX[i] != EMPTY ||
              LOOSING.ADR_CITY[i] != EMPTY ||
              LOOSING.ADR_REG[i] != EMPTY ||
              LOOSING.ADR_ZIP[i] != EMPTY ||
              LOOSING.ADR_COUNTRY[i] != EMPTY) {
              contained = 0;
              e = SIZE(WINNING.ADR_STREET) - 1;
              while (e >= 0) {
                 if (LOOSING.ADR_STREET[i] == WINNING.ADR_STREET[e] &&
                     LOOSING.ADR_ADDTL[i] == WINNING.ADR_ADDTL[e] &&
                     LOOSING.ADR_STREET_FLAGS[i] == WINNING.ADR_STREET_FLAGS[e] &&
                     LOOSING.ADR_POBOX[i] == WINNING.ADR_POBOX[e] &&
                     LOOSING.ADR_CITY[i] == WINNING.ADR_CITY[e] &&
                     LOOSING.ADR_REG[i] == WINNING.ADR_REG[e] &&
                     LOOSING.ADR_ZIP[i] == WINNING.ADR_ZIP[e] &&
                     LOOSING.ADR_COUNTRY[i] == WINNING.ADR_COUNTRY[e]) {
                     contained = 1;
                     break;
                 }
                 e = e - 1;
              }
              if (!contained) {
                 num = SIZE(WINNING.LABEL);
                 WINNING.ADR_STREET[num] = LOOSING.ADR_STREET[i];
                 WINNING.ADR_ADDTL[num] = LOOSING.ADR_ADDTL[i];
                 WINNING.ADR_STREET_FLAGS[num] = LOOSING.ADR_STREET_FLAGS[i];
                 WINNING.ADR_POBOX[num] = LOOSING.ADR_POBOX[i];
                 WINNING.ADR_CITY[num] = LOOSING.ADR_CITY[i];
                 WINNING.ADR_REG[num] = LOOSING.ADR_REG[i];
                 WINNING.ADR_ZIP[num] = LOOSING.ADR_ZIP[i];
                 WINNING.ADR_COUNTRY[num] = LOOSING.ADR_COUNTRY[i];
                 WINNING.LABEL[num] = LOOSING.LABEL[i];
                 winningchanged = 1;
              }
          }
          i = i - 1;
       }
     }
     // Never tell the engine to update the winning client item
     // in a caching sync. The engine would send an update to the
     // client, which is not what we want when caching!
     if (!CACHEDATA() && winningchanged) { SETWINNINGCHANGED(1); }
     if (loosingchanged) { SETLOOSINGCHANGED(1); }

     // Continue merge.
     MERGEFIELDS(mode, ignorefields);
   ]]></macro>
