Coverage Report

Created: 2026-08-14 09:29

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