Coverage Report

Created: 2026-05-16 08:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/openfilegdb/filegdb_relationship.h
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implements FileGDB relationship handling.
5
 * Author:   Nyall Dawson, <nyall dot dawson at gmail dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2022, Nyall Dawson <nyall dot dawson at gmail dot com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#ifndef FILEGDB_RELATIONSHIP_H
14
#define FILEGDB_RELATIONSHIP_H
15
16
#include "cpl_minixml.h"
17
#include "filegdb_gdbtoogrfieldtype.h"
18
#include "gdal.h"
19
20
/************************************************************************/
21
/*                       ParseXMLFieldDomainDef()                       */
22
/************************************************************************/
23
24
inline std::unique_ptr<GDALRelationship>
25
ParseXMLRelationshipDef(const std::string &domainDef)
26
0
{
27
0
    CPLXMLTreeCloser oTree(CPLParseXMLString(domainDef.c_str()));
28
0
    if (!oTree.get())
29
0
    {
30
0
        return nullptr;
31
0
    }
32
33
0
    const CPLXMLNode *psRelationship =
34
0
        CPLGetXMLNode(oTree.get(), "=DERelationshipClassInfo");
35
0
    if (psRelationship == nullptr)
36
0
    {
37
0
        CPLError(CE_Failure, CPLE_AppDefined,
38
0
                 "Cannot find root 'Relationship' node");
39
0
        return nullptr;
40
0
    }
41
42
0
    const char *pszName = CPLGetXMLValue(psRelationship, "Name", "");
43
44
0
    const char *pszOriginTableName =
45
0
        CPLGetXMLValue(psRelationship, "OriginClassNames.Name", nullptr);
46
0
    if (pszOriginTableName == nullptr)
47
0
    {
48
0
        CPLError(CE_Failure, CPLE_AppDefined,
49
0
                 "Cannot find OriginClassName table node");
50
0
        return nullptr;
51
0
    }
52
53
0
    const char *pszDestinationTableName =
54
0
        CPLGetXMLValue(psRelationship, "DestinationClassNames.Name", nullptr);
55
0
    if (pszDestinationTableName == nullptr)
56
0
    {
57
0
        CPLError(CE_Failure, CPLE_AppDefined,
58
0
                 "Cannot find DestinationClassNames table node");
59
0
        return nullptr;
60
0
    }
61
62
0
    const char *pszCardinality =
63
0
        CPLGetXMLValue(psRelationship, "Cardinality", "");
64
0
    if (pszCardinality == nullptr)
65
0
    {
66
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find Cardinality node");
67
0
        return nullptr;
68
0
    }
69
70
0
    GDALRelationshipCardinality eCardinality = GRC_ONE_TO_MANY;
71
0
    if (EQUAL(pszCardinality, "esriRelCardinalityOneToOne"))
72
0
    {
73
0
        eCardinality = GRC_ONE_TO_ONE;
74
0
    }
75
0
    else if (EQUAL(pszCardinality, "esriRelCardinalityOneToMany"))
76
0
    {
77
0
        eCardinality = GRC_ONE_TO_MANY;
78
0
    }
79
0
    else if (EQUAL(pszCardinality, "esriRelCardinalityManyToMany"))
80
0
    {
81
0
        eCardinality = GRC_MANY_TO_MANY;
82
0
    }
83
0
    else
84
0
    {
85
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown cardinality: %s",
86
0
                 pszCardinality);
87
0
        return nullptr;
88
0
    }
89
90
0
    auto poRelationship = std::make_unique<GDALRelationship>(
91
0
        pszName, pszOriginTableName, pszDestinationTableName, eCardinality);
92
93
0
    if (eCardinality == GRC_MANY_TO_MANY)
94
0
    {
95
        // seems to be that the middle table name always follows the
96
        // relationship name?
97
0
        poRelationship->SetMappingTableName(pszName);
98
0
    }
99
100
0
    std::vector<std::string> aosOriginKeys;
101
0
    std::vector<std::string> aosMappingOriginKeys;
102
0
    std::vector<std::string> aosDestinationKeys;
103
0
    std::vector<std::string> aosMappingDestinationKeys;
104
105
0
    const CPLXMLNode *psOriginClassKeys =
106
0
        CPLGetXMLNode(psRelationship, "OriginClassKeys");
