Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/nas/nasreader.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  NAS Reader
4
 * Purpose:  Implementation of NASReader class.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2002, Frank Warmerdam
9
 * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "gmlreaderp.h"
15
#include "gmlreader.h"
16
17
#include <algorithm>
18
19
#include "cpl_conv.h"
20
#include "cpl_error.h"
21
#include "cpl_multiproc.h"
22
#include "cpl_string.h"
23
#include "gmlutils.h"
24
#include "ogr_geometry.h"
25
26
/************************************************************************/
27
/* ==================================================================== */
28
/*                  With XERCES Library                                 */
29
/* ==================================================================== */
30
/************************************************************************/
31
32
#include "nasreaderp.h"
33
34
/************************************************************************/
35
/*                          CreateNASReader()                           */
36
/************************************************************************/
37
38
IGMLReader *CreateNASReader()
39
40
0
{
41
0
    return new NASReader();
42
0
}
43
44
/************************************************************************/
45
/*                             NASReader()                              */
46
/************************************************************************/
47
48
NASReader::NASReader()
49
0
    : m_bClassListLocked(false), m_nClassCount(0), m_papoClass(nullptr),
50
0
      m_pszFilename(nullptr), m_poNASHandler(nullptr), m_poSAXReader(nullptr),
51
0
      m_bReadStarted(false), m_bXercesInitialized(false), m_poState(nullptr),
52
0
      m_poCompleteFeature(nullptr), m_fp(nullptr), m_GMLInputSource(nullptr),
53
0
      m_pszFilteredClassName(nullptr)
54
0
{
55
0
}
56
57
/************************************************************************/
58
/*                             ~NASReader()                             */
59
/************************************************************************/
60
61
NASReader::~NASReader()
62
63
0
{
64
0
    NASReader::ClearClasses();
65
66
0
    CPLFree(m_pszFilename);
67
68
0
    CleanupParser();
69
70
0
    if (m_fp)
71
0
        VSIFCloseL(m_fp);
72
73
0
    if (m_bXercesInitialized)
74
0
        OGRDeinitializeXerces();
75
76
0
    CPLFree(m_pszFilteredClassName);
77
0
}
78
79
/************************************************************************/
80
/*                           SetSourceFile()                            */
81
/************************************************************************/
82
83
void NASReader::SetSourceFile(const char *pszFilename)
84
85
0
{
86
0
    CPLFree(m_pszFilename);
87
0
    m_pszFilename = CPLStrdup(pszFilename);
88
0
}
89
90
/************************************************************************/
91
/*                         GetSourceFileName()                          */
92
/************************************************************************/
93
94
const char *NASReader::GetSourceFileName()
95
0
{
96
0
    return m_pszFilename;
97
0
}
98
99
/************************************************************************/
100
/*                            SetupParser()                             */
101
/************************************************************************/
102
103
bool NASReader::SetupParser()
104
105
0
{
106
0
    if (m_fp == nullptr)
107
0
        m_fp = VSIFOpenL(m_pszFilename, "rb");
108
0
    if (m_fp == nullptr)
109
0
        return false;
110
0
    VSIFSeekL(m_fp, 0, SEEK_SET);
111
112
0
    if (!m_bXercesInitialized)
113
0
    {
114
0
        if (!OGRInitializeXerces())
115
0
            return false;
116
0
        m_bXercesInitialized = true;
117
0
    }
118
119
    // Cleanup any old parser.
120
0
    if (m_poSAXReader != nullptr)
121
0
        CleanupParser();
122
123
    // Create and initialize parser.
124
0
    XMLCh *xmlUriValid = nullptr;
125
0
    XMLCh *xmlUriNS = nullptr;
126
127
0
    try
128
0
    {
129
0
        m_poSAXReader = XMLReaderFactory::createXMLReader();
130
131
0
        m_poNASHandler = new NASHandler(this);
132
133
0
        m_poSAXReader->setContentHandler(m_poNASHandler);
134
0
        m_poSAXReader->setErrorHandler(m_poNASHandler);
135
0
        m_poSAXReader->setLexicalHandler(m_poNASHandler);
136
0
        m_poSAXReader->setEntityResolver(m_poNASHandler);
137
0
        m_poSAXReader->setDTDHandler(m_poNASHandler);
138
0
        m_poSAXReader->setFeature(
139
0
            XMLUni::fgXercesDisableDefaultEntityResolution, true);
140
141
0
        xmlUriValid =
142
0
            XMLString::transcode("http://xml.org/sax/features/validation");
143
0
        xmlUriNS =
144
0
            XMLString::transcode("http://xml.org/sax/features/namespaces");
145
146
#if (OGR_GML_VALIDATION)
147
        m_poSAXReader->setFeature(xmlUriValid, true);
148
        m_poSAXReader->setFeature(xmlUriNS, true);
149
150
        m_poSAXReader->setFeature(XMLUni::fgSAX2CoreNameSpaces, true);
151
        m_poSAXReader->setFeature(XMLUni::fgXercesSchema, true);
152
153
        // m_poSAXReader->setDoSchema(true);
154
        // m_poSAXReader->setValidationSchemaFullChecking(true);
155
#else
156
0
        m_poSAXReader->setFeature(XMLUni::fgSAX2CoreValidation, false);
157
158
0
        m_poSAXReader->setFeature(XMLUni::fgXercesSchema, false);
159
160
0
#endif
161
0
        XMLString::release(&xmlUriValid);
162
0
        XMLString::release(&xmlUriNS);
163
0
    }
164
0
    catch (...)
165
0
    {
166
0
        XMLString::release(&xmlUriValid);
167
0
        XMLString::release(&xmlUriNS);
168
169
0
        CPLError(CE_Warning, CPLE_AppDefined,
170
0
                 "NAS: Exception initializing Xerces based GML reader.\n");
171
0
        return false;
172
0
    }
173
174
0
    m_bReadStarted = false;
175
176
    // Push an empty state.
177
0
    PushState(new GMLReadState());
178
179
0
    if (m_GMLInputSource == nullptr)
180
0
    {
181
0
        m_GMLInputSource = OGRCreateXercesInputSource(m_fp);
182
0
    }
183
184
0
    return true;
185
0
}
186
187
/************************************************************************/
188
/*                           CleanupParser()                            */
189
/************************************************************************/
190
191
void NASReader::CleanupParser()
192
193
0
{
194
0
    if (m_poSAXReader == nullptr)
195
0
        return;
196
197
0
    while (m_poState)
198
0
        PopState();
199
200
0
    delete m_poSAXReader;
201
0
    m_poSAXReader = nullptr;
202
203
0
    delete m_poNASHandler;
204
0
    m_poNASHandler = nullptr;
205
206
0
    delete m_poCompleteFeature;
207
0
    m_poCompleteFeature = nullptr;
208
209
0
    OGRDestroyXercesInputSource(m_GMLInputSource);
210
0
    m_GMLInputSource = nullptr;
211
212
0
    m_bReadStarted = false;
213
0
}
214
215
/************************************************************************/
216
/*                            NextFeature()                             */
217
/************************************************************************/
218
219
GMLFeature *NASReader::NextFeature()
220
221
0
{
222
0
    GMLFeature *poReturn = nullptr;
223
224
0
    try
225
0
    {
226
0
        if (!m_bReadStarted)
227
0
        {
228
0
            if (m_poSAXReader == nullptr)
229
0
                SetupParser();
230
231
0
            if (m_poSAXReader == nullptr)
232
0
                return nullptr;
233
234
0
            if (!m_poSAXReader->parseFirst(*m_GMLInputSource, m_oToFill))
235
0
                return nullptr;
236
0
            m_bReadStarted = true;
237
0
        }
238
239
0
        while (m_poCompleteFeature == nullptr && !m_bStopParsing &&
240
0
               m_poSAXReader->parseNext(m_oToFill))
241
0
        {
242
0
        }
243
244
0
        poReturn = m_poCompleteFeature;
245
0
        m_poCompleteFeature = nullptr;
246
0
    }
247
0
    catch (const XMLException &toCatch)
248
0
    {
249
0
        m_bStopParsing = true;
250
0
        CPLDebug("NAS", "Error during NextFeature()! Message:\n%s",
251
0
                 transcode(toCatch.getMessage()).c_str());
252
0
    }
253
0
    catch (const SAXException &toCatch)
254
0
    {
255
0
        CPLString osErrMsg;
256
0
        transcode(toCatch.getMessage(), osErrMsg);
257
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrMsg.c_str());
258
0
        m_bStopParsing = true;
259
0
    }
