Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/openfilegdb/filegdb_fielddomain.h
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implements Open FileGDB OGR driver.
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#ifndef FILEGDB_FIELDDOMAIN_H
14
#define FILEGDB_FIELDDOMAIN_H
15
16
#include "cpl_minixml.h"
17
#include "filegdb_gdbtoogrfieldtype.h"
18
#include "ogr_p.h"
19
20
/************************************************************************/
21
/*                       ParseXMLFieldDomainDef()                       */
22
/************************************************************************/
23
24
inline std::unique_ptr<OGRFieldDomain>
25
ParseXMLFieldDomainDef(const std::string &domainDef)
26
11.6k
{
27
11.6k
    CPLXMLTreeCloser oTree(CPLParseXMLString(domainDef.c_str()));
28
11.6k
    if (!oTree.get())
29
7.27k
    {
30
7.27k
        return nullptr;
31
7.27k
    }
32
4.40k
    const CPLXMLNode *psDomain = CPLGetXMLNode(oTree.get(), "=esri:Domain");
33
4.40k
    if (psDomain == nullptr)
34
4.20k
    {
35
        // esri: namespace prefix omitted when called from the FileGDB driver
36
4.20k
        psDomain = CPLGetXMLNode(oTree.get(), "=Domain");
37
4.20k
    }
38
4.40k
    bool bIsCodedValueDomain = false;
39
4.40k
    if (psDomain == nullptr)
40
4.20k
    {
41
        // Also sometimes found...
42
4.20k
        psDomain = CPLGetXMLNode(oTree.get(), "=esri:CodedValueDomain");
43
4.20k
        if (psDomain)
44
212
            bIsCodedValueDomain = true;
45
4.20k
    }
46
4.40k
    if (psDomain == nullptr)
47
3.98k
    {
48
        // Also sometimes found...
49
3.98k
        psDomain = CPLGetXMLNode(oTree.get(), "=typens:GPCodedValueDomain2");
50
3.98k
        if (psDomain)
51
708
            bIsCodedValueDomain = true;
52
3.98k
    }
53
4.40k
    if (psDomain == nullptr)
54
3.28k
    {
55
        // Also sometimes found...
56
3.28k
        psDomain = CPLGetXMLNode(oTree.get(), "=GPCodedValueDomain2");
57
3.28k
        if (psDomain)
58
395
            bIsCodedValueDomain = true;
59
3.28k
    }
60
4.40k
    bool bIsRangeDomain = false;
61
4.40k
    if (psDomain == nullptr)
62
2.88k
    {
63
        // Also sometimes found...
64
2.88k
        psDomain = CPLGetXMLNode(oTree.get(), "=esri:RangeDomain");
65
2.88k
        if (psDomain)
66
371
            bIsRangeDomain = true;
67
2.88k
    }
68
4.40k
    if (psDomain == nullptr)
69
2.51k
    {
70
        // Also sometimes found...
71
2.51k
        psDomain = CPLGetXMLNode(oTree.get(), "=typens:GPRangeDomain2");
72
2.51k
        if (psDomain)
73
426
            bIsRangeDomain = true;
74
2.51k
    }
75
4.40k
    if (psDomain == nullptr)
76
2.08k
    {
77
        // Also sometimes found...
78
2.08k
        psDomain = CPLGetXMLNode(oTree.get(), "=GPRangeDomain2");
79
2.08k
        if (psDomain)
80
194
            bIsRangeDomain = true;
81
2.08k
    }
82
4.40k
    if (psDomain == nullptr)
83
1.89k
    {
84
1.89k
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find root 'Domain' node");
85
1.89k
        return nullptr;
86
1.89k
    }
87
2.50k
    const char *pszType = CPLGetXMLValue(psDomain, "xsi:type", "");
88
2.50k
    const char *pszName = CPLGetXMLValue(psDomain, "DomainName", "");
89
2.50k
    const char *pszDescription = CPLGetXMLValue(psDomain, "Description", "");
90
2.50k
    const char *pszFieldType = CPLGetXMLValue(psDomain, "FieldType", "");
91
2.50k
    OGRFieldType eFieldType = OFTString;
92
2.50k
    OGRFieldSubType eSubType = OFSTNone;
93
2.50k
    if (!GDBToOGRFieldType(pszFieldType, &eFieldType, &eSubType))
94
2.50k
    {
95
2.50k
        return nullptr;
96
2.50k
    }
97
98
0
    std::unique_ptr<OGRFieldDomain> domain;
99
0
    if (bIsCodedValueDomain || strcmp(pszType, "esri:CodedValueDomain") == 0)
100
0
    {
101
0
        const CPLXMLNode *psCodedValues =
102
0
            CPLGetXMLNode(psDomain, "CodedValues");
103
0
        if (psCodedValues == nullptr)
104
0
        {
105
0
            return nullptr;
106
0
        }
107
0
        std::vector<OGRCodedValue> asValues;
108
0
        for (const CPLXMLNode *psIter = psCodedValues->psChild; psIter;
109
0
             psIter = psIter->psNext)
110
0
        {
111
0
            if (psIter->eType == CXT_Element &&
112
0
                strcmp(psIter->pszValue, "CodedValue") == 0)
113
0
            {
114
0
                OGRCodedValue cv;
115
0
                cv.pszCode = CPLStrdup(CPLGetXMLValue(psIter, "Code", ""));
116
0
                cv.pszValue = CPLStrdup(CPLGetXMLValue(psIter, "Name", ""));
117
0
                asValues.emplace_back(cv);
118
0
            }
119
0
        }
120
0
        domain.reset(new OGRCodedFieldDomain(pszName, pszDescription,
121
0
                                             eFieldType, eSubType,
122
0
                                             std::move(asValues)));
123
0
    }
124
0
    else if (bIsRangeDomain || strcmp(pszType, "esri:RangeDomain") == 0)
125
0
    {
126
0
        if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
127
0
            eFieldType != OFTReal && eFieldType != OFTDateTime)
128
0
        {
129
0
            CPLError(CE_Failure, CPLE_NotSupported,
130
0
                     "Unsupported field type for range domain: %s",
131
0
                     pszFieldType);
132
0
            return nullptr;
133
0
        }
134
0
        const char *pszMinValue = CPLGetXMLValue(psDomain, "MinValue", "");
135
0
        const char *pszMaxValue = CPLGetXMLValue(psDomain, "MaxValue", "");
136
0
        OGRField sMin;
137
0
        OGRField sMax;
138
0
        OGR_RawField_SetUnset(&sMin);
139
0
        OGR_RawField_SetUnset(&sMax);
140
0
        if (eFieldType == OFTInteger)
141
0
        {
142
0
            sMin.Integer = atoi(pszMinValue);
143
0
            sMax.Integer = atoi(pszMaxValue);
144
0
        }
145
0
        else if (eFieldType == OFTInteger64)
146
0
        {
147
0
            sMin.Integer64 = CPLAtoGIntBig(pszMinValue);
148
0
            sMax.Integer64 = CPLAtoGIntBig(pszMaxValue);
149
0
        }
150
0
        else if (eFieldType == OFTReal)
151
0
        {
152
0
            sMin.Real = CPLAtof(pszMinValue);
153
0
            sMax.Real = CPLAtof(pszMaxValue);
154
0
        }
155
0
        else if (eFieldType == OFTDateTime)
156
0
        {
157
0
            if (!OGRParseXMLDateTime(pszMinValue, &sMin))
158
0
            {
159
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid MinValue: %s",
160
0
                         pszMinValue);
161
0
                return nullptr;
162
0
            }
163
0
            if (!OGRParseXMLDateTime(pszMaxValue, &sMax))
164
0
            {
165
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid MaxValue: %s",
166
0
                         pszMaxValue);
167
0
                return nullptr;
168
0
            }
