1 /******************************************************************************
2 *
3 * Project: GDAL Core
4 * Purpose: Implementation of GDALMultiDomainMetadata class. This class
5 * manages metadata items for a variable list of domains.
6 * Author: Frank Warmerdam, warmerdam@pobox.com
7 *
8 ******************************************************************************
9 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
10 * Copyright (c) 2009-2011, Even Rouault <even dot rouault at spatialys.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 #include "cpl_port.h"
32 #include "gdal_priv.h"
33
34 #include <cstring>
35
36 #include "cpl_conv.h"
37 #include "cpl_error.h"
38 #include "cpl_minixml.h"
39 #include "cpl_string.h"
40 #include "gdal_pam.h"
41
42 CPL_CVSID("$Id: gdalmultidomainmetadata.cpp 040f61f730ba200425e9791d8cf2511ba978751b 2020-02-27 23:24:20 +0100 Even Rouault $")
43
44 //! @cond Doxygen_Suppress
45 /************************************************************************/
46 /* GDALMultiDomainMetadata() */
47 /************************************************************************/
48
GDALMultiDomainMetadata()49 GDALMultiDomainMetadata::GDALMultiDomainMetadata() :
50 papszDomainList(nullptr),
51 papoMetadataLists(nullptr)
52 {}
53
54 /************************************************************************/
55 /* ~GDALMultiDomainMetadata() */
56 /************************************************************************/
57
~GDALMultiDomainMetadata()58 GDALMultiDomainMetadata::~GDALMultiDomainMetadata()
59
60 {
61 Clear();
62 }
63
64 /************************************************************************/
65 /* Clear() */
66 /************************************************************************/
67
Clear()68 void GDALMultiDomainMetadata::Clear()
69
70 {
71 const int nDomainCount = CSLCount( papszDomainList );
72 CSLDestroy( papszDomainList );
73 papszDomainList = nullptr;
74
75 for( int i = 0; i < nDomainCount; i++ )
76 {
77 delete papoMetadataLists[i];
78 }
79 CPLFree( papoMetadataLists );
80 papoMetadataLists = nullptr;
81 }
82
83 /************************************************************************/
84 /* GetMetadata() */
85 /************************************************************************/
86
GetMetadata(const char * pszDomain)87 char **GDALMultiDomainMetadata::GetMetadata( const char *pszDomain )
88
89 {
90 if( pszDomain == nullptr )
91 pszDomain = "";
92
93 const int iDomain = CSLFindString( papszDomainList, pszDomain );
94
95 if( iDomain == -1 )
96 return nullptr;
97
98 return papoMetadataLists[iDomain]->List();
99 }
100
101 /************************************************************************/
102 /* SetMetadata() */
103 /************************************************************************/
104
SetMetadata(char ** papszMetadata,const char * pszDomain)105 CPLErr GDALMultiDomainMetadata::SetMetadata( char **papszMetadata,
106 const char *pszDomain )
107
108 {
109 if( pszDomain == nullptr )
110 pszDomain = "";
111
112 int iDomain = CSLFindString( papszDomainList, pszDomain );
113
114 if( iDomain == -1 )
115 {
116 papszDomainList = CSLAddString( papszDomainList, pszDomain );
117 const int nDomainCount = CSLCount( papszDomainList );
118
119 papoMetadataLists = static_cast<CPLStringList **>(
120 CPLRealloc( papoMetadataLists, sizeof(void*)*(nDomainCount+1) ));
121 papoMetadataLists[nDomainCount] = nullptr;
122 papoMetadataLists[nDomainCount-1] = new CPLStringList();
123 iDomain = nDomainCount-1;
124 }
125
126 papoMetadataLists[iDomain]->Assign( CSLDuplicate( papszMetadata ) );
127
128 // we want to mark name/value pair domains as being sorted for fast
129 // access.
130 if( !STARTS_WITH_CI(pszDomain, "xml:") &&
131 !STARTS_WITH_CI(pszDomain, "json:") &&
132 !EQUAL(pszDomain, "SUBDATASETS") )
133 {
134 papoMetadataLists[iDomain]->Sort();
135 }
136
137 return CE_None;
138 }
139
140 /************************************************************************/
141 /* GetMetadataItem() */
142 /************************************************************************/
143
GetMetadataItem(const char * pszName,const char * pszDomain)144 const char *GDALMultiDomainMetadata::GetMetadataItem( const char *pszName,
145 const char *pszDomain )
146
147 {
148 if( pszDomain == nullptr )
149 pszDomain = "";
150
151 const int iDomain = CSLFindString( papszDomainList, pszDomain );
152
153 if( iDomain == -1 )
154 return nullptr;
155
156 return papoMetadataLists[iDomain]->FetchNameValue( pszName );
157 }
158
159 /************************************************************************/
160 /* SetMetadataItem() */
161 /************************************************************************/
162
SetMetadataItem(const char * pszName,const char * pszValue,const char * pszDomain)163 CPLErr GDALMultiDomainMetadata::SetMetadataItem( const char *pszName,
164 const char *pszValue,
165 const char *pszDomain )
166
167 {
168 if( pszDomain == nullptr )
169 pszDomain = "";
170
171 /* -------------------------------------------------------------------- */
172 /* Create the domain if it does not already exist. */
173 /* -------------------------------------------------------------------- */
174 int iDomain = CSLFindString( papszDomainList, pszDomain );
175
176 if( iDomain == -1 )
177 {
178 SetMetadata( nullptr, pszDomain );
179 iDomain = CSLFindString( papszDomainList, pszDomain );
180 }
181
182 /* -------------------------------------------------------------------- */
183 /* Set the value in the domain list. */
184 /* -------------------------------------------------------------------- */
185 papoMetadataLists[iDomain]->SetNameValue( pszName, pszValue );
186
187 return CE_None;
188 }
189
190 /************************************************************************/
191 /* XMLInit() */
192 /* */
193 /* This method should be invoked on the parent of the */
194 /* <Metadata> elements. */
195 /************************************************************************/
196
XMLInit(CPLXMLNode * psTree,int)197 int GDALMultiDomainMetadata::XMLInit( CPLXMLNode *psTree, int /* bMerge */ )
198 {
199 CPLXMLNode *psMetadata = nullptr;
200
201 /* ==================================================================== */
202 /* Process all <Metadata> elements, each for one domain. */
203 /* ==================================================================== */
204 for( psMetadata = psTree->psChild;
205 psMetadata != nullptr;
206 psMetadata = psMetadata->psNext )
207 {
208 if( psMetadata->eType != CXT_Element
209 || !EQUAL(psMetadata->pszValue,"Metadata") )
210 continue;
211
212 const char *pszDomain = CPLGetXMLValue( psMetadata, "domain", "" );
213 const char *pszFormat = CPLGetXMLValue( psMetadata, "format", "" );
214
215 // Make sure we have a CPLStringList for this domain,
216 // without wiping out an existing one.
217 if( GetMetadata( pszDomain ) == nullptr )
218 SetMetadata( nullptr, pszDomain );
219
220 const int iDomain = CSLFindString( papszDomainList, pszDomain );
221 CPLAssert( iDomain != -1 );
222
223 CPLStringList *poMDList = papoMetadataLists[iDomain];
224
225 /* -------------------------------------------------------------------- */
226 /* XML format subdocuments. */
227 /* -------------------------------------------------------------------- */
228 if( EQUAL(pszFormat,"xml") )
229 {
230 // Find first non-attribute child of current element.
231 CPLXMLNode *psSubDoc = psMetadata->psChild;
232 while( psSubDoc != nullptr && psSubDoc->eType == CXT_Attribute )
233 psSubDoc = psSubDoc->psNext;
234
235 char *pszDoc = CPLSerializeXMLTree( psSubDoc );
236
237 poMDList->Clear();
238 poMDList->AddStringDirectly( pszDoc );
239 }
240
241 /* -------------------------------------------------------------------- */
242 /* JSon format subdocuments. */
243 /* -------------------------------------------------------------------- */
244 else if( EQUAL(pszFormat,"json") )
245 {
246 // Find first text child of current element.
247 CPLXMLNode *psSubDoc = psMetadata->psChild;
248 while( psSubDoc != nullptr && psSubDoc->eType != CXT_Text )
249 psSubDoc = psSubDoc->psNext;
250 if( psSubDoc )
251 {
252 poMDList->Clear();
253 poMDList->AddString( psSubDoc->pszValue );
254 }
255 }
256
257 /* -------------------------------------------------------------------- */
258 /* Name value format. */
259 /* <MDI key="...">value_Text</MDI> */
260 /* -------------------------------------------------------------------- */
261 else
262 {
263 for( CPLXMLNode *psMDI = psMetadata->psChild;
264 psMDI != nullptr;
265 psMDI = psMDI->psNext )
266 {
267 if( !EQUAL(psMDI->pszValue,"MDI")
268 || psMDI->eType != CXT_Element
269 || psMDI->psChild == nullptr
270 || psMDI->psChild->psNext == nullptr
271 || psMDI->psChild->eType != CXT_Attribute
272 || psMDI->psChild->psChild == nullptr )
273 continue;
274
275 char* pszName = psMDI->psChild->psChild->pszValue;
276 char* pszValue = psMDI->psChild->psNext->pszValue;
277 if( pszName != nullptr && pszValue != nullptr )
278 poMDList->SetNameValue( pszName, pszValue );
279 }
280 }
281 }
282
283 return CSLCount(papszDomainList) != 0;
284 }
285
286 /************************************************************************/
287 /* Serialize() */
288 /************************************************************************/
289
Serialize()290 CPLXMLNode *GDALMultiDomainMetadata::Serialize()
291
292 {
293 CPLXMLNode *psFirst = nullptr;
294
295 for( int iDomain = 0;
296 papszDomainList != nullptr && papszDomainList[iDomain] != nullptr;
297 iDomain++ )
298 {
299 char **papszMD = papoMetadataLists[iDomain]->List();
300 // Do not serialize empty domains.
301 if( papszMD == nullptr || papszMD[0] == nullptr )
302 continue;
303
304 CPLXMLNode *psMD = CPLCreateXMLNode( nullptr, CXT_Element, "Metadata" );
305
306 if( strlen( papszDomainList[iDomain] ) > 0 )
307 CPLCreateXMLNode(
308 CPLCreateXMLNode( psMD, CXT_Attribute, "domain" ),
309 CXT_Text, papszDomainList[iDomain] );
310
311 bool bFormatXMLOrJSon = false;
312
313 if( STARTS_WITH_CI(papszDomainList[iDomain], "xml:")
314 && CSLCount(papszMD) == 1 )
315 {
316 CPLXMLNode *psValueAsXML = CPLParseXMLString( papszMD[0] );
317 if( psValueAsXML != nullptr )
318 {
319 bFormatXMLOrJSon = true;
320
321 CPLCreateXMLNode(
322 CPLCreateXMLNode( psMD, CXT_Attribute, "format" ),
323 CXT_Text, "xml" );
324
325 CPLAddXMLChild( psMD, psValueAsXML );
326 }
327 }
328
329 if( STARTS_WITH_CI(papszDomainList[iDomain], "json:")
330 && CSLCount(papszMD) == 1 )
331 {
332 bFormatXMLOrJSon = true;
333
334 CPLCreateXMLNode(
335 CPLCreateXMLNode( psMD, CXT_Attribute, "format" ),
336 CXT_Text, "json" );
337 CPLCreateXMLNode( psMD, CXT_Text, *papszMD );
338 }
339
340 if( !bFormatXMLOrJSon )
341 {
342 CPLXMLNode* psLastChild = nullptr;
343 // To go after domain attribute.
344 if( psMD->psChild != nullptr )
345 {
346 psLastChild = psMD->psChild;
347 while( psLastChild->psNext != nullptr )
348 psLastChild = psLastChild->psNext;
349 }
350 for( int i = 0; papszMD[i] != nullptr; i++ )
351 {
352 char *pszKey = nullptr;
353
354 const char *pszRawValue =
355 CPLParseNameValue( papszMD[i], &pszKey );
356
357 CPLXMLNode *psMDI =
358 CPLCreateXMLNode( nullptr, CXT_Element, "MDI" );
359 if( psLastChild == nullptr )
360 psMD->psChild = psMDI;
361 else
362 psLastChild->psNext = psMDI;
363 psLastChild = psMDI;
364
365 CPLSetXMLValue( psMDI, "#key", pszKey );
366 CPLCreateXMLNode( psMDI, CXT_Text, pszRawValue );
367
368 CPLFree( pszKey );
369 }
370 }
371
372 if( psFirst == nullptr )
373 psFirst = psMD;
374 else
375 CPLAddXMLSibling( psFirst, psMD );
376 }
377
378 return psFirst;
379 }
380 //! @endcond
381