260
261
0
    return poReturn;
262
0
}
263
264
/************************************************************************/
265
/*                            PushFeature()                             */
266
/*                                                                      */
267
/*      Create a feature based on the named element.  If the            */
268
/*      corresponding feature class doesn't exist yet, then create      */
269
/*      it now.  A new GMLReadState will be created for the feature,    */
270
/*      and it will be placed within that state.  The state is          */
271
/*      pushed onto the readstate stack.                                */
272
/************************************************************************/
273
274
void NASReader::PushFeature(const char *pszElement, const Attributes &attrs)
275
276
0
{
277
    /* -------------------------------------------------------------------- */
278
    /*      Find the class of this element.                                 */
279
    /* -------------------------------------------------------------------- */
280
0
    int iClass = 0;
281
0
    for (; iClass < GetClassCount(); iClass++)
282
0
    {
283
0
        if (strcmp(pszElement, GetClass(iClass)->GetElementName()) == 0)
284
0
            break;
285
0
    }
286
287
    /* -------------------------------------------------------------------- */
288
    /*      Create a new feature class for this element, if there is no     */
289
    /*      existing class for it.                                          */
290
    /* -------------------------------------------------------------------- */
291
0
    if (iClass == GetClassCount())
292
0
    {
293
0
        CPLAssert(!IsClassListLocked());
294
295
0
        GMLFeatureClass *poNewClass = new GMLFeatureClass(pszElement);
296
297
0
        if (EQUAL(pszElement, "Delete"))
298
0
        {
299
0
            const struct
300
0
            {
301
0
                const char *pszName;
302
0
                GMLPropertyType eType;
303
0
                int width;
304
0
            } types[] = {
305
0
                {"typeName", GMLPT_String, -1},
306
0
                {"FeatureId", GMLPT_String, -1},
307
0
                {"context", GMLPT_String, -1},
308
0
                {"safeToIgnore", GMLPT_String, -1},
309
0
                {"replacedBy", GMLPT_String, -1},
310
0
                {"anlass", GMLPT_StringList, -1},
311
0
                {"endet", GMLPT_String, 20},
312
0
                {"ignored", GMLPT_String, -1},
313
0
            };
314
315
0
            for (unsigned int i = 0; i < CPL_ARRAYSIZE(types); i++)
316
0
            {
317
0
                GMLPropertyDefn *poPDefn =
318
0
                    new GMLPropertyDefn(types[i].pszName, types[i].pszName);
319
320
0
                poPDefn->SetType(types[i].eType);
321
0
                if (types[i].width > 0)
322
0
                    poPDefn->SetWidth(types[i].width);
323
324
0
                poNewClass->AddProperty(poPDefn);
325
0
            }
326
0
        }
327
328
0
        iClass = AddClass(poNewClass);
329
0
    }
330
331
    /* -------------------------------------------------------------------- */
332
    /*      Create a feature of this feature class.                         */
333
    /* -------------------------------------------------------------------- */
334
0
    GMLFeature *poFeature = new GMLFeature(GetClass(iClass));
335
336
    /* -------------------------------------------------------------------- */
337
    /*      Create and push a new read state.                               */
338
    /* -------------------------------------------------------------------- */
339
0
    GMLReadState *poState = new GMLReadState();
340
0
    poState->m_poFeature = poFeature;
341
0
    PushState(poState);
342
343
    /* -------------------------------------------------------------------- */
344
    /*      Check for gml:id, and if found push it as an attribute named    */
345
    /*      gml_id.                                                         */
346
    /* -------------------------------------------------------------------- */
347
0
    const XMLCh achGmlId[] = {'g', 'm', 'l', ':', 'i', 'd', 0};
348
0
    int nFIDIndex = attrs.getIndex(achGmlId);
349
0
    if (nFIDIndex != -1)
350
0
    {
351
0
        char *pszFID = CPLStrdup(transcode(attrs.getValue(nFIDIndex)));
352
0
        SetFeaturePropertyDirectly("gml_id", pszFID);
353
0
    }
354
0
}
355
356
/************************************************************************/
357
/*                          IsFeatureElement()                          */
358
/*                                                                      */
359
/*      Based on context and the element name, is this element a new    */
360
/*      GML feature element?                                            */
361
/************************************************************************/
362
363
bool NASReader::IsFeatureElement(const char *pszElement)
364
365
0
{
366
0
    CPLAssert(m_poState != nullptr);
367
368
0
    const char *pszLast = m_poState->GetLastComponent();
369
0
    const int nLen = static_cast<int>(strlen(pszLast));
370
371
    // There seem to be two major NAS classes of feature identifiers
372
    // -- either a wfs:Insert or a gml:featureMember/wfs:member
373
374
0
    if ((nLen < 6 || !EQUAL(pszLast + nLen - 6, "Insert")) &&
375
0
        (nLen < 13 || !EQUAL(pszLast + nLen - 13, "featureMember")) &&
376
0
        (nLen < 6 || !EQUAL(pszLast + nLen - 6, "member")) &&
377
0
        (nLen < 7 || !EQUAL(pszLast + nLen - 7, "Replace")))
378
0
        return false;
379
380
    // If the class list isn't locked, any element that is a featureMember
381
    // will do.
382
0
    if (EQUAL(pszElement, "Filter"))
383
0
        return false;
384
385
0
    if (!IsClassListLocked())
386
0
        return true;
387
388
0
    if (EQUAL(pszElement, "Delete"))
389
0
        return false;
390
391
    // otherwise, find a class with the desired element name.
392
0
    for (int i = 0; i < GetClassCount(); i++)
393
0
    {
394
0
        if (EQUAL(pszElement, GetClass(i)->GetElementName()))
395
0
            return true;
396
0
    }
397
398
0
    return false;
399
0
}
400
401
/************************************************************************/
402
/*                         IsAttributeElement()                         */
403
/************************************************************************/
404
405
bool NASReader::IsAttributeElement(const char *pszElement,
406
                                   const Attributes &attrs)