107
0
    if (psOriginClassKeys == nullptr)
108
0
    {
109
0
        CPLError(CE_Failure, CPLE_AppDefined,
110
0
                 "Cannot find OriginClassKeys node");
111
0
        return nullptr;
112
0
    }
113
0
    for (const CPLXMLNode *psIter = psOriginClassKeys->psChild; psIter;
114
0
         psIter = psIter->psNext)
115
0
    {
116
0
        if (psIter->eType == CXT_Element &&
117
0
            strcmp(psIter->pszValue, "RelationshipClassKey") == 0)
118
0
        {
119
0
            const char *pszObjectKeyName =
120
0
                CPLGetXMLValue(psIter, "ObjectKeyName", "");
121
0
            if (pszObjectKeyName == nullptr)
122
0
            {
123
0
                continue;
124
0
            }
125
126
0
            const char *pszKeyRole = CPLGetXMLValue(psIter, "KeyRole", "");
127
0
            if (pszKeyRole == nullptr)
128
0
            {
129
0
                continue;
130
0
            }
131
0
            if (EQUAL(pszKeyRole, "esriRelKeyRoleOriginPrimary"))
132
0
            {
133
0
                aosOriginKeys.emplace_back(pszObjectKeyName);
134
0
            }
135
0
            else if (EQUAL(pszKeyRole, "esriRelKeyRoleOriginForeign"))
136
0
            {
137
0
                if (eCardinality == GRC_MANY_TO_MANY)
138
0
                    aosMappingOriginKeys.emplace_back(pszObjectKeyName);
139
0
                else
140
0
                    aosDestinationKeys.emplace_back(pszObjectKeyName);
141
0
            }
142
0
            else
143
0
            {
144
0
                CPLError(CE_Failure, CPLE_AppDefined, "Unknown KeyRole: %s",
145
0
                         pszKeyRole);
146
0
                return nullptr;
147
0
            }
148
0
        }
149
0
    }
150
151
0
    const CPLXMLNode *psDestinationClassKeys =
152
0
        CPLGetXMLNode(psRelationship, "DestinationClassKeys");
153
0
    if (psDestinationClassKeys != nullptr)
154
0
    {
155
0
        for (const CPLXMLNode *psIter = psDestinationClassKeys->psChild; psIter;
156
0
             psIter = psIter->psNext)
157
0
        {
158
0
            if (psIter->eType == CXT_Element &&
159
0
                strcmp(psIter->pszValue, "RelationshipClassKey") == 0)
160
0
            {
161
0
                const char *pszObjectKeyName =
162
0
                    CPLGetXMLValue(psIter, "ObjectKeyName", "");
163
0
                if (pszObjectKeyName == nullptr)
164
0
                {
165
0
                    continue;
166
0
                }
167
168
0
                const char *pszKeyRole = CPLGetXMLValue(psIter, "KeyRole", "");
169
0
                if (pszKeyRole == nullptr)
170
0
                {
171
0
                    continue;
172
0
                }
173
0
                if (EQUAL(pszKeyRole, "esriRelKeyRoleDestinationPrimary"))
174
0
                {
175
0
                    aosDestinationKeys.emplace_back(pszObjectKeyName);
176
0
                }
177
0
                else if (EQUAL(pszKeyRole, "esriRelKeyRoleDestinationForeign"))
178
0
                {
179
0
                    aosMappingDestinationKeys.emplace_back(pszObjectKeyName);
180
0
                }
181
0
                else
182
0
                {
183
0
                    CPLError(CE_Failure, CPLE_AppDefined, "Unknown KeyRole: %s",
184
0
                             pszKeyRole);
185
0
                    return nullptr;
186
0
                }
187
0
            }
188
0
        }
189
0
    }
190
191
0
    poRelationship->SetLeftTableFields(aosOriginKeys);
192
0
    poRelationship->SetLeftMappingTableFields(aosMappingOriginKeys);
193
0
    poRelationship->SetRightTableFields(aosDestinationKeys);
194
0
    poRelationship->SetRightMappingTableFields(aosMappingDestinationKeys);
195
196
0
    const char *pszForwardPathLabel =
197
0
        CPLGetXMLValue(psRelationship, "ForwardPathLabel", "");
198
0
    if (pszForwardPathLabel != nullptr)
