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