407
408
0
{
409
0
    if (m_poState->m_poFeature == nullptr)
410
0
        return false;
411
412
0
    GMLFeatureClass *poClass = m_poState->m_poFeature->GetClass();
413
414
    // If the schema is not yet locked, then any simple element
415
    // is potentially an attribute.
416
0
    if (!poClass->IsSchemaLocked())
417
0
        return true;
418
419
    // Otherwise build the path to this element into a single string
420
    // and compare against known attributes.
421
0
    CPLString osElemPath;
422
423
0
    if (m_poState->m_nPathLength == 0)
424
0
        osElemPath = pszElement;
425
0
    else
426
0
    {
427
0
        osElemPath = m_poState->osPath;
428
0
        osElemPath += "|";
429
0
        osElemPath += pszElement;
430
0
    }
431
432
0
    if (poClass->GetPropertyIndexBySrcElement(
433
0
            osElemPath.c_str(), static_cast<int>(osElemPath.size())) >= 0)
434
0
        return true;
435
436
0
    for (unsigned int idx = 0; idx < attrs.getLength(); ++idx)
437
0
    {
438
0
        CPLString osAttrName = transcode(attrs.getQName(idx));
439
0
        CPLString osAttrPath;
440
441
0
        const char *pszName = strchr(osAttrName.c_str(), ':');
442
0
        if (pszName)
443
0
        {
444
0
            osAttrPath = osElemPath + "@" + (pszName + 1);
445
0
            if (poClass->GetPropertyIndexBySrcElement(
446
0
                    osAttrPath.c_str(), static_cast<int>(osAttrPath.size())) >=
447
0
                0)
448
0
                return true;
449
0
        }
450
451
0
        osAttrPath = osElemPath + "@" + osAttrName;
452
0
        if (poClass->GetPropertyIndexBySrcElement(
453
0
                osAttrPath.c_str(), static_cast<int>(osAttrPath.size())) >= 0)
454
0
            return true;
455
0
    }
456
457
0
    return false;
458
0
}
459
460
/************************************************************************/
461
/*                              PopState()                              */
462
/************************************************************************/
463
464
void NASReader::PopState()
465
466
0
{
467
0
    if (m_poState != nullptr)
468
0
    {
469
0
        if (m_poState->m_poFeature != nullptr && m_poCompleteFeature == nullptr)
470
0
        {
471
0
            m_poCompleteFeature = m_poState->m_poFeature;
472
0
            m_poState->m_poFeature = nullptr;
473
0
        }
474
0
        else if (m_poState->m_poFeature != nullptr)
475
0
        {
476
0
            delete m_poState->m_poFeature;
477
0
            m_poState->m_poFeature = nullptr;
478
0
        }
479
480
0
        GMLReadState *poParent = m_poState->m_poParentState;
481
482
0
        delete m_poState;
483
0
        m_poState = poParent;
484
0
    }
485
0
}
486
487
/************************************************************************/
488
/*                             PushState()                              */
489
/************************************************************************/
490
491
void NASReader::PushState(GMLReadState *poState)
492
493
0
{
494
0
    poState->m_poParentState = m_poState;
495
0
    m_poState = poState;
496
0
}
497
498
/************************************************************************/
499
/*                              GetClass()                              */
500
/************************************************************************/
501
502
GMLFeatureClass *NASReader::GetClass(int iClass) const
503
504
0
{
505
0
    if (iClass < 0 || iClass >= m_nClassCount)
506
0
        return nullptr;
507
508
0
    return m_papoClass[iClass];
509
0
}
510
511
/************************************************************************/
512
/*                              GetClass()                              */
513
/************************************************************************/
514
515
GMLFeatureClass *NASReader::GetClass(const char *pszName) const
516
517
0
{
518
0
    for (int iClass = 0; iClass < m_nClassCount; iClass++)
519
0
    {
520
0
        if (strcmp(m_papoClass[iClass]->GetName(), pszName) == 0)
521
0
            return m_papoClass[iClass];
522
0
    }
523
524
0
    return nullptr;
525
0
}
526
527
/************************************************************************/
528
/*                              AddClass()                              */
529
/************************************************************************/
530
531
int NASReader::AddClass(GMLFeatureClass *poNewClass)
532
533
0
{
534
0
    CPLAssert(poNewClass != nullptr &&
535
0
              GetClass(poNewClass->GetName()) == nullptr);
536
537
0
    m_nClassCount++;
538
0
    m_papoClass = static_cast<GMLFeatureClass **>(
539
0
        CPLRealloc(m_papoClass, sizeof(void *) * m_nClassCount));
540
541
    // keep delete the last entry
542
0
    if (m_nClassCount > 1 &&
543
0
        EQUAL(m_papoClass[m_nClassCount - 2]->GetName(), "Delete"))
544
0
    {
545
0
        m_papoClass[m_nClassCount - 1] = m_papoClass[m_nClassCount - 2];
546
0
        m_papoClass[m_nClassCount - 2] = poNewClass;
547
0
        return m_nClassCount - 2;
548
0
    }
549
0
    else
550
0
    {
551
0
        m_papoClass[m_nClassCount - 1] = poNewClass;
552
0
        return m_nClassCount - 1;
553
0
    }
554
0
}
555
556
/************************************************************************/
557
/*                            ClearClasses()                            */
558
/************************************************************************/
559
560
void NASReader::ClearClasses()
561
562
0
{
563
0
    CPLDebug("NAS", "Clearing classes.");
564
565
0
    for (int i = 0; i < m_nClassCount; i++)
566
0
        delete m_papoClass[i];
567
0
    CPLFree(m_papoClass);
568
569
0
    m_nClassCount = 0;
570
0
    m_papoClass = nullptr;
571
0
}
572
573
/************************************************************************/
574
/*                     SetFeaturePropertyDirectly()                     */
575
/*                                                                      */
576
/*      Set the property value on the current feature, adding the       */
577
/*      property name to the GMLFeatureClass if required.               */
578
/*      The pszValue ownership is passed to this function.              */
579
/************************************************************************/
580
581
void NASReader::SetFeaturePropertyDirectly(const char *pszElement,
582
                                           char *pszValue)
