Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/csw/ogrcswdataset.cpp
Line
Count
Source
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
    ~OGRCSWLayer() override;
52
53
    void ResetReading() override;
54
    OGRFeature *GetNextFeature() override;
55
    GIntBig GetFeatureCount(int bForce = FALSE) override;
56
57
    const OGRFeatureDefn *GetLayerDefn() const override
58
0
    {
59
0
        return poFeatureDefn;
60
0
    }
61
62
    bool TestCapability(const char *) const override
63
0
    {
64
0
        return FALSE;
65
0
    }
66
67
    OGRErr ISetSpatialFilter(int iGeomField,
68
                             const OGRGeometry *poGeom) override;
69
70
    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
    ~OGRCSWDataSource() override;
93
94
    int Open(const char *pszFilename, char **papszOpenOptions);
95
96
    int GetLayerCount() const override
97
0
    {
98
0
        return poLayer != nullptr;
99
0
    }
100
101
    const OGRLayer *GetLayer(int) const 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
        CPL_IGNORE_RET_VAL(poLyr->CreateField(&oField));
568
0
        std::unique_ptr<OGRGML_SRSCache, decltype(&OGRGML_SRSCache_Destroy)>
569
0
            srsCache{OGRGML_SRSCache_Create(), OGRGML_SRSCache_Destroy};
570
0
        for (CPLXMLNode *psIter = psSearchResults->psChild; psIter;
571
0
             psIter = psIter->psNext)
572
0
        {
573
0
            if (psIter->eType == CXT_Element)
574
0
            {
575
0
                OGRFeature *poFeature = new OGRFeature(poLyr->GetLayerDefn());
576
577
0
                CPLXMLNode *psNext = psIter->psNext;
578
0
                psIter->psNext = nullptr;
579
0
                char *pszXML = CPLSerializeXMLTree(psIter);
580
581
0
                const char *pszWest = nullptr;
582
0
                const char *pszEast = nullptr;
583
0
                const char *pszSouth = nullptr;
584
0
                const char *pszNorth = nullptr;
585
0
                CPLXMLNode *psBBox =
586
0
                    CPLSearchXMLNode(psIter, "gmd:EX_GeographicBoundingBox");
587
0
                if (psBBox)
588
0
                {
589
                    /* ISO 19115/19119: http://www.isotc211.org/2005/gmd */
590
0
                    pszWest = CPLGetXMLValue(
591
0
                        psBBox, "gmd:westBoundLongitude.gco:Decimal", nullptr);
592
0
                    pszEast = CPLGetXMLValue(
593
0
                        psBBox, "gmd:eastBoundLongitude.gco:Decimal", nullptr);
594
0
                    pszSouth = CPLGetXMLValue(
595
0
                        psBBox, "gmd:southBoundLatitude.gco:Decimal", nullptr);
596
0
                    pszNorth = CPLGetXMLValue(
597
0
                        psBBox, "gmd:northBoundLatitude.gco:Decimal", nullptr);
598
0
                }
599
0
                else if ((psBBox = CPLSearchXMLNode(psIter, "spdom")) !=
600
0
                         nullptr)
601
0
                {
602
                    /* FGDC: http://www.opengis.net/cat/csw/csdgm */
603
0
                    pszWest =
604
0
                        CPLGetXMLValue(psBBox, "bounding.westbc", nullptr);
605
0
                    pszEast =
606
0
                        CPLGetXMLValue(psBBox, "bounding.eastbc", nullptr);
607
0
                    pszSouth =
608
0
                        CPLGetXMLValue(psBBox, "bounding.southbc", nullptr);
609
0
                    pszNorth =
610
0
                        CPLGetXMLValue(psBBox, "bounding.northbc", nullptr);
611
0
                }
612
0
                if (pszWest && pszEast && pszSouth && pszNorth)
613
0
                {
614
0
                    double dfMinX = CPLAtof(pszWest);
615
0
                    double dfMaxX = CPLAtof(pszEast);
616
0
                    double dfMinY = CPLAtof(pszSouth);
617
0
                    double dfMaxY = CPLAtof(pszNorth);
618
0
                    OGRLinearRing *poLR = new OGRLinearRing();
619
0
                    poLR->addPoint(dfMinX, dfMinY);
620
0
                    poLR->addPoint(dfMinX, dfMaxY);
621
0
                    poLR->addPoint(dfMaxX, dfMaxY);
622
0
                    poLR->addPoint(dfMaxX, dfMinY);
623
0
                    poLR->addPoint(dfMinX, dfMinY);
624
0
                    OGRPolygon *poPoly = new OGRPolygon();
625
0
                    poPoly->addRingDirectly(poLR);
626
0
                    poFeature->SetGeometryDirectly(poPoly);
627
0
                }
628
0
                else if ((psBBox = CPLSearchXMLNode(
629
0
                              psIter, "ows:BoundingBox")) != nullptr)
630
0
                {
631
0
                    CPLFree(psBBox->pszValue);
632
0
                    psBBox->pszValue = CPLStrdup("gml:Envelope");
633
0
                    CPLString osSRS = CPLGetXMLValue(psBBox, "crs", "");
634
0
                    OGRGeometry *poGeom =
635
0
                        GML2OGRGeometry_XMLNode(psBBox, FALSE, srsCache.get(),
636
0
                                                0, 0, false, true, false);
637
0
                    if (poGeom)
638
0
                    {
639
0
                        bool bLatLongOrder = true;
640
0
                        if (!osSRS.empty())
641
0
                            bLatLongOrder = GML_IsSRSLatLongOrder(osSRS);
642
0
                        if (bLatLongOrder &&
643
0
                            CPLTestBool(CPLGetConfigOption(
644
0
                                "GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")))
645
0
                            poGeom->swapXY();
646
0
                        poFeature->SetGeometryDirectly(poGeom);
647
0
                    }
648
0
                }
649
650
0
                psIter->psNext = psNext;
651
652
0
                poFeature->SetField(0, pszXML);
653
0
                CPL_IGNORE_RET_VAL(poLyr->CreateFeature(poFeature));
654
0
                CPLFree(pszXML);
655
0
                delete poFeature;
656
0
            }
657
0
        }
