Coverage Report

Created: 2025-07-23 09:13

/src/gdal/gcore/gdalmultidomainmetadata.cpp
Line
Count
Source (jump to first uncovered line)
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
186M
GDALMultiDomainMetadata::GDALMultiDomainMetadata() = default;
32
33
/************************************************************************/
34
/*                      ~GDALMultiDomainMetadata()                      */
35
/************************************************************************/
36
37
186M
GDALMultiDomainMetadata::~GDALMultiDomainMetadata() = default;
38
39
/************************************************************************/
40
/*                               Clear()                                */
41
/************************************************************************/
42
43
void GDALMultiDomainMetadata::Clear()
44
45
0
{
46
0
    aosDomainList.clear();
47
0
    oMetadata.clear();
48
0
}
49
50
/************************************************************************/
51
/*                           SanitizeDomain()                           */
52
/************************************************************************/
53
54
static inline const char *SanitizeDomain(const char *pszDomain)
55
328M
{
56
328M
    return pszDomain ? pszDomain : "";
57
328M
}
58
59
/************************************************************************/
60
/*                            GetMetadata()                             */
61
/************************************************************************/
62
63
char **GDALMultiDomainMetadata::GetMetadata(const char *pszDomain)
64
65
3.04M
{
66
3.04M
    const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
67
3.04M
    if (oIter == oMetadata.end())
68
1.79M
        return nullptr;
69
1.25M
    return oIter->second.List();
70
3.04M
}
71
72
/************************************************************************/
73
/*                            SetMetadata()                             */
74
/************************************************************************/
75
76
CPLErr GDALMultiDomainMetadata::SetMetadata(CSLConstList papszMetadata,
77
                                            const char *pszDomain)
78
79
691k
{
80
691k
    pszDomain = SanitizeDomain(pszDomain);
81
82
691k
    auto oIter = oMetadata.find(pszDomain);
83
691k
    if (oIter == oMetadata.end())
84
611k
    {
85
611k
        aosDomainList.AddString(pszDomain);
86
611k
        oIter =
87
611k
            oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
88
611k
                .first;
89
611k
    }
90
91
691k
    auto &oMDList = oIter->second;
92
691k
    oMDList = papszMetadata;
93
94
    // we want to mark name/value pair domains as being sorted for fast
95
    // access.
96
691k
    if (!STARTS_WITH_CI(pszDomain, "xml:") &&
97
691k
        !STARTS_WITH_CI(pszDomain, "json:") &&
98
691k
        !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
691k
        && !EQUAL(pszDomain, "IMD"))
102
675k
    {
103
675k
        oMDList.Sort();
104
675k
    }
105
106
691k
    return CE_None;
107
691k
}
108
109
/************************************************************************/
110
/*                          GetMetadataItem()                           */
111
/************************************************************************/
112
113
const char *GDALMultiDomainMetadata::GetMetadataItem(const char *pszName,
114
                                                     const char *pszDomain)
115
116
258M
{
117
258M
    const auto oIter = oMetadata.find(SanitizeDomain(pszDomain));
118
258M
    if (oIter == oMetadata.end())
119
3.65M
        return nullptr;
120
254M
    return oIter->second.FetchNameValue(pszName);
121
258M
}
122
123
/************************************************************************/
124
/*                          SetMetadataItem()                           */
125
/************************************************************************/
126
127
CPLErr GDALMultiDomainMetadata::SetMetadataItem(const char *pszName,
128
                                                const char *pszValue,
129
                                                const char *pszDomain)
