Coverage Report

Created: 2025-07-23 09:13

/src/gdal/ogr/ogrsf_frmts/csw/ogrcswdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  CSW Translator
4
 * Purpose:  Implements OGRCSWDriver.
5
 * Author:   Even Rouault, Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2015, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogrsf_frmts.h"
14
#include "cpl_conv.h"
15
#include "cpl_http.h"
16
#include "ogr_p.h"
17
#include "ogr_swq.h"
18
#include "ogrwfsfilter.h"
19
#include "gmlutils.h"
20
#include "memdataset.h"
21
22
/************************************************************************/
23
/*                             OGRCSWLayer                              */
24
/************************************************************************/
25
26
class OGRCSWDataSource;
27
28
class OGRCSWLayer final : public OGRLayer
29
{
30
    OGRCSWDataSource *poDS;
31
    OGRFeatureDefn *poFeatureDefn;
32
33
    GDALDataset *poBaseDS;
34
    OGRLayer *poBaseLayer;
35
36
    int nPagingStartIndex;
37
    int nFeatureRead;
38
    int nFeaturesInCurrentPage;
39
40
    CPLString osQuery;
41
    CPLString osCSWWhere;
42
43
    std::string m_osTmpDir{};
44
45
    GDALDataset *FetchGetRecords();
46
    GIntBig GetFeatureCountWithHits();
47
    void BuildQuery();
48
49
  public:
50
    explicit OGRCSWLayer(OGRCSWDataSource *poDS);
51
    virtual ~OGRCSWLayer();
52
53
    virtual void ResetReading() override;
54
    virtual OGRFeature *GetNextFeature() override;
55
    virtual GIntBig GetFeatureCount(int bForce = FALSE) override;
56
57
    virtual OGRFeatureDefn *GetLayerDefn() override
58
0
    {
59
0
        return poFeatureDefn;
60
0
    }
61
62
    virtual int TestCapability(const char *) override
63
0
    {
64
0
        return FALSE;
65
0
    }
66
67
    OGRErr ISetSpatialFilter(int iGeomField,
68
                             const OGRGeometry *poGeom) override;
69
70
    virtual OGRErr SetAttributeFilter(const char *) override;
71
};
72
73
/************************************************************************/
74
/*                           OGRCSWDataSource                           */
75
/************************************************************************/
76
77
class OGRCSWDataSource final : public GDALDataset
78
{
79
    CPLString osBaseURL;
80
    CPLString osVersion;
81
    CPLString osElementSetName;
82
    CPLString osOutputSchema;
83
    int nMaxRecords;
84
85
    OGRCSWLayer *poLayer;
86
    bool bFullExtentRecordsAsNonSpatial;
87
88
    CPLHTTPResult *SendGetCapabilities();
89
90
  public:
91
    OGRCSWDataSource();
92
    virtual ~OGRCSWDataSource();
93
94
    int Open(const char *pszFilename, char **papszOpenOptions);
95
96
    virtual int GetLayerCount() override
97
0
    {
98
0
        return poLayer != nullptr;
99
0
    }
100
101
    virtual OGRLayer *GetLayer(int) override;
102
103
    static CPLHTTPResult *HTTPFetch(const char *pszURL, const char *pszPost);
104
105
    const CPLString &GetBaseURL()
106
0
    {
107
0
        return osBaseURL;
108
0
    }
109
110
    const CPLString &GetVersion()
111
0
    {
112
0
        return osVersion;
113
0
    }
114
115
    const CPLString &GetElementSetName()
116
0
    {
117
0
        return osElementSetName;
118
0
    }
119
120
    const CPLString &GetOutputSchema()
121
0
    {
122
0
        return osOutputSchema;
123
0
    }
124
125
    bool FullExtentRecordsAsNonSpatial()
126
0
    {
127
0
        return bFullExtentRecordsAsNonSpatial;
128
0
    }
129
130
    int GetMaxRecords()
131
0
    {
132
0
        return nMaxRecords;
133
0
    }
134
};
135
136
/************************************************************************/
137
/*                           OGRCSWLayer()                              */
138
/************************************************************************/
139
140
OGRCSWLayer::OGRCSWLayer(OGRCSWDataSource *poDSIn)
141
0
    : poDS(poDSIn), poFeatureDefn(new OGRFeatureDefn("records")),
142
0
      poBaseDS(nullptr), poBaseLayer(nullptr), nPagingStartIndex(0),
143
0
      nFeatureRead(0), nFeaturesInCurrentPage(0)