169
0
        }
170
0
        domain.reset(new OGRRangeFieldDomain(pszName, pszDescription,
171
0
                                             eFieldType, eSubType, sMin, true,
172
0
                                             sMax, true));
173
0
    }
174
0
    else
175
0
    {
176
0
        CPLError(CE_Failure, CPLE_NotSupported,
177
0
                 "Unsupported type of File Geodatabase domain: %s", pszType);
178
0
        return nullptr;
179
0
    }
180
181
0
    const char *pszMergePolicy =
182
0
        CPLGetXMLValue(psDomain, "MergePolicy", "esriMPTDefaultValue");
183
0
    if (EQUAL(pszMergePolicy, "esriMPTDefaultValue"))
184
0
    {
185
0
        domain->SetMergePolicy(OFDMP_DEFAULT_VALUE);
186
0
    }
187
0
    else if (EQUAL(pszMergePolicy, "esriMPTSumValues"))
188
0
    {
189
0
        domain->SetMergePolicy(OFDMP_SUM);
190
0
    }
191
0
    else if (EQUAL(pszMergePolicy, "esriMPTAreaWeighted"))
192
0
    {
193
0
        domain->SetMergePolicy(OFDMP_GEOMETRY_WEIGHTED);
194
0
    }
195
196
0
    const char *pszSplitPolicy =