658
0
        CPLDestroyXMLNode(psRoot);
659
0
    }
660
0
    else
661
0
    {
662
0
        l_poBaseDS = GDALDataset::Open(osTmpFileName, GDAL_OF_VECTOR);
663
0
        if (l_poBaseDS == nullptr)
664
0
        {
665
0
            if (strstr((const char *)pabyData, "<csw:GetRecordsResponse") ==
666
0
                    nullptr &&
667
0
                strstr((const char *)pabyData, "<GetRecordsResponse") ==
668
0
                    nullptr)
669
0
            {
670
0
                if (nDataLen > 1000)
671
0
                    pabyData[1000] = 0;
672
0
                CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s",
673
0
                         pabyData);
674
0
            }
675
0
            return nullptr;
676
0
        }
677
0
    }
678
679
0
    OGRLayer *poLayer = l_poBaseDS->GetLayer(0);
680
0
    if (poLayer == nullptr)
681
0
    {
682
0
        GDALClose(l_poBaseDS);
683
0
        return nullptr;
684
0
    }
685
686
0
    return l_poBaseDS;
687
0
}
688
689
/************************************************************************/
690
/*                         ISetSpatialFilter()                          */
691
/************************************************************************/
692
693
OGRErr OGRCSWLayer::ISetSpatialFilter(int iGeomField, const OGRGeometry *poGeom)
694
0
{
695
0
    const OGRErr eErr = OGRLayer::ISetSpatialFilter(iGeomField, poGeom);
696
0
    if (eErr == OGRERR_NONE)
697
0
    {
698
0
        ResetReading();
699
0
        BuildQuery();
700
0
    }
701
0
    return eErr;
702
0
}
703
704
/************************************************************************/
705
/*                       OGRCSWAddRightPrefixes()                       */
706
/************************************************************************/
707
708
static void OGRCSWAddRightPrefixes(swq_expr_node *poNode)
709
0
{
710
0
    if (poNode->eNodeType == SNT_COLUMN)
711
0
    {
712
0
        if (EQUAL(poNode->string_value, "identifier") ||
713
0
            EQUAL(poNode->string_value, "title") ||
714
0
            EQUAL(poNode->string_value, "type") ||
715
0
            EQUAL(poNode->string_value, "subject") ||
716
0
            EQUAL(poNode->string_value, "date") ||
717
0
            EQUAL(poNode->string_value, "language") ||
718
0
            EQUAL(poNode->string_value, "rights") ||
719
0
            EQUAL(poNode->string_value, "format") ||
720
0
            EQUAL(poNode->string_value, "creator") ||
721
0
            EQUAL(poNode->string_value, "source"))
722
0
        {
723
0
            char *pszNewVal =
724
0
                CPLStrdup(CPLSPrintf("dc:%s", poNode->string_value));
725
0
            CPLFree(poNode->string_value);
726
0
            poNode->string_value = pszNewVal;
727
0
        }
728
0
        else if (EQUAL(poNode->string_value, "references") ||
729
0
                 EQUAL(poNode->string_value, "modified") ||
730
0
                 EQUAL(poNode->string_value, "abstract"))
731
0
        {
732
0
            char *pszNewVal =
733
0
                CPLStrdup(CPLSPrintf("dct:%s", poNode->string_value));
734
0
            CPLFree(poNode->string_value);
735
0
            poNode->string_value = pszNewVal;
736
0
        }
737
0
        else if (EQUAL(poNode->string_value, "other_identifiers"))
738
0
        {
739
0
            CPLFree(poNode->string_value);
740
0
            poNode->string_value = CPLStrdup("dc:identifier");
741
0
        }
742
0
        else if (EQUAL(poNode->string_value, "other_subjects"))
743
0
        {
744
0
            CPLFree(poNode->string_value);
745
0
            poNode->string_value = CPLStrdup("dc:subject");
746
0
        }
747
0
        else if (EQUAL(poNode->string_value, "other_references"))
748
0
        {
749
0
            CPLFree(poNode->string_value);
750
0
            poNode->string_value = CPLStrdup("dct:references");
751
0
        }
752
0
        else if (EQUAL(poNode->string_value, "other_formats"))
753
0
        {
754
0
            CPLFree(poNode->string_value);
755
0
            poNode->string_value = CPLStrdup("dc:format");
756
0
        }
757
0
        else if (EQUAL(poNode->string_value, "AnyText"))
758
0
        {
759
0
            CPLFree(poNode->string_value);
760
0
            poNode->string_value = CPLStrdup("csw:AnyText");
761
0
        }
762
0
        else if (EQUAL(poNode->string_value, "boundingbox"))
763
0
        {
764
0
            CPLFree(poNode->string_value);
765
0
            poNode->string_value = CPLStrdup("ows:BoundingBox");
766
0
        }
767
0
    }
768
0
    else if (poNode->eNodeType == SNT_OPERATION)
769
0
    {
770
0
        for (int i = 0; i < poNode->nSubExprCount; i++)
771
0
            OGRCSWAddRightPrefixes(poNode->papoSubExpr[i]);
772
0
    }
773
0
}
774
775
/************************************************************************/
776
/*                         SetAttributeFilter()                         */
777
/************************************************************************/
778
779
OGRErr OGRCSWLayer::SetAttributeFilter(const char *pszFilter)
780
0
{
781
0
    if (pszFilter != nullptr && pszFilter[0] == 0)
782
0
        pszFilter = nullptr;
783
784
0
    CPLFree(m_pszAttrQueryString);
785
0
    m_pszAttrQueryString = (pszFilter) ? CPLStrdup(pszFilter) : nullptr;
786
787
0
    delete m_poAttrQuery;
788
0
    m_poAttrQuery = nullptr;
789
790
0
    if (pszFilter != nullptr)
791
0
    {
792
0
        m_poAttrQuery = new OGRFeatureQuery();
793
794
0
        OGRErr eErr = m_poAttrQuery->Compile(GetLayerDefn(), pszFilter, TRUE,
795
0
                                             WFSGetCustomFuncRegistrar());
796
0
        if (eErr != OGRERR_NONE)
797
0
        {
798
0
            delete m_poAttrQuery;
799
0
            m_poAttrQuery = nullptr;
800
0
            return eErr;
801
0
        }
802
0
    }
803
804
0
    if (m_poAttrQuery != nullptr)
805
0
    {
806
0
        swq_expr_node *poNode = (swq_expr_node *)m_poAttrQuery->GetSWQExpr();
807
0
        swq_expr_node *poNodeClone = poNode->Clone();
808
0
        poNodeClone->ReplaceBetweenByGEAndLERecurse();
809
0
        OGRCSWAddRightPrefixes(poNodeClone);
810
811
0
        int bNeedsNullCheck = FALSE;
812
0
        if (poNode->field_type != SWQ_BOOLEAN)
813
0
            osCSWWhere = "";
814
0
        else
815
0
            osCSWWhere = WFS_TurnSQLFilterToOGCFilter(
816
0
                poNodeClone, nullptr, nullptr, 110, FALSE, FALSE, FALSE,
817
0
                "ogc:", &bNeedsNullCheck);
818
0
        delete poNodeClone;
819
0
    }
820
0
    else
821
0
        osCSWWhere = "";
822
823
0
    if (m_poAttrQuery != nullptr && osCSWWhere.empty())
824
0
    {
825
0
        CPLDebug("CSW", "Using client-side only mode for filter \"%s\"",
826
0
                 pszFilter);
827
0
        OGRErr eErr = OGRLayer::SetAttributeFilter(pszFilter);
828
0
        if (eErr != OGRERR_NONE)
829
0
            return eErr;
830
0
    }
831
832
0
    ResetReading();
833
0
    BuildQuery();
834
835
0
    return OGRERR_NONE;
836
0
}
837
838
/************************************************************************/
839
/*                             BuildQuery()                             */
840
/************************************************************************/
841
842
void OGRCSWLayer::BuildQuery()
843
0
{
844
0
    if (m_poFilterGeom != nullptr || !osCSWWhere.empty())
845
0
    {
846
0
        osQuery = "<csw:Constraint version=\"1.1.0\">";
847
0
        osQuery += "<ogc:Filter>";
848
0
        if (m_poFilterGeom != nullptr && !osCSWWhere.empty())
849
0
            osQuery += "<ogc:And>";
850
0
        if (m_poFilterGeom != nullptr)
851
0
        {
852
0
            osQuery += "<ogc:BBOX>";
853
0
            osQuery += "<ogc:PropertyName>ows:BoundingBox</ogc:PropertyName>";
854
0
            osQuery += "<gml:Envelope srsName=\"urn:ogc:def:crs:EPSG::4326\">";
855
0
            OGREnvelope sEnvelope;
856
0
            m_poFilterGeom->getEnvelope(&sEnvelope);
857
0
            if (CPLTestBool(CPLGetConfigOption(
858
0
                    "GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")))
859
0
            {
860
0
                osQuery +=
861
0
                    CPLSPrintf("<gml:lowerCorner>%.16g %.16g</gml:lowerCorner>",
862
0
                               sEnvelope.MinY, sEnvelope.MinX);
863
0
                osQuery +=
864
0
                    CPLSPrintf("<gml:upperCorner>%.16g %.16g</gml:upperCorner>",
865
0
                               sEnvelope.MaxY, sEnvelope.MaxX);
866
0
            }
867
0
            else
868
0
            {
869
0
                osQuery +=
870
0
                    CPLSPrintf("<gml:lowerCorner>%.16g %.16g</gml:lowerCorner>",
871
0
                               sEnvelope.MinX, sEnvelope.MinY);
872
0
                osQuery +=
873
0
                    CPLSPrintf("<gml:upperCorner>%.16g %.16g</gml:upperCorner>",
874
0
                               sEnvelope.MaxX, sEnvelope.MaxY);
875
0
            }
876
0
            osQuery += "</gml:Envelope>";
877
0
            osQuery += "</ogc:BBOX>";
878
0
        }
879
0
        osQuery += osCSWWhere;
880
0
        if (m_poFilterGeom != nullptr && !osCSWWhere.empty())
881
0
            osQuery += "</ogc:And>";
882
0
        osQuery += "</ogc:Filter>";
883
0
        osQuery += "</csw:Constraint>";
884
0
    }
885
0
    else
886
0
        osQuery = "";
887
0
}
888
889
/************************************************************************/
890
/*                          OGRCSWDataSource()                          */
891
/************************************************************************/
892
893
OGRCSWDataSource::OGRCSWDataSource()
894
279
    : nMaxRecords(500), poLayer(nullptr), bFullExtentRecordsAsNonSpatial(false)