144
0
{
145
0
    SetDescription(poFeatureDefn->GetName());
146
0
    poFeatureDefn->Reference();
147
0
    poFeatureDefn->SetGeomType(wkbPolygon);
148
0
    OGRSpatialReference *poSRS =
149
0
        new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
150
0
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
151
0
    poFeatureDefn->GetGeomFieldDefn(0)->SetName("boundingbox");
152
0
    poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
153
0
    {
154
0
        OGRFieldDefn oField("identifier", OFTString);
155
0
        poFeatureDefn->AddFieldDefn(&oField);
156
0
    }
157
0
    {
158
0
        OGRFieldDefn oField("other_identifiers", OFTStringList);
159
0
        poFeatureDefn->AddFieldDefn(&oField);
160
0
    }
161
0
    {
162
0
        OGRFieldDefn oField("title", OFTString);
163
0
        poFeatureDefn->AddFieldDefn(&oField);
164
0
    }
165
0
    {
166
0
        OGRFieldDefn oField("type", OFTString);
167
0
        poFeatureDefn->AddFieldDefn(&oField);
168
0
    }
169
0
    {
170
0
        OGRFieldDefn oField("subject", OFTString);
171
0
        poFeatureDefn->AddFieldDefn(&oField);
172
0
    }
173
0
    {
174
0
        OGRFieldDefn oField("other_subjects", OFTStringList);
175
0
        poFeatureDefn->AddFieldDefn(&oField);
176
0
    }
177
0
    {
178
0
        OGRFieldDefn oField("references", OFTString);
179
0
        poFeatureDefn->AddFieldDefn(&oField);
180
0
    }
181
0
    {
182
0
        OGRFieldDefn oField("other_references", OFTStringList);
183
0
        poFeatureDefn->AddFieldDefn(&oField);
184
0
    }
185
0
    {
186
0
        OGRFieldDefn oField("modified", OFTString);
187
0
        poFeatureDefn->AddFieldDefn(&oField);
188
0
    }
189
0
    {
190
0
        OGRFieldDefn oField("abstract", OFTString);
191
0
        poFeatureDefn->AddFieldDefn(&oField);
192
0
    }
193
0
    {
194
0
        OGRFieldDefn oField("date", OFTString);
195
0
        poFeatureDefn->AddFieldDefn(&oField);
196
0
    }
197
0
    {
198
0
        OGRFieldDefn oField("language", OFTString);
199
0
        poFeatureDefn->AddFieldDefn(&oField);
200
0
    }
201
0
    {
202
0
        OGRFieldDefn oField("rights", OFTString);
203
0
        poFeatureDefn->AddFieldDefn(&oField);
204
0
    }
205
0
    {
206
0
        OGRFieldDefn oField("format", OFTString);
207
0
        poFeatureDefn->AddFieldDefn(&oField);
208
0
    }
209
0
    {
210
0
        OGRFieldDefn oField("other_formats", OFTStringList);
211
0
        poFeatureDefn->AddFieldDefn(&oField);
212
0
    }
213
0
    {
214
0
        OGRFieldDefn oField("creator", OFTString);
215
0
        poFeatureDefn->AddFieldDefn(&oField);
216
0
    }
217
0
    {
218
0
        OGRFieldDefn oField("source", OFTString);
219
0
        poFeatureDefn->AddFieldDefn(&oField);
220
0
    }
221
0
    {
222
0
        OGRFieldDefn oField("anytext", OFTString);
223
0
        poFeatureDefn->AddFieldDefn(&oField);
224
0
    }
225
0
    if (!poDS->GetOutputSchema().empty())
226
0
    {
227
0
        OGRFieldDefn oField("raw_xml", OFTString);
228
0
        poFeatureDefn->AddFieldDefn(&oField);
229
0
    }
230
231
0
    poSRS->Release();
232
233
0
    m_osTmpDir = VSIMemGenerateHiddenFilename("csw");
234
0
}
235
236
/************************************************************************/
237
/*                          ~OGRCSWLayer()                              */
238
/************************************************************************/
239
240
OGRCSWLayer::~OGRCSWLayer()
241
0
{
242
0
    poFeatureDefn->Release();
243
0
    GDALClose(poBaseDS);
244
0
    VSIRmdirRecursive(m_osTmpDir.c_str());
245
0
}
246
247
/************************************************************************/
248
/*                          ResetReading()                              */
249
/************************************************************************/
250
251
void OGRCSWLayer::ResetReading()
252
0
{
253
0
    nPagingStartIndex = 0;
254
0
    nFeatureRead = 0;
255
0
    nFeaturesInCurrentPage = 0;
256
0
    GDALClose(poBaseDS);
257
0
    poBaseDS = nullptr;
258
0
    poBaseLayer = nullptr;
259
0
}
260
261
/************************************************************************/
262
/*                          GetNextFeature()                            */
263
/************************************************************************/
264
265
OGRFeature *OGRCSWLayer::GetNextFeature()
266
0
{
267
0
    while (true)
268
0
    {
269
0
        if (nFeatureRead == nPagingStartIndex + nFeaturesInCurrentPage)
270
0
        {
271
0
            nPagingStartIndex = nFeatureRead;
272
273
0
            GDALClose(poBaseDS);
274
0
            poBaseLayer = nullptr;
275
276
0
            poBaseDS = FetchGetRecords();
277
0
            if (poBaseDS)
278
0
            {
279
0
                poBaseLayer = poBaseDS->GetLayer(0);
280
0
                poBaseLayer->ResetReading();
281
0
                nFeaturesInCurrentPage = (int)poBaseLayer->GetFeatureCount();
282
0
            }
283
0
        }
284
0
        if (!poBaseLayer)
285
0
            return nullptr;
286
287
0
        OGRFeature *poSrcFeature = poBaseLayer->GetNextFeature();
288
0
        if (poSrcFeature == nullptr)
289
0
            return nullptr;
290
0
        nFeatureRead++;
291
292
0
        OGRFeature *poNewFeature = new OGRFeature(poFeatureDefn);
293
294
0
        for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
295
0
        {
296
0
            const char *pszFieldname =
297
0
                poFeatureDefn->GetFieldDefn(i)->GetNameRef();
298
0
            int iSrcField = poSrcFeature->GetFieldIndex(pszFieldname);
299
            /* http://www.paikkatietohakemisto.fi/geonetwork/srv/en/csw returns
300
             * URI ... */
301
0
            if (iSrcField < 0 && strcmp(pszFieldname, "references") == 0)
302
0
                iSrcField = poSrcFeature->GetFieldIndex("URI");
303
0
            if (iSrcField >= 0 && poSrcFeature->IsFieldSetAndNotNull(iSrcField))
304
0
            {
305
0
                OGRFieldType eType = poFeatureDefn->GetFieldDefn(i)->GetType();
306
0
                OGRFieldType eSrcType =
307
0
                    poSrcFeature->GetFieldDefnRef(iSrcField)->GetType();
308
0
                if (eType == eSrcType)
309
0
                {
310
0
                    poNewFeature->SetField(
311
0
                        i, poSrcFeature->GetRawFieldRef(iSrcField));
312
0
                }
313
0
                else
314
0
                {
315
0
                    if (eType == OFTString && eSrcType == OFTStringList &&
316
0
                        strcmp(pszFieldname, "identifier") == 0)
317
0
                    {
318
0
                        char **papszValues =
319
0
                            poSrcFeature->GetFieldAsStringList(iSrcField);
320
0
                        poNewFeature->SetField("identifier", *papszValues);
321
0
                        if (papszValues[1])
322
0
                            poNewFeature->SetField("other_identifiers",
323
0
                                                   papszValues + 1);
324
0
                    }
325
0
                    else if (eType == OFTString && eSrcType == OFTStringList &&
326
0
                             strcmp(pszFieldname, "subject") == 0)
327
0
                    {
328
0
                        char **papszValues =
329
0
                            poSrcFeature->GetFieldAsStringList(iSrcField);
330
0
                        poNewFeature->SetField("subject", *papszValues);
331
0
                        if (papszValues[1])
332
0
                            poNewFeature->SetField("other_subjects",
333
0
                                                   papszValues + 1);
334
0
                    }
335
0
                    else if (eType == OFTString && eSrcType == OFTStringList &&
336
0
                             strcmp(pszFieldname, "references") == 0)
337
0
                    {
338
0
                        char **papszValues =
339
0
                            poSrcFeature->GetFieldAsStringList(iSrcField);
340
0
                        poNewFeature->SetField("references", *papszValues);
341
0
                        if (papszValues[1])
342
0
                            poNewFeature->SetField("other_references",
343
0
                                                   papszValues + 1);
344
0
                    }
345
0
                    else if (eType == OFTString && eSrcType == OFTStringList &&
346
0
                             strcmp(pszFieldname, "format") == 0)
347
0
                    {
348
0
                        char **papszValues =
349
0
                            poSrcFeature->GetFieldAsStringList(iSrcField);
350
0
                        poNewFeature->SetField("format", *papszValues);
351
0
                        if (papszValues[1])
352
0
                            poNewFeature->SetField("other_formats",
353
0
                                                   papszValues + 1);
354
0
                    }
355
0
                    else
356
0
                        poNewFeature->SetField(
357
0
                            i, poSrcFeature->GetFieldAsString(iSrcField));
358
0
                }
359
0
            }
360
0
        }
361
362
0
        OGRGeometry *poGeom = poSrcFeature->StealGeometry();
363
0
        if (poGeom)
364
0
        {
365
0
            if (poDS->FullExtentRecordsAsNonSpatial())
366
0
            {
367
0
                OGREnvelope sEnvelope;
368
0
                poGeom->getEnvelope(&sEnvelope);
369
0
                if (sEnvelope.MinX == -180 && sEnvelope.MinY == -90 &&
370
0
                    sEnvelope.MaxX == 180 && sEnvelope.MaxY == 90)
371
0
                {
372
0
                    delete poGeom;
373
0
                    poGeom = nullptr;
374
0
                }
375
0
            }
376
0
            if (poGeom)
377
0
            {
378
0
                poGeom->assignSpatialReference(
379
0
                    poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef());
380
0
                poNewFeature->SetGeometryDirectly(poGeom);
381
0
            }
382
0
        }
383
384
0
        poNewFeature->SetFID(nFeatureRead);
385
0
        delete poSrcFeature;
386
387
0
        if (osCSWWhere.empty() && m_poAttrQuery != nullptr &&
388
0
            !m_poAttrQuery->Evaluate(poNewFeature))
389
0
        {
390
0
            delete poNewFeature;
391
0
        }
392
0
        else
393
0
        {
394
0
            return poNewFeature;
395
0
        }
396
0
    }
397
0
}
398
399
/************************************************************************/
400
/*                         GetFeatureCount()                            */
401
/************************************************************************/
402
403
GIntBig OGRCSWLayer::GetFeatureCount(int bForce)
404
0
{
405
0
    GIntBig nFeatures = GetFeatureCountWithHits();
406
0
    if (nFeatures >= 0)
407
0
        return nFeatures;
408
0
    return OGRLayer::GetFeatureCount(bForce);
409
0
}
410
411
/************************************************************************/
412
/*                        GetFeatureCountWithHits()                     */
413
/************************************************************************/
414
415
GIntBig OGRCSWLayer::GetFeatureCountWithHits()
416
0
{
417
0
    CPLString osPost = CPLSPrintf(
418
0
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
419
0
        "<csw:GetRecords resultType=\"hits\" service=\"CSW\" version=\"%s\""
420
0
        " xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\""
421
0
        " xmlns:gml=\"http://www.opengis.net/gml\""
422
0
        " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
423
0
        " xmlns:dct=\"http://purl.org/dc/terms/\""
424
0
        " xmlns:ogc=\"http://www.opengis.net/ogc\""
425
0
        " xmlns:ows=\"http://www.opengis.net/ows\""
426
0
        " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
427
0
        " xsi:schemaLocation=\"http://www.opengis.net/cat/csw/2.0.2 "
428
0
        "http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd\">"
429
0
        "<csw:Query typeNames=\"csw:Record\">"
430
0
        "<csw:ElementSetName>%s</csw:ElementSetName>"
431
0
        "%s"
432
0
        "</csw:Query>"
433
0
        "</csw:GetRecords>",
434
0
        poDS->GetVersion().c_str(), poDS->GetElementSetName().c_str(),
435
0
        osQuery.c_str());
436
437
0
    CPLHTTPResult *psResult =
438
0
        OGRCSWDataSource::HTTPFetch(poDS->GetBaseURL(), osPost);
439
0
    if (psResult == nullptr)
440
0
    {
441
0
        return -1;
442
0
    }
443
444
0
    CPLXMLNode *psXML = CPLParseXMLString((const char *)psResult->pabyData);
445
0
    if (psXML == nullptr)
446
0
    {
447
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
448
0
                 psResult->pabyData);
449
0
        CPLHTTPDestroyResult(psResult);
450
0
        return -1;
451
0
    }