197
0
        CPLGetXMLValue(psDomain, "SplitPolicy", "esriSPTDefaultValue");
198
0
    if (EQUAL(pszSplitPolicy, "esriSPTDefaultValue"))
199
0
    {
200
0
        domain->SetSplitPolicy(OFDSP_DEFAULT_VALUE);
201
0
    }
202
0
    else if (EQUAL(pszSplitPolicy, "esriSPTDuplicate"))
203
0
    {
204
0
        domain->SetSplitPolicy(OFDSP_DUPLICATE);
205
0
    }
206
0
    else if (EQUAL(pszSplitPolicy, "esriSPTGeometryRatio"))
207
0
    {
208
0
        domain->SetSplitPolicy(OFDSP_GEOMETRY_RATIO);
209
0
    }
210
211
0
    return domain;
212
0
}
213
214
/************************************************************************/
215
/*                       BuildXMLFieldDomainDef()                       */
216
/************************************************************************/
217
218
inline std::string BuildXMLFieldDomainDef(const OGRFieldDomain *poDomain,
219
                                          bool bForFileGDBSDK,
220
                                          std::string &failureReason)
221
0
{
222
0
    std::string osNS = "esri";
223
0
    const char *pszRootElt = "esri:Domain";
224
0
    if (!bForFileGDBSDK)
225
0
    {
226
0
        switch (poDomain->GetDomainType())
227
0
        {
228
0
            case OFDT_CODED:
229
0
            {
230
0
                pszRootElt = "typens:GPCodedValueDomain2";
231
0
                break;
232
0
            }
233
234
0
            case OFDT_RANGE:
235
0
            {
236
0
                pszRootElt = "typens:GPRangeDomain2";
237
0
                break;
238
0
            }
239
240
0
            case OFDT_GLOB:
241
0
            {
242
0
                failureReason =
243
0
                    "Glob field domain not handled for FileGeoDatabase";
244
0
                return std::string();
245
0
            }
246
0
        }
247
0
        osNS = "typens";
248
0
    }
249
250
0
    CPLXMLTreeCloser oTree(CPLCreateXMLNode(nullptr, CXT_Element, pszRootElt));
251
0
    CPLXMLNode *psRoot = oTree.get();
252
253
0
    switch (poDomain->GetDomainType())
254
0
    {
255
0
        case OFDT_CODED:
256
0
        {
257
0
            CPLAddXMLAttributeAndValue(psRoot, "xsi:type",
258
0
                                       bForFileGDBSDK
259
0
                                           ? "esri:CodedValueDomain"
260
0
                                           : "typens:GPCodedValueDomain2");
261
0
            break;
262
0
        }
263
264
0
        case OFDT_RANGE:
265
0
        {
266
0
            CPLAddXMLAttributeAndValue(
267
0
                psRoot, "xsi:type",
268
0
                bForFileGDBSDK ? "esri:RangeDomain" : "typens:GPRangeDomain2");
269
0
            break;
270
0
        }
271
272
0
        case OFDT_GLOB:
273
0
        {
274
0
            failureReason = "Glob field domain not handled for FileGeoDatabase";
275
0
            return std::string();
276
0
        }
277
0
    }
278
279
0
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:xsi",
280
0
                               "http://www.w3.org/2001/XMLSchema-instance");
281
0
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:xs",
282
0
                               "http://www.w3.org/2001/XMLSchema");
283
0
    CPLAddXMLAttributeAndValue(psRoot, ("xmlns:" + osNS).c_str(),
284
0
                               "http://www.esri.com/schemas/ArcGIS/10.1");
285
286
0
    CPLCreateXMLElementAndValue(psRoot, "DomainName",
287
0
                                poDomain->GetName().c_str());
288
0
    if (poDomain->GetFieldType() == OFTInteger)
289
0
    {
290
0
        if (poDomain->GetFieldSubType() == OFSTInt16)
291
0
            CPLCreateXMLElementAndValue(psRoot, "FieldType",
292
0
                                        "esriFieldTypeSmallInteger");
293
0
        else
294
0
            CPLCreateXMLElementAndValue(psRoot, "FieldType",
295
0
                                        "esriFieldTypeInteger");
296
0
    }