199
0
    {
200
0
        poRelationship->SetForwardPathLabel(pszForwardPathLabel);
201
0
    }
202
0
    const char *pszBackwardPathLabel =
203
0
        CPLGetXMLValue(psRelationship, "BackwardPathLabel", "");
204
0
    if (pszBackwardPathLabel != nullptr)
205
0
    {
206
0
        poRelationship->SetBackwardPathLabel(pszBackwardPathLabel);
207
0
    }
208
209
0
    const char *pszIsComposite =
210
0
        CPLGetXMLValue(psRelationship, "IsComposite", "");
211
0
    if (pszIsComposite != nullptr && EQUAL(pszIsComposite, "true"))
212
0
    {
213
0
        poRelationship->SetType(GRT_COMPOSITE);
214
0
    }
215
0
    else
216
0
    {
217
0
        poRelationship->SetType(GRT_ASSOCIATION);
218
0
    }
219
220
0
    const char *pszIsAttachmentRelationship =
221
0
        CPLGetXMLValue(psRelationship, "IsAttachmentRelationship", "");
222
0
    if (pszIsAttachmentRelationship != nullptr &&
223
0
        EQUAL(pszIsAttachmentRelationship, "true"))
224
0
    {
225
0
        poRelationship->SetRelatedTableType("media");
226
0
    }
227
0
    else
228
0
    {
229
0
        poRelationship->SetRelatedTableType("features");
230
0
    }
231
0
    return poRelationship;
232
0
}
233
234
/************************************************************************/
235
/*                      BuildXMLRelationshipDef()                       */
236
/************************************************************************/
237
238
inline std::string
239
BuildXMLRelationshipDef(const GDALRelationship *poRelationship, int iDsid,
240
                        const std::string &osMappingTableOidName,
241
                        std::string &failureReason)
242
0
{
243
0
    std::string osNS = "typens";
244
0
    const char *pszRootElt = "DERelationshipClassInfo";
245
246
0
    CPLXMLTreeCloser oTree(CPLCreateXMLNode(nullptr, CXT_Element, pszRootElt));
247
0
    CPLXMLNode *psRoot = oTree.get();
248
249
0
    CPLAddXMLAttributeAndValue(psRoot, "xsi:type",
250
0
                               "typens:DERelationshipClassInfo");
251
252
0
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:xsi",
253
0
                               "http://www.w3.org/2001/XMLSchema-instance");
254
0
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:xs",
255
0
                               "http://www.w3.org/2001/XMLSchema");
256
0
    CPLAddXMLAttributeAndValue(psRoot, ("xmlns:" + osNS).c_str(),
257
0
                               "http://www.esri.com/schemas/ArcGIS/10.1");
258
259
0
    CPLCreateXMLElementAndValue(psRoot, "CatalogPath",
260
0
                                ("\\" + poRelationship->GetName()).c_str());
261
0
    CPLCreateXMLElementAndValue(psRoot, "Name",
262
0
                                poRelationship->GetName().c_str());
263
0
    CPLCreateXMLElementAndValue(psRoot, "ChildrenExpanded", "false");
264
0
    CPLCreateXMLElementAndValue(psRoot, "DatasetType",
265
0
                                "esriDTRelationshipClass");
266
0
    CPLCreateXMLElementAndValue(psRoot, "DSID",
267
0
                                CPLString().Printf("%d", iDsid));
268
0
    CPLCreateXMLElementAndValue(psRoot, "Versioned", "false");
269
0
    CPLCreateXMLElementAndValue(psRoot, "CanVersion", "false");
270
0
    CPLCreateXMLElementAndValue(psRoot, "ConfigurationKeyword", "");
271
0
    CPLCreateXMLElementAndValue(psRoot, "RequiredGeodatabaseClientVersion",
272
0
                                "10.0");
273
0
    CPLCreateXMLElementAndValue(psRoot, "HasOID", "false");
274
275
0
    auto psGPFieldInfoExs =
276
0
        CPLCreateXMLNode(psRoot, CXT_Element, "GPFieldInfoExs");
277
0
    CPLAddXMLAttributeAndValue(psGPFieldInfoExs, "xsi:type",
278
0
                               "typens:ArrayOfGPFieldInfoEx");
279
280
    // for many-to-many relationships this is the OID field from the mapping
281
    // table