583
584
0
{
585
0
    GMLFeature *poFeature = GetState()->m_poFeature;
586
587
0
    CPLAssert(poFeature != nullptr);
588
589
    /* -------------------------------------------------------------------- */
590
    /*      Does this property exist in the feature class?  If not, add     */
591
    /*      it.                                                             */
592
    /* -------------------------------------------------------------------- */
593
0
    GMLFeatureClass *poClass = poFeature->GetClass();
594
0
    int iProperty = poClass->GetPropertyIndexBySrcElement(
595
0
        pszElement, static_cast<int>(strlen(pszElement)));
596
597
0
    if (iProperty < 0)
598
0
    {
599
0
        if (poClass->IsSchemaLocked())
600
0
        {
601
            // CPLDebug("NAS", "Encountered property %s missing from class %s schema [%s].", pszElement, poClass->GetName(), pszValue);
602
0
            CPLFree(pszValue);
603
0
            return;
604
0
        }
605
606
0
        iProperty = poClass->GetPropertyCount();
607
608
0
        CPLString osFieldName;
609
610
0
        if (strchr(pszElement, '|') == nullptr)
611
0
            osFieldName = pszElement;
612
0
        else
613
0
        {
614
0
            osFieldName = strrchr(pszElement, '|') + 1;
615
0
            if (poClass->GetPropertyIndex(osFieldName) != -1)
616
0
                osFieldName = pszElement;
617
0
        }
618
619
        // Does this conflict with an existing property name?
620
0
        while (poClass->GetProperty(osFieldName) != nullptr)
621
0
        {
622
0
            osFieldName += "_";
623
0
        }
624
625
0
        GMLPropertyDefn *poPDefn = new GMLPropertyDefn(osFieldName, pszElement);
626
627
0
        if (EQUAL(CPLGetConfigOption("GML_FIELDTYPES", ""), "ALWAYS_STRING"))
628
0
            poPDefn->SetType(GMLPT_String);
629
630
0
        poClass->AddProperty(poPDefn);
631
0
    }
632
633
0
    if (GMLPropertyDefn::IsSimpleType(
634
0
            poClass->GetProperty(iProperty)->GetType()))
635
0
    {
636
0
        const GMLProperty *poProp = poFeature->GetProperty(iProperty);
637
0
        if (poProp && poProp->nSubProperties > 0)
638
0
        {
639
0
            int iId = poClass->GetPropertyIndex("gml_id");
640
0
            const GMLProperty *poIdProp = poFeature->GetProperty(iId);
641
642
0
            CPLError(CE_Warning, CPLE_AppDefined,
643
0
                     "NAS: Overwriting existing property %s.%s of value '%s' "
644
0
                     "with '%s' (gml_id: %s; type:%d).",
645
0
                     poClass->GetName(), pszElement,
646
0
                     poProp->papszSubProperties[0], pszValue,
647
0
                     poIdProp && poIdProp->nSubProperties > 0 &&
648
0
                             poIdProp->papszSubProperties &&
649
0
                             poIdProp->papszSubProperties[0]
650
0
                         ? poIdProp->papszSubProperties[0]
651
0
                         : "(null)",
652
0
                     poClass->GetProperty(iProperty)->GetType());
653
0
        }
654
0
    }
655
656
    /* -------------------------------------------------------------------- */
657
    /*      Set the property                                                */
658
    /* -------------------------------------------------------------------- */
659
0
    poFeature->SetPropertyDirectly(iProperty, pszValue);
660
661
    /* -------------------------------------------------------------------- */
662
    /*      Do we need to update the property type?                         */
663
    /* -------------------------------------------------------------------- */
664
0
    if (!poClass->IsSchemaLocked())
665
0
    {
666
0
        auto poClassProperty = poClass->GetProperty(iProperty);
667
0
        if (poClassProperty)
668
0
        {
669
0
            const GMLProperty *poProp = poFeature->GetProperty(iProperty);
670
0
            if (poProp)
671
0
            {
672
0
                poClassProperty->AnalysePropertyValue(poProp);
673
0
            }
674
0
        }
675
0
        else
676
0
        {
677
0
            CPLAssert(false);
678
0
        }
679
0
    }
680
0
}
681
682
/************************************************************************/
683
/*                            LoadClasses()                             */
684
/************************************************************************/
685
686
bool NASReader::LoadClasses(const char *pszFile)
687
688
0
{
689
    // Add logic later to determine reasonable default schema file.
690
0
    if (pszFile == nullptr)
691
0
        return false;
692
693
0
    CPLDebug("NAS", "Loading classes from %s", pszFile);
694
695
    /* -------------------------------------------------------------------- */
696
    /*      Load the raw XML file.                                          */
697
    /* -------------------------------------------------------------------- */
698
0
    VSILFILE *fp = VSIFOpenL(pszFile, "rb");
699
700
0
    if (fp == nullptr)
701
0
    {
702
0
        CPLError(CE_Failure, CPLE_OpenFailed, "NAS: Failed to open file %s.",
703
0
                 pszFile);
704
0
        return false;
705
0
    }
706
707
0
    VSIFSeekL(fp, 0, SEEK_END);
708
0
    int nLength = static_cast<int>(VSIFTellL(fp));
709
0
    VSIFSeekL(fp, 0, SEEK_SET);
710
711
0
    char *pszWholeText = static_cast<char *>(VSIMalloc(nLength + 1));
712
0
    if (pszWholeText == nullptr)
713
0
    {
714
0
        CPLError(CE_Failure, CPLE_AppDefined,
715
0
                 "NAS: Failed to allocate %d byte buffer for %s,\n"
716
0
                 "is this really a GMLFeatureClassList file?",
717
0
                 nLength, pszFile);
718
0
        VSIFCloseL(fp);
719
0
        return false;
720
0
    }
721
722
0
    if (VSIFReadL(pszWholeText, nLength, 1, fp) != 1)
723
0
    {
724
0
        VSIFree(pszWholeText);
725
0
        VSIFCloseL(fp);
726
0
        CPLError(CE_Failure, CPLE_AppDefined, "NAS: Read failed on %s.",
727
0
                 pszFile);
728
0
        return false;
729
0
    }
730
0
    pszWholeText[nLength] = '\0';
731
732
0
    VSIFCloseL(fp);
733
734
0
    if (strstr(pszWholeText, "<GMLFeatureClassList") == nullptr)
735
0
    {
736
0
        VSIFree(pszWholeText);
737
0
        CPLError(CE_Failure, CPLE_AppDefined,
738
0
                 "NAS: File %s does not contain a GMLFeatureClassList tree.",
739
0
                 pszFile);
740
0
        return false;
741
0
    }
742
743
    /* -------------------------------------------------------------------- */
744
    /*      Convert to XML parse tree.                                      */
745
    /* -------------------------------------------------------------------- */
746
0
    CPLXMLTreeCloser psRoot(CPLParseXMLString(pszWholeText));
747
0
    VSIFree(pszWholeText);
748
749
    // We assume parser will report errors via CPL.
750
0
    if (psRoot.get() == nullptr)
751
0
        return false;
752
753
0
    if (psRoot->eType != CXT_Element ||
754
0
        !EQUAL(psRoot->pszValue, "GMLFeatureClassList"))
755
0
    {
756
0
        CPLError(CE_Failure, CPLE_AppDefined,
757
0
                 "NAS: File %s is not a GMLFeatureClassList document.",
758
0
                 pszFile);
759
0
        return false;
760
0
    }
761
762
    /* -------------------------------------------------------------------- */
763
    /*      Extract feature classes for all definitions found.              */
764
    /* -------------------------------------------------------------------- */
765
0
    for (CPLXMLNode *psThis = psRoot->psChild; psThis != nullptr;
766
0
         psThis = psThis->psNext)
767
0
    {
768
0
        if (psThis->eType == CXT_Element &&
769
0
            EQUAL(psThis->pszValue, "GMLFeatureClass"))
770
0
        {
771
0
            GMLFeatureClass *poClass = new GMLFeatureClass();
772
773
0
            if (!poClass->InitializeFromXML(psThis))
774
0
            {
775
0
                delete poClass;
776
0
                return false;
777
0
            }
778
779
0
            poClass->SetSchemaLocked(true);
780
781
0
            AddClass(poClass);
782
0
        }
783
0
    }
784
785
0
    SetClassListLocked(true);
786
787
0
    return true;
788
0
}
789
790
/************************************************************************/
791
/*                            SaveClasses()                             */
792
/************************************************************************/
793
794
bool NASReader::SaveClasses(const char *pszFile)
795
796
0
{
797
    // Add logic later to determine reasonable default schema file.
798
0
    if (pszFile == nullptr)
799
0
        return false;
800
801
    /* -------------------------------------------------------------------- */
802
    /*      Create in memory schema tree.                                   */
803
    /* -------------------------------------------------------------------- */
804
0
    CPLXMLNode *psRoot =
805
0
        CPLCreateXMLNode(nullptr, CXT_Element, "GMLFeatureClassList");
806
807
0
    for (int iClass = 0; iClass < GetClassCount(); iClass++)
808
0
    {
809
0
        GMLFeatureClass *poClass = GetClass(iClass);
810
811
0
        CPLAddXMLChild(psRoot, poClass->SerializeToXML());
812
0
    }
813
814
    /* -------------------------------------------------------------------- */
815
    /*      Serialize to disk.                                              */
816
    /* -------------------------------------------------------------------- */
817
0
    char *pszWholeText = CPLSerializeXMLTree(psRoot);
818
819
0
    CPLDestroyXMLNode(psRoot);
820
821
0
    VSILFILE *fp = VSIFOpenL(pszFile, "wb");
822
823
0
    bool bSuccess = true;
824
0
    if (fp == nullptr)
825
0
        bSuccess = false;
826
0
    else if (VSIFWriteL(pszWholeText, strlen(pszWholeText), 1, fp) != 1)
827
0
    {
828
0
        VSIFCloseL(fp);
829
0
        bSuccess = false;
830
0
    }
831
0
    else
832
0
    {
833
0
        if (VSIFWriteL(pszWholeText, strlen(pszWholeText), 1, fp) != 1)
834
0
            bSuccess = false;
835
0
        VSIFCloseL(fp);
836
0
    }
837
838
0
    CPLFree(pszWholeText);
839
840
0
    return bSuccess;
841
0
}
842
843
/************************************************************************/
844
/*                          PrescanForSchema()                          */
845
/*                                                                      */
846
/*      For now we use a pretty dumb approach of just doing a normal    */
847
/*      scan of the whole file, building up the schema information.     */
848
/*      Eventually we hope to do a more efficient scan when just        */
849
/*      looking for schema information.                                 */
850
/************************************************************************/
851
852
bool NASReader::PrescanForSchema(bool bGetExtents, bool /*bOnlyDetectSRS*/)
853
0
{
854
0
    if (m_pszFilename == nullptr)
855
0
        return false;
856
857
0
    CPLDebug("NAS", "Prescanning %s.", m_pszFilename);
858
859
0
    SetClassListLocked(false);
860
861
0
    if (!SetupParser())
862
0
        return false;
863
864
0
    std::string osWork;
865
866
0
    GMLFeature *poFeature = nullptr;
867
0
    while ((poFeature = NextFeature()) != nullptr)
868
0
    {
869
0
        GMLFeatureClass *poClass = poFeature->GetClass();
870
871
0
        if (poClass->GetFeatureCount() == -1)
872
0
            poClass->SetFeatureCount(1);
873
0
        else
874
0
            poClass->SetFeatureCount(poClass->GetFeatureCount() + 1);
875
876
0
        if (bGetExtents)
877
0
        {
878
0
            OGRGeometry *poGeometry = nullptr;
879
880
0
            const CPLXMLNode *const *papsGeometry =
881
0
                poFeature->GetGeometryList();
882
0
            if (papsGeometry[0] != nullptr)
883
0
            {
884
0
                poGeometry =
885
0
                    (OGRGeometry *)OGR_G_CreateFromGMLTree(papsGeometry[0]);
886
0
                poGeometry =
887
0
                    ConvertGeometry(std::unique_ptr<OGRGeometry>(poGeometry))
888
0
                        .release();
889
0
            }
890
891
0
            if (poGeometry != nullptr)
892
0
            {
893
0
                OGREnvelope sEnvelope;
894
895
0
                if (poClass->GetGeometryPropertyCount() == 0)
896
0
                    poClass->AddGeometryProperty(new GMLGeometryPropertyDefn(
897
0
                        "", "", wkbUnknown, -1, true));
898
899
0
                OGRwkbGeometryType eGType =
900
0
                    (OGRwkbGeometryType)poClass->GetGeometryProperty(0)
901
0
                        ->GetType();
902
903
                // Merge SRSName into layer.
904
0
                const char *pszSRSName =
905
0
                    GML_ExtractSrsNameFromGeometry(papsGeometry, osWork, false);
906
                // if (pszSRSName != NULL)
907
                //     m_bCanUseGlobalSRSName = FALSE;
908
0
                poClass->MergeSRSName(pszSRSName);
909
910
                // Merge geometry type into layer.
911
0
                if (poClass->GetFeatureCount() == 1 && eGType == wkbUnknown)
912
0
                    eGType = wkbNone;
913
914
0
                poClass->GetGeometryProperty(0)->SetType(
915
0
                    OGRMergeGeometryTypesEx(
916
0
                        eGType, poGeometry->getGeometryType(), TRUE));
917
918
                // merge extents.
919
0
                poGeometry->getEnvelope(&sEnvelope);
920
0
                delete poGeometry;
921
0
                double dfXMin = 0.0;
922
0
                double dfXMax = 0.0;
923
0
                double dfYMin = 0.0;
924
0
                double dfYMax = 0.0;
925
0
                if (poClass->GetExtents(&dfXMin, &dfXMax, &dfYMin, &dfYMax))
926
0
                {
927
0
                    dfXMin = std::min(dfXMin, sEnvelope.MinX);
928
0
                    dfXMax = std::max(dfXMax, sEnvelope.MaxX);
929
0
                    dfYMin = std::min(dfYMin, sEnvelope.MinY);
930
0
                    dfYMax = std::max(dfYMax, sEnvelope.MaxY);
931
0
                }
932
0
                else
933
0
                {
934
0
                    dfXMin = sEnvelope.MinX;
935
0
                    dfXMax = sEnvelope.MaxX;
936
0
                    dfYMin = sEnvelope.MinY;
937
0
                    dfYMax = sEnvelope.MaxY;
938
0
                }
939
940
0
                poClass->SetExtents(dfXMin, dfXMax, dfYMin, dfYMax);
941
0
            }
942
0
            else
943
0
            {
944
0
                if (poClass->GetGeometryPropertyCount() == 1 &&
945
0
                    poClass->GetGeometryProperty(0)->GetType() ==
946
0
                        (int)wkbUnknown &&
947
0
                    poClass->GetFeatureCount() == 1)
948
0
                {
949
0
                    poClass->ClearGeometryProperties();
950
0
                }
951
0
            }
952
0
        }
953
954
0
        delete poFeature;
955
0
    }
956
957
0
    CleanupParser();
958
959
    // Skip empty classes
960
0
    int j = 0;
961
0
    for (int i = 0, n = m_nClassCount; i < n; i++)
962
0
    {
963
0
        if (m_papoClass[i]->GetFeatureCount() > 0)
964
0
        {
965
0
            m_papoClass[j++] = m_papoClass[i];
966
0
            continue;
967
0
        }
968
969
0
        CPLDebug("NAS", "Skipping empty layer %s.", m_papoClass[i]->GetName());
970
971
0
        delete m_papoClass[i];
972
0
        m_papoClass[i] = nullptr;
973
0
    }
974
975
0
    m_nClassCount = j;
976
977
0
    CPLDebug("NAS", "%d remaining classes after prescan.", m_nClassCount);
978
979
0
    for (int i = 0; i < m_nClassCount; i++)
980
0
    {
981
0
        CPLDebug("NAS", "%s: " CPL_FRMT_GIB " features.",
982
0
                 m_papoClass[i]->GetName(), m_papoClass[i]->GetFeatureCount());
983
0
    }
984
985
0
    return GetClassCount() > 0;
986
0
}
987
988
/************************************************************************/
989
/*                            ResetReading()                            */
990
/************************************************************************/
991
992
void NASReader::ResetReading()
993
994
0
{
995
0
    CleanupParser();
996
0
    SetFilteredClassName(nullptr);
997
0
}
998
999
/************************************************************************/
1000
/*                      GetAttributeElementIndex()                      */
1001
/************************************************************************/
1002
1003
int NASReader::GetAttributeElementIndex(const char *pszElement, int nLen,
1004
                                        const char *pszAttrKey)
