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 16164900 : static inline const char *SanitizeDomain(const char *pszDomain)
55 : {
56 16164900 : return pszDomain ? pszDomain : "";
57 : }
58 :
59 : /************************************************************************/
60 : /* GetMetadata() */
61 : /************************************************************************/
62 :
63 203735 : char **GDALMultiDomainMetadata::GetMetadata(const char *pszDomain)
64 :
65 : {
66 203735 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
67 203735 : if (oIter == oMetadata.end())
68 150710 : return nullptr;
69 53025 : return oIter->second.List();
70 : }
71 :
72 : /************************************************************************/
73 : /* SetMetadata() */
74 : /************************************************************************/
75 :
76 30111 : CPLErr GDALMultiDomainMetadata::SetMetadata(CSLConstList papszMetadata,
77 : const char *pszDomain)
78 :
79 : {
80 30111 : pszDomain = SanitizeDomain(pszDomain);
81 :
82 30111 : auto oIter = oMetadata.find(pszDomain);
83 30111 : if (oIter == oMetadata.end())
84 : {
85 27018 : aosDomainList.AddString(pszDomain);
86 27018 : oIter =
87 27018 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
88 : .first;
89 : }
90 :
91 30111 : auto &oMDList = oIter->second;
92 30111 : oMDList = papszMetadata;
93 :
94 : // we want to mark name/value pair domains as being sorted for fast
95 : // access.
96 30111 : if (!STARTS_WITH_CI(pszDomain, "xml:") &&
97 29159 : !STARTS_WITH_CI(pszDomain, "json:") &&
98 29052 : !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 28857 : && !EQUAL(pszDomain, "IMD"))
102 : {
103 28780 : oMDList.Sort();
104 : }
105 :
106 30111 : return CE_None;
107 : }
108 :
109 : /************************************************************************/
110 : /* GetMetadataItem() */
111 : /************************************************************************/
112 :
113 12106900 : const char *GDALMultiDomainMetadata::GetMetadataItem(const char *pszName,
114 : const char *pszDomain)
115 :
116 : {
117 12106900 : const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
118 12100000 : if (oIter == oMetadata.end())
119 234258 : return nullptr;
120 11838400 : return oIter->second.FetchNameValue(pszName);
121 : }
122 :
123 : /************************************************************************/
124 : /* SetMetadataItem() */
125 : /************************************************************************/
126 :
127 3826200 : CPLErr GDALMultiDomainMetadata::SetMetadataItem(const char *pszName,
128 : const char *pszValue,
129 : const char *pszDomain)
130 :
131 : {
132 3826200 : pszDomain = SanitizeDomain(pszDomain);
133 :
134 : /* -------------------------------------------------------------------- */
135 : /* Create the domain if it does not already exist. */
136 : /* -------------------------------------------------------------------- */
137 :
138 3826120 : auto oIter = oMetadata.find(pszDomain);
139 3826080 : if (oIter == oMetadata.end())
140 : {
141 433056 : aosDomainList.AddString(pszDomain);
142 433064 : oIter =
143 433057 : oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
144 : .first;
145 : }
146 :
147 : /* -------------------------------------------------------------------- */
148 : /* Set the value in the domain list. */
149 : /* -------------------------------------------------------------------- */
150 3826080 : oIter->second.SetNameValue(pszName, pszValue);
151 :
152 3826170 : 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 6676 : int GDALMultiDomainMetadata::XMLInit(const CPLXMLNode *psTree, int /* bMerge */)
163 : {
164 6676 : const CPLXMLNode *psMetadata = nullptr;
165 :
166 : /* ==================================================================== */
167 : /* Process all <Metadata> elements, each for one domain. */
168 : /* ==================================================================== */
169 31205 : for (psMetadata = psTree->psChild; psMetadata != nullptr;
170 24529 : psMetadata = psMetadata->psNext)
171 : {
172 24529 : if (psMetadata->eType != CXT_Element ||
173 16351 : !EQUAL(psMetadata->pszValue, "Metadata"))
174 21504 : continue;
175 :
176 3025 : const char *pszDomain = CPLGetXMLValue(psMetadata, "domain", "");
177 3025 : 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 3025 : if (GetMetadata(pszDomain) == nullptr)
182 2163 : SetMetadata(nullptr, pszDomain);
183 :
184 3025 : auto oIter = oMetadata.find(pszDomain);
185 3025 : CPLAssert(oIter != oMetadata.end());
186 :
187 3025 : auto &oMDList = oIter->second;
188 :
189 : /* --------------------------------------------------------------------
190 : */
191 : /* XML format subdocuments. */
192 : /* --------------------------------------------------------------------
193 : */
194 3025 : 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 2999 : 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 2988 : for (const CPLXMLNode *psMDI = psMetadata->psChild;
234 17847 : psMDI != nullptr; psMDI = psMDI->psNext)
235 : {
236 14859 : if (!EQUAL(psMDI->pszValue, "MDI") ||
237 13696 : psMDI->eType != CXT_Element || psMDI->psChild == nullptr ||
238 13696 : psMDI->psChild->psNext == nullptr ||
239 11903 : psMDI->psChild->eType != CXT_Attribute ||
240 11903 : psMDI->psChild->psChild == nullptr)
241 2956 : continue;
242 :
243 11903 : char *pszName = psMDI->psChild->psChild->pszValue;
244 11903 : char *pszValue = psMDI->psChild->psNext->pszValue;
245 11903 : if (pszName != nullptr && pszValue != nullptr)
246 11903 : oMDList.SetNameValue(pszName, pszValue);
247 : }
248 : }
249 : }
250 :
251 6676 : return !aosDomainList.empty();
252 : }
253 :
254 : /************************************************************************/
255 : /* Serialize() */
256 : /************************************************************************/
257 :
258 5589 : CPLXMLNode *GDALMultiDomainMetadata::Serialize() const
259 :
260 : {
261 5589 : CPLXMLNode *psFirst = nullptr;
262 :
263 9930 : for (const auto &[pszDomainName, oList] : oMetadata)
264 : {
265 4341 : CSLConstList papszMD = oList.List();
266 : // Do not serialize empty domains.
267 4341 : if (papszMD == nullptr || papszMD[0] == nullptr)
268 1409 : continue;
269 :
270 2932 : CPLXMLNode *psMD = CPLCreateXMLNode(nullptr, CXT_Element, "Metadata");
271 :
272 2932 : if (strlen(pszDomainName) > 0)
273 1204 : CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "domain"),
274 : CXT_Text, pszDomainName);
275 :
276 2932 : bool bFormatXMLOrJSon = false;
277 :
278 2932 : 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 2932 : 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 2932 : if (!bFormatXMLOrJSon)
303 : {
304 2905 : CPLXMLNode *psLastChild = nullptr;
305 : // To go after domain attribute.
306 2905 : if (psMD->psChild != nullptr)
307 : {
308 1177 : psLastChild = psMD->psChild;
309 1177 : while (psLastChild->psNext != nullptr)
310 0 : psLastChild = psLastChild->psNext;
311 : }
312 13782 : for (int i = 0; papszMD[i] != nullptr; i++)
313 : {
314 10877 : char *pszKey = nullptr;
315 :
316 : const char *pszRawValue =
317 10877 : CPLParseNameValue(papszMD[i], &pszKey);
318 :
319 : CPLXMLNode *psMDI =
320 10877 : CPLCreateXMLNode(nullptr, CXT_Element, "MDI");
321 10877 : if (psLastChild == nullptr)
322 1728 : psMD->psChild = psMDI;
323 : else
324 9149 : psLastChild->psNext = psMDI;
325 10877 : psLastChild = psMDI;
326 :
327 10877 : CPLSetXMLValue(psMDI, "#key", pszKey);
328 10877 : CPLCreateXMLNode(psMDI, CXT_Text, pszRawValue);
329 :
330 10877 : CPLFree(pszKey);
331 : }
332 : }
333 :
334 2932 : if (psFirst == nullptr)
335 2239 : psFirst = psMD;
336 : else
337 693 : CPLAddXMLSibling(psFirst, psMD);
338 : }
339 :
340 5589 : return psFirst;
341 : }
342 :
343 : //! @endcond
|