297
0
    else if (poDomain->GetFieldType() == OFTReal)
298
0
    {
299
0
        if (poDomain->GetFieldSubType() == OFSTFloat32)
300
0
            CPLCreateXMLElementAndValue(psRoot, "FieldType",
301
0
                                        "esriFieldTypeSingle");
302
0
        else
303
0
            CPLCreateXMLElementAndValue(psRoot, "FieldType",
304
0
                                        "esriFieldTypeDouble");
305
0
    }
306
0
    else if (poDomain->GetFieldType() == OFTString)
307
0
    {
308
0
        CPLCreateXMLElementAndValue(psRoot, "FieldType", "esriFieldTypeString");
309
0
    }
310
0
    else if (poDomain->GetFieldType() == OFTDateTime)
311
0
    {
312
0
        CPLCreateXMLElementAndValue(psRoot, "FieldType", "esriFieldTypeDate");
313
0
    }
314
0
    else
315
0
    {
316
0
        failureReason = "Unsupported field type for FileGeoDatabase domain";
317
0
        return std::string();
318
0
    }
319
320
0
    switch (poDomain->GetMergePolicy())
321
0
    {
322
0
        case OFDMP_DEFAULT_VALUE:
323
0
            CPLCreateXMLElementAndValue(psRoot, "MergePolicy",
324
0
                                        "esriMPTDefaultValue");
325
0
            break;
326
0
        case OFDMP_SUM:
327
0
            CPLCreateXMLElementAndValue(psRoot, "MergePolicy",
328
0
                                        "esriMPTSumValues");
329
0
            break;
330
0
        case OFDMP_GEOMETRY_WEIGHTED:
331
0
            CPLCreateXMLElementAndValue(psRoot, "MergePolicy",
332
0
                                        "esriMPTAreaWeighted");
333
0
            break;
334
0
    }
335
336
0
    switch (poDomain->GetSplitPolicy())
337
0
    {
338
0
        case OFDSP_DEFAULT_VALUE:
339
0
            CPLCreateXMLElementAndValue(psRoot, "SplitPolicy",
340
0
                                        "esriSPTDefaultValue");
341
0
            break;
342
0
        case OFDSP_DUPLICATE:
343
0
            CPLCreateXMLElementAndValue(psRoot, "SplitPolicy",
344
0
                                        "esriSPTDuplicate");
345
0
            break;
346
0
        case OFDSP_GEOMETRY_RATIO:
347
0
            CPLCreateXMLElementAndValue(psRoot, "SplitPolicy",
348
0
                                        "esriSPTGeometryRatio");
349
0
            break;
350
0
    }
351
352
0
    CPLCreateXMLElementAndValue(psRoot, "Description",
353
0
                                poDomain->GetDescription().c_str());
354
0
    CPLCreateXMLElementAndValue(psRoot, "Owner", "");
355
356
0
    const auto AddFieldTypeAsXSIType = [&poDomain](CPLXMLNode *psParent)
357
0
    {
358
0
        if (poDomain->GetFieldType() == OFTInteger)
359
0
        {
360
0
            if (poDomain->GetFieldSubType() == OFSTInt16)
361
0
                CPLAddXMLAttributeAndValue(psParent, "xsi:type", "xs:short");
362
0
            else
363
0
                CPLAddXMLAttributeAndValue(psParent, "xsi:type", "xs:int");
364
0
        }
365
0
        else if (poDomain->GetFieldType() == OFTReal)
366
0
        {
367
0
            if (poDomain->GetFieldSubType() == OFSTFloat32)
368
0
                CPLAddXMLAttributeAndValue(psParent, "xsi:type", "xs:float");
369
0
            else
370
0
                CPLAddXMLAttributeAndValue(psParent, "xsi:type", "xs:double");
371
0
        }
372
0
        else if (poDomain->GetFieldType() == OFTString)
373
0
        {
374
0
            CPLAddXMLAttributeAndValue(psParent, "xsi:type", "xs:string");
375
0
        }
376
0
        else if (poDomain->GetFieldType() == OFTDateTime)
377
0
        {
378
0
            CPLAddXMLAttributeAndValue(psParent, "xsi:type", "xs:dateTime");
379
0
        }
380
0
    };
381
382
0
    switch (poDomain->GetDomainType())