1005
1006
0
{
1007
0
    GMLFeatureClass *poClass = m_poState->m_poFeature->GetClass();
1008
1009
    // Otherwise build the path to this element into a single string
1010
    // and compare against known attributes.
1011
0
    if (m_poState->m_nPathLength == 0)
1012
0
    {
1013
0
        if (pszAttrKey == nullptr)
1014
0
            return poClass->GetPropertyIndexBySrcElement(pszElement, nLen);
1015
0
        else
1016
0
        {
1017
0
            CPLString osElemPath;
1018
0
            int nFullLen = nLen + 1 + static_cast<int>(strlen(pszAttrKey));
1019
0
            osElemPath.reserve(nFullLen);
1020
0
            osElemPath.assign(pszElement, nLen);
1021
0
            osElemPath.append(1, '@');
1022
0
            osElemPath.append(pszAttrKey);
1023
0
            return poClass->GetPropertyIndexBySrcElement(osElemPath.c_str(),
1024
0
                                                         nFullLen);
1025
0
        }
1026
0
    }
1027
0
    else
1028
0
    {
1029
0
        int nFullLen = nLen + static_cast<int>(m_poState->osPath.size()) + 1;
1030
0
        if (pszAttrKey != nullptr)
1031
0
            nFullLen += 1 + static_cast<int>(strlen(pszAttrKey));
1032
1033
0
        CPLString osElemPath;
1034
0
        osElemPath.reserve(nFullLen);
1035
0
        osElemPath.assign(m_poState->osPath);
1036
0
        osElemPath.append(1, '|');
1037
0
        osElemPath.append(pszElement, nLen);
1038
0
        if (pszAttrKey != nullptr)
1039
0
        {
1040
0
            osElemPath.append(1, '@');
1041
0
            osElemPath.append(pszAttrKey);
1042
0
        }
1043
0
        return poClass->GetPropertyIndexBySrcElement(osElemPath.c_str(),
1044
0
                                                     nFullLen);
1045
0
    }
1046
0
}
1047
1048
/************************************************************************/
1049
/*                         DealWithAttributes()                         */
1050
/************************************************************************/
1051
1052
void NASReader::DealWithAttributes(const char *pszName, int nLenName,
1053
                                   const Attributes &attrs)