452
0
    CPLStripXMLNamespace(psXML, nullptr, TRUE);
453
0
    CPLHTTPDestroyResult(psResult);
454
0
    psResult = nullptr;
455
456
0
    GIntBig nFeatures = CPLAtoGIntBig(CPLGetXMLValue(
457
0
        psXML, "=GetRecordsResponse.SearchResults.numberOfRecordsMatched",
458
0
        "-1"));
459
460
0
    CPLDestroyXMLNode(psXML);
461
0
    return nFeatures;
462
0
}
463
464
/************************************************************************/
465
/*                         FetchGetRecords()                            */
466
/************************************************************************/
467
468
GDALDataset *OGRCSWLayer::FetchGetRecords()
469
0
{
470
0
    CPLHTTPResult *psResult = nullptr;
471
472
0
    CPLString osOutputSchema = poDS->GetOutputSchema();
473
0
    if (!osOutputSchema.empty())
474
0
        osOutputSchema = " outputSchema=\"" + osOutputSchema + "\"";
475
476
0
    CPLString osPost = CPLSPrintf(
477
0
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
478
0
        "<csw:GetRecords resultType=\"results\" service=\"CSW\" version=\"%s\""
479
0
        "%s"
480
0
        " startPosition=\"%d\""
481
0
        " maxRecords=\"%d\""
482
0
        " xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\""
483
0
        " xmlns:gml=\"http://www.opengis.net/gml\""
484
0
        " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
485
0
        " xmlns:dct=\"http://purl.org/dc/terms/\""
486
0
        " xmlns:ogc=\"http://www.opengis.net/ogc\""
487
0
        " xmlns:ows=\"http://www.opengis.net/ows\""
488
0
        " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
489
0
        " xsi:schemaLocation=\"http://www.opengis.net/cat/csw/2.0.2 "
490
0
        "http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd\">"
491
0
        "<csw:Query typeNames=\"csw:Record\">"
492
0
        "<csw:ElementSetName>%s</csw:ElementSetName>"
493
0
        "%s"
494
0
        "</csw:Query>"
495
0
        "</csw:GetRecords>",
496
0
        poDS->GetVersion().c_str(), osOutputSchema.c_str(),
497
0
        nPagingStartIndex + 1, poDS->GetMaxRecords(),
498
0
        poDS->GetElementSetName().c_str(), osQuery.c_str());
499
500
0
    psResult = OGRCSWDataSource::HTTPFetch(poDS->GetBaseURL(), osPost);
501
0
    if (psResult == nullptr)
502
0
    {
503
0
        return nullptr;
504
0
    }
505
506
0
    VSIMkdir(m_osTmpDir.c_str(), 0);
507
508
0
    GByte *pabyData = psResult->pabyData;
509
0
    int nDataLen = psResult->nDataLen;
510
511
0
    if (strstr((const char *)pabyData, "<ServiceExceptionReport") != nullptr ||
512
0
        strstr((const char *)pabyData, "<ows:ExceptionReport") != nullptr)
513
0
    {
514
0
        CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
515
0
                 pabyData);
516
0
        CPLHTTPDestroyResult(psResult);
517
0
        return nullptr;
518
0
    }