282
0
    if (poRelationship->GetCardinality() ==
283
0
        GDALRelationshipCardinality::GRC_MANY_TO_MANY)
284
0
    {
285
0
        CPLCreateXMLElementAndValue(psRoot, "OIDFieldName",
286
0
                                    osMappingTableOidName.c_str());
287
288
        // field info from mapping table
289
290
        // OID field
291
0
        auto psGPFieldInfoEx =
292
0
            CPLCreateXMLNode(psGPFieldInfoExs, CXT_Element, "GPFieldInfoEx");
293
0
        CPLAddXMLAttributeAndValue(psGPFieldInfoEx, "xsi:type",
294
0
                                   "typens:GPFieldInfoEx");
295
0
        CPLCreateXMLElementAndValue(psGPFieldInfoEx, "Name",
296
0
                                    osMappingTableOidName.c_str());
297
298
        // hopefully not required...
299
        // CPLCreateXMLElementAndValue(psGPFieldInfoEx, "AliasName", "false");
300
        // CPLCreateXMLElementAndValue(psGPFieldInfoEx, "FieldType", "false");
301
        // CPLCreateXMLElementAndValue(psGPFieldInfoEx, "IsNullable", "false");
302
303
        // origin foreign key field
304
0
        psGPFieldInfoEx =
305
0
            CPLCreateXMLNode(psGPFieldInfoExs, CXT_Element, "GPFieldInfoEx");
306
0
        CPLAddXMLAttributeAndValue(psGPFieldInfoEx, "xsi:type",
307
0
                                   "typens:GPFieldInfoEx");
308
0
        if (!poRelationship->GetLeftMappingTableFields().empty())
309
0
        {
310
0
            CPLCreateXMLElementAndValue(
311
0
                psGPFieldInfoEx, "Name",
312
0
                poRelationship->GetLeftMappingTableFields()[0].c_str());
313
0
        }
314
315
        // destination foreign key field
316
0
        psGPFieldInfoEx =
317
0
            CPLCreateXMLNode(psGPFieldInfoExs, CXT_Element, "GPFieldInfoEx");
318
0
        CPLAddXMLAttributeAndValue(psGPFieldInfoEx, "xsi:type",
319
0
                                   "typens:GPFieldInfoEx");
320
0
        if (!poRelationship->GetRightMappingTableFields().empty())
321
0
        {
322
0
            CPLCreateXMLElementAndValue(
323
0
                psGPFieldInfoEx, "Name",
324
0
                poRelationship->GetRightMappingTableFields()[0].c_str());
325
0
        }
326
0
    }
327
0
    else
328
0
    {
329
0
        CPLCreateXMLElementAndValue(psRoot, "OIDFieldName", "");
330
0
    }
331
332
0
    CPLCreateXMLElementAndValue(psRoot, "CLSID", "");
333
0
    CPLCreateXMLElementAndValue(psRoot, "EXTCLSID", "");
334
335
0
    auto psRelationshipClassNames =
336
0
        CPLCreateXMLNode(psRoot, CXT_Element, "RelationshipClassNames");
337
0
    CPLAddXMLAttributeAndValue(psRelationshipClassNames, "xsi:type",
338
0
                               "typens:Names");
339
340
0
    CPLCreateXMLElementAndValue(psRoot, "AliasName", "");
341
0
    CPLCreateXMLElementAndValue(psRoot, "ModelName", "");
342
0
    CPLCreateXMLElementAndValue(psRoot, "HasGlobalID", "false");
343
0
    CPLCreateXMLElementAndValue(psRoot, "GlobalIDFieldName", "");
344
0
    CPLCreateXMLElementAndValue(psRoot, "RasterFieldName", "");
345
346
0
    auto psExtensionProperties =
347
0
        CPLCreateXMLNode(psRoot, CXT_Element, "ExtensionProperties");
348
0
    CPLAddXMLAttributeAndValue(psExtensionProperties, "xsi:type",
349
0
                               "typens:PropertySet");
350
0
    auto psPropertyArray =
351
0
        CPLCreateXMLNode(psExtensionProperties, CXT_Element, "PropertyArray");
352
0
    CPLAddXMLAttributeAndValue(psPropertyArray, "xsi:type",
353
0
                               "typens:ArrayOfPropertySetProperty");
354
355
0
    auto psControllerMemberships =