130
131
66.9M
{
132
66.9M
    pszDomain = SanitizeDomain(pszDomain);
133
134
    /* -------------------------------------------------------------------- */
135
    /*      Create the domain if it does not already exist.                 */
136
    /* -------------------------------------------------------------------- */
137
138
66.9M
    auto oIter = oMetadata.find(pszDomain);
139
66.9M
    if (oIter == oMetadata.end())
140
63.0M
    {
141
63.0M
        aosDomainList.AddString(pszDomain);
142
63.0M
        oIter =
143
63.0M
            oMetadata.insert(std::pair(aosDomainList.back(), CPLStringList()))
144
63.0M
                .first;
145
63.0M
    }
146
147
    /* -------------------------------------------------------------------- */
148
    /*      Set the value in the domain list.                               */
149
    /* -------------------------------------------------------------------- */
150
66.9M
    oIter->second.SetNameValue(pszName, pszValue);
151
152
66.9M
    return CE_None;
153
66.9M
}
154
155
/************************************************************************/
156
/*                              XMLInit()                               */
157
/*                                                                      */
158
/*      This method should be invoked on the parent of the              */
159
/*      <Metadata> elements.                                            */
160
/************************************************************************/
161
162
int GDALMultiDomainMetadata::XMLInit(const CPLXMLNode *psTree, int /* bMerge */)
163
182k
{
164
182k
    const CPLXMLNode *psMetadata = nullptr;
165
166
    /* ==================================================================== */
167
    /*      Process all <Metadata> elements, each for one domain.           */
168
    /* ==================================================================== */
169
4.47M
    for (psMetadata = psTree->psChild; psMetadata != nullptr;
170
4.29M
         psMetadata = psMetadata->psNext)
171
4.29M
    {
172
4.29M
        if (psMetadata->eType != CXT_Element ||
173
4.29M
            !EQUAL(psMetadata->pszValue, "Metadata"))
174
4.22M
            continue;
175
176
72.8k
        const char *pszDomain = CPLGetXMLValue(psMetadata, "domain", "");
177
72.8k
        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
72.8k
        if (GetMetadata(pszDomain) == nullptr)
182
50.6k
            SetMetadata(nullptr, pszDomain);
183
184
72.8k
        auto oIter = oMetadata.find(pszDomain);
185
72.8k
        CPLAssert(oIter != oMetadata.end());
186
187
72.8k
        auto &oMDList = oIter->second;
188
189
        /* --------------------------------------------------------------------
190
         */
191
        /*      XML format subdocuments. */
192
        /* --------------------------------------------------------------------
193
         */
194
72.8k
        if (EQUAL(pszFormat, "xml"))
195
0
        {
196
            // Find first non-attribute child of current element.
197
0
            const CPLXMLNode *psSubDoc = psMetadata->psChild;
198
0
            while (psSubDoc != nullptr && psSubDoc->eType == CXT_Attribute)
199
0
                psSubDoc = psSubDoc->psNext;
200
201
0
            char *pszDoc = CPLSerializeXMLTree(psSubDoc);
202
203
0
            oMDList.Clear();
204
0
            oMDList.AddStringDirectly(pszDoc);
205
0
        }
206
207
        /* --------------------------------------------------------------------
208
         */
209
        /*      JSon format subdocuments. */
210
        /* --------------------------------------------------------------------
211
         */
212
72.8k
        else if (EQUAL(pszFormat, "json"))
213
0
        {
214
            // Find first text child of current element.
215
0
            const CPLXMLNode *psSubDoc = psMetadata->psChild;
216
0
            while (psSubDoc != nullptr && psSubDoc->eType != CXT_Text)
217
0
                psSubDoc = psSubDoc->psNext;
218
0
            if (psSubDoc)
219
0
            {
220
0
                oMDList.Clear();
221
0
                oMDList.AddString(psSubDoc->pszValue);
222
0
            }
223
0
        }
224
225
        /* --------------------------------------------------------------------
226
         */
227
        /*      Name value format. */
228
        /*      <MDI key="...">value_Text</MDI> */
229
        /* --------------------------------------------------------------------
230
         */
231
72.8k
        else
232
72.8k
        {
233
72.8k
            for (const CPLXMLNode *psMDI = psMetadata->psChild;
234
288k
                 psMDI != nullptr; psMDI = psMDI->psNext)
235
215k
            {
236
215k
                if (!EQUAL(psMDI->pszValue, "MDI") ||
237
215k
                    psMDI->eType != CXT_Element || psMDI->psChild == nullptr ||
238
215k
                    psMDI->psChild->psNext == nullptr ||
239
215k
                    psMDI->psChild->eType != CXT_Attribute ||
240
215k
                    psMDI->psChild->psChild == nullptr)
241
93.1k
                    continue;
242
243
122k
                char *pszName = psMDI->psChild->psChild->pszValue;
244
122k
                char *pszValue = psMDI->psChild->psNext->pszValue;
245
122k
                if (pszName != nullptr && pszValue != nullptr)
246
122k
                    oMDList.SetNameValue(pszName, pszValue);
247
122k
            }
248
72.8k
        }
249
72.8k
    }
250
251
182k
    return !aosDomainList.empty();
252
182k
}
253
254
/************************************************************************/
255
/*                             Serialize()                              */
256
/************************************************************************/
257
258
CPLXMLNode *GDALMultiDomainMetadata::Serialize() const
259
260
291k
{
261
291k
    CPLXMLNode *psFirst = nullptr;
262
263
291k
    for (const auto &[pszDomainName, oList] : oMetadata)
264
284k
    {
265
284k
        CSLConstList papszMD = oList.List();
266
        // Do not serialize empty domains.
267
284k
        if (papszMD == nullptr || papszMD[0] == nullptr)
268
111k
            continue;
269
270
173k
        CPLXMLNode *psMD = CPLCreateXMLNode(nullptr, CXT_Element, "Metadata");
271
272
173k
        if (strlen(pszDomainName) > 0)
273
89.6k
            CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "domain"),
274
89.6k
                             CXT_Text, pszDomainName);