519
    // CPLDebug("CSW", "%s", (const char*)pabyData);
520
521
0
    CPLString osTmpFileName;
522
523
0
    osTmpFileName = m_osTmpDir + "/file.gfs";
524
0
    VSIUnlink(osTmpFileName);
525
526
0
    osTmpFileName = m_osTmpDir + "/file.gml";
527
528
0
    VSILFILE *fp =
529
0
        VSIFileFromMemBuffer(osTmpFileName, pabyData, nDataLen, TRUE);
530
0
    VSIFCloseL(fp);
531
0
    psResult->pabyData = nullptr;
532
533
0
    CPLHTTPDestroyResult(psResult);
534
535
0
    GDALDataset *l_poBaseDS = nullptr;
536
537
0
    if (!poDS->GetOutputSchema().empty())
538
0
    {
539
0
        CPLXMLNode *psRoot = CPLParseXMLFile(osTmpFileName);
540
0
        if (psRoot == nullptr)
541
0
        {
542
0
            if (strstr((const char *)pabyData, "<csw:GetRecordsResponse") ==
543
0
                    nullptr &&
544
0
                strstr((const char *)pabyData, "<GetRecordsResponse") ==
545
0
                    nullptr)
546
0
            {
547
0
                if (nDataLen > 1000)
548
0
                    pabyData[1000] = 0;
549
0
                CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s",
550
0
                         pabyData);
551
0
            }
552
0
            return nullptr;
553
0
        }
554
0
        CPLXMLNode *psSearchResults =
555
0
            CPLGetXMLNode(psRoot, "=csw:GetRecordsResponse.csw:SearchResults");
556
0
        if (psSearchResults == nullptr)
557
0
        {
558
0
            CPLError(CE_Failure, CPLE_AppDefined,
559
0
                     "Cannot find GetRecordsResponse.SearchResults");
560
0
            CPLDestroyXMLNode(psRoot);
561
0
            return nullptr;
562
0
        }
563
564
0
        l_poBaseDS = MEMDataset::Create("", 0, 0, 0, GDT_Unknown, nullptr);
565
0
        OGRLayer *poLyr = l_poBaseDS->CreateLayer("records");
566
0
        OGRFieldDefn oField("raw_xml", OFTString);
567
0
        poLyr->CreateField(&oField);
568
0
        for (CPLXMLNode *psIter = psSearchResults->psChild; psIter;
569
0
             psIter = psIter->psNext)