1054
1055
0
{
1056
0
    GMLFeature *poFeature = GetState()->m_poFeature;
1057
0
    CPLAssert(poFeature != nullptr);
1058
1059
0
    for (unsigned int idx = 0; idx < attrs.getLength(); ++idx)
1060
0
    {
1061
0
        CPLString osAttrKey = transcode(attrs.getQName(idx));
1062
0
        CPLString osAttrVal = transcode(attrs.getValue(idx));
1063
1064
0
        int nAttrIndex = 0;
1065
0
        const char *pszAttrKeyNoNS = strchr(osAttrKey, ':');
1066
0
        if (pszAttrKeyNoNS)
1067
0
            pszAttrKeyNoNS++;
1068
1069
0
        if ((pszAttrKeyNoNS &&
1070
0
             (nAttrIndex = GetAttributeElementIndex(pszName, nLenName,
1071
0
                                                    pszAttrKeyNoNS)) != -1) ||
1072
0
            ((nAttrIndex = GetAttributeElementIndex(pszName, nLenName,
1073
0
                                                    osAttrKey)) != -1))
1074
0
        {
1075
0
            const char *pszAttrVal = osAttrVal;
1076
0
            if (osAttrKey == "xlink:href" ||
1077
0
                (pszAttrKeyNoNS && EQUAL(pszAttrKeyNoNS, "href")))
1078
0
            {
1079
0
                if (STARTS_WITH_CI(pszAttrVal, "urn:adv:oid:"))
1080
0
                    pszAttrVal += 12;
1081
0
                else if (STARTS_WITH_CI(
1082
0
                             pszAttrVal,
1083
0
                             "https://registry.gdi-de.org/codelist/"))
1084
0
                    pszAttrVal = strrchr(pszAttrVal, '/') + 1;
1085
0
            }
1086
1087
0
            poFeature->SetPropertyDirectly(nAttrIndex, CPLStrdup(pszAttrVal));
1088
0
            pszAttrVal = nullptr;
1089
0
        }
1090
0
    }
1091
0
}
1092
1093
/************************************************************************/
1094
/*                         HugeFileResolver()                           */
1095
/*      Returns true for success                                        */
1096
/************************************************************************/
1097
1098
bool NASReader::HugeFileResolver(const char * /*pszFile */,
1099
                                 bool /* bSqliteIsTempFile */,
1100
                                 int /* iSqliteCacheMB */)
