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 17172700 : static inline const char *SanitizeDomain(const char *pszDomain)
55 : {
56 17172700 : return pszDomain ? pszDomain : "";
57 : }
58 :
59 : /************************************************************************/
60 : /* GetMetadata() */
61 : /************************************************************************/
62 :
63 325551 : char **GDALMultiDomainMetadata::GetMetadata(const char *pszDomain)
64 :
65 : {
66 325551 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
67 325550 : if (oIter == oMetadata.end())
68 232492 : return nullptr;
69 93059 : return oIter->second.List();
70 : }
71 :
72 : /************************************************************************/
73 : /* SetMetadata() */
74 : /************************************************************************/
75 :
76 99010 : CPLErr GDALMultiDomainMetadata::SetMetadata(CSLConstList papszMetadata,
77 : const char *pszDomain)
78 :
79 : {
80 99010 : pszDomain = SanitizeDomain(pszDomain);
81 :
82 99010 : auto oIter = oMetadata.find(pszDomain);
83 99010 : if (oIter == oMetadata.end())
84 : {
85 95701 : aosDomainList.AddString(pszDomain);
86 95701 : oIter =
87 95701 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
88 : .first;
89 : }
90 :
91 99010 : auto &oMDList = oIter->second;
92 99010 : oMDList = papszMetadata;
93 :
94 : // we want to mark name/value pair domains as being sorted for fast
95 : // access.
96 99010 : if (!STARTS_WITH_CI(pszDomain, "xml:") &&
97 98054 : !STARTS_WITH_CI(pszDomain, "json:") &&
98 97945 : !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 97766 : && !EQUAL(pszDomain, "IMD"))
102 : {
103 97688 : oMDList.Sort();
104 : }
105 :
106 99010 : return CE_None;
107 : }
108 :
109 : /************************************************************************/
110 : /* GetMetadataItem() */
111 : /************************************************************************/
112 :
113 12187400 : const char *GDALMultiDomainMetadata::GetMetadataItem(const char *pszName,
114 : const char *pszDomain)
115 :
116 : {
117 12187400 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
118 12139900 : if (oIter == oMetadata.end())
119 722736 : return nullptr;
120 11414800 : return oIter->second.FetchNameValue(pszName);
121 : }
122 :
123 : /************************************************************************/
124 : /* SetMetadataItem() */
125 : /************************************************************************/
126 :
127 4567200 : CPLErr GDALMultiDomainMetadata::SetMetadataItem(const char *pszName,
128 : const char *pszValue,
129 : const char *pszDomain)
130 :
131 : {
132 4567200 : pszDomain = SanitizeDomain(pszDomain);
133 :
134 : /* -------------------------------------------------------------------- */
135 : /* Create the domain if it does not already exist. */
136 : /* -------------------------------------------------------------------- */
137 :
138 4567060 : auto oIter = oMetadata.find(pszDomain);
139 4567080 : if (oIter == oMetadata.end())
140 : {
141 562576 : aosDomainList.AddString(pszDomain);
142 562638 : oIter =
143 562599 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
144 : .first;
145 : }
146 :
147 : /* -------------------------------------------------------------------- */
148 : /* Set the value in the domain list. */
149 : /* -------------------------------------------------------------------- */
150 4567030 : oIter->second.SetNameValue(pszName, pszValue);
151 :
152 4567150 : 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 9303 : int GDALMultiDomainMetadata::XMLInit(const CPLXMLNode *psTree, int /* bMerge */)
163 : {
164 9303 : const CPLXMLNode *psMetadata = nullptr;
165 :
166 : /* ==================================================================== */
167 : /* Process all <Metadata> elements, each for one domain. */
168 : /* ==================================================================== */
169 146174 : for (psMetadata = psTree->psChild; psMetadata != nullptr;
170 136871 : psMetadata = psMetadata->psNext)
171 : {
172 136871 : if (psMetadata->eType != CXT_Element ||
173 122265 : !EQUAL(psMetadata->pszValue, "Metadata"))
174 133828 : continue;
175 :
176 3043 : const char *pszDomain = CPLGetXMLValue(psMetadata, "domain", "");
177 3043 : 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 3043 : if (GetMetadata(pszDomain) == nullptr)
182 2183 : SetMetadata(nullptr, pszDomain);
183 :
184 3043 : auto oIter = oMetadata.find(pszDomain);
185 3043 : CPLAssert(oIter != oMetadata.end());
186 :
187 3043 : auto &oMDList = oIter->second;
188 :
189 : /* --------------------------------------------------------------------
190 : */
191 : /* XML format subdocuments. */
192 : /* --------------------------------------------------------------------
193 : */
194 3043 : 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 3017 : 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 3006 : for (const CPLXMLNode *psMDI = psMetadata->psChild;
234 18044 : psMDI != nullptr; psMDI = psMDI->psNext)
235 : {
236 15038 : if (!EQUAL(psMDI->pszValue, "MDI") ||
237 13857 : psMDI->eType != CXT_Element || psMDI->psChild == nullptr ||
238 13857 : psMDI->psChild->psNext == nullptr ||
239 12065 : psMDI->psChild->eType != CXT_Attribute ||
240 12065 : psMDI->psChild->psChild == nullptr)
241 2973 : continue;
242 :
243 12065 : char *pszName = psMDI->psChild->psChild->pszValue;
244 12065 : char *pszValue = psMDI->psChild->psNext->pszValue;
245 12065 : if (pszName != nullptr && pszValue != nullptr)
246 12065 : oMDList.SetNameValue(pszName, pszValue);
247 : }
248 : }
249 : }
250 :
251 9303 : return !aosDomainList.empty();
252 : }
253 :
254 : /************************************************************************/
255 : /* Serialize() */
256 : /************************************************************************/
257 :
258 5432 : CPLXMLNode *GDALMultiDomainMetadata::Serialize() const
259 :
260 : {
261 5432 : CPLXMLNode *psFirst = nullptr;
262 :
263 9803 : for (const auto &[pszDomainName, oList] : oMetadata)
264 : {
265 4371 : CSLConstList papszMD = oList.List();
266 : // Do not serialize empty domains.
267 4371 : if (papszMD == nullptr || papszMD[0] == nullptr)
268 1447 : continue;
269 :
270 2924 : CPLXMLNode *psMD = CPLCreateXMLNode(nullptr, CXT_Element, "Metadata");
271 :
272 2924 : if (strlen(pszDomainName) > 0)
273 1261 : CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "domain"),
274 : CXT_Text, pszDomainName);
275 :
276 2924 : bool bFormatXMLOrJSon = false;
277 :
278 2924 : 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 2924 : 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 2924 : if (!bFormatXMLOrJSon)
303 : {
304 2897 : CPLXMLNode *psLastChild = nullptr;
305 : // To go after domain attribute.
306 2897 : if (psMD->psChild != nullptr)
307 : {
308 1234 : psLastChild = psMD->psChild;
309 1234 : while (psLastChild->psNext != nullptr)
310 0 : psLastChild = psLastChild->psNext;
311 : }
312 13741 : for (int i = 0; papszMD[i] != nullptr; i++)
313 : {
314 10844 : char *pszKey = nullptr;
315 :
316 : const char *pszRawValue =
317 10844 : CPLParseNameValue(papszMD[i], &pszKey);
318 :
319 : CPLXMLNode *psMDI =
320 10844 : CPLCreateXMLNode(nullptr, CXT_Element, "MDI");
321 10844 : if (psLastChild == nullptr)
322 1663 : psMD->psChild = psMDI;
323 : else
324 9181 : psLastChild->psNext = psMDI;
325 10844 : psLastChild = psMDI;
326 :
327 10844 : CPLSetXMLValue(psMDI, "#key", pszKey);
328 10844 : CPLCreateXMLNode(psMDI, CXT_Text, pszRawValue);
329 :
330 10844 : CPLFree(pszKey);
331 : }
332 : }
333 :
334 2924 : if (psFirst == nullptr)
335 2168 : psFirst = psMD;
336 : else
337 756 : CPLAddXMLSibling(psFirst, psMD);
338 : }
339 :
340 5432 : return psFirst;
341 : }
342 :
343 : //! @endcond
|