570
0
        {
571
0
            if (psIter->eType == CXT_Element)
572
0
            {
573
0
                OGRFeature *poFeature = new OGRFeature(poLyr->GetLayerDefn());
574
575
0
                CPLXMLNode *psNext = psIter->psNext;
576
0
                psIter->psNext = nullptr;
577
0
                char *pszXML = CPLSerializeXMLTree(psIter);
578
579
0
                const char *pszWest = nullptr;
580
0
                const char *pszEast = nullptr;
581
0
                const char *pszSouth = nullptr;
582
0
                const char *pszNorth = nullptr;
583
0
                CPLXMLNode *psBBox =
584
0
                    CPLSearchXMLNode(psIter, "gmd:EX_GeographicBoundingBox");
585
0
                if (psBBox)
586
0
                {
587
                    /* ISO 19115/19119: http://www.isotc211.org/2005/gmd */
588
0
                    pszWest = CPLGetXMLValue(
589
0
                        psBBox, "gmd:westBoundLongitude.gco:Decimal", nullptr);
590
0
                    pszEast = CPLGetXMLValue(
591
0
                        psBBox, "gmd:eastBoundLongitude.gco:Decimal", nullptr);
592
0
                    pszSouth = CPLGetXMLValue(
593
0
                        psBBox, "gmd:southBoundLatitude.gco:Decimal", nullptr);
594
0
                    pszNorth = CPLGetXMLValue(
595
0
                        psBBox, "gmd:northBoundLatitude.gco:Decimal", nullptr);
596
0
                }
597
0
                else if ((psBBox = CPLSearchXMLNode(psIter, "spdom")) !=
598
0
                         nullptr)
599
0
                {
600
                    /* FGDC: http://www.opengis.net/cat/csw/csdgm */
601
0
                    pszWest =
602
0
                        CPLGetXMLValue(psBBox, "bounding.westbc", nullptr);
603
0
                    pszEast =
604
0
                        CPLGetXMLValue(psBBox, "bounding.eastbc", nullptr);
605
0
                    pszSouth =
606
0
                        CPLGetXMLValue(psBBox, "bounding.southbc", nullptr);
607
0
                    pszNorth =
608
0
                        CPLGetXMLValue(psBBox, "bounding.northbc", nullptr);
609
0
                }
610
0
                if (pszWest && pszEast && pszSouth && pszNorth)
611
0
                {
612
0
                    double dfMinX = CPLAtof(pszWest);
613
0
                    double dfMaxX = CPLAtof(pszEast);
614
0
                    double dfMinY = CPLAtof(pszSouth);
615
0
                    double dfMaxY = CPLAtof(pszNorth);
616
0
                    OGRLinearRing *poLR = new OGRLinearRing();
617
0
                    poLR->addPoint(dfMinX, dfMinY);
618
0
                    poLR->addPoint(dfMinX, dfMaxY);
619
0
                    poLR->addPoint(dfMaxX, dfMaxY);
620
0
                    poLR->addPoint(dfMaxX, dfMinY);
621
0
                    poLR->addPoint(dfMinX, dfMinY);
622
0
                    OGRPolygon *poPoly = new OGRPolygon();
623
0
                    poPoly->addRingDirectly(poLR);
624
0
                    poFeature->SetGeometryDirectly(poPoly);
625
0
                }
626
0
                else if ((psBBox = CPLSearchXMLNode(
627
0
                              psIter, "ows:BoundingBox")) != nullptr)
628
0
                {
629
0
                    CPLFree(psBBox->pszValue);
630
0
                    psBBox->pszValue = CPLStrdup("gml:Envelope");
631
0
                    CPLString osSRS = CPLGetXMLValue(psBBox, "crs", "");
632
0
                    OGRGeometry *poGeom = GML2OGRGeometry_XMLNode(
633
0
                        psBBox, FALSE, 0, 0, false, true, false);
634
0
                    if (poGeom)
635
0
                    {
636
0
                        bool bLatLongOrder = true;
637
0
                        if (!osSRS.empty())
638
0
                            bLatLongOrder = GML_IsSRSLatLongOrder(osSRS);
639
0
                        if (bLatLongOrder &&
640
0
                            CPLTestBool(CPLGetConfigOption(
641
0
                                "GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")))
642
0
                            poGeom->swapXY();
643
0
                        poFeature->SetGeometryDirectly(poGeom);
644
0
                    }
645
0
                }
646
647
0
                psIter->psNext = psNext;
648
649
0
                poFeature->SetField(0, pszXML);
650
0
                CPL_IGNORE_RET_VAL(poLyr->CreateFeature(poFeature));
651
0
                CPLFree(pszXML);
652
0
                delete poFeature;
653
0
            }
654
0
        }
655
0
        CPLDestroyXMLNode(psRoot);
656
0
    }
657
0
    else
658
0
    {
659
0
        l_poBaseDS = GDALDataset::Open(osTmpFileName, GDAL_OF_VECTOR);
660
0
        if (l_poBaseDS == nullptr)
661
0
        {
662
0
            if (strstr((const char *)pabyData, "<csw:GetRecordsResponse") ==
663
0
                    nullptr &&
664
0
                strstr((const char *)pabyData, "<GetRecordsResponse") ==
665
0
                    nullptr)
666
0
            {
667
0
                if (nDataLen > 1000)
668
0
                    pabyData[1000] = 0;
669
0
                CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s",
670
0
                         pabyData);
671
0
            }
672
0
            return nullptr;
673
0
        }
674
0
    }
675
676
0
    OGRLayer *poLayer = l_poBaseDS->GetLayer(0);
677
0
    if (poLayer == nullptr)
678
0
    {
679
0
        GDALClose(l_poBaseDS);
680
0
        return nullptr;
681
0
    }
682
683
0
    return l_poBaseDS;