895
279
{
896
279
}
897
898
/************************************************************************/
899
/*                         ~OGRCSWDataSource()                          */
900
/************************************************************************/
901
902
OGRCSWDataSource::~OGRCSWDataSource()
903
279
{
904
279
    delete poLayer;
905
279
}
906
907
/************************************************************************/
908
/*                        SendGetCapabilities()                         */
909
/************************************************************************/
910
911
CPLHTTPResult *OGRCSWDataSource::SendGetCapabilities()
912
187
{
913
187
    CPLString osURL(osBaseURL);
914
915
187
    osURL = CPLURLAddKVP(osURL, "SERVICE", "CSW");
916
187
    osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
917
918
187
    CPLDebug("CSW", "%s", osURL.c_str());
919
920
187
    CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
921
187
    if (psResult == nullptr)
922
187
    {
923
187
        return nullptr;
924
187
    }
925
926
0
    if (strstr((const char *)psResult->pabyData, "<ServiceExceptionReport") !=
927
0
            nullptr ||
928
0
        strstr((const char *)psResult->pabyData, "<ows:ExceptionReport") !=
929
0
            nullptr ||
930
0
        strstr((const char *)psResult->pabyData, "<ExceptionReport") != nullptr)
931
0
    {
932
0
        CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
933
0
                 psResult->pabyData);
934
0
        CPLHTTPDestroyResult(psResult);
935
0
        return nullptr;
936
0
    }
