LCOV - code coverage report
Current view: top level - gcore - gdalmultidomainmetadata.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 117 122 95.9 %
Date: 2026-01-25 22:10:54 Functions: 7 8 87.5 %

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

Generated by: LCOV version 1.14