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