LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101readerattributes.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 664 729 91.1 %
Date: 2026-08-22 15:37:05 Functions: 19 19 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 "ogrs101featurecatalog.h"
      15             : #include "ogrs101readerconstants.h"
      16             : 
      17             : #include <algorithm>
      18             : #include <cfloat>
      19             : #include <limits>
      20             : #include <memory>
      21             : #include <optional>
      22             : #include <set>
      23             : #include <tuple>
      24             : #include <utility>
      25             : 
      26             : #include "include_fast_float.h"
      27             : 
      28             : /************************************************************************/
      29             : /*                          IngestAttributes()                          */
      30             : /************************************************************************/
      31             : 
      32             : /** For a given record that has a ATTR/INAS/FACS field, ingest all attributes
      33             :  * from a particular instance of that field
      34             :  */
      35        2157 : bool OGRS101Reader::IngestAttributes(
      36             :     const DDFRecord *poRecord, int iRecord, const char *pszIDFieldName,
      37             :     const char *pszAttrFieldName, const DDFField *poATTRField, int iField,
      38             :     bool bMultipleFields, std::vector<S101AttrDef> &asS101AttrDefs) const
      39             : {
      40        4314 :     std::set<std::tuple<AttrCode, AttrRepeat, AttrIndex>> oSetNATC_ATIX_PAIX;
      41             : 
      42          52 :     const auto GetErrorContext = [iRecord, pszIDFieldName, pszAttrFieldName,
      43         104 :                                   iField, bMultipleFields](AttrIndex iATTR)
      44             :     {
      45          52 :         if (bMultipleFields)
      46             :         {
      47           0 :             return CPLSPrintf(
      48             :                 "Record index=%d of %s, %s[%d] field, attribute idx=%d",
      49             :                 iRecord, pszIDFieldName, pszAttrFieldName, iField,
      50           0 :                 static_cast<int>(iATTR));
      51             :         }
      52             :         else
      53             :         {
      54          52 :             return CPLSPrintf(
      55             :                 "Record index=%d of %s, %s field, attribute idx=%d", iRecord,
      56          52 :                 pszIDFieldName, pszAttrFieldName, static_cast<int>(iATTR));
      57             :         }
      58        2157 :     };
      59             : 
      60             :     const AttrCode nLargestNATC(
      61        2157 :         !m_attributeCodes.empty() ? m_attributeCodes.rbegin()->first : 0);
      62             :     const bool bAttributeCodesSequential =
      63        4311 :         !m_attributeCodes.empty() && m_attributeCodes.begin()->first == 1 &&
      64        2154 :         static_cast<size_t>(static_cast<int>(nLargestNATC)) ==
      65        2154 :             m_attributeCodes.size();
      66             : 
      67        2157 :     const int nRepeatCount = poATTRField->GetRepeatCount();
      68        2157 :     AttrCode nLastNATC = -1;
      69        2157 :     AttrRepeat nLastATIX = -1;
      70        2157 :     AttrIndex nLastPAIX = -1;
      71             : 
      72             :     // Find multi-valued parts of the path
      73        4314 :     std::map<std::pair<AttrCode, AttrIndex>, int> oMapOccurrenceCount;
      74        5412 :     for (AttrIndex iATTR = 0; iATTR < nRepeatCount; ++iATTR)
      75             :     {
      76             :         const auto GetIntSubfield =
      77       13020 :             [poRecord, poATTRField, iATTR](const char *pszSubFieldName)
      78             :         {
      79        6510 :             return poRecord->GetIntSubfield(poATTRField, pszSubFieldName,
      80        6510 :                                             static_cast<int>(iATTR));
      81        3255 :         };
      82             : 
      83        3255 :         ++oMapOccurrenceCount[{GetIntSubfield(NATC_SUBFIELD),
      84        3255 :                                GetIntSubfield(PAIX_SUBFIELD)}];
      85             :     }
      86             : 
      87        2157 :     const size_t nS101AttrDefsBaseIdx = asS101AttrDefs.size();
      88             : 
      89        5391 :     for (AttrIndex iATTR = 0; iATTR < nRepeatCount; ++iATTR)
      90             :     {
      91             :         const auto GetIntSubfield =
      92       25908 :             [poRecord, poATTRField, iATTR](const char *pszSubFieldName)
      93             :         {
      94       12954 :             return poRecord->GetIntSubfield(poATTRField, pszSubFieldName,
      95       12954 :                                             static_cast<int>(iATTR));
      96        3242 :         };
      97             : 
      98        3242 :         const int nATIN = GetIntSubfield(ATIN_SUBFIELD);
      99        3242 :         if (nATIN != INSTRUCTION_INSERT)
     100             :         {
     101           3 :             if (!EMIT_ERROR_OR_WARNING(
     102             :                     CPLSPrintf("%s: wrong value %d for ATIN subfield.",
     103             :                                GetErrorContext(iATTR), nATIN)))
     104             :             {
     105           8 :                 return false;
     106             :             }
     107           2 :             nLastNATC = -1;
     108           2 :             nLastATIX = -1;
     109           2 :             nLastPAIX = -1;
     110           2 :             asS101AttrDefs.push_back(S101AttrDef());
     111           6 :             continue;
     112             :         }
     113             : 
     114        3239 :         const AttrCode nNATC(GetIntSubfield(NATC_SUBFIELD));
     115        3268 :         if (!cpl::contains(m_attributeCodes, nNATC) &&
     116          29 :             !EMIT_ERROR_OR_WARNING(
     117             :                 CPLSPrintf("%s: cannot find attribute code %d in ATCS field "
     118             :                            "of the Dataset General Information Record%s.",
     119             :                            GetErrorContext(iATTR), static_cast<int>(nNATC),
     120             :                            bAttributeCodesSequential
     121             :                                ? CPLSPrintf(". Must be in [1, %d]",
     122             :                                             static_cast<int>(nLargestNATC))
     123             :                                : "")))
     124             :         {
     125           1 :             return false;
     126             :         }
     127             : 
     128        3238 :         const AttrRepeat nATIX(GetIntSubfield(ATIX_SUBFIELD));
     129        3238 :         if (!(nATIX >= 1 && nATIX <= nRepeatCount))
     130             :         {
     131           3 :             if (!EMIT_ERROR_OR_WARNING(
     132             :                     CPLSPrintf("%s: wrong value %d for ATIX subfield. "
     133             :                                "Must be in [1, %d].",
     134             :                                GetErrorContext(iATTR), static_cast<int>(nATIX),
     135             :                                nRepeatCount)))
     136             :             {
     137           1 :                 return false;
     138             :             }
     139           2 :             nLastNATC = -1;
     140           2 :             nLastATIX = -1;
     141           2 :             nLastPAIX = -1;
     142           2 :             asS101AttrDefs.push_back(S101AttrDef());
     143           2 :             continue;
     144             :         }
     145             : 
     146        3235 :         const AttrIndex nPAIX = GetIntSubfield(PAIX_SUBFIELD);
     147             :         // The parent index must be lower than the current attribute index,
     148             :         // since parents are required to be listed before.
     149        3235 :         if (!(nPAIX >= 0 && nPAIX <= iATTR))
     150             :         {
     151           3 :             if (!EMIT_ERROR_OR_WARNING(
     152             :                     CPLSPrintf("%s: wrong value %d for PAIX subfield. "
     153             :                                "Must be in [0, %d].",
     154             :                                GetErrorContext(iATTR), static_cast<int>(nPAIX),
     155             :                                static_cast<int>(iATTR))))
     156             :             {
     157           1 :                 return false;
     158             :             }
     159           2 :             nLastNATC = -1;
     160           2 :             nLastATIX = -1;
     161           2 :             nLastPAIX = -1;
     162           2 :             asS101AttrDefs.push_back(S101AttrDef());
     163           2 :             continue;
     164             :         }
     165             : 
     166        3232 :         if (nPAIX == nLastPAIX)
     167             :         {
     168         420 :             if (nNATC == nLastNATC)
     169             :             {
     170         115 :                 if (nATIX != nLastATIX + 1 &&
     171         115 :                     !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     172             :                         "%s: wrong value %d for ATIX subfield. Expected %d.",
     173             :                         GetErrorContext(iATTR), static_cast<int>(nATIX),
     174             :                         static_cast<int>(nLastATIX + 1))))
     175             :                 {
     176           1 :                     return false;
     177             :                 }
     178             :             }
     179         308 :             else if (nATIX != 1)
     180             :             {
     181           3 :                 if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     182             :                         "%s: wrong value %d for ATIX subfield. Expected %d.",
     183             :                         GetErrorContext(iATTR), static_cast<int>(nATIX), 1)))
     184             :                 {
     185           1 :                     return false;
     186             :                 }
     187             :             }
     188             :         }
     189             : 
     190             :         // (NATC,ATIX,PAIX) tuple should be unique within a record
     191        3235 :         if (!oSetNATC_ATIX_PAIX.insert({nNATC, nATIX, nPAIX}).second &&
     192           5 :             !EMIT_ERROR_OR_WARNING(
     193             :                 CPLSPrintf("%s: several instances of "
     194             :                            "(NATC,ATIX,PAIX)=(%d,%d,%d) "
     195             :                            "in field %s of the same record.",
     196             :                            GetErrorContext(iATTR), static_cast<int>(nNATC),
     197             :                            static_cast<int>(nATIX), static_cast<int>(nPAIX),
     198             :                            pszAttrFieldName)))
     199             :         {
     200           1 :             return false;
     201             :         }
     202             : 
     203             :         // Does this attribute have a parent?
     204        3229 :         const bool bIsMultiValued = oMapOccurrenceCount[{nNATC, nPAIX}] > 1;
     205        3229 :         PathVector oReversedPath{{nNATC, bIsMultiValued ? nATIX : 0}};
     206        3229 :         if (nPAIX > 0)
     207             :         {
     208             :             // Assertion can't trigger because nPAIX <= iATTR < asS101AttrDefs.size() - nS101AttrDefsBaseIdx
     209        1555 :             CPLAssert(nS101AttrDefsBaseIdx +
     210             :                           static_cast<size_t>(static_cast<int>(nPAIX) - 1) <
     211             :                       asS101AttrDefs.size());
     212        1555 :             auto &sParentAttrDef = asS101AttrDefs[nS101AttrDefsBaseIdx +
     213        1555 :                                                   static_cast<int>(nPAIX) - 1];
     214        1555 :             sParentAttrDef.bIsParent = true;
     215        1558 :             if (!sParentAttrDef.osVal.empty() &&
     216           3 :                 !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     217             :                     "%s: parent attribute of index PAIX=%d has "
     218             :                     "a non empty ATVL subfield.",
     219             :                     GetErrorContext(iATTR), static_cast<int>(nPAIX))))
     220             :             {
     221           1 :                 return false;
     222             :             }
     223             : #if defined(__GNUC__)
     224             : #pragma GCC diagnostic push
     225             : #pragma GCC diagnostic ignored "-Wnull-dereference"
     226             : #endif
     227        1554 :             oReversedPath.insert(oReversedPath.end(),
     228             :                                  sParentAttrDef.oReversedPath.begin(),
     229        3108 :                                  sParentAttrDef.oReversedPath.end());
     230             : #if defined(__GNUC__)
     231             : #pragma GCC diagnostic pop
     232             : #endif
     233             :         }
     234             : 
     235        3228 :         const char *pszATVL = poRecord->GetStringSubfield(
     236             :             poATTRField, ATVL_SUBFIELD, static_cast<int>(iATTR));
     237        3228 :         if (!pszATVL &&
     238           0 :             !EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: cannot read ATVL subfield.",
     239             :                                               GetErrorContext(iATTR))))
     240             :         {
     241           0 :             return false;
     242             :         }
     243             : 
     244        3228 :         S101AttrDef sAttrDef;
     245        3228 :         sAttrDef.iField = iField;
     246        3228 :         sAttrDef.bMultipleFields = bMultipleFields;
     247        3228 :         sAttrDef.oReversedPath = std::move(oReversedPath);
     248        3228 :         if (pszATVL)
     249        3228 :             sAttrDef.osVal = pszATVL;
     250        3228 :         asS101AttrDefs.push_back(std::move(sAttrDef));
     251             : 
     252        3228 :         nLastNATC = nNATC;
     253        3228 :         nLastATIX = nATIX;
     254        3228 :         nLastPAIX = nPAIX;
     255             :     }
     256             : 
     257        2149 :     return true;
     258             : }
     259             : 
     260             : /************************************************************************/
     261             : /*                          IngestAttributes()                          */
     262             : /************************************************************************/
     263             : 
     264             : /** For a given record that has a ATTR/INAS/FACS field, ingest all attributes
     265             :  * from all instances of this field.
     266             :  */
     267        9396 : bool OGRS101Reader::IngestAttributes(
     268             :     const DDFRecord *poRecord, int iRecord, const char *pszIDFieldName,
     269             :     const char *pszAttrFieldName,
     270             :     std::vector<S101AttrDef> &asS101AttrDefs) const
     271             : {
     272        9396 :     asS101AttrDefs.clear();
     273             : 
     274       18792 :     const auto apoATTRFields = poRecord->GetFields(pszAttrFieldName);
     275        9396 :     const int nATTRFieldCount = static_cast<int>(apoATTRFields.size());
     276        9396 :     bool bSuccess = true;
     277        9396 :     if (EQUAL(pszAttrFieldName, ATTR_FIELD))
     278             :     {
     279        2978 :         for (int iATTRField = 0; bSuccess && iATTRField < nATTRFieldCount;
     280             :              ++iATTRField)
     281             :         {
     282         950 :             bSuccess = IngestAttributes(poRecord, iRecord, pszIDFieldName,
     283             :                                         pszAttrFieldName,
     284         950 :                                         apoATTRFields[iATTRField], iATTRField,
     285             :                                         nATTRFieldCount > 1, asS101AttrDefs);
     286             :         }
     287             :     }
     288             :     else
     289             :     {
     290        8575 :         for (int iATTRField = 0; bSuccess && iATTRField < nATTRFieldCount;
     291             :              ++iATTRField)
     292             :         {
     293        1207 :             const auto poINASOrFASCField = apoATTRFields[iATTRField];
     294        1207 :             if (poINASOrFASCField->GetParts().size() != 2)
     295             :             {
     296           0 :                 if (!EMIT_ERROR_OR_WARNING(
     297             :                         CPLSPrintf("Record index=%d of %s: missing components "
     298             :                                    "in %s field.",
     299             :                                    iRecord, pszIDFieldName, pszAttrFieldName)))
     300             :                 {
     301           0 :                     return false;
     302             :                 }
     303           0 :                 return true;
     304             :             }
     305        1207 :             const auto poATTRField = poINASOrFASCField->GetParts()[1].get();
     306             : 
     307        1207 :             bSuccess = IngestAttributes(
     308             :                 poRecord, iRecord, pszIDFieldName, pszAttrFieldName,
     309             :                 poATTRField, iATTRField, nATTRFieldCount > 1, asS101AttrDefs);
     310             :         }
     311             :     }
     312             : 
     313       12630 :     for (auto &sAttrDef : asS101AttrDefs)
     314             :     {
     315        3234 :         if (sAttrDef.bIsParent || sAttrDef.oReversedPath.empty())
     316         813 :             continue;
     317             : 
     318             :         // For last component, set the repetition part to 0, to be
     319             :         // actually able to detect multi-valued attributes!
     320        2421 :         sAttrDef.oReversedPath.front().second = 0;
     321             :     }
     322             : 
     323        9396 :     return bSuccess;
     324             : }
     325             : 
     326             : /************************************************************************/
     327             : /*                           BuildFieldName()                           */
     328             : /************************************************************************/
     329             : 
     330             : /** Returns a string with the concatenation of the parts, in reverse order.
     331             :  */
     332        2377 : std::string OGRS101Reader::BuildFieldName(const PathVector &oReversedPath,
     333             :                                           const char *pszAttrFieldName,
     334             :                                           int iField, bool bMultipleFields,
     335             :                                           const char *pszIDFieldName) const
     336             : {
     337        2377 :     std::string osAttrName;
     338        6542 :     for (size_t i = oReversedPath.size(); i > 0;)
     339             :     {
     340        4165 :         --i;
     341        4165 :         const auto &oPathComp = oReversedPath[i];
     342        4165 :         if (!osAttrName.empty())
     343        1788 :             osAttrName += '.';
     344        4165 :         const auto nCode = oPathComp.first;
     345        4165 :         const auto oIterNATC = m_attributeCodes.find(nCode);
     346        4165 :         if (oIterNATC == m_attributeCodes.end())
     347             :         {
     348          42 :             osAttrName += CPLSPrintf("code_%d", static_cast<int>(nCode));
     349             :         }
     350             :         else
     351             :         {
     352        4123 :             osAttrName += oIterNATC->second;
     353             :         }
     354             : 
     355        4165 :         if (bMultipleFields && strcmp(pszAttrFieldName, ATTR_FIELD) == 0)
     356             :         {
     357         347 :             osAttrName += '[';
     358         347 :             osAttrName += std::to_string(iField + 1);
     359         347 :             osAttrName += ']';
     360         347 :             bMultipleFields = false;
     361             :         }
     362             : 
     363        4165 :         const auto &nRepeat = oPathComp.second;
     364        4165 :         if (nRepeat > 0)
     365             :         {
     366         478 :             osAttrName += '[';
     367         478 :             osAttrName += std::to_string(static_cast<int>(nRepeat));
     368         478 :             osAttrName += ']';
     369             :         }
     370             :     }
     371             : 
     372        2377 :     if (strcmp(pszAttrFieldName, ATTR_FIELD) != 0)
     373             :     {
     374         613 :         std::string osPrefix;
     375         613 :         if (strcmp(pszIDFieldName, IRID_FIELD) == 0)
     376             :         {
     377         183 :             osPrefix = "association";
     378             :         }
     379         430 :         else if (strcmp(pszIDFieldName, FRID_FIELD) == 0)
     380             :         {
     381         348 :             if (strcmp(pszAttrFieldName, INAS_FIELD) == 0)
     382         174 :                 osPrefix = "infoAssociation";
     383             :             else
     384         174 :                 osPrefix = "featureAssociation";
     385             :         }
     386         613 :         if (!osPrefix.empty())
     387             :         {
     388         531 :             if (bMultipleFields)
     389             :             {
     390         252 :                 osPrefix += '[';
     391         252 :                 osPrefix += std::to_string(iField + 1);
     392         252 :                 osPrefix += ']';
     393             :             }
     394         531 :             osPrefix += '_';
     395             :         }
     396         613 :         osAttrName = osPrefix + osAttrName;
     397             :     }
     398             : 
     399        2377 :     return osAttrName;
     400             : }
     401             : 
     402             : /************************************************************************/
     403             : /*                          InferFeatureDefn()                          */
     404             : /************************************************************************/
     405             : 
     406             : /** Infer the feature definition from the content of INAS or ATTR records
     407             :  * of the index.
     408             :  */
     409        1995 : bool OGRS101Reader::InferFeatureDefn(
     410             :     const DDFRecordIndex &oIndex, const char *pszIDFieldName,
     411             :     const char *pszAttrFieldName, const std::vector<int> &anRecordIndices,
     412             :     OGRFeatureDefn &oFeatureDefn,
     413             :     std::map<std::string, std::unique_ptr<OGRFieldDomain>> &oMapFieldDomains,
     414             :     const OGRS101FeatureCatalogTypes::InformationType * /*psInformationType*/,
     415             :     const OGRS101FeatureCatalogTypes::FeatureType *psFeatureType) const
     416             : {
     417        1995 :     const bool bIsINAS = EQUAL(pszAttrFieldName, INAS_FIELD);
     418             : 
     419             :     struct OGRAttrDef
     420             :     {
     421             :         std::optional<OGRFieldType> oeType{};
     422             :         OGRFieldSubType eSubType = OFSTNone;
     423             :         bool bIsMultiValued = false;
     424             :         bool bMultipleFields = false;
     425             :         std::string osLongerName{};
     426             :         std::string osDefinition{};
     427             :         std::string osFieldDomainName{};
     428             :     };
     429             : 
     430             :     struct Key
     431             :     {
     432             :         PathVector path{};
     433             :         int iField = 0;
     434             :         bool bMultipleFields = false;
     435             : 
     436         600 :         Key(const PathVector &pathIn, int iFieldIn, bool bMultipleFieldsIn)
     437         600 :             : path(pathIn), iField(iFieldIn), bMultipleFields(bMultipleFieldsIn)
     438             :         {
     439         600 :         }
     440             : 
     441        3475 :         bool operator<(const Key &other) const
     442             :         {
     443        5351 :             return path < other.path ||
     444        1876 :                    (path == other.path &&
     445        1527 :                     (iField < other.iField ||
     446        1367 :                      (iField == other.iField && !bMultipleFields &&
     447        4605 :                       other.bMultipleFields)));
     448             :         }
     449             :     };
     450             : 
     451        3990 :     std::map<Key, OGRAttrDef> oMapFieldTypes;
     452             : 
     453        3990 :     std::vector<S101AttrDef> asS101AttrDefs;
     454        3990 :     std::map<Key, int> mapPathToCount;
     455        1995 :     bool bFoundValidAssocField = false;
     456             : 
     457             :     // Iterate over the records (in the index of interest) to fill the
     458             :     // oMapFieldTypes map object that will be afterwards translated as
     459             :     // OGR feature definition
     460             :     // If anRecordIndices is not empty, it defines the subset of record
     461             :     // indices to iterate over. This is used for geometry records that are
     462             :     // dispatched to different OGR layers depending on the CRS.
     463        1995 :     const int nRecords = anRecordIndices.empty()
     464        3204 :                              ? oIndex.GetCount()
     465        1209 :                              : static_cast<int>(anRecordIndices.size());
     466        1995 :     int nMaxFieldRepeat = 1;
     467        4797 :     for (int iter = 0; iter < nRecords; ++iter)
     468             :     {
     469             :         const int iRecord =
     470        2818 :             anRecordIndices.empty() ? iter : anRecordIndices[iter];
     471             : 
     472          19 :         const auto GetErrorContext = [pszIDFieldName, iRecord]()
     473             :         {
     474          19 :             return CPLSPrintf("Record index=%d of %s", iRecord, pszIDFieldName);
     475        2818 :         };
     476             : 
     477        2818 :         const auto poRecord = oIndex.GetByIndex(iRecord);
     478        2818 :         CPLAssert(poRecord);
     479             : 
     480             :         const int nRUIN =
     481        2818 :             poRecord->GetIntSubfield(pszIDFieldName, 0, RUIN_SUBFIELD, 0);
     482        2818 :         if (nRUIN != INSTRUCTION_INSERT)
     483             :         {
     484           0 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: wrong value %d for RUIN "
     485             :                                                   "subfield of %s field.",
     486             :                                                   GetErrorContext(), nRUIN,
     487             :                                                   pszIDFieldName)))
     488             :             {
     489          16 :                 return false;
     490             :             }
     491           0 :             continue;
     492             :         }
     493             : 
     494        2818 :         if (!EQUAL(pszAttrFieldName, ATTR_FIELD))
     495             :         {
     496        2179 :             const auto apoFields = poRecord->GetFields(pszAttrFieldName);
     497        2179 :             bool bSkipRecord = false;
     498        2179 :             nMaxFieldRepeat =
     499        2179 :                 std::max(nMaxFieldRepeat, static_cast<int>(apoFields.size()));
     500        2179 :             if (!apoFields.empty())
     501             :             {
     502         290 :                 const DDFField *poField = apoFields[0];
     503             : 
     504             :                 const RecordName nRRNM =
     505         290 :                     poRecord->GetIntSubfield(poField, RRNM_SUBFIELD, 0);
     506         290 :                 const RecordName nExpectedRRNM =
     507         290 :                     bIsINAS ? RECORD_NAME_INFORMATION_TYPE
     508             :                             : RECORD_NAME_FEATURE_TYPE;
     509         290 :                 if (nRRNM != nExpectedRRNM)
     510             :                 {
     511           8 :                     if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     512             :                             "%s: Invalid value for RRNM subfield of %s field: "
     513             :                             "got %d, expected %d.",
     514             :                             GetErrorContext(), pszAttrFieldName,
     515             :                             static_cast<int>(nRRNM),
     516             :                             static_cast<int>(nExpectedRRNM))))
     517             :                     {
     518           7 :                         return false;
     519             :                     }
     520             :                 }
     521             : 
     522             :                 const int nRRID =
     523         286 :                     poRecord->GetIntSubfield(poField, RRID_SUBFIELD, 0);
     524         528 :                 if ((bIsINAS &&
     525         570 :                      !m_oInformationTypeRecordIndex.FindRecord(nRRID)) ||
     526         284 :                     (!bIsINAS && !m_oFeatureTypeRecordIndex.FindRecord(nRRID)))
     527             :                 {
     528           2 :                     if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     529             :                             "%s: Invalid value %d for RRID subfield of %s "
     530             :                             "field: "
     531             :                             "does not match the record identifier of an "
     532             :                             "existing "
     533             :                             "%s record.",
     534             :                             GetErrorContext(), static_cast<int>(nRRID),
     535             :                             pszAttrFieldName,
     536             :                             bIsINAS ? "InformationType" : "FeatureType")))
     537             :                     {
     538           1 :                         return false;
     539             :                     }
     540             :                 }
     541             : 
     542         285 :                 if (bIsINAS)
     543             :                 {
     544             :                     const InfoAssocCode nNIAC =
     545         241 :                         poRecord->GetIntSubfield(poField, NIAC_SUBFIELD, 0);
     546         243 :                     if (!cpl::contains(m_informationAssociationCodes, nNIAC) &&
     547           2 :                         !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     548             :                             "%s: cannot find attribute code %d in IACS field "
     549             :                             "of the Dataset General Information Record.",
     550             :                             GetErrorContext(), static_cast<int>(nNIAC))))
     551             :                     {
     552           1 :                         return false;
     553             :                     }
     554             :                 }
     555             :                 else
     556             :                 {
     557             :                     const FeatureAssocCode nNFAC =
     558          44 :                         poRecord->GetIntSubfield(poField, NFAC_SUBFIELD, 0);
     559          45 :                     if (!cpl::contains(m_featureAssociationCodes, nNFAC) &&
     560           1 :                         !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     561             :                             "%s: cannot find attribute code %d in NFAC field "
     562             :                             "of the Dataset General Information Record.",
     563             :                             GetErrorContext(), static_cast<int>(nNFAC))))
     564             :                     {
     565           0 :                         return false;
     566             :                     }
     567             :                 }
     568             : 
     569             :                 const AssocRoleCode nNARC =
     570         284 :                     poRecord->GetIntSubfield(poField, NARC_SUBFIELD, 0);
     571         288 :                 if (!cpl::contains(m_associationRoleCodes, nNARC) &&
     572           4 :                     !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     573             :                         "%s: cannot find attribute code %d in ARCS field "
     574             :                         "of the Dataset General Information Record.",
     575             :                         GetErrorContext(), static_cast<int>(nNARC))))
     576             :                 {
     577           1 :                     return false;
     578             :                 }
     579             : 
     580         283 :                 const char *pszSubFieldName =
     581             :                     bIsINAS ? IUIN_SUBFIELD : FAUI_SUBFIELD;
     582             :                 int nInstruction =
     583         283 :                     poRecord->GetIntSubfield(poField, pszSubFieldName, 0);
     584         283 :                 if (nInstruction == 0)
     585             :                 {
     586             :                     // For 101GB00GB302045.000, non conformant
     587           0 :                     nInstruction = poRecord->GetIntSubfield(poField, "APUI", 0);
     588             :                 }
     589         283 :                 if (nInstruction != INSTRUCTION_INSERT)
     590             :                 {
     591           0 :                     if (!EMIT_ERROR_OR_WARNING(
     592             :                             CPLSPrintf("%s: wrong value %d for %s "
     593             :                                        "subfield of %s field.",
     594             :                                        GetErrorContext(), nInstruction,
     595             :                                        pszSubFieldName, pszAttrFieldName)))
     596             :                     {
     597           0 :                         return false;
     598             :                     }
     599           0 :                     bSkipRecord = true;
     600             :                 }
     601             :                 else
     602             :                 {
     603         283 :                     bFoundValidAssocField = true;
     604             :                 }
     605             :             }
     606        2172 :             if (bSkipRecord)
     607           0 :                 continue;
     608             :         }
     609             : 
     610             :         // First (inner) pass over attributes of the current record
     611             :         // to fill asS101AttrDefs, and do all needed sanity checks
     612        2811 :         if (!IngestAttributes(poRecord, iRecord, pszIDFieldName,
     613             :                               pszAttrFieldName, asS101AttrDefs))
     614           8 :             return false;
     615             : 
     616        2803 :         mapPathToCount.clear();
     617             : 
     618             :         // Update oMapFieldTypes with attributes found in this record
     619        3562 :         for (const auto &sAttrDef : asS101AttrDefs)
     620             :         {
     621         760 :             if (sAttrDef.bIsParent || sAttrDef.oReversedPath.empty())
     622         160 :                 continue;
     623             : 
     624             :             // Check that the top-level part of the attribute is expected for
     625             :             // that feature type (using feature catalog)
     626         600 :             if (psFeatureType)
     627             :             {
     628             :                 const auto oIterNATC =
     629           0 :                     m_attributeCodes.find(sAttrDef.oReversedPath.back().first);
     630           0 :                 if (oIterNATC != m_attributeCodes.end())
     631             :                 {
     632           0 :                     const std::string &osAttrCode = oIterNATC->second;
     633           0 :                     if (!cpl::contains(psFeatureType->attributeBindings,
     634             :                                        osAttrCode))
     635             :                     {
     636           0 :                         if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     637             :                                 "%s: attribute code %s not expected in feature "
     638             :                                 "type %s",
     639             :                                 GetErrorContext(), osAttrCode.c_str(),
     640             :                                 psFeatureType->code.c_str())))
     641             :                         {
     642           0 :                             return false;
     643             :                         }
     644             :                     }
     645             :                 }
     646             :             }
     647             : 
     648         600 :             const auto key = Key(sAttrDef.oReversedPath, sAttrDef.iField,
     649         600 :                                  sAttrDef.bMultipleFields);
     650         600 :             ++mapPathToCount[key];
     651             : 
     652             :             // Must be kept in that scope to create a OGR attribute even if
     653             :             // there is no field value
     654         600 :             auto &sOGRAttrDef = oMapFieldTypes[key];
     655             : 
     656         600 :             std::string typeFromCatalog;
     657         600 :             if (m_poFeatureCatalog)
     658             :             {
     659             :                 auto oIterNATC =
     660         600 :                     m_attributeCodes.find(sAttrDef.oReversedPath.front().first);
     661         600 :                 if (oIterNATC != m_attributeCodes.end())
     662             :                 {
     663             :                     const auto &oMap =
     664         592 :                         m_poFeatureCatalog->GetSimpleAttributes();
     665         592 :                     const std::string &osAttrCode = oIterNATC->second;
     666         592 :                     const auto oIterAttr = oMap.find(osAttrCode);
     667         592 :                     if (oIterAttr != oMap.end())
     668             :                     {
     669         583 :                         const auto &attrDef = oIterAttr->second;
     670         583 :                         typeFromCatalog = attrDef.type;
     671             : 
     672         583 :                         sOGRAttrDef.osLongerName = attrDef.name;
     673         583 :                         sOGRAttrDef.osDefinition = attrDef.definition;
     674             : 
     675         583 :                         if (typeFromCatalog ==
     676             :                             OGRS101FeatureCatalog::VALUE_TYPE_ENUMERATION)
     677             :                         {
     678          67 :                             sOGRAttrDef.osFieldDomainName = osAttrCode;
     679             : 
     680          67 :                             if (!sAttrDef.osVal.empty())
     681             :                             {
     682             :                                 // Checks that the coded value is an allowed code.
     683          67 :                                 const int nCode = atoi(sAttrDef.osVal.c_str());
     684          67 :                                 if (!cpl::contains(attrDef.enumeratedValues,
     685             :                                                    nCode))
     686             :                                 {
     687           2 :                                     if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     688             :                                             "%s: value %s does not belong to "
     689             :                                             "enumeration of attribute code %s",
     690             :                                             GetErrorContext(),
     691             :                                             sAttrDef.osVal.c_str(),
     692             :                                             osAttrCode.c_str())))
     693             :                                     {
     694           1 :                                         return false;
     695             :                                     }
     696             :                                 }
     697             :                             }
     698             : 
     699             :                             // Check if field domain exists. If not, create it.
     700          66 :                             if (!cpl::contains(oMapFieldDomains, osAttrCode))
     701             :                             {
     702          90 :                                 std::vector<OGRCodedValue> asValues;
     703        1170 :                                 for (const auto &[code, value] :
     704        1215 :                                      attrDef.enumeratedValues)
     705             :                                 {
     706             :                                     OGRCodedValue codedValue;
     707         585 :                                     codedValue.pszCode =
     708         585 :                                         CPLStrdup(CPLSPrintf("%d", code));
     709         585 :                                     codedValue.pszValue =
     710         585 :                                         CPLStrdup(value.c_str());
     711         585 :                                     asValues.push_back(std::move(codedValue));
     712             :                                 }
     713             :                                 auto poFieldDomain =
     714             :                                     std::make_unique<OGRCodedFieldDomain>(
     715           0 :                                         osAttrCode, attrDef.name, OFTString,
     716          90 :                                         OFSTNone, std::move(asValues));
     717          45 :                                 oMapFieldDomains[osAttrCode] =
     718          90 :                                     std::move(poFieldDomain);
     719             :                             }
     720             :                         }
     721             :                     }
     722             :                 }
     723             : 
     724         961 :                 for (size_t i = 1; i < sAttrDef.oReversedPath.size(); ++i)
     725             :                 {
     726             :                     oIterNATC =
     727         362 :                         m_attributeCodes.find(sAttrDef.oReversedPath[i].first);
     728         362 :                     if (oIterNATC != m_attributeCodes.end())
     729             :                     {
     730             :                         const auto &oMap =
     731         348 :                             m_poFeatureCatalog->GetComplexAttributes();
     732         348 :                         const std::string &osAttrCode = oIterNATC->second;
     733         348 :                         const auto oIterAttr = oMap.find(osAttrCode);
     734         348 :                         if (oIterAttr != oMap.end())
     735             :                         {
     736         348 :                             const auto &attrDef = oIterAttr->second;
     737         348 :                             sOGRAttrDef.osDefinition += ' ';
     738         348 :                             sOGRAttrDef.osDefinition += oIterNATC->second;
     739         348 :                             sOGRAttrDef.osDefinition += '=';
     740         348 :                             sOGRAttrDef.osDefinition += attrDef.definition;
     741             :                         }
     742             :                     }
     743             :                 }
     744             :             }
     745             : 
     746         599 :             sOGRAttrDef.bMultipleFields = sAttrDef.bMultipleFields;
     747             : 
     748         599 :             if (!sAttrDef.osVal.empty())
     749             :             {
     750         590 :                 const bool bNewAttrIsMultiValued = mapPathToCount[key] > 1;
     751         590 :                 if (bNewAttrIsMultiValued)
     752          24 :                     sOGRAttrDef.bIsMultiValued = true;
     753         590 :                 const auto eCPLType = CPLGetValueType(sAttrDef.osVal.c_str());
     754         717 :                 auto eOGRType = eCPLType == CPL_VALUE_STRING    ? OFTString
     755         127 :                                 : eCPLType == CPL_VALUE_INTEGER ? OFTInteger
     756             :                                                                 : OFTReal;
     757             : 
     758             :                 // Is it YYYYMMDD date ?
     759          85 :                 if (eOGRType == OFTInteger && sAttrDef.osVal.size() == 8 &&
     760         675 :                     sAttrDef.osVal[4] <= '1' && sAttrDef.osVal[6] <= '3')
     761             :                 {
     762             :                     const auto it = m_attributeCodes.find(
     763           3 :                         sAttrDef.oReversedPath.front().first);
     764           3 :                     if (it != m_attributeCodes.end())
     765             :                     {
     766           3 :                         if (cpl::ends_with(it->second, "Date") ||
     767           6 :                             cpl::ends_with(it->second, "dateStart") ||
     768           6 :                             cpl::ends_with(it->second, "dateEnd"))
     769             :                         {
     770           3 :                             eOGRType = OFTDate;
     771             :                         }
     772             :                     }
     773             :                 }
     774             :                 // Is it YYYY---- truncated date ?
     775         463 :                 else if (eOGRType == OFTString && sAttrDef.osVal.size() == 8 &&
     776           3 :                          sAttrDef.osVal[4] == '-' && sAttrDef.osVal[5] == '-' &&
     777        1050 :                          sAttrDef.osVal[6] == '-' && sAttrDef.osVal[7] == '-')
     778             :                 {
     779             :                     const auto it = m_attributeCodes.find(
     780           3 :                         sAttrDef.oReversedPath.front().first);
     781           3 :                     if (it != m_attributeCodes.end())
     782             :                     {
     783           3 :                         if (cpl::ends_with(it->second, "Date") ||
     784           3 :                             cpl::ends_with(it->second, "dateStart") ||
     785           3 :                             cpl::ends_with(it->second, "dateEnd"))
     786             :                         {
     787           3 :                             eOGRType = OFTDate;
     788             :                         }
     789             :                     }
     790             :                 }
     791             :                 // Is it time format ? ("094500", "094500", "094500+0100")
     792        1044 :                 else if ((eOGRType == OFTInteger || eOGRType == OFTString) &&
     793         941 :                          sAttrDef.osVal.size() >= 6 &&
     794         399 :                          sAttrDef.osVal.size() <= 11 &&
     795         231 :                          std::all_of(sAttrDef.osVal.begin(),
     796         231 :                                      sAttrDef.osVal.begin() + 6, [](char c)
     797        1437 :                                      { return c >= '0' && c <= '9'; }) &&
     798          10 :                          (sAttrDef.osVal.size() == 6 ||
     799           3 :                           (sAttrDef.osVal.size() == 7 &&
     800           3 :                            sAttrDef.osVal[6] == 'Z') ||
     801           3 :                           (sAttrDef.osVal.size() == 11 &&
     802           3 :                            (sAttrDef.osVal[6] == '+' ||
     803           0 :                             sAttrDef.osVal[6] == '-'))))
     804             :                 {
     805             :                     const auto it = m_attributeCodes.find(
     806           7 :                         sAttrDef.oReversedPath.front().first);
     807           7 :                     if (it != m_attributeCodes.end())
     808             :                     {
     809           7 :                         if (cpl::starts_with(it->second, "time"))
     810             :                         {
     811           6 :                             eOGRType = OFTTime;
     812             :                         }
     813             :                     }
     814             :                 }
     815             : 
     816         590 :                 if (!sOGRAttrDef.oeType.has_value())
     817             :                 {
     818         560 :                     sOGRAttrDef.oeType = eOGRType;
     819         616 :                     if (eOGRType == OFTInteger &&
     820          56 :                         typeFromCatalog ==
     821             :                             OGRS101FeatureCatalog::VALUE_TYPE_BOOLEAN)
     822             :                     {
     823           0 :                         sOGRAttrDef.eSubType = OFSTBoolean;
     824             :                     }
     825             :                 }
     826          36 :                 else if (eOGRType == OFTString &&
     827           6 :                          *sOGRAttrDef.oeType != OFTString)
     828             :                 {
     829           0 :                     sOGRAttrDef.oeType = OFTString;
     830           0 :                     sOGRAttrDef.eSubType = OFSTNone;
     831             :                 }
     832          31 :                 else if (eOGRType == OFTReal &&
     833           1 :                          *sOGRAttrDef.oeType == OFTInteger)
     834             :                 {
     835           1 :                     sOGRAttrDef.oeType = OFTReal;
     836           1 :                     sOGRAttrDef.eSubType = OFSTNone;
     837             :                 }
     838             :             }
     839             :         }
     840             :     }
     841             : 
     842        1979 :     if (bFoundValidAssocField)
     843             :     {
     844         570 :         for (int i = 0; i < nMaxFieldRepeat; ++i)
     845             :         {
     846             :             const std::string osSuffix =
     847         592 :                 nMaxFieldRepeat > 1 ? CPLSPrintf("[%d]", i + 1) : "";
     848             : 
     849         296 :             if (!bIsINAS)
     850             :             {
     851             :                 OGRFieldDefn oFieldDefn(
     852          48 :                     (OGR_FIELD_NAME_REF_FEAT_LAYER_NAME + osSuffix).c_str(),
     853          96 :                     OFTString);
     854          48 :                 oFeatureDefn.AddFieldDefn(&oFieldDefn);
     855             :             }
     856             : 
     857             :             {
     858             :                 OGRFieldDefn oFieldDefn(
     859             :                     ((bIsINAS ? OGR_FIELD_NAME_REF_INFO_RID
     860         296 :                               : OGR_FIELD_NAME_REF_FEAT_RID) +
     861             :                      osSuffix)
     862             :                         .c_str(),
     863         592 :                     OFTInteger);
     864         296 :                 oFeatureDefn.AddFieldDefn(&oFieldDefn);
     865             :             }
     866             :             {
     867             :                 OGRFieldDefn oFieldDefn(
     868         296 :                     ((bIsINAS ? OGR_FIELD_NAME_NIAC : OGR_FIELD_NAME_NFAC) +
     869             :                      osSuffix)
     870             :                         .c_str(),
     871         592 :                     OFTString);
     872         296 :                 oFeatureDefn.AddFieldDefn(&oFieldDefn);
     873             :             }
     874             :             {
     875             :                 OGRFieldDefn oFieldDefn(
     876             :                     ((bIsINAS ? OGR_FIELD_NAME_NARC
     877         296 :                               : OGR_FIELD_NAME_FEATURE_NARC) +
     878             :                      osSuffix)
     879             :                         .c_str(),
     880         592 :                     OFTString);
     881         296 :                 oFeatureDefn.AddFieldDefn(&oFieldDefn);
     882             :             }
     883             :         }
     884             :     }
     885             : 
     886             :     // Final pass to transform oMapFieldTypes into OGRField instances.
     887        2539 :     for (const auto &[key, sOGRAttrDef] : oMapFieldTypes)
     888             :     {
     889         560 :         const auto &oReversedPath = key.path;
     890         560 :         const int iField = key.iField;
     891             : 
     892             :         const std::string osAttrName =
     893             :             BuildFieldName(oReversedPath, pszAttrFieldName, iField,
     894        1120 :                            sOGRAttrDef.bMultipleFields, pszIDFieldName);
     895             : 
     896         560 :         OGRFieldType eType = OFTString;
     897         560 :         if (sOGRAttrDef.oeType.has_value())
     898             :         {
     899         553 :             if (sOGRAttrDef.bIsMultiValued)
     900             :             {
     901          25 :                 eType = (*sOGRAttrDef.oeType) == OFTInteger ? OFTIntegerList
     902           1 :                         : (*sOGRAttrDef.oeType) == OFTReal  ? OFTRealList
     903             :                                                             : OFTStringList;
     904             :             }
     905             :             else
     906             :             {
     907         529 :                 eType = *sOGRAttrDef.oeType;
     908             :             }
     909             :         }
     910           7 :         else if (sOGRAttrDef.bIsMultiValued)
     911             :         {
     912           0 :             eType = OFTStringList;
     913             :         }
     914             : 
     915         560 :         if (oFeatureDefn.GetFieldIndex(osAttrName.c_str()) >= 0)
     916             :         {
     917           0 :             if (osAttrName == OGR_FIELD_NAME_SMIN ||
     918           0 :                 osAttrName == OGR_FIELD_NAME_SMAX)
     919             :             {
     920             :                 // 101FR00368570.000 has scaleMinimum as an ATTR, and
     921             :                 // doesn't define SPAS.SMIN
     922             :             }
     923             :             else
     924             :             {
     925           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     926             :                          "Layer %s: %s field already exists",
     927           0 :                          oFeatureDefn.GetName(), osAttrName.c_str());
     928             :             }
     929             :         }
     930             :         else
     931             :         {
     932        1120 :             OGRFieldDefn oFieldDefn(osAttrName.c_str(), eType);
     933         560 :             oFieldDefn.SetSubType(sOGRAttrDef.eSubType);
     934         560 :             if (!sOGRAttrDef.osLongerName.empty() && oReversedPath.size() == 1)
     935         376 :                 oFieldDefn.SetAlternativeName(sOGRAttrDef.osLongerName.c_str());
     936         560 :             if (!sOGRAttrDef.osDefinition.empty())
     937         547 :                 oFieldDefn.SetComment(sOGRAttrDef.osDefinition.c_str());
     938         560 :             if (!sOGRAttrDef.osFieldDomainName.empty())
     939          44 :                 oFieldDefn.SetDomainName(sOGRAttrDef.osFieldDomainName.c_str());
     940         560 :             oFeatureDefn.AddFieldDefn(&oFieldDefn);
     941             :         }
     942             :     }
     943             : 
     944        1979 :     return true;
     945             : }
     946             : 
     947             : /************************************************************************/
     948             : /*                       FillFeatureAttributes()                        */
     949             : /************************************************************************/
     950             : 
     951             : /** Fill attribute fields of the provided feature.
     952             :  */
     953        6585 : bool OGRS101Reader::FillFeatureAttributes(const DDFRecordIndex &oIndex,
     954             :                                           int iRecord,
     955             :                                           const char *pszAttrFieldName,
     956             :                                           OGRFeature &oFeature) const
     957             : {
     958        6585 :     const auto poRecord = oIndex.GetByIndex(iRecord);
     959        6585 :     if (!poRecord)
     960             :     {
     961           0 :         return EMIT_ERROR("Invalid record number");
     962             :     }
     963             : 
     964        6585 :     const auto poIDField = poRecord->GetField(0);
     965        6585 :     CPLAssert(poIDField);
     966        6585 :     const char *pszIDFieldName = poIDField->GetFieldDefn()->GetName();
     967             : 
     968             :     const int nRCID =
     969        6585 :         poRecord->GetIntSubfield(pszIDFieldName, 0, RCID_SUBFIELD, 0);
     970        6585 :     if (nRCID < 1 && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     971             :                          "Wrong value %d for RCID subfield of %s field.", nRCID,
     972             :                          pszIDFieldName)))
     973             :     {
     974           0 :         return false;
     975             :     }
     976        6585 :     oFeature.SetField(OGR_FIELD_NAME_RECORD_ID, nRCID);
     977             : 
     978             :     const int nRVER =
     979        6585 :         poRecord->GetIntSubfield(pszIDFieldName, 0, RVER_SUBFIELD, 0);
     980        6585 :     oFeature.SetField(OGR_FIELD_NAME_RECORD_VERSION, nRVER);
     981             : 
     982             :     const int nRUIN =
     983        6585 :         poRecord->GetIntSubfield(pszIDFieldName, 0, RUIN_SUBFIELD, 0);
     984        6585 :     if (nRUIN != INSTRUCTION_INSERT)
     985             :     {
     986           0 :         return EMIT_ERROR_OR_WARNING(
     987             :             CPLSPrintf("Wrong value %d for RUIN subfield of %s field.", nRUIN,
     988             :                        pszIDFieldName));
     989             :     }
     990             : 
     991        6585 :     const auto poFeatureDefn = oFeature.GetDefnRef();
     992             : 
     993             :     // First pass to detect which attribute entries correspond to parent nodes
     994             :     // that don't directly hold a field value.
     995       13170 :     std::vector<S101AttrDef> asS101AttrDefs;
     996        6585 :     if (!IngestAttributes(poRecord, iRecord, pszIDFieldName, pszAttrFieldName,
     997             :                           asS101AttrDefs))
     998           0 :         return false;
     999             : 
    1000             :     struct OGRFieldIndexTag
    1001             :     {
    1002             :     };
    1003             : 
    1004             :     using OGRFieldIndex = cpl::IntWrapper<OGRFieldIndexTag>;
    1005             : 
    1006       13170 :     std::map<OGRFieldIndex, CPLStringList> stringListAttrs;
    1007       13170 :     std::map<OGRFieldIndex, std::vector<int>> intListAttrs;
    1008       13170 :     std::map<OGRFieldIndex, std::vector<double>> doubleListAttrs;
    1009             :     // Second pass to set single-valued attributes, or store multi-valued
    1010             :     // attributes in the 3 above maps.
    1011        9054 :     for (const auto &sAttrDef : asS101AttrDefs)
    1012             :     {
    1013        2469 :         if (sAttrDef.bIsParent || sAttrDef.oReversedPath.empty())
    1014         652 :             continue;
    1015             : 
    1016             :         const std::string osAttrName = BuildFieldName(
    1017        1817 :             sAttrDef.oReversedPath, pszAttrFieldName, sAttrDef.iField,
    1018        1817 :             sAttrDef.bMultipleFields, pszIDFieldName);
    1019             : 
    1020             :         const int iOGRFieldIdx =
    1021        1817 :             poFeatureDefn->GetFieldIndex(osAttrName.c_str());
    1022             :         // Shouldn't normally happen given all preceding run logic
    1023        1817 :         CPLAssert(iOGRFieldIdx >= 0);
    1024        1817 :         const auto eType = poFeatureDefn->GetFieldDefn(iOGRFieldIdx)->GetType();
    1025        1817 :         const char *const pszATVL = sAttrDef.osVal.c_str();
    1026        1817 :         switch (eType)
    1027             :         {
    1028         279 :             case OFTInteger:
    1029             :             case OFTIntegerList:
    1030             :             {
    1031         279 :                 if (pszATVL[0])
    1032             :                 {
    1033         279 :                     const char *const last = pszATVL + sAttrDef.osVal.size();
    1034         279 :                     char *endptr = nullptr;
    1035         279 :                     const auto nVal64 = std::strtoll(pszATVL, &endptr, 10);
    1036         279 :                     if (nVal64 >= INT_MIN && nVal64 <= INT_MAX &&
    1037         278 :                         endptr == last)
    1038             :                     {
    1039         278 :                         const int nVal = static_cast<int>(nVal64);
    1040         278 :                         if (eType == OFTInteger)
    1041         112 :                             oFeature.SetField(iOGRFieldIdx, nVal);
    1042             :                         else
    1043         278 :                             intListAttrs[iOGRFieldIdx].push_back(nVal);
    1044             :                     }
    1045           1 :                     else if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1046             :                                  "Record index=%d of %s, attribute %s: "
    1047             :                                  "non integer value '%s'.",
    1048             :                                  iRecord, pszIDFieldName, osAttrName.c_str(),
    1049             :                                  pszATVL)))
    1050             :                     {
    1051           0 :                         return false;
    1052             :                     }
    1053             :                 }
    1054           0 :                 else if (eType == OFTIntegerList)
    1055             :                 {
    1056           0 :                     intListAttrs[iOGRFieldIdx].push_back(
    1057           0 :                         std::numeric_limits<int>::min());
    1058             :                 }
    1059         279 :                 break;
    1060             :             }
    1061             : 
    1062         140 :             case OFTReal:
    1063             :             case OFTRealList:
    1064             :             {
    1065         140 :                 if (pszATVL[0])
    1066             :                 {
    1067         140 :                     const char *const last = pszATVL + sAttrDef.osVal.size();
    1068         140 :                     double dfVal = -1;
    1069         140 :                     const fast_float::parse_options options{
    1070             :                         fast_float::chars_format::general, '.'};
    1071             :                     auto [ptr, ec] = fast_float::from_chars_advanced(
    1072         140 :                         pszATVL, last, dfVal, options);
    1073         140 :                     if (ec == std::errc() && ptr == last)
    1074             :                     {
    1075         139 :                         if (eType == OFTReal)
    1076         137 :                             oFeature.SetField(iOGRFieldIdx, dfVal);
    1077             :                         else
    1078           2 :                             doubleListAttrs[iOGRFieldIdx].push_back(dfVal);
    1079             :                     }
    1080           1 :                     else if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1081             :                                  "Record index=%d of %s, attribute %s: "
    1082             :                                  "non double value '%s'.",
    1083             :                                  iRecord, pszIDFieldName, osAttrName.c_str(),
    1084             :                                  pszATVL)))
    1085             :                     {
    1086           0 :                         return false;
    1087             :                     }
    1088             :                 }
    1089           0 :                 else if (eType == OFTRealList)
    1090             :                 {
    1091           0 :                     doubleListAttrs[iOGRFieldIdx].push_back(
    1092           0 :                         std::numeric_limits<double>::quiet_NaN());
    1093             :                 }
    1094         140 :                 break;
    1095             :             }
    1096             : 
    1097        1294 :             case OFTString:
    1098             :             case OFTStringList:
    1099             :             {
    1100           0 :                 std::unique_ptr<char, VSIFreeReleaser> pszTmpStr;
    1101        1294 :                 const char *pszStr = pszATVL;
    1102        1294 :                 if (!CPLIsUTF8(pszATVL,
    1103        1294 :                                static_cast<int>(sAttrDef.osVal.size())))
    1104             :                 {
    1105             :                     // Not supposed to happen in compliant products
    1106           1 :                     if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1107             :                             "Record index=%d of %s, attribute %s: non "
    1108             :                             "UTF-8 string '%s'.",
    1109             :                             iRecord, pszIDFieldName, osAttrName.c_str(),
    1110             :                             pszATVL)))
    1111             :                     {
    1112           0 :                         return false;
    1113             :                     }
    1114           1 :                     pszTmpStr.reset(CPLUTF8ForceToASCII(pszATVL, '_'));
    1115           1 :                     pszStr = pszTmpStr.get();
    1116             :                 }
    1117        1294 :                 if (eType == OFTString)
    1118        1294 :                     oFeature.SetField(iOGRFieldIdx, pszStr);
    1119             :                 else
    1120           0 :                     stringListAttrs[iOGRFieldIdx].push_back(pszStr);
    1121        1294 :                 break;
    1122             :             }
    1123             : 
    1124          52 :             case OFTDate:
    1125             :             {
    1126         104 :                 if (sAttrDef.osVal.size() == 8 &&
    1127          52 :                     std::all_of(sAttrDef.osVal.begin(), sAttrDef.osVal.end(),
    1128         338 :                                 [](char c) { return c >= '0' && c <= '9'; }))
    1129             :                 {
    1130          26 :                     const int nYear = (sAttrDef.osVal[0] - '0') * 1000 +
    1131          26 :                                       (sAttrDef.osVal[1] - '0') * 100 +
    1132          26 :                                       (sAttrDef.osVal[2] - '0') * 10 +
    1133          26 :                                       (sAttrDef.osVal[3] - '0');
    1134          26 :                     const int nMonth = (sAttrDef.osVal[4] - '0') * 10 +
    1135          26 :                                        (sAttrDef.osVal[5] - '0');
    1136          26 :                     const int nDay = (sAttrDef.osVal[6] - '0') * 10 +
    1137          26 :                                      (sAttrDef.osVal[7] - '0');
    1138          26 :                     oFeature.SetField(iOGRFieldIdx, nYear, nMonth, nDay);
    1139             :                 }
    1140          26 :                 else if (sAttrDef.osVal.size() == 8 &&
    1141          26 :                          sAttrDef.osVal[0] >= '0' && sAttrDef.osVal[0] <= '9' &&
    1142          26 :                          sAttrDef.osVal[1] >= '0' && sAttrDef.osVal[1] <= '9' &&
    1143          26 :                          sAttrDef.osVal[2] >= '0' && sAttrDef.osVal[2] <= '9' &&
    1144          26 :                          sAttrDef.osVal[3] >= '0' && sAttrDef.osVal[3] <= '9' &&
    1145          26 :                          sAttrDef.osVal[4] == '-' && sAttrDef.osVal[5] == '-' &&
    1146          52 :                          sAttrDef.osVal[6] == '-' && sAttrDef.osVal[7] == '-')
    1147             :                 {
    1148          26 :                     const int nYear = (sAttrDef.osVal[0] - '0') * 1000 +
    1149          26 :                                       (sAttrDef.osVal[1] - '0') * 100 +
    1150          26 :                                       (sAttrDef.osVal[2] - '0') * 10 +
    1151          26 :                                       (sAttrDef.osVal[3] - '0');
    1152          26 :                     if (cpl::ends_with(osAttrName, "dateEnd"))
    1153             :                     {
    1154           0 :                         oFeature.SetField(iOGRFieldIdx, nYear, 12, 31);
    1155             :                     }
    1156             :                     else
    1157             :                     {
    1158          26 :                         oFeature.SetField(iOGRFieldIdx, nYear, 1, 1);
    1159             :                     }
    1160             :                 }
    1161          52 :                 break;
    1162             :             }
    1163             : 
    1164          52 :             case OFTTime:
    1165             :             {
    1166         104 :                 if (sAttrDef.osVal.size() >= 6 &&
    1167          52 :                     std::all_of(sAttrDef.osVal.begin(),
    1168         104 :                                 sAttrDef.osVal.begin() + 6,
    1169         312 :                                 [](char c) { return c >= '0' && c <= '9'; }))
    1170             :                 {
    1171          52 :                     const int nHour = (sAttrDef.osVal[0] - '0') * 10 +
    1172          52 :                                       (sAttrDef.osVal[1] - '0');
    1173          52 :                     const int nMin = (sAttrDef.osVal[2] - '0') * 10 +
    1174          52 :                                      (sAttrDef.osVal[3] - '0');
    1175          52 :                     const int nSec = (sAttrDef.osVal[4] - '0') * 10 +
    1176          52 :                                      (sAttrDef.osVal[5] - '0');
    1177          52 :                     int nTZFlag = OGR_TZFLAG_UNKNOWN;
    1178          52 :                     if (sAttrDef.osVal.size() == 7 && sAttrDef.osVal[6] == 'Z')
    1179           0 :                         nTZFlag = OGR_TZFLAG_UTC;
    1180          52 :                     else if (sAttrDef.osVal.size() == 11)
    1181             :                     {
    1182          26 :                         const int nTZHour = (sAttrDef.osVal[7] - '0') * 10 +
    1183          26 :                                             (sAttrDef.osVal[8] - '0');
    1184          26 :                         const int nTZMin = (sAttrDef.osVal[9] - '0') * 10 +
    1185          26 :                                            (sAttrDef.osVal[10] - '0');
    1186          26 :                         const int n15Minutes = (nTZHour * 60 + nTZMin) / 15;
    1187          26 :                         if (sAttrDef.osVal[6] == '+')
    1188          26 :                             nTZFlag = OGR_TZFLAG_UTC + n15Minutes;
    1189             :                         else
    1190           0 :                             nTZFlag = OGR_TZFLAG_UTC - n15Minutes;
    1191             :                     }
    1192          52 :                     oFeature.SetField(iOGRFieldIdx, 0, 0, 0, nHour, nMin,
    1193             :                                       static_cast<float>(nSec), nTZFlag);
    1194             :                 }
    1195          52 :                 break;
    1196             :             }
    1197             : 
    1198           0 :             default:
    1199           0 :                 CPLAssert(false);
    1200             :         }
    1201             :     }
    1202             : 
    1203             :     // Set multi-valued fields.
    1204        6585 :     for (const auto &[iOGRFieldIdx, aosStrings] : stringListAttrs)
    1205           0 :         oFeature.SetField(static_cast<int>(iOGRFieldIdx), aosStrings.List());
    1206             : 
    1207        6668 :     for (const auto &[iOGRFieldIdx, anVals] : intListAttrs)
    1208         166 :         oFeature.SetField(static_cast<int>(iOGRFieldIdx),
    1209          83 :                           static_cast<int>(anVals.size()), anVals.data());
    1210             : 
    1211        6586 :     for (const auto &[iOGRFieldIdx, adfVals] : doubleListAttrs)
    1212           2 :         oFeature.SetField(static_cast<int>(iOGRFieldIdx),
    1213           1 :                           static_cast<int>(adfVals.size()), adfVals.data());
    1214             : 
    1215        6585 :     return true;
    1216             : }
    1217             : 
    1218             : /************************************************************************/
    1219             : /*                               AttrNode                               */
    1220             : /************************************************************************/
    1221             : 
    1222             : namespace
    1223             : {
    1224             : using AttrCode = OGRS101Reader::AttrCode;
    1225             : using AttrRepeat = OGRS101Reader::AttrRepeat;
    1226             : using AttrIndex = OGRS101Reader::AttrIndex;
    1227             : 
    1228             : struct AttrNode
    1229             : {
    1230             :     AttrCode code = 0;
    1231             :     AttrRepeat indexOfSameCode = 0;
    1232             :     std::string value{};
    1233             :     std::vector<std::shared_ptr<AttrNode>> children{};
    1234             : 
    1235          37 :     std::string Encode() const
    1236             :     {
    1237          37 :         std::string s;
    1238          37 :         AttrIndex thisIdx = 0;
    1239          37 :         AttrIndex curIdx = 0;
    1240          79 :         for (const auto &child : children)
    1241             :         {
    1242          42 :             child->Encode(s, thisIdx, curIdx, "");
    1243             :         }
    1244          74 :         return s;
    1245             :     }
    1246             : 
    1247             :   private:
    1248             :     void Encode(std::string &s, AttrIndex parentIdx, AttrIndex &curIdx,
    1249             :                 const std::string &indent);
    1250             : };
    1251             : 
    1252         107 : void AttrNode::Encode(std::string &s, AttrIndex parentIdx, AttrIndex &curIdx,
    1253             :                       const std::string &indent)
    1254             : {
    1255         107 :     ++curIdx;
    1256             :     if constexpr (false)
    1257             :     {
    1258             :         const int idx = static_cast<int>(curIdx);
    1259             :         CPLDebug("S101", "%s[%d].code = %d", indent.c_str(), idx,
    1260             :                  static_cast<int>(code));
    1261             :         CPLDebug("S101", "%s[%d].indexOfSameCode = %d\n", indent.c_str(), idx,
    1262             :                  static_cast<int>(indexOfSameCode));
    1263             :         CPLDebug("S101", "%s[%d].parentIdx = %d", indent.c_str(), idx,
    1264             :                  static_cast<int>(parentIdx));
    1265             :         CPLDebug("S101", "%s[%d].value = %s", indent.c_str(), idx,
    1266             :                  value.c_str());
    1267             :     }
    1268         107 :     OGRS101Reader::AppendUInt16(s,
    1269         107 :                                 static_cast<uint16_t>(static_cast<int>(code)));
    1270         107 :     OGRS101Reader::AppendUInt16(
    1271         107 :         s, static_cast<uint16_t>(static_cast<int>(indexOfSameCode)));
    1272         107 :     OGRS101Reader::AppendUInt16(
    1273         107 :         s, static_cast<uint16_t>(static_cast<int>(parentIdx)));
    1274         107 :     OGRS101Reader::AppendUInt8(s, static_cast<uint8_t>(INSTRUCTION_INSERT));
    1275         107 :     s.append(value);
    1276         107 :     OGRS101Reader::AppendUInt8(s, static_cast<uint8_t>(DDF_UNIT_TERMINATOR));
    1277             : 
    1278         107 :     if (!children.empty())
    1279             :     {
    1280          30 :         const auto thisIdx = curIdx;
    1281          60 :         const std::string newIndent = indent + "  ";
    1282          95 :         for (const auto &child : children)
    1283             :         {
    1284          65 :             child->Encode(s, thisIdx, curIdx, newIndent);
    1285             :         }
    1286             :     }
    1287         107 : }
    1288             : 
    1289             : }  // namespace
    1290             : 
    1291             : /************************************************************************/
    1292             : /*                  ProcessUpdateAttributeLikeField()                   */
    1293             : /************************************************************************/
    1294             : 
    1295             : /** Implement update of ATTR/INAS/FASC field described in 10a-5.1.2
    1296             :  * "Updating of the Attribute field"
    1297             :  */
    1298          47 : bool OGRS101Reader::ProcessUpdateAttributeLikeField(
    1299             :     const DDFRecord *poUpdateRecord, const DDFField *poUpdateField,
    1300             :     DDFRecord *poTargetRecord, DDFField *poTargetField,
    1301             :     int iFieldInstance) const
    1302             : {
    1303          47 :     const char *pszAttrFieldName = poUpdateField->GetFieldDefn()->GetName();
    1304          47 :     CPLAssert(EQUAL(pszAttrFieldName, ATTR_FIELD) ||
    1305             :               EQUAL(pszAttrFieldName, INAS_FIELD) ||
    1306             :               EQUAL(pszAttrFieldName, FASC_FIELD));
    1307             : 
    1308          47 :     const auto poIDField = poUpdateRecord->GetField(0);
    1309          47 :     CPLAssert(poIDField);
    1310          47 :     const char *pszIDFieldName = poIDField->GetFieldDefn()->GetName();
    1311          47 :     CPLAssert(pszIDFieldName);
    1312             : 
    1313             :     // Record name
    1314             :     const RecordName nRCNM =
    1315          47 :         poUpdateRecord->GetIntSubfield(pszIDFieldName, 0, RCNM_SUBFIELD, 0);
    1316             : 
    1317             :     // Record identifier
    1318             :     const int nRCID =
    1319          47 :         poUpdateRecord->GetIntSubfield(pszIDFieldName, 0, RCID_SUBFIELD, 0);
    1320             : 
    1321          94 :     AttrNode root;
    1322             : 
    1323          47 :     auto poActualTargetField = poTargetField->GetParts().size() == 2
    1324          47 :                                    ? poTargetField->GetParts()[1].get()
    1325          47 :                                    : poTargetField;
    1326          47 :     const int nTargetRepeatCount = poActualTargetField->GetRepeatCount();
    1327             : 
    1328          47 :     const auto poActualUpdateField = poUpdateField->GetParts().size() == 2
    1329          47 :                                          ? poUpdateField->GetParts()[1].get()
    1330          47 :                                          : poUpdateField;
    1331          47 :     const int nUpdateRepeatCount = poActualUpdateField->GetRepeatCount();
    1332             : 
    1333          47 :     constexpr int PASS_TARGET = 0;
    1334          47 :     constexpr int PASS_UPDATE = 1;
    1335         131 :     for (int iPass = PASS_TARGET; iPass <= PASS_UPDATE; ++iPass)
    1336             :     {
    1337          94 :         const auto poCurRecord =
    1338          94 :             (iPass == PASS_TARGET) ? poTargetRecord : poUpdateRecord;
    1339          94 :         const auto poCurField =
    1340          94 :             (iPass == PASS_TARGET) ? poTargetField : poUpdateField;
    1341          94 :         const int nRepeatCount =
    1342          94 :             (iPass == PASS_TARGET) ? nTargetRepeatCount : nUpdateRepeatCount;
    1343             : 
    1344             :         // mapAttributeIndexToNode and oSetNATC_ATIX_PAIX do need to be reset
    1345             :         // at each pass
    1346          94 :         std::map<AttrIndex, std::weak_ptr<AttrNode>> mapAttributeIndexToNode;
    1347             :         std::set<std::tuple<AttrCode, AttrRepeat, AttrIndex>>
    1348          94 :             oSetNATC_ATIX_PAIX;
    1349             : 
    1350         413 :         for (int i = 0; i < nRepeatCount; ++i)
    1351             :         {
    1352         329 :             const AttrIndex curIdx = i + 1;
    1353             :             const int nInstruction =
    1354         329 :                 poCurRecord->GetIntSubfield(poCurField, ATIN_SUBFIELD, i);
    1355         329 :             if (nInstruction != INSTRUCTION_INSERT &&
    1356           9 :                 nInstruction != INSTRUCTION_UPDATE &&
    1357             :                 nInstruction != INSTRUCTION_DELETE)
    1358             :             {
    1359          10 :                 return EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1360             :                     "%s, RCNM=%d, RCID=%d, %s field, instance %d, entry %d: "
    1361             :                     "invalid ATIN=%d",
    1362             :                     m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
    1363             :                     pszAttrFieldName, iFieldInstance, i, nInstruction));
    1364             :             }
    1365             : 
    1366         327 :             auto poNode = std::make_shared<AttrNode>();
    1367         327 :             poNode->code =
    1368             :                 poCurRecord->GetIntSubfield(poCurField, NATC_SUBFIELD, i);
    1369         327 :             poNode->indexOfSameCode =
    1370             :                 poCurRecord->GetIntSubfield(poCurField, ATIX_SUBFIELD, i);
    1371             :             const AttrIndex parentIndex =
    1372         327 :                 poCurRecord->GetIntSubfield(poCurField, PAIX_SUBFIELD, i);
    1373             : 
    1374             :             // (NATC,ATIX,PAIX) tuple should be unique within a record
    1375         327 :             if (!oSetNATC_ATIX_PAIX
    1376             :                      .insert(
    1377         327 :                          {poNode->code, poNode->indexOfSameCode, parentIndex})
    1378         327 :                      .second)
    1379             :             {
    1380           2 :                 return EMIT_ERROR_OR_WARNING(
    1381             :                     CPLSPrintf("%s, RCNM=%d, RCID=%d, %s field, instance %d: "
    1382             :                                "entry %d refers to (NATC,ATIX,PAIX)=(%d,%d,%d) "
    1383             :                                "already encountered.",
    1384             :                                m_osFilename.c_str(), static_cast<int>(nRCNM),
    1385             :                                nRCID, pszAttrFieldName, iFieldInstance, i,
    1386             :                                static_cast<int>(poNode->code),
    1387             :                                static_cast<int>(poNode->indexOfSameCode),
    1388             :                                static_cast<int>(parentIndex)));
    1389             :             }
    1390             : 
    1391             :             const char *pszATVL =
    1392         325 :                 poCurRecord->GetStringSubfield(poCurField, ATVL_SUBFIELD, i);
    1393         325 :             if (pszATVL)
    1394         325 :                 poNode->value = pszATVL;
    1395             : 
    1396             :             // Find the parent node (which is the root node if no parent)
    1397           0 :             std::shared_ptr<AttrNode> poParentNodeSharedPtr;
    1398         325 :             AttrNode *poParentNode = &root;
    1399         325 :             if (parentIndex > 0)
    1400             :             {
    1401         214 :                 auto oIter = mapAttributeIndexToNode.find(parentIndex);
    1402         214 :                 if (oIter == mapAttributeIndexToNode.end())
    1403             :                 {
    1404           2 :                     return EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1405             :                         "%s, RCNM=%d, RCID=%d, %s field, instance %d: entry %d "
    1406             :                         "refers to a PAIX=%d that does not exist",
    1407             :                         m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
    1408             :                         pszAttrFieldName, iFieldInstance, i,
    1409             :                         static_cast<int>(parentIndex)));
    1410             :                 }
    1411         212 :                 poParentNodeSharedPtr = oIter->second.lock();
    1412         212 :                 if (!poParentNodeSharedPtr)
    1413             :                 {
    1414             :                     // I don't think that can happen given the
    1415             :                     // (NATC,ATIX,PAIX) unicity check
    1416           0 :                     return EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1417             :                         "%s, RCNM=%d, RCID=%d, %s field, instance %d: entry %d "
    1418             :                         "refers to a PAIX=%d that has been deleted",
    1419             :                         m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
    1420             :                         pszAttrFieldName, iFieldInstance, i,
    1421             :                         static_cast<int>(parentIndex)));
    1422             :                 }
    1423         212 :                 poParentNode = poParentNodeSharedPtr.get();
    1424             :             }
    1425             : 
    1426         323 :             if (nInstruction == INSTRUCTION_INSERT)
    1427             :             {
    1428             :                 // Find a sibling of same code and whose index is just one before
    1429             :                 // the one to insert.
    1430         248 :                 auto iterInsertion = poParentNode->children.begin();
    1431         400 :                 for (; iterInsertion != poParentNode->children.end();
    1432         152 :                      ++iterInsertion)
    1433             :                 {
    1434         201 :                     if ((*iterInsertion)->code == poNode->code &&
    1435          42 :                         (*iterInsertion)->indexOfSameCode >=
    1436          42 :                             poNode->indexOfSameCode)
    1437             :                     {
    1438           7 :                         break;
    1439             :                     }
    1440             :                 }
    1441             : 
    1442         248 :                 if (iterInsertion != poParentNode->children.end())
    1443             :                 {
    1444           7 :                     if (poCurRecord == poTargetRecord)
    1445             :                     {
    1446           0 :                         const auto iterNext = std::next(iterInsertion);
    1447           0 :                         if (iterNext != poParentNode->children.end() &&
    1448           0 :                             (*iterNext)->code == poNode->code &&
    1449           0 :                             (*iterNext)->indexOfSameCode ==
    1450           0 :                                 poNode->indexOfSameCode)
    1451             :                         {
    1452           0 :                             return EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1453             :                                 "%s, RCNM=%d, RCID=%d, %s field, instance %d: "
    1454             :                                 "entry %d collides with another entry of same "
    1455             :                                 "(NATC, ATIX)=(%d,%d)",
    1456             :                                 m_osFilename.c_str(), static_cast<int>(nRCNM),
    1457             :                                 nRCID, pszAttrFieldName, iFieldInstance, i,
    1458             :                                 static_cast<int>(poNode->code),
    1459             :                                 static_cast<int>(poNode->indexOfSameCode)));
    1460             :                         }
    1461             :                     }
    1462             :                     else
    1463             :                     {
    1464             :                         // Renumber indexOfSameCode of children right to the inserted one
    1465          28 :                         for (auto iterChild = iterInsertion;
    1466          28 :                              iterChild != poParentNode->children.end();
    1467          21 :                              ++iterChild)
    1468             :                         {
    1469          28 :                             if ((*iterChild)->code == poNode->code &&
    1470           7 :                                 (*iterChild)->indexOfSameCode >=
    1471           7 :                                     poNode->indexOfSameCode)
    1472             :                             {
    1473           7 :                                 ++((*iterChild)->indexOfSameCode);
    1474             :                             }
    1475             :                         }
    1476             :                     }
    1477             :                 }
    1478             : 
    1479         248 :                 mapAttributeIndexToNode[curIdx] = poNode;
    1480             : 
    1481         248 :                 poParentNode->children.insert(iterInsertion, poNode);
    1482             :             }
    1483             :             else
    1484             :             {
    1485             :                 // Identify child with desired (code, indexOfSameCode)
    1486          75 :                 auto iterChild = poParentNode->children.begin();
    1487          94 :                 for (; iterChild != poParentNode->children.end(); ++iterChild)
    1488             :                 {
    1489         161 :                     if ((*iterChild)->code == poNode->code &&
    1490          71 :                         (*iterChild)->indexOfSameCode ==
    1491          71 :                             poNode->indexOfSameCode)
    1492             :                     {
    1493          71 :                         break;
    1494             :                     }
    1495             :                 }
    1496          75 :                 if (iterChild == poParentNode->children.end())
    1497             :                 {
    1498           4 :                     return EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1499             :                         "%s, RCNM=%d, RCID=%d, %s field, instance %d: entry %d "
    1500             :                         "references unexisting entry (NATC, ATIX)=(%d,%d)",
    1501             :                         m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
    1502             :                         pszAttrFieldName, iFieldInstance, i,
    1503             :                         static_cast<int>(poNode->code),
    1504             :                         static_cast<int>(poNode->indexOfSameCode)));
    1505             :                 }
    1506             : 
    1507          71 :                 if (nInstruction == INSTRUCTION_DELETE)
    1508             :                 {
    1509           7 :                     poParentNode->children.erase(iterChild);
    1510             :                     // No need to explicitly modify mapAttributeIndexToNode
    1511             :                     // As it contains weak pointers, if the removed node or
    1512             :                     // one of its children was in the map, the weak pointer
    1513             :                     // will be invalidated.
    1514             :                 }
    1515             :                 else
    1516             :                 {
    1517          64 :                     const auto &poModifiedNode = *iterChild;
    1518          64 :                     mapAttributeIndexToNode[curIdx] = poModifiedNode;
    1519          64 :                     poModifiedNode->value = poNode->value;
    1520             :                 }
    1521             :             }
    1522             :         }
    1523             :     }
    1524             : 
    1525          74 :     std::string s;
    1526          37 :     if (EQUAL(pszAttrFieldName, INAS_FIELD) ||
    1527          19 :         EQUAL(pszAttrFieldName, FASC_FIELD))
    1528             :     {
    1529          25 :         constexpr int SIZE_OF_NON_REPEATED_FIELDS = 1 + 4 + 2 + 2 + 1;
    1530          25 :         if (poUpdateField->GetDataSize() < SIZE_OF_NON_REPEATED_FIELDS)
    1531             :         {
    1532             :             // Should probably not occur given earlier checks, but...
    1533           0 :             return EMIT_ERROR_OR_WARNING(
    1534             :                 CPLSPrintf("%s, RCNM=%d, RCID=%d, %s field, instance %d: "
    1535             :                            "invalid update field",
    1536             :                            m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
    1537             :                            pszAttrFieldName, iFieldInstance));
    1538             :         }
    1539          25 :         s.append(poUpdateField->GetData(), SIZE_OF_NON_REPEATED_FIELDS - 1);
    1540          25 :         AppendUInt8(s, INSTRUCTION_INSERT);
    1541             :     }
    1542          37 :     s += root.Encode();
    1543          37 :     AppendUInt8(s, DDF_FIELD_TERMINATOR);
    1544          37 :     poTargetRecord->SetFieldRaw(poTargetField, s.data(),
    1545          37 :                                 static_cast<int>(s.size()));
    1546             : 
    1547          37 :     return true;
    1548             : }
    1549             : 
    1550             : /************************************************************************/
    1551             : /*                         ProcessUpdateATTR()                          */
    1552             : /************************************************************************/
    1553             : 
    1554             : /** Update all instances of ATTR field
    1555             :  */
    1556          33 : bool OGRS101Reader::ProcessUpdateATTR(const DDFRecord *poUpdateRecord,
    1557             :                                       DDFRecord *poTargetRecord) const
    1558             : {
    1559          33 :     const auto poIDField = poUpdateRecord->GetField(0);
    1560          33 :     CPLAssert(poIDField);
    1561          33 :     const char *pszIDFieldName = poIDField->GetFieldDefn()->GetName();
    1562          33 :     CPLAssert(pszIDFieldName);
    1563             : 
    1564             :     // Record name
    1565             :     const RecordName nRCNM =
    1566          33 :         poUpdateRecord->GetIntSubfield(pszIDFieldName, 0, RCNM_SUBFIELD, 0);
    1567             : 
    1568             :     // Record identifier
    1569             :     const int nRCID =
    1570          33 :         poUpdateRecord->GetIntSubfield(pszIDFieldName, 0, RCID_SUBFIELD, 0);
    1571             : 
    1572          66 :     auto apoUpdateFields = poUpdateRecord->GetFields(ATTR_FIELD);
    1573          33 :     if (apoUpdateFields.empty())
    1574          12 :         return true;
    1575          42 :     auto apoTargetFields = poTargetRecord->GetFields(ATTR_FIELD);
    1576          21 :     if (apoTargetFields.size() != apoUpdateFields.size())
    1577             :     {
    1578           0 :         return EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1579             :             "%s, RCNM=%d, RCID=%d, %s field: target record has %d field "
    1580             :             "instances, whereas update record has %d",
    1581             :             m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, ATTR_FIELD,
    1582             :             static_cast<int>(apoTargetFields.size()),
    1583             :             static_cast<int>(apoUpdateFields.size())));
    1584             :     }
    1585             : 
    1586          38 :     for (size_t i = 0; i < apoUpdateFields.size(); ++i)
    1587             :     {
    1588          21 :         if (!ProcessUpdateAttributeLikeField(poUpdateRecord, apoUpdateFields[i],
    1589          21 :                                              poTargetRecord, apoTargetFields[i],
    1590             :                                              static_cast<int>(i)))
    1591             :         {
    1592           4 :             return false;
    1593             :         }
    1594             :     }
    1595             : 
    1596          17 :     return true;
    1597             : }
    1598             : 
    1599             : /************************************************************************/
    1600             : /*                      ProcessUpdateINASOrFASC()                       */
    1601             : /************************************************************************/
    1602             : 
    1603             : /** Update all instances of INAS or FASC field
    1604             :  */
    1605         156 : bool OGRS101Reader::ProcessUpdateINASOrFASC(const DDFRecord *poUpdateRecord,
    1606             :                                             DDFRecord *poTargetRecord,
    1607             :                                             const char *pszFieldName) const
    1608             : {
    1609         156 :     CPLAssert(EQUAL(pszFieldName, INAS_FIELD) ||
    1610             :               EQUAL(pszFieldName, FASC_FIELD));
    1611             : 
    1612         156 :     const auto poIDField = poUpdateRecord->GetField(0);
    1613         156 :     CPLAssert(poIDField);
    1614             : 
    1615             :     // Record name
    1616             :     const RecordName nRCNM =
    1617         156 :         poUpdateRecord->GetIntSubfield(poIDField, RCNM_SUBFIELD, 0);
    1618             : 
    1619             :     // Record identifier
    1620             :     const int nRCID =
    1621         156 :         poUpdateRecord->GetIntSubfield(poIDField, RCID_SUBFIELD, 0);
    1622             : 
    1623         312 :     auto apoUpdateFields = poUpdateRecord->GetFields(pszFieldName);
    1624         156 :     if (apoUpdateFields.empty())
    1625         126 :         return true;
    1626             : 
    1627          60 :     auto apoTargetFields = poTargetRecord->GetFields(pszFieldName);
    1628             : 
    1629          80 :     for (int iUpdate = 0; iUpdate < static_cast<int>(apoUpdateFields.size());
    1630             :          ++iUpdate)
    1631             :     {
    1632          54 :         const auto poUpdateField = apoUpdateFields[iUpdate];
    1633          54 :         const int nInstruction = poUpdateRecord->GetIntSubfield(
    1634             :             poUpdateField,
    1635          54 :             EQUAL(pszFieldName, INAS_FIELD) ? IUIN_SUBFIELD : FAUI_SUBFIELD, 0);
    1636          54 :         if (nInstruction == INSTRUCTION_INSERT)
    1637             :         {
    1638             :             const auto poINASFieldDefn =
    1639          12 :                 m_oMainModule.FindFieldDefn(pszFieldName);
    1640          12 :             if (!poINASFieldDefn)
    1641             :             {
    1642           0 :                 return EMIT_ERROR(CPLSPrintf("Cannot find %s field definition",
    1643             :                                              pszFieldName));
    1644             :             }
    1645          12 :             auto poFieldTarget = poTargetRecord->AddField(poINASFieldDefn);
    1646          12 :             CPLAssert(poFieldTarget);
    1647             : 
    1648          12 :             poTargetRecord->SetFieldRaw(poFieldTarget, poUpdateField->GetData(),
    1649             :                                         poUpdateField->GetDataSize());
    1650          12 :             apoTargetFields.push_back(poFieldTarget);
    1651             :         }
    1652          42 :         else if (nInstruction == INSTRUCTION_UPDATE ||
    1653             :                  nInstruction == INSTRUCTION_DELETE)
    1654             :         {
    1655             :             const RecordName RRNM =
    1656          40 :                 poUpdateRecord->GetIntSubfield(poUpdateField, RRNM_SUBFIELD, 0);
    1657             :             const int RRID =
    1658          40 :                 poUpdateRecord->GetIntSubfield(poUpdateField, RRID_SUBFIELD, 0);
    1659             : 
    1660          40 :             bool bMatchFound = false;
    1661          56 :             for (size_t iTarget = 0; iTarget < apoTargetFields.size();
    1662             :                  ++iTarget)
    1663             :             {
    1664          54 :                 auto poTargetField = apoTargetFields[iTarget];
    1665             :                 const RecordName RRNMTarget = poTargetRecord->GetIntSubfield(
    1666          54 :                     poTargetField, RRNM_SUBFIELD, 0);
    1667          54 :                 const int RRIDTarget = poTargetRecord->GetIntSubfield(
    1668             :                     poTargetField, RRID_SUBFIELD, 0);
    1669          54 :                 if (RRNM == RRNMTarget && RRID == RRIDTarget)
    1670             :                 {
    1671          38 :                     bMatchFound = true;
    1672          38 :                     if (nInstruction == INSTRUCTION_DELETE)
    1673             :                     {
    1674          12 :                         poTargetRecord->DeleteField(poTargetField);
    1675           0 :                         apoTargetFields.erase(apoTargetFields.begin() +
    1676          12 :                                               iTarget);
    1677             :                     }
    1678             :                     else
    1679             :                     {
    1680          26 :                         if (!ProcessUpdateAttributeLikeField(
    1681             :                                 poUpdateRecord, poUpdateField, poTargetRecord,
    1682             :                                 poTargetField, iUpdate))
    1683             :                         {
    1684           0 :                             return false;
    1685             :                         }
    1686             :                     }
    1687          38 :                     break;
    1688             :                 }
    1689             :             }
    1690          40 :             if (!bMatchFound)
    1691             :             {
    1692           2 :                 return EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1693             :                     "%s, RCNM=%d, RCID=%d, %s field, %d instance: found no "
    1694             :                     "matching (RRNM,RRID)=(%d,%d) to %s",
    1695             :                     m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
    1696             :                     pszFieldName, iUpdate, static_cast<int>(RRNM), RRID,
    1697             :                     nInstruction == INSTRUCTION_UPDATE ? "update" : "delete"));
    1698          38 :             }
    1699             :         }
    1700             :         else
    1701             :         {
    1702           2 :             return EMIT_ERROR_OR_WARNING(
    1703             :                 CPLSPrintf("%s, RCNM=%d, RCID=%d, %s field, %d instance: "
    1704             :                            "invalid instruction = %d",
    1705             :                            m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
    1706             :                            pszFieldName, iUpdate, nInstruction));
    1707             :         }
    1708             :     }
    1709             : 
    1710          26 :     return true;
    1711             : }

Generated by: LCOV version 1.14