684
0
}
685
686
/************************************************************************/
687
/*                         ISetSpatialFilter()                          */
688
/************************************************************************/
689
690
OGRErr OGRCSWLayer::ISetSpatialFilter(int iGeomField, const OGRGeometry *poGeom)
691
0
{
692
0
    const OGRErr eErr = OGRLayer::ISetSpatialFilter(iGeomField, poGeom);
693
0
    if (eErr == OGRERR_NONE)
694
0
    {
695
0
        ResetReading();
696
0
        BuildQuery();
697
0
    }
698
0
    return eErr;
699
0
}
700
701
/************************************************************************/
702
/*                         OGRCSWAddRightPrefixes()                     */
703
/************************************************************************/
704
705
static void OGRCSWAddRightPrefixes(swq_expr_node *poNode)
706
0
{
707
0
    if (poNode->eNodeType == SNT_COLUMN)
708
0
    {
709
0
        if (EQUAL(poNode->string_value, "identifier") ||
710
0
            EQUAL(poNode->string_value, "title") ||
711
0
            EQUAL(poNode->string_value, "type") ||
712
0
            EQUAL(poNode->string_value, "subject") ||
713
0
            EQUAL(poNode->string_value, "date") ||
714
0
            EQUAL(poNode->string_value, "language") ||
715
0
            EQUAL(poNode->string_value, "rights") ||
716
0
            EQUAL(poNode->string_value, "format") ||
717
0
            EQUAL(poNode->string_value, "creator") ||
718
0
            EQUAL(poNode->string_value, "source"))
719
0
        {
720
0
            char *pszNewVal =
721
0
                CPLStrdup(CPLSPrintf("dc:%s", poNode->string_value));
722
0
            CPLFree(poNode->string_value);
723
0
            poNode->string_value = pszNewVal;
724
0
        }
725
0
        else if (EQUAL(poNode->string_value, "references") ||
726
0
                 EQUAL(poNode->string_value, "modified") ||
727
0
                 EQUAL(poNode->string_value, "abstract"))
728
0
        {
729
0
            char *pszNewVal =
730
0
                CPLStrdup(CPLSPrintf("dct:%s", poNode->string_value));
731
0
            CPLFree(poNode->string_value);
732
0
            poNode->string_value = pszNewVal;
733
0
        }
734
0
        else if (EQUAL(poNode->string_value, "other_identifiers"))
735
0
        {
736
0
            CPLFree(poNode->string_value);
737
0
            poNode->string_value = CPLStrdup("dc:identifier");
738
0
        }
739
0
        else if (EQUAL(poNode->string_value, "other_subjects"))
740
0
        {
741
0
            CPLFree(poNode->string_value);
742
0
            poNode->string_value = CPLStrdup("dc:subject");
743
0
        }
744
0
        else if (EQUAL(poNode->string_value, "other_references"))
745
0
        {
746
0
            CPLFree(poNode->string_value);
747
0
            poNode->string_value = CPLStrdup("dct:references");
748
0
        }
749
0
        else if (EQUAL(poNode->string_value, "other_formats"))
750
0
        {
751
0
            CPLFree(poNode->string_value);
752
0
            poNode->string_value = CPLStrdup("dc:format");
753
0
        }
754
0
        else if (EQUAL(poNode->string_value, "AnyText"))
755
0
        {
756
0
            CPLFree(poNode->string_value);
757
0
            poNode->string_value = CPLStrdup("csw:AnyText");
758
0
        }
759
0
        else if (EQUAL(poNode->string_value, "boundingbox"))
760
0
        {
761
0
            CPLFree(poNode->string_value);
762
0
            poNode->string_value = CPLStrdup("ows:BoundingBox");
763
0
        }
764
0
    }
765
0
    else if (poNode->eNodeType == SNT_OPERATION)
766
0
    {
767
0
        for (int i = 0; i < poNode->nSubExprCount; i++)
768
0
            OGRCSWAddRightPrefixes(poNode->papoSubExpr[i]);
769
0
    }
770
0
}
771
772
/************************************************************************/
773
/*                        SetAttributeFilter()                          */
774
/************************************************************************/
775
776
OGRErr OGRCSWLayer::SetAttributeFilter(const char *pszFilter)
777
0
{
778
0
    if (pszFilter != nullptr && pszFilter[0] == 0)
779
0
        pszFilter = nullptr;
780
781
0
    CPLFree(m_pszAttrQueryString);
782
0
    m_pszAttrQueryString = (pszFilter) ? CPLStrdup(pszFilter) : nullptr;
783
784
0
    delete m_poAttrQuery;
785
0
    m_poAttrQuery = nullptr;
786
787
0
    if (pszFilter != nullptr)
788
0
    {
789
0
        m_poAttrQuery = new OGRFeatureQuery();
790
791
0
        OGRErr eErr = m_poAttrQuery->Compile(GetLayerDefn(), pszFilter, TRUE,
792
0
                                             WFSGetCustomFuncRegistrar());
793
0
        if (eErr != OGRERR_NONE)
794
0
        {
795
0
            delete m_poAttrQuery;
796
0
            m_poAttrQuery = nullptr;
797
0
            return eErr;
798
0
        }
799
0
    }
800
801
0
    if (m_poAttrQuery != nullptr)
802
0
    {
803
0
        swq_expr_node *poNode = (swq_expr_node *)m_poAttrQuery->GetSWQExpr();
804
0
        swq_expr_node *poNodeClone = poNode->Clone();
805
0
        poNodeClone->ReplaceBetweenByGEAndLERecurse();
806
0
        OGRCSWAddRightPrefixes(poNodeClone);
807
808
0
        int bNeedsNullCheck = FALSE;
809
0
        if (poNode->field_type != SWQ_BOOLEAN)
810
0
            osCSWWhere = "";
811
0
        else
812
0
            osCSWWhere = WFS_TurnSQLFilterToOGCFilter(
813
0
                poNodeClone, nullptr, nullptr, 110, FALSE, FALSE, FALSE,
814
0
                "ogc:", &bNeedsNullCheck);
815
0
        delete poNodeClone;
816
0
    }
817
0
    else
818
0
        osCSWWhere = "";
819
820
0
    if (m_poAttrQuery != nullptr && osCSWWhere.empty())
821
0
    {
822
0
        CPLDebug("CSW", "Using client-side only mode for filter \"%s\"",
823
0
                 pszFilter);
824
0
        OGRErr eErr = OGRLayer::SetAttributeFilter(pszFilter);
825
0
        if (eErr != OGRERR_NONE)
826
0
            return eErr;
827
0
    }
828
829
0
    ResetReading();
830
0
    BuildQuery();
831
832
0
    return OGRERR_NONE;
833
0
}
834
835
/************************************************************************/
836
/*                                BuildQuery()                          */
837
/************************************************************************/
838
839
void OGRCSWLayer::BuildQuery()
840
0
{
841
0
    if (m_poFilterGeom != nullptr || !osCSWWhere.empty())
842
0
    {
843
0
        osQuery = "<csw:Constraint version=\"1.1.0\">";
844
0
        osQuery += "<ogc:Filter>";
845
0
        if (m_poFilterGeom != nullptr && !osCSWWhere.empty())
846
0
            osQuery += "<ogc:And>";
847
0
        if (m_poFilterGeom != nullptr)
848
0
        {
849
0
            osQuery += "<ogc:BBOX>";
850
0
            osQuery += "<ogc:PropertyName>ows:BoundingBox</ogc:PropertyName>";
851
0
            osQuery += "<gml:Envelope srsName=\"urn:ogc:def:crs:EPSG::4326\">";
852
0
            OGREnvelope sEnvelope;
853
0
            m_poFilterGeom->getEnvelope(&sEnvelope);
854
0
            if (CPLTestBool(CPLGetConfigOption(
855
0
                    "GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")))
856
0
            {
857
0
                osQuery +=
858
0
                    CPLSPrintf("<gml:lowerCorner>%.16g %.16g</gml:lowerCorner>",
859
0
                               sEnvelope.MinY, sEnvelope.MinX);
860
0
                osQuery +=
861
0
                    CPLSPrintf("<gml:upperCorner>%.16g %.16g</gml:upperCorner>",
862
0
                               sEnvelope.MaxY, sEnvelope.MaxX);
863
0
            }
864
0
            else
865
0
            {
866
0
                osQuery +=
867
0
                    CPLSPrintf("<gml:lowerCorner>%.16g %.16g</gml:lowerCorner>",
868
0
                               sEnvelope.MinX, sEnvelope.MinY);
869
0
                osQuery +=
870
0
                    CPLSPrintf("<gml:upperCorner>%.16g %.16g</gml:upperCorner>",
871
0
                               sEnvelope.MaxX, sEnvelope.MaxY);
872
0
            }
873
0
            osQuery += "</gml:Envelope>";
874
0
            osQuery += "</ogc:BBOX>";
875
0
        }
876
0
        osQuery += osCSWWhere;
877
0
        if (m_poFilterGeom != nullptr && !osCSWWhere.empty())
878
0
            osQuery += "</ogc:And>";
879
0
        osQuery += "</ogc:Filter>";
880
0
        osQuery += "</csw:Constraint>";
881
0
    }
882
0
    else
883
0
        osQuery = "";
884
0
}
885
886
/************************************************************************/
887
/*                          OGRCSWDataSource()                          */
888
/************************************************************************/
889
890
OGRCSWDataSource::OGRCSWDataSource()
891
235
    : nMaxRecords(500), poLayer(nullptr), bFullExtentRecordsAsNonSpatial(false)
