Line data Source code
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 : //! @cond Doxygen_Suppress
43 : /************************************************************************/
44 : /* GDALMultiDomainMetadata() */
45 : /************************************************************************/
46 :
47 : GDALMultiDomainMetadata::GDALMultiDomainMetadata() = default;
48 :
49 : /************************************************************************/
50 : /* ~GDALMultiDomainMetadata() */
51 : /************************************************************************/
52 :
53 : GDALMultiDomainMetadata::~GDALMultiDomainMetadata() = default;
54 :
55 : /************************************************************************/
56 : /* Clear() */
57 : /************************************************************************/
58 :
59 0 : void GDALMultiDomainMetadata::Clear()
60 :
61 : {
62 0 : aosDomainList.clear();
63 0 : oMetadata.clear();
64 0 : }
65 :
66 : /************************************************************************/
67 : /* SanitizeDomain() */
68 : /************************************************************************/
69 :
70 13964300 : static inline const char *SanitizeDomain(const char *pszDomain)
71 : {
72 13964300 : return pszDomain ? pszDomain : "";
73 : }
74 :
75 : /************************************************************************/
76 : /* GetMetadata() */
77 : /************************************************************************/
78 :
79 153693 : char **GDALMultiDomainMetadata::GetMetadata(const char *pszDomain)
80 :
81 : {
82 153693 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
83 153693 : if (oIter == oMetadata.end())
84 119119 : return nullptr;
85 34574 : return oIter->second.List();
86 : }
87 :
88 : /************************************************************************/
89 : /* SetMetadata() */
90 : /************************************************************************/
91 :
92 21151 : CPLErr GDALMultiDomainMetadata::SetMetadata(CSLConstList papszMetadata,
93 : const char *pszDomain)
94 :
95 : {
96 21151 : pszDomain = SanitizeDomain(pszDomain);
97 :
98 21151 : auto oIter = oMetadata.find(pszDomain);
99 21151 : if (oIter == oMetadata.end())
100 : {
101 19035 : aosDomainList.AddString(pszDomain);
102 19035 : oIter =
103 19035 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
104 : .first;
105 : }
106 :
107 21151 : auto &oMDList = oIter->second;
108 21151 : oMDList = papszMetadata;
109 :
110 : // we want to mark name/value pair domains as being sorted for fast
111 : // access.
112 21151 : if (!STARTS_WITH_CI(pszDomain, "xml:") &&
113 20213 : !STARTS_WITH_CI(pszDomain, "json:") && !EQUAL(pszDomain, "SUBDATASETS"))
114 : {
115 19923 : oMDList.Sort();
116 : }
117 :
118 21151 : return CE_None;
119 : }
120 :
121 : /************************************************************************/
122 : /* GetMetadataItem() */
123 : /************************************************************************/
124 :
125 10454100 : const char *GDALMultiDomainMetadata::GetMetadataItem(const char *pszName,
126 : const char *pszDomain)
127 :
128 : {
129 10454100 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
130 10479000 : if (oIter == oMetadata.end())
131 186541 : return nullptr;
132 10292800 : return oIter->second.FetchNameValue(pszName);
133 : }
134 :
135 : /************************************************************************/
136 : /* SetMetadataItem() */
137 : /************************************************************************/
138 :
139 3304460 : CPLErr GDALMultiDomainMetadata::SetMetadataItem(const char *pszName,
140 : const char *pszValue,
141 : const char *pszDomain)
142 :
143 : {
144 3304460 : pszDomain = SanitizeDomain(pszDomain);
145 :
146 : /* -------------------------------------------------------------------- */
147 : /* Create the domain if it does not already exist. */
148 : /* -------------------------------------------------------------------- */
149 :
150 3304440 : auto oIter = oMetadata.find(pszDomain);
151 3304420 : if (oIter == oMetadata.end())
152 : {
153 374380 : aosDomainList.AddString(pszDomain);
154 374377 : oIter =
155 374377 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
156 : .first;
157 : }
158 :
159 : /* -------------------------------------------------------------------- */
160 : /* Set the value in the domain list. */
161 : /* -------------------------------------------------------------------- */
162 3304430 : oIter->second.SetNameValue(pszName, pszValue);
163 :
164 3304440 : return CE_None;
165 : }
166 :
167 : /************************************************************************/
168 : /* XMLInit() */
169 : /* */
170 : /* This method should be invoked on the parent of the */
171 : /* <Metadata> elements. */
172 : /************************************************************************/
173 :
174 6227 : int GDALMultiDomainMetadata::XMLInit(const CPLXMLNode *psTree, int /* bMerge */)
175 : {
176 6227 : const CPLXMLNode *psMetadata = nullptr;
177 :
178 : /* ==================================================================== */
179 : /* Process all <Metadata> elements, each for one domain. */
180 : /* ==================================================================== */
181 27858 : for (psMetadata = psTree->psChild; psMetadata != nullptr;
182 21631 : psMetadata = psMetadata->psNext)
183 : {
184 21631 : if (psMetadata->eType != CXT_Element ||
185 13836 : !EQUAL(psMetadata->pszValue, "Metadata"))
186 18900 : continue;
187 :
188 2731 : const char *pszDomain = CPLGetXMLValue(psMetadata, "domain", "");
189 2731 : const char *pszFormat = CPLGetXMLValue(psMetadata, "format", "");
190 :
191 : // Make sure we have a CPLStringList for this domain,
192 : // without wiping out an existing one.
193 2731 : if (GetMetadata(pszDomain) == nullptr)
194 1935 : SetMetadata(nullptr, pszDomain);
195 :
196 2731 : auto oIter = oMetadata.find(pszDomain);
197 2731 : CPLAssert(oIter != oMetadata.end());
198 :
199 2731 : auto &oMDList = oIter->second;
200 :
201 : /* --------------------------------------------------------------------
202 : */
203 : /* XML format subdocuments. */
204 : /* --------------------------------------------------------------------
205 : */
206 2731 : if (EQUAL(pszFormat, "xml"))
207 : {
208 : // Find first non-attribute child of current element.
209 26 : const CPLXMLNode *psSubDoc = psMetadata->psChild;
210 78 : while (psSubDoc != nullptr && psSubDoc->eType == CXT_Attribute)
211 52 : psSubDoc = psSubDoc->psNext;
212 :
213 26 : char *pszDoc = CPLSerializeXMLTree(psSubDoc);
214 :
215 26 : oMDList.Clear();
216 26 : oMDList.AddStringDirectly(pszDoc);
217 : }
218 :
219 : /* --------------------------------------------------------------------
220 : */
221 : /* JSon format subdocuments. */
222 : /* --------------------------------------------------------------------
223 : */
224 2705 : else if (EQUAL(pszFormat, "json"))
225 : {
226 : // Find first text child of current element.
227 11 : const CPLXMLNode *psSubDoc = psMetadata->psChild;
228 33 : while (psSubDoc != nullptr && psSubDoc->eType != CXT_Text)
229 22 : psSubDoc = psSubDoc->psNext;
230 11 : if (psSubDoc)
231 : {
232 11 : oMDList.Clear();
233 11 : oMDList.AddString(psSubDoc->pszValue);
234 : }
235 : }
236 :
237 : /* --------------------------------------------------------------------
238 : */
239 : /* Name value format. */
240 : /* <MDI key="...">value_Text</MDI> */
241 : /* --------------------------------------------------------------------
242 : */
243 : else
244 : {
245 2694 : for (const CPLXMLNode *psMDI = psMetadata->psChild;
246 16370 : psMDI != nullptr; psMDI = psMDI->psNext)
247 : {
248 13676 : if (!EQUAL(psMDI->pszValue, "MDI") ||
249 12636 : psMDI->eType != CXT_Element || psMDI->psChild == nullptr ||
250 12636 : psMDI->psChild->psNext == nullptr ||
251 10890 : psMDI->psChild->eType != CXT_Attribute ||
252 10890 : psMDI->psChild->psChild == nullptr)
253 2786 : continue;
254 :
255 10890 : char *pszName = psMDI->psChild->psChild->pszValue;
256 10890 : char *pszValue = psMDI->psChild->psNext->pszValue;
257 10890 : if (pszName != nullptr && pszValue != nullptr)
258 10890 : oMDList.SetNameValue(pszName, pszValue);
259 : }
260 : }
261 : }
262 :
263 6227 : return !aosDomainList.empty();
264 : }
265 :
266 : /************************************************************************/
267 : /* Serialize() */
268 : /************************************************************************/
269 :
270 5320 : CPLXMLNode *GDALMultiDomainMetadata::Serialize() const
271 :
272 : {
273 5320 : CPLXMLNode *psFirst = nullptr;
274 :
275 9356 : for (const auto &[pszDomainName, oList] : oMetadata)
276 : {
277 4036 : CSLConstList papszMD = oList.List();
278 : // Do not serialize empty domains.
279 4036 : if (papszMD == nullptr || papszMD[0] == nullptr)
280 1318 : continue;
281 :
282 2718 : CPLXMLNode *psMD = CPLCreateXMLNode(nullptr, CXT_Element, "Metadata");
283 :
284 2718 : if (strlen(pszDomainName) > 0)
285 1085 : CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "domain"),
286 : CXT_Text, pszDomainName);
287 :
288 2718 : bool bFormatXMLOrJSon = false;
289 :
290 2718 : if (STARTS_WITH_CI(pszDomainName, "xml:") && CSLCount(papszMD) == 1)
291 : {
292 22 : CPLXMLNode *psValueAsXML = CPLParseXMLString(papszMD[0]);
293 22 : if (psValueAsXML != nullptr)
294 : {
295 22 : bFormatXMLOrJSon = true;
296 :
297 22 : CPLCreateXMLNode(
298 : CPLCreateXMLNode(psMD, CXT_Attribute, "format"), CXT_Text,
299 : "xml");
300 :
301 22 : CPLAddXMLChild(psMD, psValueAsXML);
302 : }
303 : }
304 :
305 2718 : if (STARTS_WITH_CI(pszDomainName, "json:") && CSLCount(papszMD) == 1)
306 : {
307 5 : bFormatXMLOrJSon = true;
308 :
309 5 : CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "format"),
310 : CXT_Text, "json");
311 5 : CPLCreateXMLNode(psMD, CXT_Text, *papszMD);
312 : }
313 :
314 2718 : if (!bFormatXMLOrJSon)
315 : {
316 2691 : CPLXMLNode *psLastChild = nullptr;
317 : // To go after domain attribute.
318 2691 : if (psMD->psChild != nullptr)
319 : {
320 1058 : psLastChild = psMD->psChild;
321 1058 : while (psLastChild->psNext != nullptr)
322 0 : psLastChild = psLastChild->psNext;
323 : }
324 12784 : for (int i = 0; papszMD[i] != nullptr; i++)
325 : {
326 10093 : char *pszKey = nullptr;
327 :
328 : const char *pszRawValue =
329 10093 : CPLParseNameValue(papszMD[i], &pszKey);
330 :
331 : CPLXMLNode *psMDI =
332 10093 : CPLCreateXMLNode(nullptr, CXT_Element, "MDI");
333 10093 : if (psLastChild == nullptr)
334 1633 : psMD->psChild = psMDI;
335 : else
336 8460 : psLastChild->psNext = psMDI;
337 10093 : psLastChild = psMDI;
338 :
339 10093 : CPLSetXMLValue(psMDI, "#key", pszKey);
340 10093 : CPLCreateXMLNode(psMDI, CXT_Text, pszRawValue);
341 :
342 10093 : CPLFree(pszKey);
343 : }
344 : }
345 :
346 2718 : if (psFirst == nullptr)
347 2104 : psFirst = psMD;
348 : else
349 614 : CPLAddXMLSibling(psFirst, psMD);
350 : }
351 :
352 5320 : return psFirst;
353 : }
354 :
355 : //! @endcond
|