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

Generated by: LCOV version 1.14