892
235
{
893
235
}
894
895
/************************************************************************/
896
/*                         ~OGRCSWDataSource()                          */
897
/************************************************************************/
898
899
OGRCSWDataSource::~OGRCSWDataSource()
900
235
{
901
235
    delete poLayer;
902
235
}
903
904
/************************************************************************/
905
/*                          SendGetCapabilities()                       */
906
/************************************************************************/
907
908
CPLHTTPResult *OGRCSWDataSource::SendGetCapabilities()
909
175
{
910
175
    CPLString osURL(osBaseURL);
911
912
175
    osURL = CPLURLAddKVP(osURL, "SERVICE", "CSW");
913
175
    osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
914
915
175
    CPLDebug("CSW", "%s", osURL.c_str());
916
917
175
    CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
918
175
    if (psResult == nullptr)
919
175
    {
920
175
        return nullptr;
921
175
    }
922
923
0
    if (strstr((const char *)psResult->pabyData, "<ServiceExceptionReport") !=
924
0
            nullptr ||
925
0
        strstr((const char *)psResult->pabyData, "<ows:ExceptionReport") !=
926
0
            nullptr ||
927
0
        strstr((const char *)psResult->pabyData, "<ExceptionReport") != nullptr)
928
0
    {
929
0
        CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
930
0
                 psResult->pabyData);
931
0
        CPLHTTPDestroyResult(psResult);
932
0
        return nullptr;
933
0
    }
934
935
0
    return psResult;
936
0
}
937
938
/************************************************************************/
939
/*                                Open()                                */
940
/************************************************************************/
941
942
int OGRCSWDataSource::Open(const char *pszFilename, char **papszOpenOptionsIn)
943
235
{
944
235
    const char *pszBaseURL = CSLFetchNameValue(papszOpenOptionsIn, "URL");
945
235
    if (pszBaseURL == nullptr)
946
235
    {
947
235
        pszBaseURL = pszFilename;
948
235
        if (STARTS_WITH_CI(pszFilename, "CSW:"))
949
235
            pszBaseURL += 4;
950
235
        if (pszBaseURL[0] == '\0')
951
0
        {
952
0
            CPLError(CE_Failure, CPLE_AppDefined, "Missing URL open option");
953
0
            return FALSE;
954
0
        }
955
235
    }
956
235
    osBaseURL = pszBaseURL;
957
235
    osElementSetName =
958
235
        CSLFetchNameValueDef(papszOpenOptionsIn, "ELEMENTSETNAME", "full");
959
235
    bFullExtentRecordsAsNonSpatial = CPLFetchBool(
960
235
        papszOpenOptionsIn, "FULL_EXTENT_RECORDS_AS_NON_SPATIAL", false);
961
235
    osOutputSchema =
962
235
        CSLFetchNameValueDef(papszOpenOptionsIn, "OUTPUT_SCHEMA", "");
963
235
    if (EQUAL(osOutputSchema, "gmd"))
964
0
        osOutputSchema = "http://www.isotc211.org/2005/gmd";
965
235
    else if (EQUAL(osOutputSchema, "csw"))
966
0
        osOutputSchema = "http://www.opengis.net/cat/csw/2.0.2";
967
235
    nMaxRecords =
968
235
        atoi(CSLFetchNameValueDef(papszOpenOptionsIn, "MAX_RECORDS", "500"));
969
970
235
    if (!STARTS_WITH(osBaseURL, "http://") &&
971
235
        !STARTS_WITH(osBaseURL, "https://") &&
972
235
        !STARTS_WITH(osBaseURL, "/vsimem/"))
973
60
        return FALSE;
974
975
175
    CPLHTTPResult *psResult = SendGetCapabilities();
976
175
    if (psResult == nullptr)
977
175
        return FALSE;
978
979
0
    CPLXMLNode *psXML = CPLParseXMLString((const char *)psResult->pabyData);
980
0
    if (psXML == nullptr)
981
0
    {
982
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
983
0
                 psResult->pabyData);
984
0
        CPLHTTPDestroyResult(psResult);
985
0
        return FALSE;
986
0
    }
987
0
    CPLStripXMLNamespace(psXML, nullptr, TRUE);
988
0
    CPLHTTPDestroyResult(psResult);
