1 /******************************************************************************
2  * Project:  OGR
3  * Purpose:  OGRGMLASDriver implementation
4  * Author:   Even Rouault, <even dot rouault at spatialys dot com>
5  *
6  * Initial development funded by the European Earth observation programme
7  * Copernicus
8  *
9  ******************************************************************************
10  * Copyright (c) 2016, Even Rouault, <even dot rouault at spatialys dot com>
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a
13  * copy of this software and associated documentation files (the "Software"),
14  * to deal in the Software without restriction, including without limitation
15  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16  * and/or sell copies of the Software, and to permit persons to whom the
17  * Software is furnished to do so, subject to the following conditions:
18  *
19  * The above copyright notice and this permission notice shall be included
20  * in all copies or substantial portions of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28  * DEALINGS IN THE SOFTWARE.
29  ****************************************************************************/
30 
31 // Must be first for DEBUG_BOOL case
32 #include "ogr_gmlas.h"
33 
34 #define CONSTANT_DEFINITION
35 #undef OGR_GMLAS_CONSTS_INCLUDED_REDEFINABLE
36 #include "ogr_gmlas_consts.h"
37 
38 #include "cpl_minixml.h"
39 
40 #include <algorithm>
41 
42 CPL_CVSID("$Id: ogrgmlasconf.cpp 8ca42e1b9c2e54b75d35e49885df9789a2643aa4 2020-05-17 21:43:40 +0200 Even Rouault $")
43 
44 /************************************************************************/
45 /*                          GMLASConfiguration()                        */
46 /************************************************************************/
47 
GMLASConfiguration()48 GMLASConfiguration::GMLASConfiguration()
49     : m_bAllowRemoteSchemaDownload(ALLOW_REMOTE_SCHEMA_DOWNLOAD_DEFAULT)
50     , m_bAlwaysGenerateOGRId(ALWAYS_GENERATE_OGR_ID_DEFAULT)
51     , m_bRemoveUnusedLayers(REMOVE_UNUSED_LAYERS_DEFAULT)
52     , m_bRemoveUnusedFields(REMOVE_UNUSED_FIELDS_DEFAULT)
53     , m_bUseArrays(USE_ARRAYS_DEFAULT)
54     , m_bUseNullState(USE_NULL_STATE_DEFAULT)
55     , m_bIncludeGeometryXML(INCLUDE_GEOMETRY_XML_DEFAULT)
56     , m_bInstantiateGMLFeaturesOnly(INSTANTIATE_GML_FEATURES_ONLY_DEFAULT)
57     , m_nIdentifierMaxLength(0)
58     , m_bCaseInsensitiveIdentifier(CASE_INSENSITIVE_IDENTIFIER_DEFAULT)
59     , m_bPGIdentifierLaundering(PG_IDENTIFIER_LAUNDERING_DEFAULT)
60     , m_nMaximumFieldsForFlattening(MAXIMUM_FIELDS_FLATTENING_DEFAULT)
61     , m_bAllowXSDCache(ALLOW_XSD_CACHE_DEFAULT)
62     , m_bSchemaFullChecking(SCHEMA_FULL_CHECKING_DEFAULT)
63     , m_bHandleMultipleImports(HANDLE_MULTIPLE_IMPORTS_DEFAULT)
64     , m_bValidate(VALIDATE_DEFAULT)
65     , m_bFailIfValidationError(FAIL_IF_VALIDATION_ERROR_DEFAULT)
66     , m_bExposeMetadataLayers(WARN_IF_EXCLUDED_XPATH_FOUND_DEFAULT)
67     , m_eSWEActivationMode(SWE_ACTIVATE_IF_NAMESPACE_FOUND)
68     , m_bSWEProcessDataRecord(SWE_PROCESS_DATA_RECORD_DEFAULT)
69     , m_bSWEProcessDataArray(SWE_PROCESS_DATA_ARRAY_DEFAULT)
70     , m_nIndentSize(INDENT_SIZE_DEFAULT)
71     , m_osSRSNameFormat(szSRSNAME_DEFAULT)
72     , m_osWrapping(szWFS2_FEATURECOLLECTION)
73     , m_osWFS20SchemaLocation(szWFS20_SCHEMALOCATION)
74 {
75 }
76 
77 /************************************************************************/
78 /*                         ~GMLASConfiguration()                        */
79 /************************************************************************/
80 
~GMLASConfiguration()81 GMLASConfiguration::~GMLASConfiguration()
82 {
83 }
84 
85 /************************************************************************/
86 /*                        GetBaseCacheDirectory()                       */
87 /************************************************************************/
88 
GetBaseCacheDirectory()89 CPLString GMLASConfiguration::GetBaseCacheDirectory()
90 {
91 #ifdef WIN32
92     const char* pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
93 #else
94     const char* pszHome = CPLGetConfigOption("HOME", nullptr);
95 #endif
96     if( pszHome != nullptr )
97     {
98         return CPLFormFilename( pszHome, ".gdal", nullptr) ;
99     }
100     else
101     {
102         const char *pszDir = CPLGetConfigOption( "CPL_TMPDIR", nullptr );
103 
104         if( pszDir == nullptr )
105             pszDir = CPLGetConfigOption( "TMPDIR", nullptr );
106 
107         if( pszDir == nullptr )
108             pszDir = CPLGetConfigOption( "TEMP", nullptr );
109 
110         const char* pszUsername = CPLGetConfigOption("USERNAME", nullptr);
111         if( pszUsername == nullptr )
112             pszUsername = CPLGetConfigOption("USER", nullptr);
113 
114         if( pszDir != nullptr && pszUsername != nullptr )
115         {
116             return CPLFormFilename( pszDir,
117                     CPLSPrintf(".gdal_%s", pszUsername), nullptr) ;
118         }
119     }
120     return CPLString();
121 }
122 
123 /************************************************************************/
124 /*                              Finalize()                              */
125 /************************************************************************/
126 
Finalize()127 void GMLASConfiguration::Finalize()
128 {
129     if( m_bAllowXSDCache && m_osXSDCacheDirectory.empty() )
130     {
131         m_osXSDCacheDirectory = GetBaseCacheDirectory();
132         if( m_osXSDCacheDirectory.empty() )
133         {
134             CPLError(CE_Warning, CPLE_AppDefined,
135                     "Could not determine a directory for GMLAS XSD cache");
136         }
137         else
138         {
139             m_osXSDCacheDirectory = CPLFormFilename( m_osXSDCacheDirectory,
140                                                   "gmlas_xsd_cache", nullptr) ;
141             CPLDebug("GMLAS", "XSD cache directory: %s",
142                      m_osXSDCacheDirectory.c_str());
143         }
144     }
145 }
146 
147 /************************************************************************/
148 /*                          CPLGetXMLBoolValue()                        */
149 /************************************************************************/
150 
CPLGetXMLBoolValue(CPLXMLNode * psNode,const char * pszKey,bool bDefault)151 static bool CPLGetXMLBoolValue(CPLXMLNode* psNode,
152                                const char* pszKey,
153                                bool bDefault)
154 {
155     const char* pszVal = CPLGetXMLValue(psNode, pszKey, nullptr);
156     if( pszVal )
157         return CPLTestBool(pszVal);
158     else
159         return bDefault;
160 }
161 
162 /************************************************************************/
163 /*                            IsValidXPath()                            */
164 /************************************************************************/
165 
IsValidXPath(const CPLString & osXPath)166 static bool IsValidXPath(const CPLString& osXPath )
167 {
168     // Check that the XPath syntax belongs to the subset we
169     // understand
170     bool bOK = !osXPath.empty();
171     for(size_t i = 0; i < osXPath.size(); ++i )
172     {
173         const char chCur = osXPath[i];
174         if( chCur == '/' )
175         {
176             // OK
177         }
178         else if( chCur == '@' &&
179                  (i == 0 || osXPath[i-1] == '/') &&
180                  i < osXPath.size()-1 &&
181                  isalpha( static_cast<int>(osXPath[i+1]) ) )
182         {
183             // OK
184         }
185         else if( chCur == '_' ||
186                     isalpha( static_cast<int>(chCur) ) )
187         {
188             // OK
189         }
190         else if( isdigit( static_cast<int>(chCur) ) &&
191                     i > 0 &&
192                     (isalnum( static_cast<int>(osXPath[i-1]) ) ||
193                     osXPath[i-1] == '_') )
194         {
195             // OK
196         }
197         else if( chCur == ':' &&
198                  i > 0 &&
199                  (isalnum( static_cast<int>(osXPath[i-1]) ) ||
200                   osXPath[i-1] == '_') &&
201                  i < osXPath.size()-1 &&
202                  isalpha( static_cast<int>(osXPath[i+1]) ) )
203         {
204             // OK
205         }
206         else
207         {
208             bOK = false;
209             break;
210         }
211     }
212     return bOK;
213 }
214 
215 /************************************************************************/
216 /*                    GMLASConfigurationErrorHandler()                  */
217 /************************************************************************/
218 
GMLASConfigurationErrorHandler(CPLErr,CPLErrorNum,const char * pszMsg)219 static void CPL_STDCALL GMLASConfigurationErrorHandler(CPLErr /*eErr*/,
220                                                        CPLErrorNum /*nType*/,
221                                                        const char* pszMsg)
222 {
223     std::vector<CPLString>* paosErrors =
224             (std::vector<CPLString>* )CPLGetErrorHandlerUserData();
225     paosErrors->push_back(pszMsg);
226 }
227 
228 /************************************************************************/
229 /*                           ParseNamespaces()                          */
230 /************************************************************************/
231 
ParseNamespaces(CPLXMLNode * psContainerNode,std::map<CPLString,CPLString> & oMap)232 static void ParseNamespaces(CPLXMLNode* psContainerNode,
233                             std::map<CPLString, CPLString>& oMap)
234 {
235     CPLXMLNode* psNamespaces = CPLGetXMLNode(psContainerNode, "Namespaces");
236     if( psNamespaces != nullptr )
237     {
238         for( CPLXMLNode* psIter = psNamespaces->psChild;
239                             psIter != nullptr;
240                             psIter = psIter->psNext )
241         {
242             if( psIter->eType == CXT_Element &&
243                 EQUAL(psIter->pszValue, "Namespace") )
244             {
245                 CPLString osPrefix = CPLGetXMLValue(psIter, "prefix", "");
246                 CPLString osURI = CPLGetXMLValue(psIter, "uri", "");
247                 if( !osPrefix.empty() && !osURI.empty() )
248                 {
249                     if( oMap.find(osPrefix) ==
250                         oMap.end() )
251                     {
252                         oMap[osPrefix] = osURI;
253                     }
254                     else
255                     {
256                         CPLError(CE_Warning, CPLE_AppDefined,
257                                     "Prefix %s was already mapped to %s. "
258                                     "Attempt to map it to %s ignored",
259                                     osPrefix.c_str(),
260                                     oMap[osPrefix].
261                                                                     c_str(),
262                                     osURI.c_str());
263                     }
264                 }
265             }
266         }
267     }
268 }
269 
270 /************************************************************************/
271 /*                                 Load()                               */
272 /************************************************************************/
273 
Load(const char * pszFilename)274 bool GMLASConfiguration::Load(const char* pszFilename)
275 {
276     // Allow configuration to be inlined
277     CPLXMLNode* psRoot = STARTS_WITH(pszFilename, "<Configuration") ?
278                                 CPLParseXMLString(pszFilename) :
279                                 CPLParseXMLFile(pszFilename);
280     if( psRoot == nullptr )
281     {
282         Finalize();
283         return false;
284     }
285     CPLXMLTreeCloser oCloser(psRoot);
286     CPL_IGNORE_RET_VAL(oCloser);
287 
288     // Validate the configuration file
289     if( CPLTestBool(CPLGetConfigOption("GDAL_XML_VALIDATION", "YES")) )
290     {
291         const char* pszXSD = CPLFindFile( "gdal", "gmlasconf.xsd" );
292         if( pszXSD != nullptr )
293         {
294             std::vector<CPLString> aosErrors;
295             const CPLErr eErrClass = CPLGetLastErrorType();
296             const CPLErrorNum nErrNum = CPLGetLastErrorNo();
297             const CPLString osErrMsg = CPLGetLastErrorMsg();
298             CPLPushErrorHandlerEx(GMLASConfigurationErrorHandler, &aosErrors);
299             int bRet = CPLValidateXML(pszFilename, pszXSD, nullptr);
300             CPLPopErrorHandler();
301             if( !bRet && !aosErrors.empty() &&
302                 strstr(aosErrors[0].c_str(), "missing libxml2 support") == nullptr )
303             {
304                 for(size_t i = 0; i < aosErrors.size(); i++)
305                 {
306                     CPLError(CE_Warning, CPLE_AppDefined,
307                              "%s", aosErrors[i].c_str());
308                 }
309             }
310             else
311             {
312                 CPLErrorSetState(eErrClass, nErrNum, osErrMsg);
313             }
314         }
315     }
316 
317     m_bAllowRemoteSchemaDownload = CPLGetXMLBoolValue(psRoot,
318                                 "=Configuration.AllowRemoteSchemaDownload",
319                                 ALLOW_REMOTE_SCHEMA_DOWNLOAD_DEFAULT );
320 
321     m_bAllowXSDCache = CPLGetXMLBoolValue( psRoot,
322                                            "=Configuration.SchemaCache.enabled",
323                                            ALLOW_XSD_CACHE_DEFAULT );
324     if( m_bAllowXSDCache )
325     {
326         m_osXSDCacheDirectory =
327             CPLGetXMLValue(psRoot, "=Configuration.SchemaCache.Directory",
328                            "");
329     }
330 
331     m_bSchemaFullChecking = CPLGetXMLBoolValue( psRoot,
332                     "=Configuration.SchemaAnalysisOptions.SchemaFullChecking",
333                     SCHEMA_FULL_CHECKING_DEFAULT);
334 
335     m_bHandleMultipleImports = CPLGetXMLBoolValue( psRoot,
336                     "=Configuration.SchemaAnalysisOptions.HandleMultipleImports",
337                     HANDLE_MULTIPLE_IMPORTS_DEFAULT);
338 
339     m_bValidate = CPLGetXMLBoolValue( psRoot,
340                                       "=Configuration.Validation.enabled",
341                                       VALIDATE_DEFAULT );
342 
343     if( m_bValidate )
344     {
345         m_bFailIfValidationError = CPLGetXMLBoolValue(psRoot,
346                                         "=Configuration.Validation.FailIfError",
347                                         FAIL_IF_VALIDATION_ERROR_DEFAULT );
348     }
349 
350     m_bExposeMetadataLayers = CPLGetXMLBoolValue( psRoot,
351                                       "=Configuration.ExposeMetadataLayers",
352                                       EXPOSE_METADATA_LAYERS_DEFAULT );
353 
354     m_bAlwaysGenerateOGRId = CPLGetXMLBoolValue( psRoot,
355                                 "=Configuration.LayerBuildingRules.AlwaysGenerateOGRId",
356                                 ALWAYS_GENERATE_OGR_ID_DEFAULT );
357 
358     m_bRemoveUnusedLayers = CPLGetXMLBoolValue( psRoot,
359                                 "=Configuration.LayerBuildingRules.RemoveUnusedLayers",
360                                 REMOVE_UNUSED_LAYERS_DEFAULT );
361 
362     m_bRemoveUnusedFields = CPLGetXMLBoolValue( psRoot,
363                                 "=Configuration.LayerBuildingRules.RemoveUnusedFields",
364                                 REMOVE_UNUSED_FIELDS_DEFAULT );
365 
366     m_bUseArrays = CPLGetXMLBoolValue( psRoot,
367                                 "=Configuration.LayerBuildingRules.UseArrays",
368                                 USE_ARRAYS_DEFAULT );
369     m_bUseNullState = CPLGetXMLBoolValue( psRoot,
370                                 "=Configuration.LayerBuildingRules.UseNullState",
371                                 USE_NULL_STATE_DEFAULT );
372     m_bIncludeGeometryXML = CPLGetXMLBoolValue( psRoot,
373                        "=Configuration.LayerBuildingRules.GML.IncludeGeometryXML",
374                        INCLUDE_GEOMETRY_XML_DEFAULT );
375     m_bInstantiateGMLFeaturesOnly = CPLGetXMLBoolValue( psRoot,
376                 "=Configuration.LayerBuildingRules.GML.InstantiateGMLFeaturesOnly",
377                 INSTANTIATE_GML_FEATURES_ONLY_DEFAULT );
378     m_nIdentifierMaxLength = atoi( CPLGetXMLValue( psRoot,
379                 "=Configuration.LayerBuildingRules.IdentifierMaxLength",
380                 "0" ) );
381     m_bCaseInsensitiveIdentifier = CPLGetXMLBoolValue( psRoot,
382                 "=Configuration.LayerBuildingRules.CaseInsensitiveIdentifier",
383                 CASE_INSENSITIVE_IDENTIFIER_DEFAULT );
384     m_bPGIdentifierLaundering = CPLGetXMLBoolValue( psRoot,
385                 "=Configuration.LayerBuildingRules.PostgreSQLIdentifierLaundering",
386                 PG_IDENTIFIER_LAUNDERING_DEFAULT );
387 
388     CPLXMLNode* psFlatteningRules = CPLGetXMLNode(psRoot,
389                         "=Configuration.LayerBuildingRules.FlatteningRules");
390     if( psFlatteningRules )
391     {
392         m_nMaximumFieldsForFlattening = atoi( CPLGetXMLValue( psFlatteningRules,
393                 "MaximumNumberOfFields",
394                 CPLSPrintf("%d", MAXIMUM_FIELDS_FLATTENING_DEFAULT) ) );
395 
396         ParseNamespaces(psFlatteningRules, m_oMapPrefixToURIFlatteningRules);
397 
398         for( CPLXMLNode* psIter = psFlatteningRules->psChild;
399                          psIter != nullptr;
400                          psIter = psIter->psNext )
401         {
402             if( psIter->eType == CXT_Element &&
403                 EQUAL(psIter->pszValue, "ForceFlatteningXPath") )
404             {
405                 m_osForcedFlattenedXPath.push_back(
406                     CPLGetXMLValue(psIter, "", "") );
407             }
408             else if( psIter->eType == CXT_Element &&
409                      EQUAL(psIter->pszValue, "DisableFlatteningXPath") )
410             {
411                 m_osDisabledFlattenedXPath.push_back(
412                     CPLGetXMLValue(psIter, "", "") );
413             }
414         }
415     }
416 
417     const char* pszSWEProcessingActivation = CPLGetXMLValue( psRoot,
418         "=Configuration.LayerBuildingRules.SWEProcessing.Activation",
419         "ifSWENamespaceFoundInTopElement" );
420     if( EQUAL(pszSWEProcessingActivation, "ifSWENamespaceFoundInTopElement") )
421         m_eSWEActivationMode = SWE_ACTIVATE_IF_NAMESPACE_FOUND;
422     else if( CPLTestBool(pszSWEProcessingActivation) )
423         m_eSWEActivationMode = SWE_ACTIVATE_TRUE;
424     else
425         m_eSWEActivationMode = SWE_ACTIVATE_FALSE;
426     m_bSWEProcessDataRecord = CPLTestBool(CPLGetXMLValue( psRoot,
427         "=Configuration.LayerBuildingRules.SWEProcessing.ProcessDataRecord",
428         "true" ));
429     m_bSWEProcessDataArray = CPLTestBool(CPLGetXMLValue( psRoot,
430         "=Configuration.LayerBuildingRules.SWEProcessing.ProcessDataArray",
431         "true" ));
432 
433     CPLXMLNode* psTypingConstraints = CPLGetXMLNode(psRoot,
434                                             "=Configuration.TypingConstraints");
435     if( psTypingConstraints )
436     {
437         ParseNamespaces(psTypingConstraints, m_oMapPrefixToURITypeConstraints);
438 
439         for( CPLXMLNode* psIter = psTypingConstraints->psChild;
440                          psIter != nullptr;
441                          psIter = psIter->psNext )
442         {
443             if( psIter->eType == CXT_Element &&
444                 EQUAL(psIter->pszValue, "ChildConstraint") )
445             {
446                 const CPLString& osXPath( CPLGetXMLValue(psIter, "ContainerXPath", "") );
447                 CPLXMLNode* psChildrenTypes = CPLGetXMLNode(psIter, "ChildrenElements");
448                 if( IsValidXPath(osXPath) )
449                 {
450                     for( CPLXMLNode* psIter2 =
451                             psChildrenTypes ? psChildrenTypes->psChild : nullptr;
452                                      psIter2 != nullptr;
453                                      psIter2 = psIter2->psNext )
454                     {
455                         if( psIter2->eType == CXT_Element &&
456                             EQUAL(psIter2->pszValue, "Element") )
457                         {
458                             m_oMapChildrenElementsConstraints[osXPath].push_back(
459                                 CPLGetXMLValue(psIter2, "", "") );
460                         }
461                     }
462                 }
463                 else
464                 {
465                     CPLError(CE_Warning, CPLE_AppDefined,
466                              "XPath syntax %s not supported",
467                              osXPath.c_str());
468                 }
469             }
470         }
471     }
472 
473     CPLXMLNode* psIgnoredXPaths = CPLGetXMLNode(psRoot,
474                                             "=Configuration.IgnoredXPaths");
475     if( psIgnoredXPaths )
476     {
477         const bool bGlobalWarnIfIgnoredXPathFound = CPLGetXMLBoolValue(
478                        psIgnoredXPaths,
479                        "WarnIfIgnoredXPathFoundInDocInstance",
480                        WARN_IF_EXCLUDED_XPATH_FOUND_DEFAULT );
481 
482         ParseNamespaces(psIgnoredXPaths, m_oMapPrefixToURIIgnoredXPaths);
483 
484         for( CPLXMLNode* psIter = psIgnoredXPaths->psChild;
485                          psIter != nullptr;
486                          psIter = psIter->psNext )
487         {
488             if( psIter->eType == CXT_Element &&
489                 EQUAL(psIter->pszValue, "XPath") )
490             {
491                 const CPLString& osXPath( CPLGetXMLValue(psIter, "", "") );
492                 if( IsValidXPath(osXPath) )
493                 {
494                     m_aosIgnoredXPaths.push_back( osXPath );
495 
496                     const bool bWarnIfIgnoredXPathFound = CPLGetXMLBoolValue(
497                                 psIter,
498                                 "warnIfIgnoredXPathFoundInDocInstance",
499                                 bGlobalWarnIfIgnoredXPathFound );
500                     m_oMapIgnoredXPathToWarn[ osXPath ] = bWarnIfIgnoredXPathFound;
501                 }
502                 else
503                 {
504                     CPLError(CE_Warning, CPLE_AppDefined,
505                              "XPath syntax %s not supported",
506                              osXPath.c_str());
507                 }
508             }
509         }
510     }
511 
512     CPLXMLNode* psXLinkResolutionNode = CPLGetXMLNode( psRoot,
513                                             "=Configuration.XLinkResolution");
514     if( psXLinkResolutionNode != nullptr )
515         m_oXLinkResolution.LoadFromXML( psXLinkResolutionNode );
516 
517     // Parse WriterConfig
518     CPLXMLNode* psWriterConfig = CPLGetXMLNode( psRoot,
519                                                 "=Configuration.WriterConfig");
520     if( psWriterConfig != nullptr )
521     {
522         m_nIndentSize = atoi( CPLGetXMLValue( psWriterConfig,
523                                     "IndentationSize",
524                                     CPLSPrintf("%d", INDENT_SIZE_DEFAULT ) ) );
525         m_nIndentSize = std::min( INDENT_SIZE_MAX,
526                                   std::max( INDENT_SIZE_MIN, m_nIndentSize ) );
527 
528         m_osComment = CPLGetXMLValue( psWriterConfig, "Comment", "" );
529 
530         m_osLineFormat = CPLGetXMLValue( psWriterConfig, "LineFormat", "" );
531 
532         m_osSRSNameFormat = CPLGetXMLValue( psWriterConfig, "SRSNameFormat", "" );
533 
534         m_osWrapping = CPLGetXMLValue( psWriterConfig, "Wrapping",
535                                        szWFS2_FEATURECOLLECTION );
536 
537         m_osTimestamp = CPLGetXMLValue( psWriterConfig, "Timestamp", "" );
538 
539         m_osWFS20SchemaLocation = CPLGetXMLValue( psWriterConfig,
540                                                   "WFS20SchemaLocation",
541                                                   szWFS20_SCHEMALOCATION );
542     }
543 
544     Finalize();
545 
546     return true;
547 }
548 
549 /************************************************************************/
550 /*                         GMLASXLinkResolutionConf()                   */
551 /************************************************************************/
552 
GMLASXLinkResolutionConf()553 GMLASXLinkResolutionConf::GMLASXLinkResolutionConf() :
554     m_nTimeOut(0),
555     m_nMaxFileSize(MAX_FILE_SIZE_DEFAULT),
556     m_nMaxGlobalResolutionTime(0),
557     m_bDefaultResolutionEnabled(DEFAULT_RESOLUTION_ENABLED_DEFAULT),
558     m_bDefaultAllowRemoteDownload(ALLOW_REMOTE_DOWNLOAD_DEFAULT),
559     m_eDefaultResolutionMode(RawContent),
560     m_nDefaultResolutionDepth(1),
561     m_bDefaultCacheResults(CACHE_RESULTS_DEFAULT),
562     m_bResolveInternalXLinks(INTERNAL_XLINK_RESOLUTION_DEFAULT)
563 {
564 }
565 
566 /************************************************************************/
567 /*                               LoadFromXML()                          */
568 /************************************************************************/
569 
LoadFromXML(CPLXMLNode * psRoot)570 bool GMLASXLinkResolutionConf::LoadFromXML(CPLXMLNode* psRoot)
571 {
572     m_nTimeOut = atoi( CPLGetXMLValue( psRoot, "Timeout", "0" ) );
573 
574     m_nMaxFileSize = atoi( CPLGetXMLValue( psRoot, "MaxFileSize",
575                                 CPLSPrintf("%d", MAX_FILE_SIZE_DEFAULT)) );
576 
577     m_nMaxGlobalResolutionTime = atoi(
578                 CPLGetXMLValue( psRoot, "MaxGlobalResolutionTime", "0" ) );
579 
580     m_osProxyServerPort = CPLGetXMLValue( psRoot, "ProxyServerPort", "" );
581     m_osProxyUserPassword = CPLGetXMLValue( psRoot, "ProxyUserPassword", "" );
582     m_osProxyAuth = CPLGetXMLValue( psRoot, "ProxyAuth", "" );
583 
584     m_osCacheDirectory = CPLGetXMLValue( psRoot, "CacheDirectory", "" );
585     if( m_osCacheDirectory.empty() )
586     {
587         m_osCacheDirectory = GMLASConfiguration::GetBaseCacheDirectory();
588         if( !m_osCacheDirectory.empty() )
589         {
590             m_osCacheDirectory = CPLFormFilename( m_osCacheDirectory,
591                                                   "xlink_resolved_cache",
592                                                   nullptr );
593         }
594     }
595 
596     m_bDefaultResolutionEnabled = CPLGetXMLBoolValue( psRoot,
597                                         "DefaultResolution.enabled",
598                                         DEFAULT_RESOLUTION_ENABLED_DEFAULT);
599 
600     m_bDefaultAllowRemoteDownload = CPLGetXMLBoolValue( psRoot,
601                                 "DefaultResolution.AllowRemoteDownload",
602                                 ALLOW_REMOTE_DOWNLOAD_DEFAULT);
603 
604     // TODO when we support other modes
605     // m_eDefaultResolutionMode =
606 
607     m_nDefaultResolutionDepth = atoi( CPLGetXMLValue( psRoot,
608                                 "DefaultResolution.ResolutionDepth", "1") );
609 
610     m_bDefaultCacheResults = CPLGetXMLBoolValue( psRoot,
611                                     "DefaultResolution.CacheResults",
612                                     CACHE_RESULTS_DEFAULT);
613 
614     CPLXMLNode* psIterURL = psRoot->psChild;
615     for( ; psIterURL != nullptr; psIterURL = psIterURL->psNext )
616     {
617         if( psIterURL->eType == CXT_Element &&
618             strcmp(psIterURL->pszValue, "URLSpecificResolution") == 0 )
619         {
620             GMLASXLinkResolutionConf::URLSpecificResolution oItem;
621             oItem.m_osURLPrefix = CPLGetXMLValue(psIterURL, "URLPrefix", "");
622 
623             oItem.m_bAllowRemoteDownload = CPLGetXMLBoolValue( psIterURL,
624                                                 "AllowRemoteDownload",
625                                                 ALLOW_REMOTE_DOWNLOAD_DEFAULT);
626 
627             const char* pszResolutionModel = CPLGetXMLValue(psIterURL,
628                                                 "ResolutionMode", "RawContent");
629             if( EQUAL(pszResolutionModel, "RawContent") )
630                 oItem.m_eResolutionMode = RawContent;
631             else
632                 oItem.m_eResolutionMode = FieldsFromXPath;
633 
634             oItem.m_nResolutionDepth = atoi( CPLGetXMLValue( psIterURL,
635                                         "ResolutionDepth", "1") );
636 
637             oItem.m_bCacheResults = CPLGetXMLBoolValue( psIterURL,
638                                                         "CacheResults",
639                                                         CACHE_RESULTS_DEFAULT);
640 
641             CPLXMLNode* psIter = psIterURL->psChild;
642             for( ; psIter != nullptr; psIter = psIter->psNext )
643             {
644                 if( psIter->eType == CXT_Element &&
645                     strcmp(psIter->pszValue, "HTTPHeader") == 0 )
646                 {
647                     CPLString osName( CPLGetXMLValue(psIter, "Name", "") );
648                     CPLString osValue( CPLGetXMLValue(psIter, "Value", "") );
649                     oItem.m_aosNameValueHTTPHeaders.push_back(
650                             std::pair<CPLString, CPLString>(osName, osValue));
651                 }
652                 else if( psIter->eType == CXT_Element &&
653                          strcmp(psIter->pszValue, "Field") == 0 )
654                 {
655                     URLSpecificResolution::XPathDerivedField oField;
656                     oField.m_osName = CPLGetXMLValue(psIter, "Name", "");
657                     oField.m_osType = CPLGetXMLValue(psIter, "Type", "");
658                     oField.m_osXPath = CPLGetXMLValue(psIter, "XPath", "");
659                     oItem.m_aoFields.push_back( oField );
660                 }
661             }
662 
663             m_aoURLSpecificRules.push_back( oItem );
664         }
665     }
666 
667     m_bResolveInternalXLinks = CPLGetXMLBoolValue( psRoot,
668                                     "ResolveInternalXLinks",
669                                     INTERNAL_XLINK_RESOLUTION_DEFAULT);
670 
671     return true;
672 }
673 
674 /************************************************************************/
675 /*                          URLSpecificResolution()                     */
676 /************************************************************************/
677 
URLSpecificResolution()678 GMLASXLinkResolutionConf::URLSpecificResolution::URLSpecificResolution() :
679     m_bAllowRemoteDownload(false),
680     m_eResolutionMode(RawContent),
681     m_nResolutionDepth(1),
682     m_bCacheResults(false)
683 {
684 }
685