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 19379700 : static inline const char *SanitizeDomain(const char *pszDomain)
55 : {
56 19379700 : return pszDomain ? pszDomain : "";
57 : }
58 :
59 : /************************************************************************/
60 : /* GetMetadata() */
61 : /************************************************************************/
62 :
63 344381 : char **GDALMultiDomainMetadata::GetMetadata(const char *pszDomain)
64 :
65 : {
66 344381 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
67 344381 : if (oIter == oMetadata.end())
68 248715 : return nullptr;
69 95666 : return oIter->second.List();
70 : }
71 :
72 : /************************************************************************/
73 : /* SetMetadata() */
74 : /************************************************************************/
75 :
76 100066 : CPLErr GDALMultiDomainMetadata::SetMetadata(CSLConstList papszMetadata,
77 : const char *pszDomain)
78 :
79 : {
80 100066 : pszDomain = SanitizeDomain(pszDomain);
81 :
82 100066 : auto oIter = oMetadata.find(pszDomain);
83 100066 : if (oIter == oMetadata.end())
84 : {
85 96753 : aosDomainList.AddString(pszDomain);
86 96753 : oIter =
87 96753 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
88 : .first;
89 : }
90 :
91 100066 : auto &oMDList = oIter->second;
92 100066 : oMDList = papszMetadata;
93 :
94 : // we want to mark name/value pair domains as being sorted for fast
95 : // access.
96 100066 : if (!STARTS_WITH_CI(pszDomain, "xml:") &&
97 99111 : !STARTS_WITH_CI(pszDomain, "json:") &&
98 98988 : !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 98792 : && !EQUAL(pszDomain, "IMD"))
102 : {
103 98714 : oMDList.Sort();
104 : }
105 :
106 100066 : return CE_None;
107 : }
108 :
109 : /************************************************************************/
110 : /* GetMetadataItem() */
111 : /************************************************************************/
112 :
113 : const char *
114 13893000 : GDALMultiDomainMetadata::GetMetadataItem(const char *pszName,
115 : const char *pszDomain) const
116 :
117 : {
118 13893000 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
119 13893000 : if (oIter == oMetadata.end())
120 870371 : return nullptr;
121 13022600 : return oIter->second.FetchNameValue(pszName);
122 : }
123 :
124 : /************************************************************************/
125 : /* SetMetadataItem() */
126 : /************************************************************************/
127 :
128 5042290 : CPLErr GDALMultiDomainMetadata::SetMetadataItem(const char *pszName,
129 : const char *pszValue,
130 : const char *pszDomain)
131 :
132 : {
133 5042290 : pszDomain = SanitizeDomain(pszDomain);
134 :
135 : /* -------------------------------------------------------------------- */
136 : /* Create the domain if it does not already exist. */
137 : /* -------------------------------------------------------------------- */
138 :
139 5042290 : auto oIter = oMetadata.find(pszDomain);
140 5042290 : if (oIter == oMetadata.end())
141 : {
142 604748 : aosDomainList.AddString(pszDomain);
143 604748 : oIter =
144 604748 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
145 : .first;
146 : }
147 :
148 : /* -------------------------------------------------------------------- */
149 : /* Set the value in the domain list. */
150 : /* -------------------------------------------------------------------- */
151 5042290 : oIter->second.SetNameValue(pszName, pszValue);
152 :
153 5042290 : return CE_None;
154 : }
155 :
156 : /************************************************************************/
157 : /* XMLInit() */
158 : /* */
159 : /* This method should be invoked on the parent of the */
160 : /* <Metadata> elements. */
161 : /************************************************************************/
162 :
163 9357 : int GDALMultiDomainMetadata::XMLInit(const CPLXMLNode *psTree, int /* bMerge */)
164 : {
165 9357 : const CPLXMLNode *psMetadata = nullptr;
166 :
167 : /* ==================================================================== */
168 : /* Process all <Metadata> elements, each for one domain. */
169 : /* ==================================================================== */
170 146451 : for (psMetadata = psTree->psChild; psMetadata != nullptr;
171 137094 : psMetadata = psMetadata->psNext)
172 : {
173 137094 : if (psMetadata->eType != CXT_Element ||
174 122386 : !EQUAL(psMetadata->pszValue, "Metadata"))
175 134038 : continue;
176 :
177 3056 : const char *pszDomain = CPLGetXMLValue(psMetadata, "domain", "");
178 3056 : const char *pszFormat = CPLGetXMLValue(psMetadata, "format", "");
179 :
180 : // Make sure we have a CPLStringList for this domain,
181 : // without wiping out an existing one.
182 3056 : if (GetMetadata(pszDomain) == nullptr)
183 2197 : SetMetadata(nullptr, pszDomain);
184 :
185 3056 : auto oIter = oMetadata.find(pszDomain);
186 3056 : CPLAssert(oIter != oMetadata.end());
187 :
188 3056 : auto &oMDList = oIter->second;
189 :
190 : /* --------------------------------------------------------------------
191 : */
192 : /* XML format subdocuments. */
193 : /* --------------------------------------------------------------------
194 : */
195 3056 : if (EQUAL(pszFormat, "xml"))
196 : {
197 : // Find first non-attribute child of current element.
198 26 : const CPLXMLNode *psSubDoc = psMetadata->psChild;
199 78 : while (psSubDoc != nullptr && psSubDoc->eType == CXT_Attribute)
200 52 : psSubDoc = psSubDoc->psNext;
201 :
202 26 : char *pszDoc = CPLSerializeXMLTree(psSubDoc);
203 :
204 26 : oMDList.Clear();
205 26 : oMDList.AddStringDirectly(pszDoc);
206 : }
207 :
208 : /* --------------------------------------------------------------------
209 : */
210 : /* JSon format subdocuments. */
211 : /* --------------------------------------------------------------------
212 : */
213 3030 : else if (EQUAL(pszFormat, "json"))
214 : {
215 : // Find first text child of current element.
216 6 : const CPLXMLNode *psSubDoc = psMetadata->psChild;
217 18 : while (psSubDoc != nullptr && psSubDoc->eType != CXT_Text)
218 12 : psSubDoc = psSubDoc->psNext;
219 6 : if (psSubDoc)
220 : {
221 6 : oMDList.Clear();
222 6 : oMDList.AddString(psSubDoc->pszValue);
223 : }
224 : }
225 :
226 : /* --------------------------------------------------------------------
227 : */
228 : /* Name value format. */
229 : /* <MDI key="...">value_Text</MDI> */
230 : /* --------------------------------------------------------------------
231 : */
232 : else
233 : {
234 3024 : for (const CPLXMLNode *psMDI = psMetadata->psChild;
235 17837 : psMDI != nullptr; psMDI = psMDI->psNext)
236 : {
237 14813 : if (!EQUAL(psMDI->pszValue, "MDI") ||
238 13602 : psMDI->eType != CXT_Element || psMDI->psChild == nullptr ||
239 13602 : psMDI->psChild->psNext == nullptr ||
240 11913 : psMDI->psChild->eType != CXT_Attribute ||
241 11913 : psMDI->psChild->psChild == nullptr)
242 2900 : continue;
243 :
244 11913 : char *pszName = psMDI->psChild->psChild->pszValue;
245 11913 : char *pszValue = psMDI->psChild->psNext->pszValue;
246 11913 : if (pszName != nullptr && pszValue != nullptr)
247 11913 : oMDList.SetNameValue(pszName, pszValue);
248 : }
249 : }
250 : }
251 :
252 9357 : return !aosDomainList.empty();
253 : }
254 :
255 : /************************************************************************/
256 : /* Serialize() */
257 : /************************************************************************/
258 :
259 5638 : CPLXMLNode *GDALMultiDomainMetadata::Serialize() const
260 :
261 : {
262 5638 : CPLXMLNode *psFirst = nullptr;
263 :
264 10083 : for (const auto &[pszDomainName, oList] : oMetadata)
265 : {
266 4445 : CSLConstList papszMD = oList.List();
267 : // Do not serialize empty domains.
268 4445 : if (papszMD == nullptr || papszMD[0] == nullptr)
269 1445 : continue;
270 :
271 3000 : CPLXMLNode *psMD = CPLCreateXMLNode(nullptr, CXT_Element, "Metadata");
272 :
273 3000 : if (strlen(pszDomainName) > 0)
274 1292 : CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "domain"),
275 : CXT_Text, pszDomainName);
276 :
277 3000 : bool bFormatXMLOrJSon = false;
278 :
279 3000 : if (STARTS_WITH_CI(pszDomainName, "xml:") && CSLCount(papszMD) == 1)
280 : {
281 22 : CPLXMLNode *psValueAsXML = CPLParseXMLString(papszMD[0]);
282 22 : if (psValueAsXML != nullptr)
283 : {
284 22 : bFormatXMLOrJSon = true;
285 :
286 22 : CPLCreateXMLNode(
287 : CPLCreateXMLNode(psMD, CXT_Attribute, "format"), CXT_Text,
288 : "xml");
289 :
290 22 : CPLAddXMLChild(psMD, psValueAsXML);
291 : }
292 : }
293 :
294 3000 : if (STARTS_WITH_CI(pszDomainName, "json:") && CSLCount(papszMD) == 1)
295 : {
296 4 : bFormatXMLOrJSon = true;
297 :
298 4 : CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "format"),
299 : CXT_Text, "json");
300 4 : CPLCreateXMLNode(psMD, CXT_Text, *papszMD);
301 : }
302 :
303 3000 : if (!bFormatXMLOrJSon)
304 : {
305 2974 : CPLXMLNode *psLastChild = nullptr;
306 : // To go after domain attribute.
307 2974 : if (psMD->psChild != nullptr)
308 : {
309 1266 : psLastChild = psMD->psChild;
310 1266 : while (psLastChild->psNext != nullptr)
311 0 : psLastChild = psLastChild->psNext;
312 : }
313 13823 : for (int i = 0; papszMD[i] != nullptr; i++)
314 : {
315 10849 : char *pszKey = nullptr;
316 :
317 : const char *pszRawValue =
318 10849 : CPLParseNameValue(papszMD[i], &pszKey);
319 :
320 : CPLXMLNode *psMDI =
321 10849 : CPLCreateXMLNode(nullptr, CXT_Element, "MDI");
322 10849 : if (psLastChild == nullptr)
323 1708 : psMD->psChild = psMDI;
324 : else
325 9141 : psLastChild->psNext = psMDI;
326 10849 : psLastChild = psMDI;
327 :
328 10849 : CPLSetXMLValue(psMDI, "#key", pszKey);
329 10849 : CPLCreateXMLNode(psMDI, CXT_Text, pszRawValue);
330 :
331 10849 : CPLFree(pszKey);
332 : }
333 : }
334 :
335 3000 : if (psFirst == nullptr)
336 2229 : psFirst = psMD;
337 : else
338 771 : CPLAddXMLSibling(psFirst, psMD);
339 : }
340 :
341 5638 : return psFirst;
342 : }
343 :
344 : //! @endcond
|