1101
0
{
1102
0
    CPLDebug("NAS", "HugeFileResolver() not currently implemented for NAS.");
1103
0
    return false;
1104
0
}
1105
1106
/************************************************************************/
1107
/*                         PrescanForTemplate()                         */
1108
/*      Returns true for success                                        */
1109
/************************************************************************/
1110
1111
bool NASReader::PrescanForTemplate(void)
1112
1113
0
{
1114
0
    CPLDebug("NAS", "PrescanForTemplate() not currently implemented for NAS.");
1115
0
    return false;
1116
0
}
1117
1118
/************************************************************************/
1119
/*                           ResolveXlinks()                            */
1120
/*      Returns true for success                                        */
1121
/************************************************************************/
1122
1123
bool NASReader::ResolveXlinks(const char * /*pszFile */,
1124
                              bool * /*pbOutIsTempFile */,
1125
                              char ** /*papszSkip */, const bool /*bStrict */)
1126
0
{
1127
0
    CPLDebug("NAS", "ResolveXlinks() not currently implemented for NAS.");
1128
0
    return false;
1129
0
}
1130
1131
/************************************************************************/
1132
/*                        SetFilteredClassName()                        */
1133
/************************************************************************/
1134
1135
bool NASReader::SetFilteredClassName(const char *pszClassName)
1136
0
{
1137
0
    CPLFree(m_pszFilteredClassName);
1138
0
    m_pszFilteredClassName = pszClassName ? CPLStrdup(pszClassName) : nullptr;
1139
0
    return true;
1140
0
}
1141
1142
/************************************************************************/
1143
/*                          ConvertGeometry()                           */
1144
/************************************************************************/
1145
1146
std::unique_ptr<OGRGeometry>
1147
NASReader::ConvertGeometry(std::unique_ptr<OGRGeometry> poGeom)
1148
0
{
1149
0
    if (poGeom == nullptr ||
1150
0
        wkbFlatten(poGeom->getGeometryType()) != wkbMultiLineString)
1151
0
    {
1152
0
        return poGeom;
1153
0
    }
1154
1155
0
    return OGRGeometryFactory::forceTo(std::move(poGeom), wkbLineString);
1156
0
}