356
0
        CPLCreateXMLNode(psRoot, CXT_Element, "ControllerMemberships");
357
0
    CPLAddXMLAttributeAndValue(psControllerMemberships, "xsi:type",
358
0
                               "typens:ArrayOfControllerMembership");
359
360
0
    CPLCreateXMLElementAndValue(psRoot, "EditorTrackingEnabled", "false");
361
0
    CPLCreateXMLElementAndValue(psRoot, "CreatorFieldName", "");
362
0
    CPLCreateXMLElementAndValue(psRoot, "CreatedAtFieldName", "");
363
0
    CPLCreateXMLElementAndValue(psRoot, "EditorFieldName", "");
364
0
    CPLCreateXMLElementAndValue(psRoot, "EditedAtFieldName", "");
365
0
    CPLCreateXMLElementAndValue(psRoot, "IsTimeInUTC", "true");
366
367
0
    switch (poRelationship->GetCardinality())
368
0
    {
369
0
        case GDALRelationshipCardinality::GRC_ONE_TO_ONE:
370
0
            CPLCreateXMLElementAndValue(psRoot, "Cardinality",
371
0
                                        "esriRelCardinalityOneToOne");
372
0
            break;
373
0
        case GDALRelationshipCardinality::GRC_ONE_TO_MANY:
374
0
            CPLCreateXMLElementAndValue(psRoot, "Cardinality",
375
0
                                        "esriRelCardinalityOneToMany");
376
0
            break;
377
0
        case GDALRelationshipCardinality::GRC_MANY_TO_MANY:
378
0
            CPLCreateXMLElementAndValue(psRoot, "Cardinality",
379
0
                                        "esriRelCardinalityManyToMany");
380
0
            break;
381
0
        case GDALRelationshipCardinality::GRC_MANY_TO_ONE:
382
0
            failureReason = "Many to one relationships are not supported";
383
0
            return {};
384
0
    }
385
386
0
    CPLCreateXMLElementAndValue(psRoot, "Notification",
387
0
                                "esriRelNotificationNone");
388
0
    CPLCreateXMLElementAndValue(psRoot, "IsAttributed", "false");
389
390
0
    switch (poRelationship->GetType())
391
0
    {
392
0
        case GDALRelationshipType::GRT_ASSOCIATION:
393
0
            CPLCreateXMLElementAndValue(psRoot, "IsComposite", "false");
394
0
            break;
395
396
0
        case GDALRelationshipType::GRT_COMPOSITE:
397
0
            CPLCreateXMLElementAndValue(psRoot, "IsComposite", "true");
398
0
            break;
399
400
0
        case GDALRelationshipType::GRT_AGGREGATION:
401
0
            failureReason = "Aggregate relationships are not supported";
402
0
            return {};
403
0
    }
404
405
0
    auto psOriginClassNames =
406
0
        CPLCreateXMLNode(psRoot, CXT_Element, "OriginClassNames");
407
0
    CPLAddXMLAttributeAndValue(psOriginClassNames, "xsi:type", "typens:Names");
408
0
    CPLCreateXMLElementAndValue(psOriginClassNames, "Name",
409
0
                                poRelationship->GetLeftTableName().c_str());
410
411
0
    auto psDestinationClassNames =
412
0
        CPLCreateXMLNode(psRoot, CXT_Element, "DestinationClassNames");
413
0
    CPLAddXMLAttributeAndValue(psDestinationClassNames, "xsi:type",
414
0
                               "typens:Names");
415
0
    CPLCreateXMLElementAndValue(psDestinationClassNames, "Name",
416
0
                                poRelationship->GetRightTableName().c_str());
417
418
0
    CPLCreateXMLElementAndValue(psRoot, "KeyType", "esriRelKeyTypeSingle");
419
0
    CPLCreateXMLElementAndValue(psRoot, "ClassKey", "esriRelClassKeyUndefined");
420
0
    CPLCreateXMLElementAndValue(psRoot, "ForwardPathLabel",
421
0
                                poRelationship->GetForwardPathLabel().c_str());
422
0
    CPLCreateXMLElementAndValue(psRoot, "BackwardPathLabel",
423
0
                                poRelationship->GetBackwardPathLabel().c_str());
424
425
0
    CPLCreateXMLElementAndValue(psRoot, "IsReflexive", "false");
