LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101reader.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 384 417 92.1 %
Date: 2026-05-29 23:25:07 Functions: 13 13 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  S-101 driver
       4             :  * Purpose:  Implements OGRS101Reader
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "ogr_s101.h"
      14             : #include "ogrs101readerconstants.h"
      15             : #include "ogrs101featurecatalog.h"
      16             : 
      17             : #include "cpl_enumerate.h"
      18             : 
      19             : #include <limits>
      20             : #include <memory>
      21             : #include <utility>
      22             : 
      23             : /************************************************************************/
      24             : /*                           OGRS101Reader()                            */
      25             : /************************************************************************/
      26             : 
      27             : OGRS101Reader::OGRS101Reader() = default;
      28             : 
      29             : /************************************************************************/
      30             : /*                           ~OGRS101Reader()                           */
      31             : /************************************************************************/
      32             : 
      33             : OGRS101Reader::~OGRS101Reader() = default;
      34             : 
      35             : /************************************************************************/
      36             : /*                         EmitErrorOrWarning()                         */
      37             : /************************************************************************/
      38             : 
      39             : /*static */ bool
      40         579 : OGRS101Reader::EmitErrorOrWarning(const char *pszFile, const char *pszFunc,
      41             :                                   int nLine, const char *pszMsg, bool bError,
      42             :                                   bool bRecoverable)
      43             : {
      44             : #ifdef _WIN32
      45             :     const char *lastPathSep = strrchr(pszFile, '\\');
      46             : #else
      47         579 :     const char *lastPathSep = strrchr(pszFile, '/');
      48             : #endif
      49         579 :     if (lastPathSep)
      50         579 :         pszFile = lastPathSep + 1;
      51             : 
      52         579 :     if (bError)
      53             :     {
      54         214 :         if (bRecoverable)
      55             :         {
      56         196 :             CPLError(
      57             :                 CE_Failure, CPLE_AppDefined,
      58             :                 "at %s:%d (%s()): %s\n"
      59             :                 "You can potentially try to overcome this error by setting "
      60             :                 "the STRICT open option to FALSE",
      61             :                 pszFile, nLine, pszFunc, pszMsg);
      62             :         }
      63             :         else
      64             :         {
      65          18 :             CPLError(CE_Failure, CPLE_AppDefined, "at %s:%d (%s()): %s",
      66             :                      pszFile, nLine, pszFunc, pszMsg);
      67             :         }
      68             :     }
      69             :     else
      70             :     {
      71         365 :         CPLError(CE_Warning, CPLE_AppDefined, "at %s:%d (%s()): %s", pszFile,
      72             :                  nLine, pszFunc, pszMsg);
      73             :     }
      74         579 :     return !bError;
      75             : }
      76             : 
      77             : /************************************************************************/
      78             : /*                                Load()                                */
      79             : /************************************************************************/
      80             : 
      81             : /** Load a dataset.
      82             :  *
      83             :  * Ingests records in the various DDFRecordIndex members and build layer
      84             :  * definitions.
      85             :  */
      86         501 : bool OGRS101Reader::Load(GDALOpenInfo *poOpenInfo)
      87             : {
      88         501 :     if (!poOpenInfo->fpL)
      89           0 :         return false;
      90             : 
      91         501 :     m_bStrict = CPLTestBool(
      92         501 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "STRICT", "YES"));
      93         501 :     VSILFILE *fp = poOpenInfo->fpL;
      94         501 :     poOpenInfo->fpL = nullptr;
      95             : 
      96         501 :     if (!Load(poOpenInfo->pszFilename, fp, &m_oMainModule))
      97          90 :         return false;
      98             : 
      99         411 :     m_aosMetadata.SetNameValue("STATUS", "VALID");
     100             : 
     101             :     // Browse and load update files
     102         822 :     if (poOpenInfo->IsExtensionEqualToCI("000") &&
     103         411 :         EQUAL(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "UPDATES",
     104             :                                    "APPLY"),
     105             :               "APPLY"))
     106             :     {
     107         410 :         m_bInUpdate = true;
     108         481 :         for (int iUpdate = 1; iUpdate <= 999; ++iUpdate)
     109             :         {
     110         481 :             DDFModule oTmpModule;
     111             :             const std::string osUpdateFilename = CPLResetExtensionSafe(
     112         481 :                 poOpenInfo->pszFilename, CPLSPrintf("%03d", iUpdate));
     113             :             VSIStatBufL sStatBuf;
     114         481 :             if (VSIStatL(osUpdateFilename.c_str(), &sStatBuf) != 0)
     115         356 :                 break;
     116         125 :             CPLDebug("S101", "Loading update file %s",
     117             :                      osUpdateFilename.c_str());
     118         125 :             if (!Load(osUpdateFilename.c_str(), nullptr, &oTmpModule))
     119          49 :                 return false;
     120          76 :             if (m_bCancelled)
     121           5 :                 break;
     122             :         }
     123         361 :         m_bInUpdate = false;
     124             :     }
     125             : 
     126         724 :     return ReadFeatureCatalog() && CreateInformationTypeFeatureDefn() &&
     127         353 :            CreatePointFeatureDefns() && CreateMultiPointFeatureDefns() &&
     128         345 :            CreateCurveFeatureDefn() && CreateCompositeCurveFeatureDefn() &&
     129         724 :            CreateSurfaceFeatureDefn() && CreateFeatureTypeFeatureDefns();
     130             : }
     131             : 
     132             : /************************************************************************/
     133             : /*                                Load()                                */
     134             : /************************************************************************/
     135             : 
     136         626 : bool OGRS101Reader::Load(const std::string &osFilename, VSILFILE *fp,
     137             :                          DDFModule *poCurModule)
     138             : {
     139         626 :     m_osFilename = osFilename;
     140         626 :     if (!poCurModule->Open(m_osFilename.c_str(), false, fp))
     141           0 :         return false;
     142             : 
     143         626 :     if (!poCurModule->FindFieldDefn(DSID_FIELD))
     144             :     {
     145           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     146             :                  "%s is an ISO8211 file, but not a S-101 data file.",
     147             :                  m_osFilename.c_str());
     148           0 :         return false;
     149             :     }
     150             : 
     151         626 :     DDFRecord *poRecord = poCurModule->ReadRecord();
     152         626 :     if (!ReadDatasetGeneralInformationRecord(poRecord))
     153          53 :         return false;
     154             : 
     155         573 :     poRecord = poCurModule->ReadRecord();
     156         573 :     if (m_bInUpdate)
     157             :     {
     158         120 :         if (!poRecord)
     159           6 :             return true;
     160         114 :         if (poRecord->FindField(CSID_FIELD))
     161             :         {
     162           0 :             if (!EMIT_ERROR_OR_WARNING(
     163             :                     "CSID field found in update file but not expected."))
     164           0 :                 return false;
     165           0 :             poRecord = poCurModule->ReadRecord();
     166           0 :             if (!poRecord)
     167           0 :                 return true;
     168             :         }
     169             : 
     170         114 :         return IngestUpdateRecords(poRecord, poCurModule);
     171             :     }
     172             :     else
     173             :     {
     174         453 :         if (!poRecord)
     175           1 :             return EMIT_ERROR("no Dataset Coordinate Reference System record.");
     176         452 :         bool bSkipFirstReadRecord = false;
     177         452 :         if (!m_bStrict && !poRecord->FindField(CSID_FIELD))
     178             :         {
     179           1 :             EMIT_ERROR_OR_WARNING("CSID field not found.");
     180           1 :             bSkipFirstReadRecord = true;
     181             :         }
     182         451 :         else if (!ReadCSID(poRecord))
     183          29 :             return false;
     184             : 
     185         423 :         return IngestInitialRecords(bSkipFirstReadRecord ? poRecord : nullptr);
     186             :     }
     187             : }
     188             : 
     189             : /************************************************************************/
     190             : /*                         ReadFeatureCatalog()                         */
     191             : /************************************************************************/
     192             : 
     193         362 : bool OGRS101Reader::ReadFeatureCatalog()
     194             : {
     195             :     OGRS101FeatureCatalog::LoadingStatus status;
     196         362 :     std::tie(status, m_poFeatureCatalog) =
     197         724 :         OGRS101FeatureCatalog::GetSingletonFeatureCatalog(m_bStrict);
     198         362 :     return status != OGRS101FeatureCatalog::LoadingStatus::ERROR;
     199             : }
     200             : 
     201             : /************************************************************************/
     202             : /*                       CheckFieldDefinitions()                        */
     203             : /************************************************************************/
     204             : 
     205             : /** Check that the fields found in the record match the expectations
     206             :  * from the spec. That is check there are no missing required field,
     207             :  * no duplicate field or unexpected field.
     208             :  */
     209        2243 : bool OGRS101Reader::CheckFieldDefinitions(
     210             :     const DDFRecord *poRecord, int iRecord, RecordName nRCNM, int nRCID,
     211             :     const std::map<RecordName, std::vector<std::vector<NameOccMinOccMax>>>
     212             :         &mapExpectedFields) const
     213             : {
     214        4486 :     std::vector<std::string> fieldsInRecord;
     215        8725 :     for (const auto &poField : poRecord->GetFields())
     216             :     {
     217        6482 :         fieldsInRecord.push_back(poField->GetFieldDefn()->GetName());
     218             :     }
     219        2243 :     const auto oIter = mapExpectedFields.find(nRCNM);
     220        2243 :     if (oIter != mapExpectedFields.end())
     221             :     {
     222        2240 :         bool bMatch = false;
     223        2336 :         for (const auto &expectedFields : oIter->second)
     224             :         {
     225        2320 :             bMatch = true;
     226        2320 :             size_t iExpected = 0;
     227        8966 :             for (size_t i = 0; bMatch && i < fieldsInRecord.size(); ++i)
     228             :             {
     229        8961 :                 for (; iExpected < expectedFields.size(); ++iExpected)
     230             :                 {
     231        8956 :                     if (fieldsInRecord[i] ==
     232        8956 :                         std::get<0>(expectedFields[iExpected]))
     233             :                     {
     234             :                         const int nMaxOcc =
     235        6569 :                             std::get<2>(expectedFields[iExpected]);
     236        6569 :                         if (nMaxOcc == 1)
     237             :                         {
     238        6264 :                             if (i > 0 &&
     239        1972 :                                 fieldsInRecord[i - 1] == fieldsInRecord[i])
     240             :                             {
     241           0 :                                 bMatch = false;
     242             :                             }
     243             :                             else
     244             :                             {
     245        4292 :                                 ++iExpected;
     246             :                             }
     247             :                         }
     248        2891 :                         else if (i + 1 == fieldsInRecord.size() ||
     249         614 :                                  fieldsInRecord[i + 1] != fieldsInRecord[i])
     250             :                         {
     251        2123 :                             ++iExpected;
     252             :                         }
     253        6569 :                         break;
     254             :                     }
     255             :                     else
     256             :                     {
     257             :                         const int nMinOcc =
     258        2387 :                             std::get<1>(expectedFields[iExpected]);
     259        2387 :                         if (nMinOcc == 1)
     260             :                         {
     261          74 :                             bMatch = false;
     262          74 :                             break;
     263             :                         }
     264             :                     }
     265             :                 }
     266             : 
     267             :                 // If we have reached the end of expected fields but
     268             :                 // there are remaining fields, then there are missing
     269             :                 // compulsory fields.
     270        8184 :                 if (iExpected == expectedFields.size() &&
     271        1536 :                     i + 1 < fieldsInRecord.size())
     272             :                 {
     273           2 :                     bMatch = false;
     274           2 :                     break;
     275             :                 }
     276             :             }
     277             : 
     278             :             // Skip optional fields after the last one in the record
     279        2320 :             if (bMatch && iExpected < expectedFields.size())
     280             :             {
     281        1795 :                 for (; iExpected < expectedFields.size(); ++iExpected)
     282             :                 {
     283        1105 :                     const int nMinOcc = std::get<1>(expectedFields[iExpected]);
     284        1105 :                     if (nMinOcc > 0)
     285             :                     {
     286          20 :                         bMatch = false;
     287          20 :                         break;
     288             :                     }
     289             :                 }
     290             :             }
     291        2320 :             bMatch = bMatch && iExpected == expectedFields.size();
     292        2320 :             if (bMatch)
     293        2224 :                 break;
     294             :         }
     295        2240 :         if (!bMatch)
     296             :         {
     297          16 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     298             :                     "Record index %d, RCNM=%d, RCID=%d: invalid "
     299             :                     "sequence of fields.",
     300             :                     iRecord, static_cast<int>(nRCNM), static_cast<int>(nRCID))))
     301             :             {
     302           8 :                 return false;
     303             :             }
     304             :         }
     305             :     }
     306             : 
     307        2235 :     return true;
     308             : }
     309             : 
     310             : /************************************************************************/
     311             : /*                        IngestInitialRecords()                        */
     312             : /************************************************************************/
     313             : 
     314             : /** Ingest records of .000 initial file into the various m_oXXXXXIndex members
     315             :  *
     316             :  * @param poRecordIn Already read record, or nullptr to advance to the next one
     317             :  */
     318         423 : bool OGRS101Reader::IngestInitialRecords(const DDFRecord *poRecordIn)
     319             : {
     320             :     // Must NOT be set as static, as it depends on "this" !
     321             :     /* NOT STATIC */ const struct
     322             :     {
     323             :         const char *pszFieldName;
     324             :         const char *pszType;
     325             :         RecordName nRCNM;
     326             :         int nExpectedCount;
     327             :         DDFRecordIndex &oIndex;
     328         423 :     } asRecordTypeDesc[] = {
     329             :         {IRID_FIELD, "information type", RECORD_NAME_INFORMATION_TYPE,
     330         423 :          m_nCountInformationRecord, m_oInformationTypeRecordIndex},
     331         423 :         {PRID_FIELD, "point", RECORD_NAME_POINT, m_nCountPointRecord,
     332         423 :          m_oPointRecordIndex},
     333             :         {MRID_FIELD, "multipoint", RECORD_NAME_MULTIPOINT,
     334         423 :          m_nCountMultiPointRecord, m_oMultiPointRecordIndex},
     335         423 :         {CRID_FIELD, "curve", RECORD_NAME_CURVE, m_nCountCurveRecord,
     336         423 :          m_oCurveRecordIndex},
     337             :         {CCID_FIELD, "composite curve", RECORD_NAME_COMPOSITE_CURVE,
     338         423 :          m_nCountCompositeCurveRecord, m_oCompositeCurveRecordIndex},
     339         423 :         {SRID_FIELD, "surface", RECORD_NAME_SURFACE, m_nCountSurfaceRecord,
     340         423 :          m_oSurfaceRecordIndex},
     341             :         {FRID_FIELD, "feature type", RECORD_NAME_FEATURE_TYPE,
     342         423 :          m_nCountFeatureTypeRecord, m_oFeatureTypeRecordIndex},
     343         423 :     };
     344             : 
     345         423 :     constexpr int STAR = std::numeric_limits<int>::max();
     346             :     const std::map<RecordName, std::vector<std::vector<NameOccMinOccMax>>>
     347             :         mapExpectedFields = {
     348             :             {
     349             :                 RECORD_NAME_INFORMATION_TYPE,
     350             :                 {{{IRID_FIELD, 1, 1},
     351             :                   {ATTR_FIELD, 0, STAR},
     352             :                   {INAS_FIELD, 0, STAR}}},
     353             :             },
     354             :             {RECORD_NAME_POINT,
     355             :              {
     356             :                  {{{PRID_FIELD, 1, 1},
     357             :                    {INAS_FIELD, 0, STAR},
     358             :                    {C2IT_FIELD, 1, 1}}},
     359             :                  {{{PRID_FIELD, 1, 1},
     360             :                    {INAS_FIELD, 0, STAR},
     361             :                    {C3IT_FIELD, 1, 1}}},
     362             :              }},
     363             :             {RECORD_NAME_MULTIPOINT,
     364             :              {
     365             :                  {{{MRID_FIELD, 1, 1},
     366             :                    {INAS_FIELD, 0, STAR},
     367             :                    {C2IL_FIELD, 1, STAR}}},
     368             :                  {{{MRID_FIELD, 1, 1},
     369             :                    {INAS_FIELD, 0, STAR},
     370             :                    {C3IL_FIELD, 1, STAR}}},
     371             :              }},
     372             :             {RECORD_NAME_CURVE,
     373             :              {
     374             :                  {{{CRID_FIELD, 1, 1},
     375             :                    {INAS_FIELD, 0, STAR},
     376             :                    {PTAS_FIELD, 1, 1},
     377             :                    {SEGH_FIELD, 1, 1},
     378             :                    {C2IL_FIELD, 1, STAR}}},
     379             :              }},
     380             :             {RECORD_NAME_COMPOSITE_CURVE,
     381             :              {
     382             :                  {{{CCID_FIELD, 1, 1},
     383             :                    {INAS_FIELD, 0, STAR},
     384             :                    {CUCO_FIELD, 1, STAR}}},
     385             :              }},
     386             :             {RECORD_NAME_SURFACE,
     387             :              {
     388             :                  {{{SRID_FIELD, 1, 1},
     389             :                    {INAS_FIELD, 0, STAR},
     390             :                    {RIAS_FIELD, 1, STAR}}},
     391             :              }},
     392             :             {RECORD_NAME_FEATURE_TYPE,
     393             :              {
     394             :                  {{{FRID_FIELD, 1, 1},
     395             :                    {FOID_FIELD, 1, 1},
     396             :                    {ATTR_FIELD, 0, STAR},
     397             :                    {INAS_FIELD, 0, STAR},
     398             :                    {SPAS_FIELD, 0, STAR},
     399             :                    {FASC_FIELD, 0, STAR},
     400             :                    {MASK_FIELD, 0, STAR}}},
     401             :              }},
     402       11421 :         };
     403             : 
     404             :     // Loop through all records (except first two DSID and CRID already parsed)
     405             :     // and dispatch them to the appropriate DDFRecordIndex member variable.
     406         423 :     const DDFRecord *poRecord = nullptr;
     407         423 :     const int iFirstRecord = poRecordIn ? 1 : 2;
     408        2452 :     for (int iRecord = iFirstRecord;
     409        2452 :          poRecordIn || (poRecord = m_oMainModule.ReadRecord()) != nullptr;
     410             :          ++iRecord)
     411             :     {
     412        2043 :         if (poRecordIn)
     413           1 :             std::swap(poRecord, poRecordIn);
     414             : 
     415        2043 :         const auto poField = poRecord->GetField(0);
     416        2043 :         if (!poField)
     417          11 :             return EMIT_ERROR(
     418             :                 CPLSPrintf("Record index %d without field.", iRecord));
     419        2043 :         const char *pszFieldName = poField->GetFieldDefn()->GetName();
     420             : 
     421             :         // Record name
     422             :         const RecordName nRCNM =
     423        2043 :             poRecord->GetIntSubfield(pszFieldName, 0, RCNM_SUBFIELD, 0);
     424             : 
     425             :         // Record identifier
     426             :         const int nRCID =
     427        2043 :             poRecord->GetIntSubfield(pszFieldName, 0, RCID_SUBFIELD, 0);
     428             : 
     429        2043 :         if (!CheckFieldDefinitions(poRecord, iRecord, nRCNM, nRCID,
     430             :                                    mapExpectedFields))
     431           8 :             return false;
     432             : 
     433        2035 :         const auto iterRecordTypeDesc = std::find_if(
     434             :             std::begin(asRecordTypeDesc), std::end(asRecordTypeDesc),
     435        7942 :             [pszFieldName](const auto &sRecordTypeDesc)
     436             :             {
     437        7942 :                 return strcmp(pszFieldName, sRecordTypeDesc.pszFieldName) == 0;
     438             :             });
     439        2035 :         if (iterRecordTypeDesc != std::end(asRecordTypeDesc))
     440             :         {
     441        2034 :             const auto &sRecordTypeDesc = *iterRecordTypeDesc;
     442             : 
     443        2034 :             if (nRCNM != sRecordTypeDesc.nRCNM &&
     444           0 :                 !EMIT_ERROR_OR_WARNING(
     445             :                     CPLSPrintf("Record index %d: invalid value %d for RCNM "
     446             :                                "subfield of %s.",
     447             :                                iRecord, static_cast<int>(nRCNM), pszFieldName)))
     448             :             {
     449           0 :                 return false;
     450             :             }
     451             : 
     452        2034 :             if (nRCID <= 0)
     453             :             {
     454           6 :                 if (!EMIT_ERROR_OR_WARNING(
     455             :                         CPLSPrintf("Record index %d: invalid value %d for "
     456             :                                    "RCID subfield of %s.",
     457             :                                    iRecord, nRCID, pszFieldName)))
     458             :                 {
     459           3 :                     return false;
     460             :                 }
     461           3 :                 break;
     462             :             }
     463             : 
     464        2028 :             if (sRecordTypeDesc.oIndex.FindRecord(nRCID) &&
     465           0 :                 !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     466             :                     "Record index %d: several %s records have RCID = %d.",
     467             :                     iRecord, pszFieldName, nRCID)))
     468             :             {
     469           0 :                 return false;
     470             :             }
     471        2028 :             sRecordTypeDesc.oIndex.AddRecord(nRCID, poRecord->Clone());
     472             :         }
     473           1 :         else if (!EMIT_ERROR_OR_WARNING(
     474             :                      CPLSPrintf("Record index %d: unknown field name %s.",
     475             :                                 iRecord, pszFieldName)))
     476             :         {
     477           0 :             return false;
     478             :         }
     479             :     }
     480             : 
     481             :     // Check consistency between number of records of each category (information
     482             :     // type, point, etc.) and the number actually found.
     483        3289 :     for (const auto &sRecordTypeDesc : asRecordTypeDesc)
     484             :     {
     485        2878 :         if (sRecordTypeDesc.oIndex.GetCount() !=
     486        2890 :                 sRecordTypeDesc.nExpectedCount &&
     487          12 :             !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     488             :                 "%d %s records mentioned in DSSI field, but %d actually found.",
     489             :                 sRecordTypeDesc.nExpectedCount, sRecordTypeDesc.pszType,
     490             :                 sRecordTypeDesc.oIndex.GetCount())))
     491             :         {
     492           1 :             return false;
     493             :         }
     494             :     }
     495             : 
     496         411 :     return true;
     497             : }
     498             : 
     499             : /************************************************************************/
     500             : /*                        IngestUpdateRecords()                         */
     501             : /************************************************************************/
     502             : 
     503             : /** Ingest records of .00x (x>=1) update file into the various m_oXXXXXIndex members
     504             :  *
     505             :  * @param poRecordIn Already read record
     506             :  */
     507         114 : bool OGRS101Reader::IngestUpdateRecords(const DDFRecord *poRecordIn,
     508             :                                         DDFModule *poCurModule)
     509             : {
     510             :     // Must NOT be set as static, as it depends on "this" !
     511             :     /* NOT STATIC */ const struct
     512             :     {
     513             :         const char *pszFieldName;
     514             :         const char *pszType;
     515             :         RecordName nRCNM;
     516             :         int &nExpectedCount;
     517             :         DDFRecordIndex &oIndex;
     518         114 :     } asRecordTypeDesc[] = {
     519             :         {IRID_FIELD, "information type", RECORD_NAME_INFORMATION_TYPE,
     520         114 :          m_nCountInformationRecord, m_oInformationTypeRecordIndex},
     521         114 :         {PRID_FIELD, "point", RECORD_NAME_POINT, m_nCountPointRecord,
     522         114 :          m_oPointRecordIndex},
     523             :         {MRID_FIELD, "multipoint", RECORD_NAME_MULTIPOINT,
     524         114 :          m_nCountMultiPointRecord, m_oMultiPointRecordIndex},
     525         114 :         {CRID_FIELD, "curve", RECORD_NAME_CURVE, m_nCountCurveRecord,
     526         114 :          m_oCurveRecordIndex},
     527             :         {CCID_FIELD, "composite curve", RECORD_NAME_COMPOSITE_CURVE,
     528         114 :          m_nCountCompositeCurveRecord, m_oCompositeCurveRecordIndex},
     529         114 :         {SRID_FIELD, "surface", RECORD_NAME_SURFACE, m_nCountSurfaceRecord,
     530         114 :          m_oSurfaceRecordIndex},
     531             :         {FRID_FIELD, "feature type", RECORD_NAME_FEATURE_TYPE,
     532         114 :          m_nCountFeatureTypeRecord, m_oFeatureTypeRecordIndex},
     533         114 :     };
     534             : 
     535         114 :     constexpr int STAR = std::numeric_limits<int>::max();
     536             :     const std::map<RecordName, std::vector<std::vector<NameOccMinOccMax>>>
     537             :         mapExpectedFields = {
     538             :             {
     539             :                 RECORD_NAME_INFORMATION_TYPE,
     540             :                 {{{IRID_FIELD, 1, 1},
     541             :                   {ATTR_FIELD, 0, STAR},
     542             :                   {INAS_FIELD, 0, STAR}}},
     543             :             },
     544             :             {RECORD_NAME_POINT,
     545             :              {
     546             :                  {{{PRID_FIELD, 1, 1},
     547             :                    {INAS_FIELD, 0, STAR},
     548             :                    {C2IT_FIELD, 0, 1}}},
     549             :                  {{{PRID_FIELD, 1, 1},
     550             :                    {INAS_FIELD, 0, STAR},
     551             :                    {C3IT_FIELD, 0, 1}}},
     552             :              }},
     553             :             {RECORD_NAME_MULTIPOINT,
     554             :              {
     555             :                  {{{MRID_FIELD, 1, 1},
     556             :                    {INAS_FIELD, 0, STAR},
     557             :                    {COCC_FIELD, 0, 1},
     558             :                    {C2IL_FIELD, 0, STAR}}},
     559             :                  {{{MRID_FIELD, 1, 1},
     560             :                    {INAS_FIELD, 0, STAR},
     561             :                    {COCC_FIELD, 0, 1},
     562             :                    {C3IL_FIELD, 0, STAR}}},
     563             :              }},
     564             :             {RECORD_NAME_CURVE,
     565             :              {
     566             :                  {{{CRID_FIELD, 1, 1},
     567             :                    {INAS_FIELD, 0, STAR},
     568             :                    {PTAS_FIELD, 0, 1},
     569             :                    {SECC_FIELD, 0, 1},
     570             :                    {SEGH_FIELD, 0, 1},
     571             :                    {COCC_FIELD, 0, 1},
     572             :                    {C2IL_FIELD, 0, STAR}}},
     573             :              }},
     574             :             {RECORD_NAME_COMPOSITE_CURVE,
     575             :              {
     576             :                  {{{CCID_FIELD, 1, 1},
     577             :                    {INAS_FIELD, 0, STAR},
     578             :                    {CCOC_FIELD, 0, STAR},
     579             :                    {CUCO_FIELD, 0, STAR}}},
     580             :              }},
     581             :             {RECORD_NAME_SURFACE,
     582             :              {
     583             :                  {{{SRID_FIELD, 1, 1},
     584             :                    {INAS_FIELD, 0, STAR},
     585             :                    {RIAS_FIELD, 0, STAR}}},
     586             :              }},
     587             :             {RECORD_NAME_FEATURE_TYPE,
     588             :              {
     589             :                  {{{FRID_FIELD, 1, 1},
     590             :                    {FOID_FIELD, 0, 1},
     591             :                    {ATTR_FIELD, 0, STAR},
     592             :                    {INAS_FIELD, 0, STAR},
     593             :                    {SPAS_FIELD, 0, STAR},
     594             :                    {FASC_FIELD, 0, STAR},
     595             :                    {MASK_FIELD, 0, STAR}}},
     596             :              }},
     597        3078 :         };
     598             : 
     599             :     // Loop through all records (except first DSID already parsed)
     600             :     // and dispatch them to the appropriate DDFRecordIndex member variable.
     601         114 :     const DDFRecord *poRecord = nullptr;
     602         114 :     const int iFirstRecord = 1;
     603         269 :     for (int iRecord = iFirstRecord;
     604         269 :          poRecordIn || (poRecord = poCurModule->ReadRecord()) != nullptr;
     605             :          ++iRecord)
     606             :     {
     607         200 :         if (poRecordIn)
     608         114 :             std::swap(poRecord, poRecordIn);
     609             : 
     610         200 :         const auto poField = poRecord->GetField(0);
     611         200 :         if (!poField)
     612          44 :             return EMIT_ERROR(
     613             :                 CPLSPrintf("Record index %d without field.", iRecord));
     614         200 :         const char *pszFieldName = poField->GetFieldDefn()->GetName();
     615             : 
     616             :         // Record name
     617             :         const RecordName nRCNM =
     618         200 :             poRecord->GetIntSubfield(pszFieldName, 0, RCNM_SUBFIELD, 0);
     619             : 
     620             :         // Record identifier
     621             :         const int nRCID =
     622         200 :             poRecord->GetIntSubfield(pszFieldName, 0, RCID_SUBFIELD, 0);
     623             : 
     624         200 :         if (!CheckFieldDefinitions(poRecord, iRecord, nRCNM, nRCID,
     625             :                                    mapExpectedFields))
     626           0 :             return false;
     627             : 
     628             :         // Record version
     629             :         const int nRVER =
     630         200 :             poRecord->GetIntSubfield(pszFieldName, 0, RVER_SUBFIELD, 0);
     631             : 
     632             :         // Record update instruction
     633             :         const int nRUIN =
     634         200 :             poRecord->GetIntSubfield(pszFieldName, 0, RUIN_SUBFIELD, 0);
     635             : 
     636         200 :         const auto iterRecordTypeDesc = std::find_if(
     637             :             std::begin(asRecordTypeDesc), std::end(asRecordTypeDesc),
     638         745 :             [pszFieldName](const auto &sRecordTypeDesc)
     639             :             {
     640         745 :                 return strcmp(pszFieldName, sRecordTypeDesc.pszFieldName) == 0;
     641             :             });
     642         200 :         if (iterRecordTypeDesc != std::end(asRecordTypeDesc))
     643             :         {
     644         200 :             const auto &sRecordTypeDesc = *iterRecordTypeDesc;
     645             : 
     646         202 :             if (nRCNM != sRecordTypeDesc.nRCNM &&
     647           2 :                 !EMIT_ERROR_OR_WARNING(
     648             :                     CPLSPrintf("Record index %d: invalid value %d for RCNM "
     649             :                                "subfield of %s.",
     650             :                                iRecord, static_cast<int>(nRCNM), pszFieldName)))
     651             :             {
     652           1 :                 return false;
     653             :             }
     654             : 
     655         199 :             if (nRCID <= 0)
     656             :             {
     657           2 :                 if (!EMIT_ERROR_OR_WARNING(
     658             :                         CPLSPrintf("Record index %d: invalid value %d for "
     659             :                                    "RCID subfield of %s.",
     660             :                                    iRecord, nRCID, pszFieldName)))
     661             :                 {
     662           1 :                     return false;
     663             :                 }
     664           1 :                 break;
     665             :             }
     666             : 
     667             :             DDFRecord *poExistingRecord =
     668         197 :                 sRecordTypeDesc.oIndex.FindRecord(nRCID);
     669         197 :             if (nRUIN == INSTRUCTION_UPDATE || nRUIN == INSTRUCTION_DELETE)
     670             :             {
     671         154 :                 if (!poExistingRecord)
     672             :                 {
     673           2 :                     if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     674             :                             "Record index %d, RCNM=%d, RCID=%d: no such "
     675             :                             "record.",
     676             :                             iRecord, static_cast<int>(nRCNM), nRCID)))
     677             :                     {
     678           1 :                         return false;
     679             :                     }
     680           1 :                     continue;
     681             :                 }
     682             : 
     683         152 :                 const int nOldRVER = poExistingRecord->GetIntSubfield(
     684             :                     pszFieldName, 0, RVER_SUBFIELD, 0);
     685         152 :                 if (nRVER != nOldRVER + 1)
     686             :                 {
     687           2 :                     if (!EMIT_ERROR_OR_WARNING(
     688             :                             CPLSPrintf("Record index %d, RCNM=%d, RCID=%d: got "
     689             :                                        "RVER=%d, expected %d.",
     690             :                                        iRecord, static_cast<int>(nRCNM), nRCID,
     691             :                                        nRVER, nOldRVER + 1)))
     692             :                     {
     693           1 :                         return false;
     694             :                     }
     695             :                 }
     696             :             }
     697             : 
     698         194 :             if (nRUIN == INSTRUCTION_INSERT)
     699             :             {
     700          43 :                 if (poExistingRecord &&
     701           2 :                     !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     702             :                         "Record index %d: several %s records have RCID = %d.",
     703             :                         iRecord, pszFieldName, nRCID)))
     704             :                 {
     705           2 :                     return false;
     706             :                 }
     707             : 
     708          40 :                 auto poClone = poRecord->Clone();
     709          40 :                 if (!UpdateCodesInRecord(poClone.get()))
     710           0 :                     return false;
     711             : 
     712          40 :                 if (!poClone->TransferTo(&m_oMainModule))
     713           1 :                     return false;
     714             : 
     715          39 :                 sRecordTypeDesc.oIndex.AddRecord(nRCID, std::move(poClone));
     716             : 
     717          39 :                 sRecordTypeDesc.nExpectedCount++;
     718             :             }
     719         153 :             else if (nRUIN == INSTRUCTION_UPDATE)
     720             :             {
     721         144 :                 CPLAssert(poExistingRecord);
     722             : 
     723         144 :                 auto poClone = poRecord->Clone();
     724         144 :                 if (!UpdateCodesInRecord(poClone.get()))
     725           7 :                     return false;
     726             : 
     727         137 :                 if (!ProcessUpdateRecord(poClone.get(), poExistingRecord))
     728          30 :                     return false;
     729             :             }
     730           9 :             else if (nRUIN == INSTRUCTION_DELETE)
     731             :             {
     732           7 :                 CPLAssert(poExistingRecord);
     733             : 
     734           7 :                 sRecordTypeDesc.oIndex.RemoveRecord(nRCID);
     735             : 
     736           7 :                 sRecordTypeDesc.nExpectedCount--;
     737             :             }
     738           2 :             else if (!EMIT_ERROR_OR_WARNING(
     739             :                          CPLSPrintf("Record index %d, RCNM=%d, RCID=%d: wrong "
     740             :                                     "value %d for RUIN "
     741             :                                     "subfield of %s field.",
     742             :                                     iRecord, static_cast<int>(nRCNM), nRCID,
     743             :                                     nRUIN, pszFieldName)))
     744             :             {
     745           1 :                 return false;
     746             :             }
     747             :         }
     748           0 :         else if (!EMIT_ERROR_OR_WARNING(
     749             :                      CPLSPrintf("Record index %d: unknown field name %s.",
     750             :                                 iRecord, pszFieldName)))
     751             :         {
     752           0 :             return false;
     753             :         }
     754             :     }
     755             : 
     756             : #ifdef DEBUG
     757             :     // Check consistency between number of records of each category (information
     758             :     // type, point, etc.) and the number actually found.
     759         560 :     for (const auto &sRecordTypeDesc : asRecordTypeDesc)
     760             :     {
     761         490 :         if (sRecordTypeDesc.oIndex.GetCount() !=
     762         490 :                 sRecordTypeDesc.nExpectedCount &&
     763           0 :             !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     764             :                 "%d %s records mentioned in DSSI field, but %d actually found.",
     765             :                 sRecordTypeDesc.nExpectedCount, sRecordTypeDesc.pszType,
     766             :                 sRecordTypeDesc.oIndex.GetCount())))
     767             :         {
     768           0 :             return false;
     769             :         }
     770             :     }
     771             : #endif
     772             : 
     773          70 :     return true;
     774             : }
     775             : 
     776             : /************************************************************************/
     777             : /*                        UpdateCodesInRecord()                         */
     778             : /************************************************************************/
     779             : 
     780             : /** Update NITC, NFTC, NATC, NIAC, NFAC and NARC subfields from the value
     781             :  * used in the update file to the value of the merged dataset.
     782             :  */
     783         184 : bool OGRS101Reader::UpdateCodesInRecord(DDFRecord *poRecord) const
     784             : {
     785         184 :     const auto poIDField = poRecord->GetField(0);
     786         184 :     CPLAssert(poIDField);
     787         184 :     const char *pszIDFieldName = poIDField->GetFieldDefn()->GetName();
     788         184 :     CPLAssert(pszIDFieldName);
     789             : 
     790             :     // Record name
     791             :     const RecordName nRCNM =
     792         184 :         poRecord->GetIntSubfield(pszIDFieldName, 0, RCNM_SUBFIELD, 0);
     793             : 
     794             :     // Record identifier
     795             :     const int nRCID =
     796         184 :         poRecord->GetIntSubfield(pszIDFieldName, 0, RCID_SUBFIELD, 0);
     797             : 
     798         184 :     if (EQUAL(pszIDFieldName, IRID_FIELD))
     799             :     {
     800             :         const InfoTypeCode nNITC(
     801          18 :             poRecord->GetIntSubfield(poIDField, NITC_SUBFIELD, 0));
     802             : 
     803          18 :         const auto oIter = m_informationTypeCodesRemapping.find(nNITC);
     804          18 :         if (oIter == m_informationTypeCodesRemapping.end())
     805             :         {
     806           2 :             if (!EMIT_ERROR_OR_WARNING(
     807             :                     CPLSPrintf("%s, RCNM=%d, RCID=%d: unknown NITC=%d",
     808             :                                m_osFilename.c_str(), static_cast<int>(nRCNM),
     809             :                                nRCID, static_cast<int>(nNITC))))
     810             :             {
     811           1 :                 return false;
     812             :             }
     813             :         }
     814             :         else
     815             :         {
     816          16 :             poRecord->SetIntSubfield(pszIDFieldName, 0, NITC_SUBFIELD, 0,
     817          16 :                                      static_cast<int>(oIter->second));
     818             :         }
     819             :     }
     820         166 :     else if (EQUAL(pszIDFieldName, FRID_FIELD))
     821             :     {
     822             :         const FeatureTypeCode nNFTC(
     823          24 :             poRecord->GetIntSubfield(poIDField, NFTC_SUBFIELD, 0));
     824             : 
     825          24 :         const auto oIter = m_featureTypeCodesRemapping.find(nNFTC);
     826          24 :         if (oIter == m_featureTypeCodesRemapping.end())
     827             :         {
     828           2 :             if (!EMIT_ERROR_OR_WARNING(
     829             :                     CPLSPrintf("%s, RCNM=%d, RCID=%d: unknown NFTC=%d",
     830             :                                m_osFilename.c_str(), static_cast<int>(nRCNM),
     831             :                                nRCID, static_cast<int>(nNFTC))))
     832             :             {
     833           1 :                 return false;
     834             :             }
     835             :         }
     836             :         else
     837             :         {
     838          22 :             poRecord->SetIntSubfield(pszIDFieldName, 0, NFTC_SUBFIELD, 0,
     839          22 :                                      static_cast<int>(oIter->second));
     840             :         }
     841             :     }
     842             : 
     843         723 :     for (const char *pszFieldName : {ATTR_FIELD, INAS_FIELD, FASC_FIELD})
     844             :     {
     845         543 :         auto apoFields = poRecord->GetFields(pszFieldName);
     846         638 :         for (auto [fieldIdxSizeT, poField] : cpl::enumerate(apoFields))
     847             :         {
     848          97 :             const int fieldIdx = static_cast<int>(fieldIdxSizeT);
     849             :             const int nRepeatCount =
     850          97 :                 poField->GetParts().size() == 2
     851          97 :                     ? poField->GetParts()[1]->GetRepeatCount()
     852          28 :                     : poField->GetRepeatCount();
     853         288 :             for (int iSubField = 0; iSubField < nRepeatCount; ++iSubField)
     854             :             {
     855             :                 const AttrCode nNATC =
     856         193 :                     poRecord->GetIntSubfield(poField, NATC_SUBFIELD, iSubField);
     857         193 :                 const auto oIter = m_attributeCodesRemapping.find(nNATC);
     858         193 :                 if (oIter == m_attributeCodesRemapping.end())
     859             :                 {
     860           4 :                     if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     861             :                             "%s, RCNM=%d, RCID=%d, %s[iField=%d, "
     862             :                             "iSubField=%d]: unknown NATC=%d",
     863             :                             m_osFilename.c_str(), static_cast<int>(nRCNM),
     864             :                             nRCID, pszFieldName, fieldIdx, iSubField,
     865             :                             static_cast<int>(nNATC))))
     866             :                     {
     867           2 :                         return false;
     868             :                     }
     869             :                 }
     870             :                 else
     871             :                 {
     872         189 :                     poRecord->SetIntSubfield(pszFieldName, fieldIdx,
     873             :                                              NATC_SUBFIELD, iSubField,
     874         189 :                                              static_cast<int>(oIter->second));
     875             :                 }
     876             :             }
     877             :         }
     878             :     }
     879             : 
     880         536 :     for (const char *pszFieldName : {INAS_FIELD, FASC_FIELD})
     881             :     {
     882         359 :         auto apoFields = poRecord->GetFields(pszFieldName);
     883         423 :         for (auto [fieldIdxSizeT, poField] : cpl::enumerate(apoFields))
     884             :         {
     885          67 :             const int fieldIdx = static_cast<int>(fieldIdxSizeT);
     886             :             const int nRepeatCount =
     887          67 :                 poField->GetParts().size() == 2
     888          67 :                     ? poField->GetParts()[1]->GetRepeatCount()
     889           0 :                     : poField->GetRepeatCount();
     890         115 :             for (int iSubField = 0; iSubField < nRepeatCount; ++iSubField)
     891             :             {
     892          51 :                 if (EQUAL(pszFieldName, INAS_FIELD))
     893             :                 {
     894             :                     const InfoAssocCode nNIAC = poRecord->GetIntSubfield(
     895          42 :                         poField, NIAC_SUBFIELD, iSubField);
     896             :                     const auto oIter =
     897          42 :                         m_informationAssociationCodesRemapping.find(nNIAC);
     898          42 :                     if (oIter == m_informationAssociationCodesRemapping.end())
     899             :                     {
     900           0 :                         if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     901             :                                 "%s, RCNM=%d, RCID=%d, %s[iField=%d, "
     902             :                                 "iSubField=%d]: unknown NIAC=%d",
     903             :                                 m_osFilename.c_str(), static_cast<int>(nRCNM),
     904             :                                 nRCID, pszFieldName, fieldIdx, iSubField,
     905             :                                 static_cast<int>(nNIAC))))
     906             :                         {
     907           0 :                             return false;
     908             :                         }
     909             :                     }
     910             :                     else
     911             :                     {
     912          42 :                         poRecord->SetIntSubfield(
     913             :                             pszFieldName, fieldIdx, NIAC_SUBFIELD, iSubField,
     914          42 :                             static_cast<int>(oIter->second));
     915             :                     }
     916             :                 }
     917             :                 else
     918             :                 {
     919             :                     const FeatureAssocCode nNFAC = poRecord->GetIntSubfield(
     920           9 :                         poField, NFAC_SUBFIELD, iSubField);
     921             :                     const auto oIter =
     922           9 :                         m_featureAssociationCodesRemapping.find(nNFAC);
     923           9 :                     if (oIter == m_featureAssociationCodesRemapping.end())
     924             :                     {
     925           2 :                         if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     926             :                                 "%s, RCNM=%d, RCID=%d, %s[iField=%d, "
     927             :                                 "iSubField=%d]: unknown NFAC=%d",
     928             :                                 m_osFilename.c_str(), static_cast<int>(nRCNM),
     929             :                                 nRCID, pszFieldName, fieldIdx, iSubField,
     930             :                                 static_cast<int>(nNFAC))))
     931             :                         {
     932           1 :                             return false;
     933             :                         }
     934             :                     }
     935             :                     else
     936             :                     {
     937           7 :                         poRecord->SetIntSubfield(
     938             :                             pszFieldName, fieldIdx, NFAC_SUBFIELD, iSubField,
     939           7 :                             static_cast<int>(oIter->second));
     940             :                     }
     941             :                 }
     942             : 
     943             :                 {
     944             :                     const AssocRoleCode nNARC = poRecord->GetIntSubfield(
     945          50 :                         poField, NARC_SUBFIELD, iSubField);
     946             :                     const auto oIter =
     947          50 :                         m_associationRoleCodesRemapping.find(nNARC);
     948          50 :                     if (oIter == m_associationRoleCodesRemapping.end())
     949             :                     {
     950           4 :                         if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     951             :                                 "%s, RCNM=%d, RCID=%d, %s[iField=%d, "
     952             :                                 "iSubField=%d]: unknown NARC=%d",
     953             :                                 m_osFilename.c_str(), static_cast<int>(nRCNM),
     954             :                                 nRCID, pszFieldName, fieldIdx, iSubField,
     955             :                                 static_cast<int>(nNARC))))
     956             :                         {
     957           2 :                             return false;
     958             :                         }
     959             :                     }
     960             :                     else
     961             :                     {
     962          46 :                         poRecord->SetIntSubfield(
     963             :                             pszFieldName, fieldIdx, NARC_SUBFIELD, iSubField,
     964          46 :                             static_cast<int>(oIter->second));
     965             :                     }
     966             :                 }
     967             :             }
     968             :         }
     969             :     }
     970             : 
     971         177 :     return true;
     972             : }
     973             : 
     974             : /************************************************************************/
     975             : /*                        ProcessUpdateRecord()                         */
     976             : /************************************************************************/
     977             : 
     978         137 : bool OGRS101Reader::ProcessUpdateRecord(const DDFRecord *poUpdateRecord,
     979             :                                         DDFRecord *poTargetRecord) const
     980             : {
     981         137 :     const auto poIDField = poUpdateRecord->GetField(0);
     982         137 :     CPLAssert(poIDField);
     983         137 :     const char *pszIDFieldName = poIDField->GetFieldDefn()->GetName();
     984         137 :     CPLAssert(pszIDFieldName);
     985             : 
     986             :     // Record version
     987             :     const int nRVER =
     988         137 :         poUpdateRecord->GetIntSubfield(pszIDFieldName, 0, RVER_SUBFIELD, 0);
     989         137 :     poTargetRecord->SetIntSubfield(pszIDFieldName, 0, RVER_SUBFIELD, 0, nRVER);
     990             : 
     991         137 :     if (!ProcessUpdateINASOrFASC(poUpdateRecord, poTargetRecord, INAS_FIELD))
     992           2 :         return false;
     993             : 
     994         135 :     bool bRet = true;
     995         135 :     if (EQUAL(pszIDFieldName, IRID_FIELD))
     996             :     {
     997          14 :         bRet = ProcessUpdateATTR(poUpdateRecord, poTargetRecord);
     998             :     }
     999         121 :     else if (EQUAL(pszIDFieldName, PRID_FIELD))
    1000             :     {
    1001          11 :         bRet = ProcessUpdateRecordPoint(poUpdateRecord, poTargetRecord);
    1002             :     }
    1003         110 :     else if (EQUAL(pszIDFieldName, MRID_FIELD))
    1004             :     {
    1005          33 :         bRet = ProcessUpdateRecordMultiPoint(poUpdateRecord, poTargetRecord);
    1006             :     }
    1007          77 :     else if (EQUAL(pszIDFieldName, CRID_FIELD))
    1008             :     {
    1009          15 :         bRet = ProcessUpdateRecordCurve(poUpdateRecord, poTargetRecord);
    1010             :     }
    1011          62 :     else if (EQUAL(pszIDFieldName, CCID_FIELD))
    1012             :     {
    1013             :         bRet =
    1014          36 :             ProcessUpdateRecordCompositeCurve(poUpdateRecord, poTargetRecord);
    1015             :     }
    1016          26 :     else if (EQUAL(pszIDFieldName, SRID_FIELD))
    1017             :     {
    1018           7 :         bRet = ProcessUpdateRecordSurface(poUpdateRecord, poTargetRecord);
    1019             :     }
    1020          19 :     else if (EQUAL(pszIDFieldName, FRID_FIELD))
    1021             :     {
    1022          38 :         bRet = ProcessUpdateATTR(poUpdateRecord, poTargetRecord) &&
    1023          19 :                ProcessUpdateINASOrFASC(poUpdateRecord, poTargetRecord,
    1024          38 :                                        FASC_FIELD) &&
    1025          19 :                ProcessUpdateRecordFeatureType(poUpdateRecord, poTargetRecord);
    1026             :     }
    1027             : 
    1028         135 :     return bRet;
    1029             : }
    1030             : 
    1031             : /************************************************************************/
    1032             : /*                FillFeatureWithNonAttrAssocSubfields()                */
    1033             : /************************************************************************/
    1034             : 
    1035             : /** Fill attribute fields of the provided feature with the fixed subfields
    1036             :  * of the INAS or FASC field.
    1037             :  */
    1038        5155 : bool OGRS101Reader::FillFeatureWithNonAttrAssocSubfields(
    1039             :     const DDFRecord *poRecord, int iRecord, const char *pszFieldName,
    1040             :     OGRFeature &oFeature) const
    1041             : {
    1042        5155 :     const bool bIsINAS = EQUAL(pszFieldName, INAS_FIELD);
    1043             : 
    1044       10310 :     const auto apoAssocFields = poRecord->GetFields(pszFieldName);
    1045       10310 :     const bool bMultipleAssocs = oFeature.GetDefnRef()->GetFieldIndex(
    1046             :                                      bIsINAS ? OGR_FIELD_NAME_REF_INFO_RID
    1047        5155 :                                              : OGR_FIELD_NAME_REF_FEAT_RID) < 0;
    1048        5155 :     const auto &assocRecord =
    1049             :         bIsINAS ? m_oInformationTypeRecordIndex : m_oFeatureTypeRecordIndex;
    1050             : 
    1051        5990 :     for (const auto &[iField, poField] : cpl::enumerate(apoAssocFields))
    1052             :     {
    1053             :         const std::string osSuffix =
    1054         156 :             bMultipleAssocs ? CPLSPrintf("[%d]", static_cast<int>(iField) + 1)
    1055         991 :                             : "";
    1056             : 
    1057          14 :         const auto GetErrorContext = [poRecord, iRecord]()
    1058             :         {
    1059           7 :             const auto poIDField = poRecord->GetField(0);
    1060           7 :             CPLAssert(poIDField);
    1061           7 :             const char *pszIDFieldName = poIDField->GetFieldDefn()->GetName();
    1062           7 :             return CPLSPrintf("Record index=%d of %s", iRecord, pszIDFieldName);
    1063         835 :         };
    1064             : 
    1065             :         const RecordName nRRNM =
    1066         835 :             poRecord->GetIntSubfield(poField, RRNM_SUBFIELD, 0);
    1067         835 :         const RecordName nExpectedRRNM =
    1068         835 :             bIsINAS ? RECORD_NAME_INFORMATION_TYPE : RECORD_NAME_FEATURE_TYPE;
    1069         835 :         if (nRRNM != nExpectedRRNM)
    1070             :         {
    1071           4 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1072             :                     "%s: Invalid value for RRNM subfield of %s field: "
    1073             :                     "got %d, expected %d.",
    1074             :                     GetErrorContext(), pszFieldName, static_cast<int>(nRRNM),
    1075             :                     static_cast<int>(nExpectedRRNM))))
    1076             :             {
    1077           0 :                 return false;
    1078             :             }
    1079             :         }
    1080             : 
    1081         835 :         const int nRRID = poRecord->GetIntSubfield(poField, RRID_SUBFIELD, 0);
    1082         835 :         if (!assocRecord.FindRecord(nRRID))
    1083             :         {
    1084           1 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1085             :                     "%s: Invalid value %d for RRID subfield of %s field: "
    1086             :                     "does not match the record identifier of an existing "
    1087             :                     "InformationType record.",
    1088             :                     GetErrorContext(), static_cast<int>(nRRID), pszFieldName)))
    1089             :             {
    1090           0 :                 return false;
    1091             :             }
    1092             :         }
    1093             : 
    1094         835 :         if (bIsINAS)
    1095             :         {
    1096         709 :             oFeature.SetField((OGR_FIELD_NAME_REF_INFO_RID + osSuffix).c_str(),
    1097             :                               nRRID);
    1098             : 
    1099             :             const InfoAssocCode nNIAC =
    1100         709 :                 poRecord->GetIntSubfield(poField, NIAC_SUBFIELD, 0);
    1101         709 :             const auto iterIAC = m_informationAssociationCodes.find(nNIAC);
    1102         709 :             if (iterIAC == m_informationAssociationCodes.end())
    1103             :             {
    1104           1 :                 if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1105             :                         "%s: cannot find attribute code %d in IACS field "
    1106             :                         "of the Dataset General Information Record.",
    1107             :                         GetErrorContext(), static_cast<int>(nNIAC))))
    1108             :                 {
    1109           0 :                     return false;
    1110             :                 }
    1111             :                 else
    1112             :                 {
    1113           1 :                     oFeature.SetField((OGR_FIELD_NAME_NIAC + osSuffix).c_str(),
    1114             :                                       CPLSPrintf("informationAssociationCode%d",
    1115             :                                                  static_cast<int>(nNIAC)));
    1116             :                 }
    1117             :             }
    1118             :             else
    1119             :             {
    1120         708 :                 oFeature.SetField((OGR_FIELD_NAME_NIAC + osSuffix).c_str(),
    1121         708 :                                   iterIAC->second.c_str());
    1122             :             }
    1123             :         }
    1124             :         else
    1125             :         {
    1126         126 :             const auto oIterFID = m_oMapFeatureTypeIdToFDefn.find(nRRID);
    1127         126 :             if (oIterFID != m_oMapFeatureTypeIdToFDefn.end())
    1128             :             {
    1129         126 :                 oFeature.SetField(
    1130         252 :                     (OGR_FIELD_NAME_REF_FEAT_LAYER_NAME + osSuffix).c_str(),
    1131         126 :                     oIterFID->second->GetName());
    1132             :             }
    1133             : 
    1134         126 :             oFeature.SetField((OGR_FIELD_NAME_REF_FEAT_RID + osSuffix).c_str(),
    1135             :                               nRRID);
    1136             : 
    1137             :             const FeatureAssocCode nNFAC =
    1138         126 :                 poRecord->GetIntSubfield(poField, NFAC_SUBFIELD, 0);
    1139         126 :             const auto iterFAC = m_featureAssociationCodes.find(nNFAC);
    1140         126 :             if (iterFAC == m_featureAssociationCodes.end())
    1141             :             {
    1142           0 :                 if (!EMIT_ERROR_OR_WARNING(
    1143             :                         CPLSPrintf("%s: cannot find feature association code "
    1144             :                                    "%d in FACS field "
    1145             :                                    "of the Dataset General Information Record.",
    1146             :                                    GetErrorContext(), static_cast<int>(nNFAC))))
    1147             :                 {
    1148           0 :                     return false;
    1149             :                 }
    1150             :                 else
    1151             :                 {
    1152           0 :                     oFeature.SetField((OGR_FIELD_NAME_NFAC + osSuffix).c_str(),
    1153             :                                       CPLSPrintf("featureAssociationCode%d",
    1154             :                                                  static_cast<int>(nNFAC)));
    1155             :                 }
    1156             :             }
    1157             :             else
    1158             :             {
    1159         126 :                 oFeature.SetField((OGR_FIELD_NAME_NFAC + osSuffix).c_str(),
    1160         126 :                                   iterFAC->second.c_str());
    1161             :             }
    1162             :         }
    1163             : 
    1164             :         const AssocRoleCode nNARC =
    1165         835 :             poRecord->GetIntSubfield(poField, NARC_SUBFIELD, 0);
    1166         835 :         const auto iterARC = m_associationRoleCodes.find(nNARC);
    1167         835 :         if (iterARC == m_associationRoleCodes.end())
    1168             :         {
    1169           1 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1170             :                     "%s: cannot find attribute code %d in ARCS field "
    1171             :                     "of the Dataset General Information Record.",
    1172             :                     GetErrorContext(), static_cast<int>(nNARC))))
    1173             :             {
    1174           0 :                 return false;
    1175             :             }
    1176             :             else
    1177             :             {
    1178           2 :                 oFeature.SetField(((bIsINAS ? OGR_FIELD_NAME_NARC
    1179           2 :                                             : OGR_FIELD_NAME_FEATURE_NARC) +
    1180             :                                    osSuffix)
    1181             :                                       .c_str(),
    1182             :                                   CPLSPrintf("associationRoleCode%d",
    1183             :                                              static_cast<int>(nNARC)));
    1184             :             }
    1185             :         }
    1186             :         else
    1187             :         {
    1188        1668 :             oFeature.SetField(
    1189        1668 :                 ((bIsINAS ? OGR_FIELD_NAME_NARC : OGR_FIELD_NAME_FEATURE_NARC) +
    1190             :                  osSuffix)
    1191             :                     .c_str(),
    1192         834 :                 iterARC->second.c_str());
    1193             :         }
    1194             : 
    1195         835 :         const char *pszSubFieldName = bIsINAS ? IUIN_SUBFIELD : FAUI_SUBFIELD;
    1196             :         const int nInstruction =
    1197         835 :             poRecord->GetIntSubfield(poField, pszSubFieldName, 0);
    1198         835 :         if (nInstruction != INSTRUCTION_INSERT)
    1199             :         {
    1200           0 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: wrong value %d for %s "
    1201             :                                                   "subfield of %s field.",
    1202             :                                                   GetErrorContext(),
    1203             :                                                   nInstruction, pszSubFieldName,
    1204             :                                                   pszFieldName)))
    1205             :             {
    1206           0 :                 return false;
    1207             :             }
    1208             :         }
    1209             :     }
    1210             : 
    1211        5155 :     return true;
    1212             : }

Generated by: LCOV version 1.14