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 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_port.h"
16 : #include "gdal_priv.h"
17 :
18 : #include <cstring>
19 :
20 : #include "cpl_conv.h"
21 : #include "cpl_error.h"
22 : #include "cpl_minixml.h"
23 : #include "cpl_string.h"
24 : #include "gdal_pam.h"
25 :
26 : //! @cond Doxygen_Suppress
27 : /************************************************************************/
28 : /* GDALMultiDomainMetadata() */
29 : /************************************************************************/
30 :
31 : GDALMultiDomainMetadata::GDALMultiDomainMetadata() = default;
32 :
33 : /************************************************************************/
34 : /* ~GDALMultiDomainMetadata() */
35 : /************************************************************************/
36 :
37 : GDALMultiDomainMetadata::~GDALMultiDomainMetadata() = default;
38 :
39 : /************************************************************************/
40 : /* Clear() */
41 : /************************************************************************/
42 :
43 0 : void GDALMultiDomainMetadata::Clear()
44 :
45 : {
46 0 : aosDomainList.clear();
47 0 : oMetadata.clear();
48 0 : }
49 :
50 : /************************************************************************/
51 : /* SanitizeDomain() */
52 : /************************************************************************/
53 :
54 16822000 : static inline const char *SanitizeDomain(const char *pszDomain)
55 : {
56 16822000 : return pszDomain ? pszDomain : "";
57 : }
58 :
59 : /************************************************************************/
60 : /* GetMetadata() */
61 : /************************************************************************/
62 :
63 325293 : char **GDALMultiDomainMetadata::GetMetadata(const char *pszDomain)
64 :
65 : {
66 325293 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
67 325290 : if (oIter == oMetadata.end())
68 233093 : return nullptr;
69 92198 : return oIter->second.List();
70 : }
71 :
72 : /************************************************************************/
73 : /* SetMetadata() */
74 : /************************************************************************/
75 :
76 98733 : CPLErr GDALMultiDomainMetadata::SetMetadata(CSLConstList papszMetadata,
77 : const char *pszDomain)
78 :
79 : {
80 98733 : pszDomain = SanitizeDomain(pszDomain);
81 :
82 98732 : auto oIter = oMetadata.find(pszDomain);
83 98733 : if (oIter == oMetadata.end())
84 : {
85 95435 : aosDomainList.AddString(pszDomain);
86 95434 : oIter =
87 95434 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
88 : .first;
89 : }
90 :
91 98733 : auto &oMDList = oIter->second;
92 98733 : oMDList = papszMetadata;
93 :
94 : // we want to mark name/value pair domains as being sorted for fast
95 : // access.
96 98733 : if (!STARTS_WITH_CI(pszDomain, "xml:") &&
97 97776 : !STARTS_WITH_CI(pszDomain, "json:") &&
98 97669 : !EQUAL(pszDomain, "SUBDATASETS")
99 : // The IMD metadata domain should not be sorted, as order matters
100 : // when writing it back. Cf https://github.com/OSGeo/gdal/issues/11470
101 97490 : && !EQUAL(pszDomain, "IMD"))
102 : {
103 97411 : oMDList.Sort();
104 : }
105 :
106 98733 : return CE_None;
107 : }
108 :
109 : /************************************************************************/
110 : /* GetMetadataItem() */
111 : /************************************************************************/
112 :
113 11951400 : const char *GDALMultiDomainMetadata::GetMetadataItem(const char *pszName,
114 : const char *pszDomain)
115 :
116 : {
117 11951400 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
118 11960200 : if (oIter == oMetadata.end())
119 620295 : return nullptr;
120 11343300 : return oIter->second.FetchNameValue(pszName);
121 : }
122 :
123 : /************************************************************************/
124 : /* SetMetadataItem() */
125 : /************************************************************************/
126 :
127 4432130 : CPLErr GDALMultiDomainMetadata::SetMetadataItem(const char *pszName,
128 : const char *pszValue,
129 : const char *pszDomain)
130 :
131 : {
132 4432130 : pszDomain = SanitizeDomain(pszDomain);
133 :
134 : /* -------------------------------------------------------------------- */
135 : /* Create the domain if it does not already exist. */
136 : /* -------------------------------------------------------------------- */
137 :
138 4432070 : auto oIter = oMetadata.find(pszDomain);
139 4432010 : if (oIter == oMetadata.end())
140 : {
141 549529 : aosDomainList.AddString(pszDomain);
142 549540 : oIter =
143 549552 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
144 : .first;
145 : }
146 :
147 : /* -------------------------------------------------------------------- */
148 : /* Set the value in the domain list. */
149 : /* -------------------------------------------------------------------- */
150 4432020 : oIter->second.SetNameValue(pszName, pszValue);
151 :
152 4432110 : return CE_None;
153 : }
154 :
155 : /************************************************************************/
156 : /* XMLInit() */
157 : /* */
158 : /* This method should be invoked on the parent of the */
159 : /* <Metadata> elements. */
160 : /************************************************************************/
161 :
162 8951 : int GDALMultiDomainMetadata::XMLInit(const CPLXMLNode *psTree, int /* bMerge */)
163 : {
164 8951 : const CPLXMLNode *psMetadata = nullptr;
165 :
166 : /* ==================================================================== */
167 : /* Process all <Metadata> elements, each for one domain. */
168 : /* ==================================================================== */
169 45655 : for (psMetadata = psTree->psChild; psMetadata != nullptr;
170 36704 : psMetadata = psMetadata->psNext)
171 : {
172 36704 : if (psMetadata->eType != CXT_Element ||
173 22865 : !EQUAL(psMetadata->pszValue, "Metadata"))
174 33670 : continue;
175 :
176 3034 : const char *pszDomain = CPLGetXMLValue(psMetadata, "domain", "");
177 3034 : const char *pszFormat = CPLGetXMLValue(psMetadata, "format", "");
178 :
179 : // Make sure we have a CPLStringList for this domain,
180 : // without wiping out an existing one.
181 3034 : if (GetMetadata(pszDomain) == nullptr)
182 2176 : SetMetadata(nullptr, pszDomain);
183 :
184 3034 : auto oIter = oMetadata.find(pszDomain);
185 3034 : CPLAssert(oIter != oMetadata.end());
186 :
187 3034 : auto &oMDList = oIter->second;
188 :
189 : /* --------------------------------------------------------------------
190 : */
191 : /* XML format subdocuments. */
192 : /* --------------------------------------------------------------------
193 : */
194 3034 : if (EQUAL(pszFormat, "xml"))
195 : {
196 : // Find first non-attribute child of current element.
197 26 : const CPLXMLNode *psSubDoc = psMetadata->psChild;
198 78 : while (psSubDoc != nullptr && psSubDoc->eType == CXT_Attribute)
199 52 : psSubDoc = psSubDoc->psNext;
200 :
201 26 : char *pszDoc = CPLSerializeXMLTree(psSubDoc);
202 :
203 26 : oMDList.Clear();
204 26 : oMDList.AddStringDirectly(pszDoc);
205 : }
206 :
207 : /* --------------------------------------------------------------------
208 : */
209 : /* JSon format subdocuments. */
210 : /* --------------------------------------------------------------------
211 : */
212 3008 : else if (EQUAL(pszFormat, "json"))
213 : {
214 : // Find first text child of current element.
215 11 : const CPLXMLNode *psSubDoc = psMetadata->psChild;
216 33 : while (psSubDoc != nullptr && psSubDoc->eType != CXT_Text)
217 22 : psSubDoc = psSubDoc->psNext;
218 11 : if (psSubDoc)
219 : {
220 11 : oMDList.Clear();
221 11 : oMDList.AddString(psSubDoc->pszValue);
222 : }
223 : }
224 :
225 : /* --------------------------------------------------------------------
226 : */
227 : /* Name value format. */
228 : /* <MDI key="...">value_Text</MDI> */
229 : /* --------------------------------------------------------------------
230 : */
231 : else
232 : {
233 2997 : for (const CPLXMLNode *psMDI = psMetadata->psChild;
234 18000 : psMDI != nullptr; psMDI = psMDI->psNext)
235 : {
236 15003 : if (!EQUAL(psMDI->pszValue, "MDI") ||
237 13827 : psMDI->eType != CXT_Element || psMDI->psChild == nullptr ||
238 13827 : psMDI->psChild->psNext == nullptr ||
239 12035 : psMDI->psChild->eType != CXT_Attribute ||
240 12035 : psMDI->psChild->psChild == nullptr)
241 2968 : continue;
242 :
243 12035 : char *pszName = psMDI->psChild->psChild->pszValue;
244 12035 : char *pszValue = psMDI->psChild->psNext->pszValue;
245 12035 : if (pszName != nullptr && pszValue != nullptr)
246 12035 : oMDList.SetNameValue(pszName, pszValue);
247 : }
248 : }
249 : }
250 :
251 8951 : return !aosDomainList.empty();
252 : }
253 :
254 : /************************************************************************/
255 : /* Serialize() */
256 : /************************************************************************/
257 :
258 5352 : CPLXMLNode *GDALMultiDomainMetadata::Serialize() const
259 :
260 : {
261 5352 : CPLXMLNode *psFirst = nullptr;
262 :
263 9700 : for (const auto &[pszDomainName, oList] : oMetadata)
264 : {
265 4348 : CSLConstList papszMD = oList.List();
266 : // Do not serialize empty domains.
267 4348 : if (papszMD == nullptr || papszMD[0] == nullptr)
268 1444 : continue;
269 :
270 2904 : CPLXMLNode *psMD = CPLCreateXMLNode(nullptr, CXT_Element, "Metadata");
271 :
272 2904 : if (strlen(pszDomainName) > 0)
273 1259 : CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "domain"),
274 : CXT_Text, pszDomainName);
275 :
276 2904 : bool bFormatXMLOrJSon = false;
277 :
278 2904 : if (STARTS_WITH_CI(pszDomainName, "xml:") && CSLCount(papszMD) == 1)
279 : {
280 22 : CPLXMLNode *psValueAsXML = CPLParseXMLString(papszMD[0]);
281 22 : if (psValueAsXML != nullptr)
282 : {
283 22 : bFormatXMLOrJSon = true;
284 :
285 22 : CPLCreateXMLNode(
286 : CPLCreateXMLNode(psMD, CXT_Attribute, "format"), CXT_Text,
287 : "xml");
288 :
289 22 : CPLAddXMLChild(psMD, psValueAsXML);
290 : }
291 : }
292 :
293 2904 : if (STARTS_WITH_CI(pszDomainName, "json:") && CSLCount(papszMD) == 1)
294 : {
295 5 : bFormatXMLOrJSon = true;
296 :
297 5 : CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "format"),
298 : CXT_Text, "json");
299 5 : CPLCreateXMLNode(psMD, CXT_Text, *papszMD);
300 : }
301 :
302 2904 : if (!bFormatXMLOrJSon)
303 : {
304 2877 : CPLXMLNode *psLastChild = nullptr;
305 : // To go after domain attribute.
306 2877 : if (psMD->psChild != nullptr)
307 : {
308 1232 : psLastChild = psMD->psChild;
309 1232 : while (psLastChild->psNext != nullptr)
310 0 : psLastChild = psLastChild->psNext;
311 : }
312 13675 : for (int i = 0; papszMD[i] != nullptr; i++)
313 : {
314 10798 : char *pszKey = nullptr;
315 :
316 : const char *pszRawValue =
317 10798 : CPLParseNameValue(papszMD[i], &pszKey);
318 :
319 : CPLXMLNode *psMDI =
320 10798 : CPLCreateXMLNode(nullptr, CXT_Element, "MDI");
321 10798 : if (psLastChild == nullptr)
322 1645 : psMD->psChild = psMDI;
323 : else
324 9153 : psLastChild->psNext = psMDI;
325 10798 : psLastChild = psMDI;
326 :
327 10798 : CPLSetXMLValue(psMDI, "#key", pszKey);
328 10798 : CPLCreateXMLNode(psMDI, CXT_Text, pszRawValue);
329 :
330 10798 : CPLFree(pszKey);
331 : }
332 : }
333 :
334 2904 : if (psFirst == nullptr)
335 2149 : psFirst = psMD;
336 : else
337 755 : CPLAddXMLSibling(psFirst, psMD);
338 : }
339 :
340 5352 : return psFirst;
341 : }
342 :
343 : //! @endcond
|