426
427
0
    auto psOriginClassKeys =
428
0
        CPLCreateXMLNode(psRoot, CXT_Element, "OriginClassKeys");
429
0
    CPLAddXMLAttributeAndValue(psOriginClassKeys, "xsi:type",
430
0
                               "typens:ArrayOfRelationshipClassKey");
431
432
0
    auto psRelationshipClassKeyOrigin = CPLCreateXMLNode(
433
0
        psOriginClassKeys, CXT_Element, "RelationshipClassKey");
434
0
    CPLAddXMLAttributeAndValue(psRelationshipClassKeyOrigin, "xsi:type",
435
0
                               "typens:RelationshipClassKey");
436
0
    if (!poRelationship->GetLeftTableFields().empty())
437
0
    {
438
0
        CPLCreateXMLElementAndValue(
439
0
            psRelationshipClassKeyOrigin, "ObjectKeyName",
440
0
            poRelationship->GetLeftTableFields()[0].c_str());
441
0
    }
442
0
    CPLCreateXMLElementAndValue(psRelationshipClassKeyOrigin, "ClassKeyName",
443
0
                                "");
444
0
    CPLCreateXMLElementAndValue(psRelationshipClassKeyOrigin, "KeyRole",
445
0
                                "esriRelKeyRoleOriginPrimary");
446
447
0
    if (poRelationship->GetCardinality() ==
448
0
        GDALRelationshipCardinality::GRC_MANY_TO_MANY)
449
0
    {
450
0
        auto psRelationshipClassKeyOriginManyToMany = CPLCreateXMLNode(
451
0
            psOriginClassKeys, CXT_Element, "RelationshipClassKey");
452
0
        CPLAddXMLAttributeAndValue(psRelationshipClassKeyOriginManyToMany,
453
0
                                   "xsi:type", "typens:RelationshipClassKey");
454
0
        if (!poRelationship->GetLeftMappingTableFields().empty())
455
0
        {
456
0
            CPLCreateXMLElementAndValue(
457
0
                psRelationshipClassKeyOriginManyToMany, "ObjectKeyName",
458
0
                poRelationship->GetLeftMappingTableFields()[0].c_str());
459
0
        }
460
0
        CPLCreateXMLElementAndValue(psRelationshipClassKeyOriginManyToMany,
461
0
                                    "ClassKeyName", "");
462
0
        CPLCreateXMLElementAndValue(psRelationshipClassKeyOriginManyToMany,
463
0
                                    "KeyRole", "esriRelKeyRoleOriginForeign");
464
0
    }
465
466
0
    if (poRelationship->GetCardinality() !=
467
0
        GDALRelationshipCardinality::GRC_MANY_TO_MANY)
468
0
    {
469
0
        auto psRelationshipClassKeyForeign = CPLCreateXMLNode(
470
0
            psOriginClassKeys, CXT_Element, "RelationshipClassKey");
471
0
        CPLAddXMLAttributeAndValue(psRelationshipClassKeyForeign, "xsi:type",
472
0
                                   "typens:RelationshipClassKey");
473
0
        if (!poRelationship->GetRightTableFields().empty())
474
0
        {
475
0
            CPLCreateXMLElementAndValue(
476
0
                psRelationshipClassKeyForeign, "ObjectKeyName",
477
0
                poRelationship->GetRightTableFields()[0].c_str());
478
0
        }
479
0
        CPLCreateXMLElementAndValue(psRelationshipClassKeyForeign,
480
0
                                    "ClassKeyName", "");
481
0
        CPLCreateXMLElementAndValue(psRelationshipClassKeyForeign, "KeyRole",
482
0
                                    "esriRelKeyRoleOriginForeign");
483
0
    }
484
0
    else
