public function Process(CMDBChange $oChange = null)
 {
     // Note: $oChange can be null, in which case the aim is to check what would be done
     // Debug...
     //
     if (false) {
         echo "<pre>\n";
         echo "Attributes:\n";
         print_r($this->m_aAttList);
         echo "ExtKeys:\n";
         print_r($this->m_aExtKeys);
         echo "Reconciliation:\n";
         print_r($this->m_aReconcilKeys);
         echo "Synchro scope:\n";
         print_r($this->m_sSynchroScope);
         echo "Synchro changes:\n";
         print_r($this->m_aOnDisappear);
         //echo "Data:\n";
         //print_r($this->m_aData);
         echo "</pre>\n";
         exit;
     }
     $aResult = array();
     if (!is_null($this->m_sDateFormat) && strlen($this->m_sDateFormat) > 0) {
         // Translate dates from the source data
         //
         foreach ($this->m_aAttList as $sAttCode => $iCol) {
             $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
             if ($oAttDef instanceof AttributeDateTime) {
                 foreach ($this->m_aData as $iRow => $aRowData) {
                     $sNewDate = utils::StringToTime($this->m_aData[$iRow][$iCol], $this->m_sDateFormat);
                     if ($sNewDate !== false) {
                         // Todo - improve the reporting
                         $this->m_aData[$iRow][$iCol] = $sNewDate;
                     } else {
                         // Leave the cell unchanged
                         $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
                         $aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, $this->m_aData[$iRow][$iCol], Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
                     }
                 }
             }
         }
     }
     // Compute the results
     //
     if (!is_null($this->m_sSynchroScope)) {
         $aVisited = array();
     }
     $iPreviousTimeLimit = ini_get('max_execution_time');
     $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
     foreach ($this->m_aData as $iRow => $aRowData) {
         set_time_limit($iLoopTimeLimit);
         if (isset($aResult[$iRow]["__STATUS__"])) {
             // An issue at the earlier steps - skip the rest
             continue;
         }
         try {
             $oReconciliationFilter = new DBObjectSearch($this->m_sClass);
             $bSkipQuery = false;
             foreach ($this->m_aReconcilKeys as $sAttCode) {
                 $valuecondition = null;
                 if (array_key_exists($sAttCode, $this->m_aExtKeys)) {
                     if ($this->IsNullExternalKeySpec($aRowData, $sAttCode)) {
                         $oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
                         if ($oExtKey->IsNullAllowed()) {
                             $valuecondition = $oExtKey->GetNullValue();
                             $aResult[$iRow][$sAttCode] = new CellStatus_Void($oExtKey->GetNullValue());
                         } else {
                             $aResult[$iRow][$sAttCode] = new CellStatus_NullIssue();
                         }
                     } else {
                         // The value has to be found or verified
                         list($sQuery, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]);
                         if (count($aMatches) == 1) {
                             $oRemoteObj = reset($aMatches);
                             // first item
                             $valuecondition = $oRemoteObj->GetKey();
                             $aResult[$iRow][$sAttCode] = new CellStatus_Void($oRemoteObj->GetKey());
                         } elseif (count($aMatches) == 0) {
                             $aResult[$iRow][$sAttCode] = new CellStatus_SearchIssue();
                         } else {
                             $aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $sQuery);
                         }
                     }
                 } else {
                     // The value is given in the data row
                     $iCol = $this->m_aAttList[$sAttCode];
                     if ($sAttCode == 'id') {
                         $valuecondition = $aRowData[$iCol];
                     } else {
                         $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
                         $valuecondition = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
                     }
                 }
                 if (is_null($valuecondition)) {
                     $bSkipQuery = true;
                 } else {
                     $oReconciliationFilter->AddCondition($sAttCode, $valuecondition, '=');
                 }
             }
             if ($bSkipQuery) {
                 $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Reconciliation'));
             } else {
                 $oReconciliationSet = new CMDBObjectSet($oReconciliationFilter);
                 switch ($oReconciliationSet->Count()) {
                     case 0:
                         $oTargetObj = $this->CreateObject($aResult, $iRow, $aRowData, $oChange);
                         // $aResult[$iRow]["__STATUS__"]=> set in CreateObject
                         $aVisited[] = $oTargetObj->GetKey();
                         break;
                     case 1:
                         $oTargetObj = $oReconciliationSet->Fetch();
                         $this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange);
                         // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject
                         if (!is_null($this->m_sSynchroScope)) {
                             $aVisited[] = $oTargetObj->GetKey();
                         }
                         break;
                     default:
                         // Found several matches, ambiguous
                         $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Ambiguous'));
                         $aResult[$iRow]["id"] = new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->ToOql());
                         $aResult[$iRow]["finalclass"] = 'n/a';
                 }
             }
         } catch (Exception $e) {
             $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::Format('UI:CSVReport-Row-Issue-Internal', get_class($e), $e->getMessage()));
         }
     }
     if (!is_null($this->m_sSynchroScope)) {
         // Compute the delta between the scope and visited objects
         $oScopeSearch = DBObjectSearch::FromOQL($this->m_sSynchroScope);
         $oScopeSet = new DBObjectSet($oScopeSearch);
         while ($oObj = $oScopeSet->Fetch()) {
             $iObj = $oObj->GetKey();
             if (!in_array($iObj, $aVisited)) {
                 set_time_limit($iLoopTimeLimit);
                 $iRow++;
                 $this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
             }
         }
     }
     set_time_limit($iPreviousTimeLimit);
     // Fill in the blanks - the result matrix is expected to be 100% complete
     //
     foreach ($this->m_aData as $iRow => $aRowData) {
         foreach ($this->m_aAttList as $iCol) {
             if (!array_key_exists($iCol, $aResult[$iRow])) {
                 $aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]);
             }
         }
         foreach ($this->m_aExtKeys as $sAttCode => $aForeignAtts) {
             if (!array_key_exists($sAttCode, $aResult[$iRow])) {
                 $aResult[$iRow][$sAttCode] = new CellStatus_Void('n/a');
             }
             foreach ($aForeignAtts as $sForeignAttCode => $iCol) {
                 if (!array_key_exists($iCol, $aResult[$iRow])) {
                     // The foreign attribute is one of our reconciliation key
                     $aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]);
                 }
             }
         }
     }
     return $aResult;
 }