937
938
0
    return psResult;
939
0
}
940
941
/************************************************************************/
942
/*                                Open()                                */
943
/************************************************************************/
944
945
int OGRCSWDataSource::Open(const char *pszFilename, char **papszOpenOptionsIn)
946
279
{
947
279
    const char *pszBaseURL = CSLFetchNameValue(papszOpenOptionsIn, "URL");
948
279
    if (pszBaseURL == nullptr)
949
279
    {
950
279
        pszBaseURL = pszFilename;
951
279
        if (STARTS_WITH_CI(pszFilename, "CSW:"))
952
279
            pszBaseURL += 4;
953
279
        if (pszBaseURL[0] == '\0')
954
0
        {
955
0
            CPLError(CE_Failure, CPLE_AppDefined, "Missing URL open option");
956
0
            return FALSE;
957
0
        }
958
279
    }
959
279
    osBaseURL = pszBaseURL;
960
279
    osElementSetName =
961
279
        CSLFetchNameValueDef(papszOpenOptionsIn, "ELEMENTSETNAME", "full");
962
279
    bFullExtentRecordsAsNonSpatial = CPLFetchBool(
963
279
        papszOpenOptionsIn, "FULL_EXTENT_RECORDS_AS_NON_SPATIAL", false);
964
279
    osOutputSchema =
965
279
        CSLFetchNameValueDef(papszOpenOptionsIn, "OUTPUT_SCHEMA", "");
966
279
    if (EQUAL(osOutputSchema, "gmd"))
967
0
        osOutputSchema = "http://www.isotc211.org/2005/gmd";
968
279
    else if (EQUAL(osOutputSchema, "csw"))
969
0
        osOutputSchema = "http://www.opengis.net/cat/csw/2.0.2";
970
279
    nMaxRecords =
971
279
        atoi(CSLFetchNameValueDef(papszOpenOptionsIn, "MAX_RECORDS", "500"));
972
973
279
    if (!STARTS_WITH(osBaseURL, "http://") &&
974
279
        !STARTS_WITH(osBaseURL, "https://") &&
975
279
        !STARTS_WITH(osBaseURL, "/vsimem/"))
976
92
        return FALSE;
977
978
187
    CPLHTTPResult *psResult = SendGetCapabilities();
979
187
    if (psResult == nullptr)
980
187
        return FALSE;
981
982
0
    CPLXMLNode *psXML = CPLParseXMLString((const char *)psResult->pabyData);
983
0
    if (psXML == nullptr)
984
0
    {
985
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
986
0
                 psResult->pabyData);
987
0
        CPLHTTPDestroyResult(psResult);
988
0
        return FALSE;
989
0
    }