485
0
    {
486
0
        auto psDestinationClassKeys =
487
0
            CPLCreateXMLNode(psRoot, CXT_Element, "DestinationClassKeys");
488
0
        CPLAddXMLAttributeAndValue(psDestinationClassKeys, "xsi:type",
489
0
                                   "typens:ArrayOfRelationshipClassKey");
490
491
0
        auto psRelationshipClassKeyForeign = CPLCreateXMLNode(
492
0
            psDestinationClassKeys, CXT_Element, "RelationshipClassKey");
493
0
        CPLAddXMLAttributeAndValue(psRelationshipClassKeyForeign, "xsi:type",
494
0
                                   "typens:RelationshipClassKey");
495
0
        if (!poRelationship->GetRightTableFields().empty())
496
0
        {
497
0
            CPLCreateXMLElementAndValue(
498
0
                psRelationshipClassKeyForeign, "ObjectKeyName",
499
0
                poRelationship->GetRightTableFields()[0].c_str());
500
0
        }
501
0
        CPLCreateXMLElementAndValue(psRelationshipClassKeyForeign,
502
0
                                    "ClassKeyName", "");
503
0
        CPLCreateXMLElementAndValue(psRelationshipClassKeyForeign, "KeyRole",
504
0
                                    "esriRelKeyRoleDestinationPrimary");
505
506
0
        auto psRelationshipClassKeyForeignManyToMany = CPLCreateXMLNode(
507
0
            psDestinationClassKeys, CXT_Element, "RelationshipClassKey");
508
0
        CPLAddXMLAttributeAndValue(psRelationshipClassKeyForeignManyToMany,
509
0
                                   "xsi:type", "typens:RelationshipClassKey");
510
0
        if (!poRelationship->GetRightMappingTableFields().empty())
511
0
        {
512
0
            CPLCreateXMLElementAndValue(
513
0
                psRelationshipClassKeyForeignManyToMany, "ObjectKeyName",
514
0
                poRelationship->GetRightMappingTableFields()[0].c_str());
515
0
        }
516
0
        CPLCreateXMLElementAndValue(psRelationshipClassKeyForeignManyToMany,
517
0
                                    "ClassKeyName", "");
518
0
        CPLCreateXMLElementAndValue(psRelationshipClassKeyForeignManyToMany,
519
0
                                    "KeyRole",
520
0
                                    "esriRelKeyRoleDestinationForeign");
521
0
    }
522
523
0
    auto psRelationshipRules =
524
0
        CPLCreateXMLNode(psRoot, CXT_Element, "RelationshipRules");
525
0
    CPLAddXMLAttributeAndValue(psRelationshipRules, "xsi:type",
526
0
                               "typens:ArrayOfRelationshipRule");
527
528
0
    CPLCreateXMLElementAndValue(
529
0
        psRoot, "IsAttachmentRelationship",
530
0
        poRelationship->GetRelatedTableType() == "media" ? "true" : "false");
531
0
    CPLCreateXMLElementAndValue(psRoot, "ChangeTracked", "false");
532
0
    CPLCreateXMLElementAndValue(psRoot, "ReplicaTracked", "false");
533
534
0
    char *pszXML = CPLSerializeXMLTree(oTree.get());
535
0
    std::string osXML(pszXML);
536
0
    CPLFree(pszXML);
537
0
    return osXML;
538
0
}
539
540
/************************************************************************/
541
/*                    BuildXMLRelationshipItemInfo()                    */
542
/************************************************************************/
543
544
inline std::string
545
BuildXMLRelationshipItemInfo(const GDALRelationship *poRelationship,
546
                             std::string & /*failureReason*/)
