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