Coverage Report

Created: 2026-06-30 08:33

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