547
0
{
548
0
    CPLXMLTreeCloser oTree(
549
0
        CPLCreateXMLNode(nullptr, CXT_Element, "ESRI_ItemInformation"));
550
0
    CPLXMLNode *psRoot = oTree.get();
551
552
0
    CPLAddXMLAttributeAndValue(psRoot, "culture", "");
553
554
0
    CPLCreateXMLElementAndValue(psRoot, "name",
555
0
                                poRelationship->GetName().c_str());
556
0
    CPLCreateXMLElementAndValue(psRoot, "catalogPath",
557
0
                                ("\\" + poRelationship->GetName()).c_str());
558
0
    CPLCreateXMLElementAndValue(psRoot, "snippet", "");
559
0
    CPLCreateXMLElementAndValue(psRoot, "description", "");
560
0
    CPLCreateXMLElementAndValue(psRoot, "summary", "");
561
0
    CPLCreateXMLElementAndValue(psRoot, "title",
562
0
                                poRelationship->GetName().c_str());
563
0
    CPLCreateXMLElementAndValue(psRoot, "tags", "");
564
0
    CPLCreateXMLElementAndValue(psRoot, "type",
565
0
                                "File Geodatabase Relationship Class");
566
567
0
    auto psTypeKeywords = CPLCreateXMLNode(psRoot, CXT_Element, "typeKeywords");
568
0
    CPLCreateXMLElementAndValue(psTypeKeywords, "typekeyword", "Data");
569
0
    CPLCreateXMLElementAndValue(psTypeKeywords, "typekeyword", "Dataset");
570
0
    CPLCreateXMLElementAndValue(psTypeKeywords, "typekeyword", "Vector Data");
571
0
    CPLCreateXMLElementAndValue(psTypeKeywords, "typekeyword", "Feature Data");
572
0
    CPLCreateXMLElementAndValue(psTypeKeywords, "typekeyword",
573
0
                                "File Geodatabase");
574
0
    CPLCreateXMLElementAndValue(psTypeKeywords, "typekeyword", "GDB");
575
0
    CPLCreateXMLElementAndValue(psTypeKeywords, "typekeyword",
576
0
                                "Relationship Class");
577
578
0
    CPLCreateXMLElementAndValue(psRoot, "url", "");
579
0
    CPLCreateXMLElementAndValue(psRoot, "datalastModifiedTime", "");
580
581
0
    auto psExtent = CPLCreateXMLNode(psRoot, CXT_Element, "extent");
582
0
    CPLCreateXMLElementAndValue(psExtent, "xmin", "");
583
0
    CPLCreateXMLElementAndValue(psExtent, "ymin", "");
584
0
    CPLCreateXMLElementAndValue(psExtent, "xmax", "");
585
0
    CPLCreateXMLElementAndValue(psExtent, "ymax", "");
586
587
0
    CPLCreateXMLElementAndValue(psRoot, "minScale", "0");
588
0
    CPLCreateXMLElementAndValue(psRoot, "maxScale", "0");
589
0
    CPLCreateXMLElementAndValue(psRoot, "spatialReference", "");
590
0
    CPLCreateXMLElementAndValue(psRoot, "accessInformation", "");
591
0
    CPLCreateXMLElementAndValue(psRoot, "licenseInfo", "");
592
0
    CPLCreateXMLElementAndValue(psRoot, "typeID", "fgdb_relationship");
593
0
    CPLCreateXMLElementAndValue(psRoot, "isContainer", "false");
594
0
    CPLCreateXMLElementAndValue(psRoot, "browseDialogOnly", "false");
595
0
    CPLCreateXMLElementAndValue(psRoot, "propNames", "");
596
0
    CPLCreateXMLElementAndValue(psRoot, "propValues", "");
597
598
0
    char *pszXML = CPLSerializeXMLTree(oTree.get());
599
0
    std::string osXML(pszXML);
600
0
    CPLFree(pszXML);
601
0
    return osXML;
602
0
}
603
604
/************************************************************************/
605
/*                 BuildXMLRelationshipDocumentation()                  */
606
/************************************************************************/
607
608
inline std::string
609
BuildXMLRelationshipDocumentation(const GDALRelationship * /*poRelationship*/,
610
                                  std::string & /*failureReason*/)
611
0
{
612
0
    CPLXMLTreeCloser oTree(CPLCreateXMLNode(nullptr, CXT_Element, "metadata"));
613
0
    CPLXMLNode *psRoot = oTree.get();
614
615
0
    CPLAddXMLAttributeAndValue(psRoot, "xml:lang", "en");
616
617
0
    auto psEsri = CPLCreateXMLNode(psRoot, CXT_Element, "Esri");
618
0
    CPLCreateXMLElementAndValue(psEsri, "CreaDate", "");
619
0
    CPLCreateXMLElementAndValue(psEsri, "CreaTime", "");
620
0
    CPLCreateXMLElementAndValue(psEsri, "ArcGISFormat", "1.0");
621
0
    CPLCreateXMLElementAndValue(psEsri, "SyncOnce", "TRUE");
622
623
0
    auto psDataProperties =
624
0
        CPLCreateXMLNode(psEsri, CXT_Element, "DataProperties");
625
0
    CPLCreateXMLNode(psDataProperties, CXT_Element, "lineage");
626
627
0
    char *pszXML = CPLSerializeXMLTree(oTree.get());
628
0
    std::string osXML(pszXML);
629
0
    CPLFree(pszXML);
630
0
    return osXML;
631
0
}
632
633
#endif  // FILEGDB_RELATIONSHIP_H