989
0
    psResult = nullptr;
990
991
0
    const char *pszVersion =
992
0
        CPLGetXMLValue(psXML, "=Capabilities.version", nullptr);
993
0
    if (pszVersion == nullptr)
994
0
    {
995
0
        CPLError(CE_Failure, CPLE_AppDefined,
996
0
                 "Cannot find Capabilities.version");
997
0
        CPLDestroyXMLNode(psXML);
998
0
        return FALSE;
999
0
    }
1000
0
    if (!EQUAL(pszVersion, "2.0.2"))
1001
0
        CPLDebug(
1002
0
            "CSW",
1003
0
            "Presumably only work properly with 2.0.2. Reported version is %s",
1004
0
            pszVersion);
1005
0
    osVersion = pszVersion;
1006
0
    CPLDestroyXMLNode(psXML);
1007
1008
0
    poLayer = new OGRCSWLayer(this);
1009
1010
0
    return TRUE;
1011
0
}
1012
1013
/************************************************************************/
1014
/*                              GetLayer()                              */
1015
/************************************************************************/
1016
1017
OGRLayer *OGRCSWDataSource::GetLayer(int iLayer)
1018
1019
0
{
1020
0
    if (iLayer < 0 || iLayer >= ((poLayer != nullptr) ? 1 : 0))
1021
0
        return nullptr;
1022
0
    else
1023
0
        return poLayer;
1024
0
}
1025
1026
/************************************************************************/
1027
/*                            HTTPFetch()                               */
1028
/************************************************************************/
1029
1030
CPLHTTPResult *OGRCSWDataSource::HTTPFetch(const char *pszURL,
1031
                                           const char *pszPost)
1032
175
{
1033
175
    char **papszOptions = nullptr;
1034
175
    if (pszPost)
1035
0
    {
1036
0
        papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", pszPost);
1037
0
        papszOptions =
1038
0
            CSLAddNameValue(papszOptions, "HEADERS",
1039
0
                            "Content-Type: application/xml; charset=UTF-8");
1040
0
    }
1041
175
    CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, papszOptions);
1042
175
    CSLDestroy(papszOptions);
1043
1044
175
    if (psResult == nullptr)
1045
0
    {
1046
0
        return nullptr;
1047
0
    }
1048
175
    if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
1049
175
    {
1050
175
        CPLError(CE_Failure, CPLE_AppDefined,
1051
175
                 "Error returned by server : %s (%d)",
1052
175
                 (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
1053
175
                 psResult->nStatus);
1054
175
        CPLHTTPDestroyResult(psResult);
1055
175
        return nullptr;
1056
175
    }
1057
0
    if (psResult->pabyData == nullptr)
1058
0
    {
1059
0
        CPLError(CE_Failure, CPLE_AppDefined,
1060
0
                 "Empty content returned by server");
1061
0
        CPLHTTPDestroyResult(psResult);
1062
0
        return nullptr;
1063
0
    }
1064
0
    return psResult;
1065
0
}
1066
1067
/************************************************************************/
1068
/*                             Identify()                               */
1069
/************************************************************************/
1070
1071
static int OGRCSWDriverIdentify(GDALOpenInfo *poOpenInfo)
1072
1073
98.9k
{
1074
98.9k
    return STARTS_WITH_CI(poOpenInfo->pszFilename, "CSW:");
1075
98.9k
}
1076
1077
/************************************************************************/
1078
/*                                Open()                                */
1079
/************************************************************************/
1080
1081
static GDALDataset *OGRCSWDriverOpen(GDALOpenInfo *poOpenInfo)
1082
1083
235
{
1084
235
    if (!OGRCSWDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
1085
0
        return nullptr;
1086
1087
235
    OGRCSWDataSource *poDS = new OGRCSWDataSource();
1088
1089
235
    if (!poDS->Open(poOpenInfo->pszFilename, poOpenInfo->papszOpenOptions))
1090
235
    {
1091
235
        delete poDS;
1092
235
        poDS = nullptr;
1093
235
    }
1094
1095
235
    return poDS;
1096
235
}
1097
1098
/************************************************************************/
1099
/*                           RegisterOGRCSW()                           */
1100
/************************************************************************/
1101
1102
void RegisterOGRCSW()
1103
1104
22
{
1105
22
    if (GDALGetDriverByName("CSW") != nullptr)
1106
0
        return;
1107
1108
22
    GDALDriver *poDriver = new GDALDriver();
1109
1110
22
    poDriver->SetDescription("CSW");
1111
22
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1112
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1113
22
                              "OGC CSW (Catalog  Service for the Web)");
1114
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/csw.html");
1115
1116
22
    poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "CSW:");
1117
22
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
1118
1119
22
    poDriver->SetMetadataItem(
1120
22
        GDAL_DMD_OPENOPTIONLIST,
1121
22
        "<OpenOptionList>"
1122
22
        "  <Option name='URL' type='string' description='URL to the CSW server "
1123
22
        "endpoint' required='true'/>"
1124
22
        "  <Option name='ELEMENTSETNAME' type='string-select' "
1125
22
        "description='Level of details of properties' default='full'>"
1126
22
        "    <Value>brief</Value>"
1127
22
        "    <Value>summary</Value>"
1128
22
        "    <Value>full</Value>"
1129
22
        "  </Option>"
1130
22
        "  <Option name='FULL_EXTENT_RECORDS_AS_NON_SPATIAL' type='boolean' "
1131
22
        "description='Whether records with (-180,-90,180,90) extent should be "
1132
22
        "considered non-spatial' default='false'/>"
1133
22
        "  <Option name='OUTPUT_SCHEMA' type='string' description='Value of "
1134
22
        "outputSchema parameter'/>"
1135
22
        "  <Option name='MAX_RECORDS' type='int' description='Maximum number "
1136
22
        "of records to retrieve in a single time' default='500'/>"
1137
22
        "</OpenOptionList>");
1138
1139
22
    poDriver->pfnIdentify = OGRCSWDriverIdentify;
1140
22
    poDriver->pfnOpen = OGRCSWDriverOpen;
1141
1142
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1143
22
}