LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/openfilegdb - filegdb_relationship.h (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 281 308 91.2 %
Date: 2024-05-02 22:57:13 Functions: 4 4 100.0 %

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

Generated by: LCOV version 1.14