LCOV - code coverage report
Current view: top level - gcore - gdaljp2metadatagenerator.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 198 213 93.0 %
Date: 2024-05-04 12:52:34 Functions: 15 15 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  GDALJP2Metadata: metadata generator
       5             :  * Author:   Even Rouault <even dot rouault at spatialys dot com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2015, European Union Satellite Centre
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "cpl_port.h"
      30             : #include "gdaljp2metadatagenerator.h"
      31             : 
      32             : #include <cstddef>
      33             : 
      34             : #ifdef HAVE_LIBXML2
      35             : 
      36             : #if defined(__GNUC__)
      37             : #pragma GCC diagnostic push
      38             : #pragma GCC diagnostic ignored "-Wold-style-cast"
      39             : #endif
      40             : 
      41             : #ifdef __clang__
      42             : #pragma clang diagnostic push
      43             : #pragma clang diagnostic ignored "-Wunknown-pragmas"
      44             : #pragma clang diagnostic ignored "-Wdocumentation"
      45             : #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
      46             : #endif
      47             : 
      48             : #include <libxml/parser.h>
      49             : #include <libxml/tree.h>
      50             : #include <libxml/xpath.h>
      51             : #include <libxml/xpathInternals.h>
      52             : 
      53             : #ifdef __clang__
      54             : #pragma clang diagnostic pop
      55             : #endif
      56             : 
      57             : #if defined(__GNUC__)
      58             : #pragma GCC diagnostic pop
      59             : #endif
      60             : 
      61             : // For CHECK_ARITY and clang 5
      62             : // CHECK_ARITY: check the number of args passed to an XPath function matches.
      63             : #undef NULL
      64             : #define NULL nullptr
      65             : 
      66             : // Simplified version of the macro proposed by libxml2
      67             : // The reason is when running against filegdbAPI which includes it own copy
      68             : // of libxml2, and the check 'ctxt->valueNr < ctxt->valueFrame + (x)'
      69             : // done by libxml2 CHECK_ARITY() thus points to garbage
      70             : #undef CHECK_ARITY
      71             : #define CHECK_ARITY(x)                                                         \
      72             :     if (ctxt == NULL)                                                          \
      73             :         return;                                                                \
      74             :     if (nargs != (x))                                                          \
      75             :         XP_ERROR(XPATH_INVALID_ARITY);
      76             : 
      77             : /************************************************************************/
      78             : /*                            GDALGMLJP2Expr                            */
      79             : /************************************************************************/
      80             : 
      81             : enum class GDALGMLJP2ExprType
      82             : {
      83             :     GDALGMLJP2Expr_Unknown,
      84             :     GDALGMLJP2Expr_XPATH,
      85             :     GDALGMLJP2Expr_STRING_LITERAL,
      86             : };
      87             : 
      88             : class GDALGMLJP2Expr
      89             : {
      90             :     static void SkipSpaces(const char *&pszStr);
      91             : 
      92             :   public:
      93             :     GDALGMLJP2ExprType eType = GDALGMLJP2ExprType::GDALGMLJP2Expr_Unknown;
      94             :     CPLString osValue{};
      95             : 
      96           8 :     GDALGMLJP2Expr() = default;
      97             : 
      98           2 :     explicit GDALGMLJP2Expr(const char *pszVal)
      99           2 :         : eType(GDALGMLJP2ExprType::GDALGMLJP2Expr_STRING_LITERAL),
     100           2 :           osValue(pszVal)
     101             :     {
     102           2 :     }
     103             : 
     104           5 :     explicit GDALGMLJP2Expr(const CPLString &osVal)
     105           5 :         : eType(GDALGMLJP2ExprType::GDALGMLJP2Expr_STRING_LITERAL),
     106           5 :           osValue(osVal)
     107             :     {
     108           5 :     }
     109             : 
     110          15 :     ~GDALGMLJP2Expr() = default;
     111             : 
     112             :     GDALGMLJP2Expr Evaluate(xmlXPathContextPtr pXPathCtx, xmlDocPtr pDoc);
     113             : 
     114             :     static GDALGMLJP2Expr *Build(const char *pszOriStr, const char *&pszStr);
     115             :     static void
     116             :     ReportError(const char *pszOriStr, const char *pszStr,
     117             :                 const char *pszIntroMessage = "Parsing error at:\n");
     118             : };
     119             : 
     120             : /************************************************************************/
     121             : /*                         ReportError()                                */
     122             : /************************************************************************/
     123             : 
     124           5 : void GDALGMLJP2Expr::ReportError(const char *pszOriStr, const char *pszStr,
     125             :                                  const char *pszIntroMessage)
     126             : {
     127           5 :     size_t nDist = static_cast<size_t>(pszStr - pszOriStr);
     128           5 :     if (nDist > 40)
     129           0 :         nDist = 40;
     130          10 :     CPLString osErrMsg(pszIntroMessage);
     131          15 :     CPLString osInvalidExpr = CPLString(pszStr - nDist).substr(0, nDist + 20);
     132          60 :     for (int i = static_cast<int>(nDist) - 1; i >= 0; --i)
     133             :     {
     134          55 :         if (osInvalidExpr[i] == '\n')
     135             :         {
     136           0 :             osInvalidExpr = osInvalidExpr.substr(i + 1);
     137           0 :             nDist -= i + 1;
     138           0 :             break;
     139             :         }
     140             :     }
     141          79 :     for (size_t i = nDist; i < osInvalidExpr.size(); ++i)
     142             :     {
     143          74 :         if (osInvalidExpr[i] == '\n')
     144             :         {
     145           0 :             osInvalidExpr.resize(i);
     146           0 :             break;
     147             :         }
     148             :     }
     149           5 :     osErrMsg += osInvalidExpr;
     150           5 :     osErrMsg += "\n";
     151          60 :     for (size_t i = 0; i < nDist; ++i)
     152          55 :         osErrMsg += " ";
     153           5 :     osErrMsg += "^";
     154           5 :     CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrMsg.c_str());
     155           5 : }
     156             : 
     157             : /************************************************************************/
     158             : /*                        SkipSpaces()                                  */
     159             : /************************************************************************/
     160             : 
     161          47 : void GDALGMLJP2Expr::SkipSpaces(const char *&pszStr)
     162             : {
     163          47 :     while (*pszStr == ' ' || *pszStr == '\t' || *pszStr == '\r' ||
     164          39 :            *pszStr == '\n')
     165           8 :         ++pszStr;
     166          39 : }
     167             : 
     168             : /************************************************************************/
     169             : /*                             Build()                                  */
     170             : /************************************************************************/
     171             : 
     172          24 : GDALGMLJP2Expr *GDALGMLJP2Expr::Build(const char *pszOriStr,
     173             :                                       const char *&pszStr)
     174             : {
     175          24 :     if (STARTS_WITH_CI(pszStr, "{{{"))
     176             :     {
     177          12 :         pszStr += strlen("{{{");
     178          12 :         SkipSpaces(pszStr);
     179          12 :         GDALGMLJP2Expr *poExpr = Build(pszOriStr, pszStr);
     180          12 :         if (poExpr == nullptr)
     181           4 :             return nullptr;
     182           8 :         SkipSpaces(pszStr);
     183           8 :         if (!STARTS_WITH_CI(pszStr, "}}}"))
     184             :         {
     185           1 :             ReportError(pszOriStr, pszStr);
     186           1 :             delete poExpr;
     187           1 :             return nullptr;
     188             :         }
     189           7 :         pszStr += strlen("}}}");
     190           7 :         return poExpr;
     191             :     }
     192          12 :     else if (STARTS_WITH_CI(pszStr, "XPATH"))
     193             :     {
     194          10 :         pszStr += strlen("XPATH");
     195          10 :         SkipSpaces(pszStr);
     196          10 :         if (*pszStr != '(')
     197             :         {
     198           1 :             ReportError(pszOriStr, pszStr);
     199           1 :             return nullptr;
     200             :         }
     201           9 :         ++pszStr;
     202           9 :         SkipSpaces(pszStr);
     203          18 :         CPLString l_osValue;
     204           9 :         int nParenthesisIndent = 0;
     205           9 :         char chLiteralQuote = '\0';
     206         138 :         while (*pszStr)
     207             :         {
     208         137 :             if (chLiteralQuote != '\0')
     209             :             {
     210          38 :                 if (*pszStr == chLiteralQuote)
     211           5 :                     chLiteralQuote = '\0';
     212          38 :                 l_osValue += *pszStr;
     213          38 :                 ++pszStr;
     214             :             }
     215          99 :             else if (*pszStr == '\'' || *pszStr == '"')
     216             :             {
     217           5 :                 chLiteralQuote = *pszStr;
     218           5 :                 l_osValue += *pszStr;
     219           5 :                 ++pszStr;
     220             :             }
     221          94 :             else if (*pszStr == '(')
     222             :             {
     223           7 :                 ++nParenthesisIndent;
     224           7 :                 l_osValue += *pszStr;
     225           7 :                 ++pszStr;
     226             :             }
     227          87 :             else if (*pszStr == ')')
     228             :             {
     229          15 :                 nParenthesisIndent--;
     230          15 :                 if (nParenthesisIndent < 0)
     231             :                 {
     232           8 :                     pszStr++;
     233           8 :                     GDALGMLJP2Expr *poExpr = new GDALGMLJP2Expr();
     234           8 :                     poExpr->eType = GDALGMLJP2ExprType::GDALGMLJP2Expr_XPATH;
     235           8 :                     poExpr->osValue = l_osValue;
     236             : #if DEBUG_VERBOSE
     237             :                     CPLDebug("GMLJP2", "XPath expression '%s'",
     238             :                              l_osValue.c_str());
     239             : #endif
     240           8 :                     return poExpr;
     241             :                 }
     242           7 :                 l_osValue += *pszStr;
     243           7 :                 ++pszStr;
     244             :             }
     245             :             else
     246             :             {
     247          72 :                 l_osValue += *pszStr;
     248          72 :                 pszStr++;
     249             :             }
     250             :         }
     251           1 :         ReportError(pszOriStr, pszStr);
     252           1 :         return nullptr;
     253             :     }
     254             :     else
     255             :     {
     256           2 :         ReportError(pszOriStr, pszStr);
     257           2 :         return nullptr;
     258             :     }
     259             : }
     260             : 
     261             : /************************************************************************/
     262             : /*                       GDALGMLJP2HexFormatter()                       */
     263             : /************************************************************************/
     264             : 
     265          16 : static const char *GDALGMLJP2HexFormatter(GByte nVal)
     266             : {
     267          16 :     return CPLSPrintf("%02X", nVal);
     268             : }
     269             : 
     270             : /************************************************************************/
     271             : /*                            Evaluate()                                */
     272             : /************************************************************************/
     273             : 
     274             : static CPLString GDALGMLJP2EvalExpr(const CPLString &osTemplate,
     275             :                                     xmlXPathContextPtr pXPathCtx,
     276             :                                     xmlDocPtr pDoc);
     277             : 
     278           7 : GDALGMLJP2Expr GDALGMLJP2Expr::Evaluate(xmlXPathContextPtr pXPathCtx,
     279             :                                         xmlDocPtr pDoc)
     280             : {
     281           7 :     switch (eType)
     282             :     {
     283           7 :         case GDALGMLJP2ExprType::GDALGMLJP2Expr_XPATH:
     284             :         {
     285           7 :             xmlXPathObjectPtr pXPathObj = xmlXPathEvalExpression(
     286           7 :                 reinterpret_cast<const xmlChar *>(osValue.c_str()), pXPathCtx);
     287           7 :             if (pXPathObj == nullptr)
     288           2 :                 return GDALGMLJP2Expr("");
     289             : 
     290             :             // Add result of the evaluation.
     291          10 :             CPLString osXMLRes;
     292           5 :             if (pXPathObj->type == XPATH_STRING)
     293           2 :                 osXMLRes = reinterpret_cast<const char *>(pXPathObj->stringval);
     294           3 :             else if (pXPathObj->type == XPATH_BOOLEAN)
     295           1 :                 osXMLRes = pXPathObj->boolval ? "true" : "false";
     296           2 :             else if (pXPathObj->type == XPATH_NUMBER)
     297           1 :                 osXMLRes = CPLSPrintf("%.16g", pXPathObj->floatval);
     298           1 :             else if (pXPathObj->type == XPATH_NODESET)
     299             :             {
     300           1 :                 xmlNodeSetPtr pNodes = pXPathObj->nodesetval;
     301           1 :                 int nNodes = (pNodes) ? pNodes->nodeNr : 0;
     302           2 :                 for (int i = 0; i < nNodes; i++)
     303             :                 {
     304           1 :                     xmlNodePtr pCur = pNodes->nodeTab[i];
     305             : 
     306           1 :                     xmlBufferPtr pBuf = xmlBufferCreate();
     307           1 :                     xmlNodeDump(pBuf, pDoc, pCur, 2, 1);
     308             :                     osXMLRes +=
     309           1 :                         reinterpret_cast<const char *>(xmlBufferContent(pBuf));
     310           1 :                     xmlBufferFree(pBuf);
     311             :                 }
     312             :             }
     313             : 
     314           5 :             xmlXPathFreeObject(pXPathObj);
     315           5 :             return GDALGMLJP2Expr(osXMLRes);
     316             :         }
     317           0 :         default:
     318           0 :             CPLAssert(false);
     319             :             return GDALGMLJP2Expr("");
     320             :     }
     321             : }
     322             : 
     323             : /************************************************************************/
     324             : /*                        GDALGMLJP2EvalExpr()                          */
     325             : /************************************************************************/
     326             : 
     327           8 : static CPLString GDALGMLJP2EvalExpr(const CPLString &osTemplate,
     328             :                                     xmlXPathContextPtr pXPathCtx,
     329             :                                     xmlDocPtr pDoc)
     330             : {
     331           8 :     CPLString osXMLRes;
     332           8 :     size_t nPos = 0;
     333             :     while (true)
     334             :     {
     335             :         // Get next expression.
     336          15 :         size_t nStartPos = osTemplate.find("{{{", nPos);
     337          15 :         if (nStartPos == std::string::npos)
     338             :         {
     339             :             // Add terminating portion of the template.
     340           3 :             osXMLRes += osTemplate.substr(nPos);
     341           3 :             break;
     342             :         }
     343             : 
     344             :         // Add portion of template before the expression.
     345          12 :         osXMLRes += osTemplate.substr(nPos, nStartPos - nPos);
     346             : 
     347          12 :         const char *pszExpr = osTemplate.c_str() + nStartPos;
     348          12 :         GDALGMLJP2Expr *poExpr = GDALGMLJP2Expr::Build(pszExpr, pszExpr);
     349          12 :         if (poExpr == nullptr)
     350           5 :             break;
     351           7 :         nPos = static_cast<size_t>(pszExpr - osTemplate.c_str());
     352           7 :         osXMLRes += poExpr->Evaluate(pXPathCtx, pDoc).osValue;
     353           7 :         delete poExpr;
     354           7 :     }
     355           8 :     return osXMLRes;
     356             : }
     357             : 
     358             : /************************************************************************/
     359             : /*                      GDALGMLJP2XPathErrorHandler()                   */
     360             : /************************************************************************/
     361             : 
     362           2 : static void GDALGMLJP2XPathErrorHandler(void * /* userData */,
     363             : #if LIBXML_VERSION >= 21200
     364             :                                         const xmlError *error
     365             : #else
     366             :                                         xmlErrorPtr error
     367             : #endif
     368             : )
     369             : {
     370           2 :     if (error->domain == XML_FROM_XPATH && error->str1 != nullptr &&
     371           2 :         error->int1 < static_cast<int>(strlen(error->str1)))
     372             :     {
     373           0 :         GDALGMLJP2Expr::ReportError(error->str1, error->str1 + error->int1,
     374             :                                     "XPath error:\n");
     375             :     }
     376             :     else
     377             :     {
     378           2 :         CPLError(CE_Failure, CPLE_AppDefined, "An error occurred in libxml2");
     379             :     }
     380           2 : }
     381             : 
     382             : /************************************************************************/
     383             : /*                    GDALGMLJP2RegisterNamespaces()                    */
     384             : /************************************************************************/
     385             : 
     386          28 : static void GDALGMLJP2RegisterNamespaces(xmlXPathContextPtr pXPathCtx,
     387             :                                          xmlNode *pNode)
     388             : {
     389          28 :     for (; pNode; pNode = pNode->next)
     390             :     {
     391          10 :         if (pNode->type == XML_ELEMENT_NODE)
     392             :         {
     393           9 :             if (pNode->ns != nullptr && pNode->ns->prefix != nullptr)
     394             :             {
     395             :                 // printf("%s %s\n",pNode->ns->prefix, pNode->ns->href);
     396           0 :                 if (xmlXPathRegisterNs(pXPathCtx, pNode->ns->prefix,
     397           0 :                                        pNode->ns->href) != 0)
     398             :                 {
     399           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     400             :                              "Registration of namespace %s failed",
     401           0 :                              reinterpret_cast<const char *>(pNode->ns->prefix));
     402             :                 }
     403             :             }
     404             :         }
     405             : 
     406          10 :         GDALGMLJP2RegisterNamespaces(pXPathCtx, pNode->children);
     407             :     }
     408          18 : }
     409             : 
     410             : /************************************************************************/
     411             : /*                         GDALGMLJP2XPathIf()                          */
     412             : /************************************************************************/
     413             : 
     414           2 : static void GDALGMLJP2XPathIf(xmlXPathParserContextPtr ctxt, int nargs)
     415             : {
     416             :     xmlXPathObjectPtr cond_val, then_val, else_val;
     417             : 
     418           2 :     CHECK_ARITY(3);
     419           2 :     else_val = valuePop(ctxt);
     420           2 :     then_val = valuePop(ctxt);
     421           2 :     CAST_TO_BOOLEAN
     422           2 :     cond_val = valuePop(ctxt);
     423             : 
     424           2 :     if (cond_val->boolval)
     425             :     {
     426           1 :         xmlXPathFreeObject(else_val);
     427           1 :         valuePush(ctxt, then_val);
     428             :     }
     429             :     else
     430             :     {
     431           1 :         xmlXPathFreeObject(then_val);
     432           1 :         valuePush(ctxt, else_val);
     433             :     }
     434           2 :     xmlXPathFreeObject(cond_val);
     435             : }
     436             : 
     437             : /************************************************************************/
     438             : /*                        GDALGMLJP2XPathUUID()                         */
     439             : /************************************************************************/
     440             : 
     441           1 : static void GDALGMLJP2XPathUUID(xmlXPathParserContextPtr ctxt, int nargs)
     442             : {
     443           1 :     CHECK_ARITY(0);
     444             : 
     445           2 :     CPLString osRet;
     446             :     static int nCounter = 0;
     447             :     // coverity[store_truncates_time_t]
     448           1 :     srand(static_cast<unsigned int>(time(nullptr)) + nCounter);
     449           1 :     ++nCounter;
     450           5 :     for (int i = 0; i < 4; i++)
     451           4 :         osRet += GDALGMLJP2HexFormatter(rand() & 0xFF);
     452           1 :     osRet += "-";
     453           1 :     osRet += GDALGMLJP2HexFormatter(rand() & 0xFF);
     454           1 :     osRet += GDALGMLJP2HexFormatter(rand() & 0xFF);
     455           1 :     osRet += "-";
     456             :     // Set the version number bits (4 == random).
     457           1 :     osRet += GDALGMLJP2HexFormatter((rand() & 0x0F) | 0x40);
     458           1 :     osRet += GDALGMLJP2HexFormatter(rand() & 0xFF);
     459           1 :     osRet += "-";
     460             :     // Set the variant bits.
     461           1 :     osRet += GDALGMLJP2HexFormatter((rand() & 0x3F) | 0x80);
     462           1 :     osRet += GDALGMLJP2HexFormatter(rand() & 0xFF);
     463           1 :     osRet += "-";
     464           7 :     for (int i = 0; i < 6; ++i)
     465             :     {
     466             :         // coverity[dont_call]
     467           6 :         osRet += GDALGMLJP2HexFormatter(rand() & 0xFF);
     468             :     }
     469             : 
     470           1 :     valuePush(ctxt, xmlXPathNewString(
     471           1 :                         reinterpret_cast<const xmlChar *>(osRet.c_str())));
     472             : }
     473             : 
     474             : #endif  // LIBXML2
     475             : 
     476             : /************************************************************************/
     477             : /*                      GDALGMLJP2GenerateMetadata()                    */
     478             : /************************************************************************/
     479             : 
     480             : #ifdef HAVE_LIBXML2
     481          11 : CPLXMLNode *GDALGMLJP2GenerateMetadata(const CPLString &osTemplateFile,
     482             :                                        const CPLString &osSourceFile)
     483             : {
     484          11 :     GByte *pabyStr = nullptr;
     485          11 :     if (!VSIIngestFile(nullptr, osTemplateFile, &pabyStr, nullptr, -1))
     486           1 :         return nullptr;
     487          20 :     CPLString osTemplate(reinterpret_cast<char *>(pabyStr));
     488          10 :     CPLFree(pabyStr);
     489             : 
     490          10 :     if (!VSIIngestFile(nullptr, osSourceFile, &pabyStr, nullptr, -1))
     491           1 :         return nullptr;
     492          18 :     CPLString osSource(reinterpret_cast<char *>(pabyStr));
     493           9 :     CPLFree(pabyStr);
     494             : 
     495             :     xmlDocPtr pDoc =
     496           9 :         xmlParseDoc(reinterpret_cast<const xmlChar *>(osSource.c_str()));
     497           9 :     if (pDoc == nullptr)
     498             :     {
     499           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse %s",
     500             :                  osSourceFile.c_str());
     501           1 :         return nullptr;
     502             :     }
     503             : 
     504           8 :     xmlXPathContextPtr pXPathCtx = xmlXPathNewContext(pDoc);
     505           8 :     if (pXPathCtx == nullptr)
     506             :     {
     507           0 :         xmlFreeDoc(pDoc);
     508           0 :         return nullptr;
     509             :     }
     510             : 
     511           8 :     xmlXPathRegisterFunc(pXPathCtx, reinterpret_cast<const xmlChar *>("if"),
     512             :                          GDALGMLJP2XPathIf);
     513           8 :     xmlXPathRegisterFunc(pXPathCtx, reinterpret_cast<const xmlChar *>("uuid"),
     514             :                          GDALGMLJP2XPathUUID);
     515             : 
     516           8 :     pXPathCtx->error = GDALGMLJP2XPathErrorHandler;
     517             : 
     518           8 :     GDALGMLJP2RegisterNamespaces(pXPathCtx, xmlDocGetRootElement(pDoc));
     519             : 
     520          16 :     CPLString osXMLRes = GDALGMLJP2EvalExpr(osTemplate, pXPathCtx, pDoc);
     521             : 
     522           8 :     xmlXPathFreeContext(pXPathCtx);
     523           8 :     xmlFreeDoc(pDoc);
     524             : 
     525           8 :     return CPLParseXMLString(osXMLRes);
     526             : }
     527             : #else   // !HAVE_LIBXML2
     528             : CPLXMLNode *GDALGMLJP2GenerateMetadata(const CPLString & /* osTemplateFile */,
     529             :                                        const CPLString & /* osSourceFile */
     530             : )
     531             : {
     532             :     return nullptr;
     533             : }
     534             : #endif  // HAVE_LIBXML2

Generated by: LCOV version 1.14