LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101readerattributes.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 661 727 90.9 %
Date: 2026-06-19 21:24:00 Functions: 19 19 100.0 %

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

Generated by: LCOV version 1.14