990
0
    CPLStripXMLNamespace(psXML, nullptr, TRUE);
991
0
    CPLHTTPDestroyResult(psResult);
992
0
    psResult = nullptr;
993
994
0
    const char *pszVersion =
995
0
        CPLGetXMLValue(psXML, "=Capabilities.version", nullptr);
996
0
    if (pszVersion == nullptr)
997
0
    {
998
0
        CPLError(CE_Failure, CPLE_AppDefined,
999
0
                 "Cannot find Capabilities.version");
1000
0
        CPLDestroyXMLNode(psXML);
1001
0
        return FALSE;
1002
0
    }
1003
0
    if (!EQUAL(pszVersion, "2.0.2"))
1004
0
        CPLDebug(
1005
0
            "CSW",
1006
0
            "Presumably only work properly with 2.0.2. Reported version is %s",
1007
0
            pszVersion);
1008
0
    osVersion = pszVersion;
1009
0
    CPLDestroyXMLNode(psXML);
1010
1011
0
    poLayer = new OGRCSWLayer(this);
1012
1013
0
    return TRUE;
1014
0
}
1015
1016
/************************************************************************/
1017
/*                              GetLayer()                              */
1018
/************************************************************************/
1019
1020
const OGRLayer *OGRCSWDataSource::GetLayer(int iLayer) const
1021
1022
0
{
1023
0
    if (iLayer < 0 || iLayer >= ((poLayer != nullptr) ? 1 : 0))
1024
0
        return nullptr;
1025
0
    else
1026
0
        return poLayer;
1027
0
}
1028
1029
/************************************************************************/
1030
/*                             HTTPFetch()                              */
1031
/************************************************************************/
1032
1033
CPLHTTPResult *OGRCSWDataSource::HTTPFetch(const char *pszURL,
1034
                                           const char *pszPost)
