Coverage Report

Created: 2026-02-14 06:52

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