383
0
    {
384
0
        case OFDT_CODED:
385
0
        {
386
0
            auto psCodedValues =
387
0
                CPLCreateXMLNode(psRoot, CXT_Element, "CodedValues");
388
0
            CPLAddXMLAttributeAndValue(psCodedValues, "xsi:type",
389
0
                                       (osNS + ":ArrayOfCodedValue").c_str());
390
391
0
            auto poCodedDomain =
392
0
                cpl::down_cast<const OGRCodedFieldDomain *>(poDomain);
393
0
            const OGRCodedValue *psEnumeration =
394
0
                poCodedDomain->GetEnumeration();
395
0
            for (; psEnumeration->pszCode != nullptr; ++psEnumeration)
396
0
            {
397
0
                auto psCodedValue =
398
0
                    CPLCreateXMLNode(psCodedValues, CXT_Element, "CodedValue");
399
0
                CPLAddXMLAttributeAndValue(psCodedValue, "xsi:type",
400
0
                                           (osNS + ":CodedValue").c_str());
401
0
                CPLCreateXMLElementAndValue(
402
0
                    psCodedValue, "Name",
403
0
                    psEnumeration->pszValue ? psEnumeration->pszValue : "");
404
405
0
                auto psCode =
406
0
                    CPLCreateXMLNode(psCodedValue, CXT_Element, "Code");
407
0
                AddFieldTypeAsXSIType(psCode);
408
0
                CPLCreateXMLNode(psCode, CXT_Text, psEnumeration->pszCode);
409
0
            }
410
0
            break;
411
0
        }
412
413
0
        case OFDT_RANGE:
414
0
        {
415
0
            auto poRangeDomain =
416
0
                cpl::down_cast<const OGRRangeFieldDomain *>(poDomain);
417
418
0
            const auto SerializeMinOrMax =
419
0
                [&AddFieldTypeAsXSIType, &poDomain,
420
0
                 psRoot](const char *pszElementName, const OGRField &oValue)
421
0
            {
422
0
                auto psValue =
423
0
                    CPLCreateXMLNode(psRoot, CXT_Element, pszElementName);
424
0
                AddFieldTypeAsXSIType(psValue);
425
0
                if (poDomain->GetFieldType() == OFTInteger)
426
0
                {
427
0
                    CPLCreateXMLNode(psValue, CXT_Text,
428
0
                                     CPLSPrintf("%d", oValue.Integer));
429
0
                }
430
0
                else if (poDomain->GetFieldType() == OFTReal)
431
0
                {
432
0
                    CPLCreateXMLNode(psValue, CXT_Text,
433
0
                                     CPLSPrintf("%.18g", oValue.Real));
434
0
                }
435
0
                else if (poDomain->GetFieldType() == OFTDateTime)
436
0
                {
437
0
                    CPLCreateXMLNode(
438
0
                        psValue, CXT_Text,
439
0
                        CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02d",
440
0
                                   oValue.Date.Year, oValue.Date.Month,
441
0
                                   oValue.Date.Day, oValue.Date.Hour,
442
0
                                   oValue.Date.Minute,
443
0
                                   static_cast<int>(oValue.Date.Second + 0.5)));
444
0
                }
445
0
            };
446
447
0
            bool bIsInclusiveOut = false;
448
0
            const OGRField &oMin = poRangeDomain->GetMin(bIsInclusiveOut);
449
0
            const OGRField &oMax = poRangeDomain->GetMax(bIsInclusiveOut);
450
0
            if (OGR_RawField_IsUnset(&oMin) || OGR_RawField_IsUnset(&oMax))
451
0
            {
452
0
                failureReason =
453
0
                    "FileGeoDatabase requires that both minimum and maximum "
454
0
                    "values of a range field domain are set.";
455
0
                return std::string();
456
0
            }
457
458
0
            SerializeMinOrMax("MaxValue", oMax);
459
0
            SerializeMinOrMax("MinValue", oMin);
460
461
0
            break;
462
0
        }
463
464
0
        case OFDT_GLOB:
465
0
        {
466
0
            CPLAssert(false);
467
0
            break;
468
0
        }
469
0
    }
470
471
0
    char *pszXML = CPLSerializeXMLTree(oTree.get());
472
0
    std::string osXML(pszXML);
473
0
    CPLFree(pszXML);
474
0
    return osXML;
475
0
}
476
477
#endif  // FILEGDB_FIELDDOMAIN_H