1035
187
{
1036
187
    char **papszOptions = nullptr;
1037
187
    if (pszPost)
1038
0
    {
1039
0
        papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", pszPost);
1040
0
        papszOptions =
1041
0
            CSLAddNameValue(papszOptions, "HEADERS",
1042
0
                            "Content-Type: application/xml; charset=UTF-8");
1043
0
    }
1044
187
    CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, papszOptions);
1045
187
    CSLDestroy(papszOptions);
1046
1047
187
    if (psResult == nullptr)
1048
0
    {
1049
0
        return nullptr;
1050
0
    }
1051
187
    if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
1052
187
    {
1053
187
        CPLError(CE_Failure, CPLE_AppDefined,
1054
187
                 "Error returned by server : %s (%d)",
1055
187
                 (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
1056
187
                 psResult->nStatus);
1057
187
        CPLHTTPDestroyResult(psResult);
1058
187
        return nullptr;
1059
187
    }
1060
0
    if (psResult->pabyData == nullptr)
1061
0
    {
1062
0
        CPLError(CE_Failure, CPLE_AppDefined,
1063
0
                 "Empty content returned by server");
1064
0
        CPLHTTPDestroyResult(psResult);
1065
0
        return nullptr;
1066
0
    }
1067
0
    return psResult;
1068
0
}
1069
1070
/************************************************************************/
1071
/*                              Identify()                              */
1072
/************************************************************************/
1073
1074
static int OGRCSWDriverIdentify(GDALOpenInfo *poOpenInfo)
1075
1076
111k
{
1077
111k
    return STARTS_WITH_CI(poOpenInfo->pszFilename, "CSW:");
1078
111k
}
1079
1080
/************************************************************************/
1081
/*                                Open()                                */
1082
/************************************************************************/
1083
1084
static GDALDataset *OGRCSWDriverOpen(GDALOpenInfo *poOpenInfo)
1085
1086
279
{
1087
279
    if (!OGRCSWDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
1088
0
        return nullptr;
1089
1090
279
    OGRCSWDataSource *poDS = new OGRCSWDataSource();
1091
1092
279
    if (!poDS->Open(poOpenInfo->pszFilename, poOpenInfo->papszOpenOptions))
1093
279
    {
1094
279
        delete poDS;
1095
279
        poDS = nullptr;
1096
279
    }
1097
1098
279
    return poDS;
1099
279
}
1100
1101
/************************************************************************/
1102
/*                           RegisterOGRCSW()                           */
1103
/************************************************************************/
1104
1105
void RegisterOGRCSW()
1106
1107
24
{
1108
24
    if (GDALGetDriverByName("CSW") != nullptr)
1109
0
        return;
1110
1111
24
    GDALDriver *poDriver = new GDALDriver();
1112
1113
24
    poDriver->SetDescription("CSW");
1114
24
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1115
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1116
24
                              "OGC CSW (Catalog  Service for the Web)");
1117
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/csw.html");
1118
1119
24
    poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "CSW:");