275
276
173k
        bool bFormatXMLOrJSon = false;
277
278
173k
        if (STARTS_WITH_CI(pszDomainName, "xml:") && CSLCount(papszMD) == 1)
279
0
        {
280
0
            CPLXMLNode *psValueAsXML = CPLParseXMLString(papszMD[0]);
281
0
            if (psValueAsXML != nullptr)
282
0
            {
283
0
                bFormatXMLOrJSon = true;
284
285
0
                CPLCreateXMLNode(
286
0
                    CPLCreateXMLNode(psMD, CXT_Attribute, "format"), CXT_Text,
287
0
                    "xml");
288
289
0
                CPLAddXMLChild(psMD, psValueAsXML);
290
0
            }
291
0
        }
292
293
173k
        if (STARTS_WITH_CI(pszDomainName, "json:") && CSLCount(papszMD) == 1)
294
0
        {
295
0
            bFormatXMLOrJSon = true;
296
297
0
            CPLCreateXMLNode(CPLCreateXMLNode(psMD, CXT_Attribute, "format"),
298
0
                             CXT_Text, "json");
299
0
            CPLCreateXMLNode(psMD, CXT_Text, *papszMD);
300
0
        }
301
302
173k
        if (!bFormatXMLOrJSon)
303
173k
        {
304
173k
            CPLXMLNode *psLastChild = nullptr;
305
            // To go after domain attribute.
306
173k
            if (psMD->psChild != nullptr)
307
89.6k
            {
308
89.6k
                psLastChild = psMD->psChild;
309
89.6k
                while (psLastChild->psNext != nullptr)
310
0
                    psLastChild = psLastChild->psNext;
311
89.6k
            }
312
645k
            for (int i = 0; papszMD[i] != nullptr; i++)
313
472k
            {
314
472k
                char *pszKey = nullptr;
315
316
472k
                const char *pszRawValue =
317
472k
                    CPLParseNameValue(papszMD[i], &pszKey);
318
319
472k
                CPLXMLNode *psMDI =
320
472k
                    CPLCreateXMLNode(nullptr, CXT_Element, "MDI");
321
472k
                if (psLastChild == nullptr)
322
83.3k
                    psMD->psChild = psMDI;
323
388k
                else
324
388k
                    psLastChild->psNext = psMDI;
325
472k
                psLastChild = psMDI;
326
327
472k
                CPLSetXMLValue(psMDI, "#key", pszKey);
328
472k
                CPLCreateXMLNode(psMDI, CXT_Text, pszRawValue);
329
330
472k
                CPLFree(pszKey);
331
472k
            }
332
173k
        }
333
334
173k
        if (psFirst == nullptr)
335
151k
            psFirst = psMD;
336
21.4k
        else
337
21.4k
            CPLAddXMLSibling(psFirst, psMD);
338
173k
    }
339
340
291k
    return psFirst;
341
291k
}
342
343
//! @endcond