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