1120
24
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
1121
1122
24
    poDriver->SetMetadataItem(
1123
24
        GDAL_DMD_OPENOPTIONLIST,
1124
24
        "<OpenOptionList>"
1125
24
        "  <Option name='URL' type='string' description='URL to the CSW server "
1126
24
        "endpoint' required='true'/>"
1127
24
        "  <Option name='ELEMENTSETNAME' type='string-select' "
1128
24
        "description='Level of details of properties' default='full'>"
1129
24
        "    <Value>brief</Value>"
1130
24
        "    <Value>summary</Value>"
1131
24
        "    <Value>full</Value>"
1132
24
        "  </Option>"
1133
24
        "  <Option name='FULL_EXTENT_RECORDS_AS_NON_SPATIAL' type='boolean' "
1134
24
        "description='Whether records with (-180,-90,180,90) extent should be "
1135
24
        "considered non-spatial' default='false'/>"
1136
24
        "  <Option name='OUTPUT_SCHEMA' type='string' description='Value of "
1137
24
        "outputSchema parameter'/>"
1138
24
        "  <Option name='MAX_RECORDS' type='int' description='Maximum number "
1139
24
        "of records to retrieve in a single time' default='500'/>"
1140
24
        "</OpenOptionList>");
1141
1142
24
    poDriver->pfnIdentify = OGRCSWDriverIdentify;
1143
24
    poDriver->pfnOpen = OGRCSWDriverOpen;
1144
1145
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
1146
24
}