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

Generated by: LCOV version 1.14