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

Generated by: LCOV version 1.14