LCOV - code coverage report
Current view: top level - frmts/miramon - miramon_rel.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 423 499 84.8 %
Date: 2026-02-12 23:49:34 Functions: 27 27 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  MiraMonRaster driver
       4             :  * Purpose:  Implements MMRRel: provides access to the REL file, which
       5             :  *           holds all the necessary metadata to correctly interpret and
       6             :  *           access the associated raw data.
       7             :  * Author:   Abel Pau
       8             :  *
       9             :  ******************************************************************************
      10             :  * Copyright (c) 2025, Xavier Pons
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "cpl_port.h"
      16             : #include "gdal_priv.h"
      17             : #include "cpl_string.h"
      18             : #include <set>
      19             : 
      20             : #include "miramon_rel.h"
      21             : #include "miramon_band.h"
      22             : 
      23             : #include "../miramon_common/mm_gdal_functions.h"  // For MMCheck_REL_FILE()
      24             : 
      25             : CPLString MMRRel::m_szImprobableRELChain = "@#&%$|``|$%&#@";
      26             : 
      27             : /************************************************************************/
      28             : /*                               MMRRel()                               */
      29             : /************************************************************************/
      30         247 : MMRRel::MMRRel(const CPLString &osRELFilenameIn, bool bIMGMustExist)
      31         247 :     : m_osRelFileName(osRELFilenameIn)
      32             : {
      33         494 :     CPLString osRelCandidate = osRELFilenameIn;
      34             : 
      35             :     // Getting the name of the REL
      36         494 :     const CPLString osMMRPrefix = "MiraMonRaster:";
      37         247 :     if (STARTS_WITH(osRelCandidate, osMMRPrefix))
      38             :     {
      39             :         // SUBDATASET case: gets the names of the bands in the subdataset
      40           6 :         size_t nPos = osRelCandidate.ifind(osMMRPrefix);
      41           6 :         if (nPos != 0)
      42           0 :             return;
      43             : 
      44           6 :         CPLString osSDSReL = osRelCandidate.substr(osMMRPrefix.size());
      45             : 
      46             :         // Getting the internal names of the bands
      47           6 :         const CPLStringList aosTokens(CSLTokenizeString2(osSDSReL, ",", 0));
      48           6 :         const int nTokens = CSLCount(aosTokens);
      49             : 
      50           6 :         if (nTokens < 1)
      51           0 :             return;
      52             : 
      53           6 :         osRelCandidate = aosTokens[0];
      54           6 :         osRelCandidate.replaceAll("\"", "");
      55             : 
      56             :         // Getting the list of bands in the subdataset
      57          12 :         for (int nIBand = 0; nIBand < nTokens - 1; nIBand++)
      58             :         {
      59             :             // Raw band name
      60          12 :             CPLString osBandName = aosTokens[nIBand + 1];
      61           6 :             osBandName.replaceAll("\"", "");
      62           6 :             m_papoSDSBands.emplace_back(osBandName);
      63             :         }
      64           6 :         m_bIsAMiraMonFile = true;
      65             :     }
      66             :     else
      67             :     {
      68             :         // Getting the metadata file name. If it's already a REL file,
      69             :         // then same name is returned.
      70         241 :         osRelCandidate = GetAssociatedMetadataFileName(m_osRelFileName.c_str());
      71         241 :         if (osRelCandidate.empty())
      72             :         {
      73         170 :             if (m_bIsAMiraMonFile)
      74             :             {
      75           0 :                 CPLError(CE_Failure, CPLE_OpenFailed,
      76             :                          "Metadata file for %s should exist.",
      77             :                          m_osRelFileName.c_str());
      78             :             }
      79         170 :             if (!bIMGMustExist)
      80             :             {
      81             :                 // Simulates that we have a MiraMon file
      82             :                 // and we can ask things to this Rel file.
      83           4 :                 UpdateRELNameChar(m_osRelFileName);
      84           4 :                 m_bIsAMiraMonFile = true;
      85           4 :                 if (!OpenRELFile("rb"))
      86           0 :                     return;
      87             :             }
      88         170 :             return;
      89             :         }
      90             :         else
      91             :         {
      92             :             // It's a REL and it's not empty, so it's a MiraMon file
      93          71 :             VSILFILE *pF = VSIFOpenL(osRelCandidate, "r");
      94          71 :             if (!pF)
      95             :             {
      96           0 :                 CPLError(CE_Failure, CPLE_OpenFailed,
      97             :                          "Metadata file %s could not be opened.",
      98             :                          m_osRelFileName.c_str());
      99           0 :                 return;
     100             :             }
     101          71 :             VSIFSeekL(pF, 0, SEEK_END);
     102          71 :             if (VSIFTellL(pF))
     103          71 :                 m_bIsAMiraMonFile = true;
     104             :             else
     105             :             {
     106           0 :                 CPLError(
     107             :                     CE_Failure, CPLE_OpenFailed,
     108             :                     "Metadata file for %s should have some information in.",
     109             :                     m_osRelFileName.c_str());
     110             : 
     111           0 :                 VSIFCloseL(pF);
     112           0 :                 return;
     113             :             }
     114          71 :             VSIFCloseL(pF);
     115             :         }
     116             :     }
     117             : 
     118             :     // If rel name was not a REL name, we update that
     119             :     // from the one found in the process of discovering it.
     120          77 :     UpdateRELNameChar(osRelCandidate);
     121             : 
     122             :     // We let it be opened
     123          77 :     if (!OpenRELFile("rb"))
     124           0 :         return;
     125             : 
     126             :     // Collect band information
     127          77 :     if (ParseBandInfo() != CE_None)
     128           8 :         return;
     129             : 
     130             :     // We have a valid object MMRREL.
     131          69 :     m_bIsValid = true;
     132             : 
     133          69 :     return;
     134             : }
     135             : 
     136             : /************************************************************************/
     137             : /*                              ~MMRRel()                               */
     138             : /************************************************************************/
     139             : 
     140         247 : MMRRel::~MMRRel()
     141             : {
     142         247 :     CloseRELFile();
     143         247 : }
     144             : 
     145             : /************************************************************************/
     146             : /*                      Getting section-key-value                       */
     147             : /************************************************************************/
     148             : // Used when the MMRREL is not yet constructed.
     149             : CPLString
     150         247 : MMRRel::GetValueFromSectionKeyPriorToREL(const CPLString &osPriorRelName,
     151             :                                          const CPLString &osSection,
     152             :                                          const CPLString &osKey)
     153             : {
     154         247 :     if (osPriorRelName.empty())
     155           0 :         return "";
     156             : 
     157         247 :     VSILFILE *pPriorRELFile = VSIFOpenL(osPriorRelName, "rb");
     158         247 :     if (!pPriorRELFile)
     159           0 :         return "";
     160             : 
     161         494 :     CPLString osValue = GetValueFromSectionKey(pPriorRELFile, osSection, osKey);
     162         247 :     VSIFCloseL(pPriorRELFile);
     163         247 :     return osValue;
     164             : }
     165             : 
     166             : // Used when the MMRREL is already constructed.
     167        5049 : CPLString MMRRel::GetValueFromSectionKeyFromREL(const CPLString &osSection,
     168             :                                                 const CPLString &osKey)
     169             : {
     170        5049 :     if (!GetRELFile())
     171             :     {
     172           0 :         CPLError(CE_Failure, CPLE_AppDefined, "REL file is not opened: \"%s\"",
     173             :                  m_osRelFileName.c_str());
     174           0 :         return "";
     175             :     }
     176             : 
     177        5049 :     return GetValueFromSectionKey(GetRELFile(), osSection, osKey);
     178             : }
     179             : 
     180             : // This function is the C++ equivalent of MMReturnValueFromSectionINIFile().
     181             : // It improves upon the original by using CPLString instead of raw char pointers,
     182             : // and by operating on an already opened file pointer rather than reopening the file
     183             : // on each invocation.
     184             : // MMReturnValueFromSectionINIFile() is retained in miramon_common because it is
     185             : // widely used by existing, already OGR tested code (and in the common code itself).
     186             : // At least in C++ code the modern version is used
     187        5296 : CPLString MMRRel::GetValueFromSectionKey(VSILFILE *pf,
     188             :                                          const CPLString &osSection,
     189             :                                          const CPLString &osKey)
     190             : {
     191        5296 :     if (!pf)
     192           0 :         return "";
     193             : 
     194       10592 :     CPLString osCurrentSection;
     195       10592 :     CPLString osCurrentKey, osCurrentValue;
     196        5296 :     bool bIAmInMySection = false;
     197             : 
     198             :     const char *pszLine;
     199             : 
     200        5296 :     VSIFSeekL(pf, 0, SEEK_SET);
     201      641802 :     while ((pszLine = CPLReadLine2L(pf, 10000, nullptr)) != nullptr)
     202             :     {
     203      640346 :         CPLString rawLine = pszLine;
     204             : 
     205      640346 :         rawLine.Recode(CPL_ENC_ISO8859_1, CPL_ENC_UTF8);
     206      640346 :         rawLine.Trim();
     207             : 
     208      640346 :         if (rawLine.empty() || rawLine[0] == ';' || rawLine[0] == '#')
     209      101067 :             continue;
     210             : 
     211      539279 :         if (rawLine[0] == '[' && rawLine[rawLine.size() - 1] == ']')
     212             :         {
     213      106287 :             if (bIAmInMySection)
     214             :             {
     215             :                 // This is the next section to mine, so nothing to find here.
     216        1438 :                 return m_szImprobableRELChain;
     217             :             }
     218             : 
     219      104849 :             osCurrentSection = rawLine.substr(1, rawLine.size() - 2);
     220      104849 :             osCurrentSection.Trim();
     221             : 
     222      104849 :             if (!EQUAL(osCurrentSection, osSection))
     223      100699 :                 bIAmInMySection = false;
     224             :             else
     225        4150 :                 bIAmInMySection = true;
     226             : 
     227      104849 :             continue;
     228             :         }
     229             : 
     230      432992 :         if (!bIAmInMySection)
     231      414466 :             continue;
     232             : 
     233       18526 :         size_t equalPos = rawLine.find('=');
     234       18526 :         if (equalPos != CPLString::npos)
     235             :         {
     236       18526 :             osCurrentKey = rawLine.substr(0, equalPos);
     237       18526 :             osCurrentValue = rawLine.substr(equalPos + 1);
     238       18526 :             osCurrentKey.Trim();
     239       18526 :             osCurrentValue.Trim();
     240             : 
     241       18526 :             if (EQUAL(osCurrentKey, osKey))
     242        2402 :                 return osCurrentValue;
     243             :         }
     244             :     }
     245             : 
     246        1456 :     return m_szImprobableRELChain;  // Key not found
     247             : }
     248             : 
     249             : /************************************************************************/
     250             : /*                           Other functions                            */
     251             : /************************************************************************/
     252             : 
     253             : // Converts FileNameI.rel to FileName
     254         130 : CPLString MMRRel::MMRGetFileNameWithOutI(const CPLString &osRELFile)
     255             : {
     256         130 :     if (osRELFile.empty())
     257           0 :         return "";
     258             : 
     259         260 :     CPLString osFile = CPLString(CPLResetExtensionSafe(osRELFile, "").c_str());
     260             : 
     261         130 :     if (osFile.length() < 2)
     262           0 :         return "";
     263             : 
     264         130 :     osFile.resize(osFile.size() - 2);  // I.
     265             : 
     266         130 :     return osFile;
     267             : }
     268             : 
     269             : // Converts FileNameI.rel to FileName.xxx (where xxx is an extension)
     270         130 : CPLString MMRRel::MMRGetFileNameFromRelName(const CPLString &osRELFile,
     271             :                                             const CPLString osExtension)
     272             : {
     273         130 :     if (osRELFile.empty())
     274           0 :         return "";
     275             : 
     276             :     // Extracts I.rel
     277             :     CPLString osFile =
     278         390 :         MMRGetFileNameWithOutI(CPLResetExtensionSafe(osRELFile, ""));
     279             : 
     280         130 :     if (!osExtension.empty())
     281             :     {
     282             :         // Adds extension (with the ".", ex: ".img")
     283         130 :         osFile += osExtension;
     284             :     }
     285             : 
     286         130 :     return osFile;
     287             : }
     288             : 
     289             : // Converts FileName.img to FileNameI.rel
     290         183 : CPLString MMRRel::MMRGetSimpleMetadataName(const CPLString &osLayerName)
     291             : {
     292         183 :     if (osLayerName.empty())
     293           0 :         return "";
     294             : 
     295             :     // Extract extension
     296             :     CPLString osRELFile =
     297         366 :         CPLString(CPLResetExtensionSafe(osLayerName, "").c_str());
     298             : 
     299         183 :     if (!osRELFile.length())
     300           0 :         return "";
     301             : 
     302             :     // Extract "."
     303         183 :     osRELFile.resize(osRELFile.size() - 1);
     304             :     // Add "I.rel"
     305         183 :     osRELFile += pszExtRasterREL;
     306             : 
     307         183 :     return osRELFile;
     308             : }
     309             : 
     310             : // Gets the value from a section-key accessing directly to the RELFile.
     311             : // It happens when MMRel is used to access a REL that is not an IMG sidecar
     312             : // or at the Identify() process, when we don't have already the MMRRel constructed.
     313         187 : bool MMRRel::GetAndExcludeMetadataValueDirectly(const CPLString &osRELFile,
     314             :                                                 const CPLString &osSection,
     315             :                                                 const CPLString &osKey,
     316             :                                                 CPLString &osValue)
     317             : {
     318         187 :     addExcludedSectionKey(osSection, osKey);
     319         187 :     return GetMetadataValueDirectly(osRELFile, osSection, osKey, osValue);
     320             : }
     321             : 
     322         247 : bool MMRRel::GetMetadataValueDirectly(const CPLString &osRELFile,
     323             :                                       const CPLString &osSection,
     324             :                                       const CPLString &osKey,
     325             :                                       CPLString &osValue)
     326             : {
     327         247 :     osValue = GetValueFromSectionKeyPriorToREL(osRELFile, osSection, osKey);
     328             : 
     329         247 :     if (osValue != m_szImprobableRELChain)
     330         130 :         return true;  // Found
     331             : 
     332         117 :     osValue = "";
     333         117 :     return false;  // Key not found
     334             : }
     335             : 
     336          81 : bool MMRRel::SameFile(const CPLString &osFile1, const CPLString &osFile2)
     337             : {
     338          81 :     if (EQUAL(osFile1, osFile2))
     339          17 :         return true;
     340             : 
     341             :     // Just to be more sure:
     342         128 :     CPLString osLayerName1 = osFile1;
     343          64 :     osLayerName1.replaceAll("\\", "/");
     344         128 :     CPLString osLayerName2 = osFile2;
     345          64 :     osLayerName2.replaceAll("\\", "/");
     346             : 
     347          64 :     if (EQUAL(osLayerName1, osLayerName2))
     348           0 :         return true;
     349             : 
     350          64 :     return false;
     351             : }
     352             : 
     353             : // Gets the state (enum class MMRNomFitxerState) of NomFitxer in the
     354             : // specified section
     355             : // [pszSection]
     356             : // NomFitxer=Value
     357          81 : MMRNomFitxerState MMRRel::MMRStateOfNomFitxerInSection(
     358             :     const CPLString &osLayerName, const CPLString &osSection,
     359             :     const CPLString &osRELFile, bool bNomFitxerMustExist)
     360             : {
     361         162 :     CPLString osDocumentedLayerName;
     362             : 
     363          81 :     if (!GetAndExcludeMetadataValueDirectly(osRELFile, osSection, KEY_NomFitxer,
     364          90 :                                             osDocumentedLayerName) ||
     365           9 :         osDocumentedLayerName.empty())
     366             :     {
     367             :         CPLString osIIMGFromREL =
     368         144 :             MMRGetFileNameFromRelName(osRELFile, pszExtRaster);
     369          72 :         if (SameFile(osIIMGFromREL, osLayerName))
     370          13 :             return MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED;
     371             : 
     372          59 :         if (bNomFitxerMustExist)
     373          22 :             return MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED;
     374             :         else
     375          37 :             return MMRNomFitxerState::NOMFITXER_NOT_FOUND;
     376             :     }
     377             : 
     378          18 :     CPLString osFileAux = CPLFormFilenameSafe(CPLGetPathSafe(osRELFile).c_str(),
     379          18 :                                               osDocumentedLayerName, "");
     380             : 
     381           9 :     osDocumentedLayerName.Trim();
     382           9 :     if (*osDocumentedLayerName == '*' || *osDocumentedLayerName == '?')
     383           0 :         return MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED;
     384             : 
     385           9 :     if (SameFile(osFileAux, osLayerName))
     386           4 :         return MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED;
     387             : 
     388           5 :     return MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED;
     389             : }
     390             : 
     391             : // Tries to find a reference to the IMG file 'pszLayerName'
     392             : // we are opening in the REL file 'pszRELFile'
     393          51 : CPLString MMRRel::MMRGetAReferenceToIMGFile(const CPLString &osLayerName,
     394             :                                             const CPLString &osRELFile)
     395             : {
     396          51 :     if (osRELFile.empty())
     397             :     {
     398           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "Expected File name.");
     399           0 :         return "";
     400             :     }
     401             : 
     402             :     // [ATTRIBUTE_DATA]
     403             :     // NomFitxer=
     404             :     // It should be empty but if it's not, at least,
     405             :     // the value has to be osLayerName
     406          51 :     MMRNomFitxerState iState = MMRStateOfNomFitxerInSection(
     407             :         osLayerName, SECTION_ATTRIBUTE_DATA, osRELFile, false);
     408             : 
     409          51 :     if (iState == MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED ||
     410             :         iState == MMRNomFitxerState::NOMFITXER_VALUE_EMPTY)
     411             :     {
     412          14 :         return osRELFile;
     413             :     }
     414          37 :     else if (iState == MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED)
     415             :     {
     416           0 :         if (m_bIsAMiraMonFile)
     417             :         {
     418           0 :             CPLError(
     419             :                 CE_Failure, CPLE_OpenFailed,
     420             :                 "Unexpected value for SECTION_ATTRIBUTE_DATA [NomFitxer] in "
     421             :                 "%s file.",
     422             :                 osRELFile.c_str());
     423             :         }
     424           0 :         return "";
     425             :     }
     426             : 
     427             :     // Discarting not supported via SDE (some files
     428             :     // could have this option)
     429          74 :     CPLString osVia;
     430          37 :     if (GetAndExcludeMetadataValueDirectly(osRELFile, SECTION_ATTRIBUTE_DATA,
     431             :                                            KEY_via, osVia))
     432             :     {
     433           0 :         if (!osVia.empty() && !EQUAL(osVia, "SDE"))
     434             :         {
     435           0 :             if (m_bIsAMiraMonFile)
     436             :             {
     437           0 :                 CPLError(CE_Failure, CPLE_OpenFailed,
     438             :                          "Unexpected Via in %s file", osRELFile.c_str());
     439             :             }
     440           0 :             return "";
     441             :         }
     442             :     }
     443             : 
     444          74 :     CPLString osFieldNames;
     445             : 
     446          37 :     if (!GetAndExcludeMetadataValueDirectly(osRELFile, SECTION_ATTRIBUTE_DATA,
     447             :                                             Key_IndexesNomsCamps,
     448          68 :                                             osFieldNames) ||
     449          31 :         osFieldNames.empty())
     450             :     {
     451           8 :         if (m_bIsAMiraMonFile)
     452             :         {
     453           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     454             :                      "IndexesNomsCamps not found in %s file",
     455             :                      osRELFile.c_str());
     456             :         }
     457           8 :         return "";
     458             :     }
     459             : 
     460             :     // Getting the internal names of the bands
     461          58 :     const CPLStringList aosTokens(CSLTokenizeString2(osFieldNames, ",", 0));
     462          29 :     const int nTokenBands = CSLCount(aosTokens);
     463             : 
     464          58 :     CPLString osBandSectionKey;
     465          58 :     CPLString osAttributeDataName;
     466          58 :     for (int nIBand = 0; nIBand < nTokenBands; nIBand++)
     467             :     {
     468          32 :         osBandSectionKey = KEY_NomCamp;
     469          32 :         osBandSectionKey.append("_");
     470          32 :         osBandSectionKey.append(aosTokens[nIBand]);
     471             : 
     472          32 :         CPLString osBandSectionValue;
     473             : 
     474          32 :         if (!GetAndExcludeMetadataValueDirectly(
     475             :                 osRELFile, SECTION_ATTRIBUTE_DATA, osBandSectionKey,
     476          62 :                 osBandSectionValue) ||
     477          30 :             osBandSectionValue.empty())
     478           2 :             continue;  // A band without name (·$· unexpected)
     479             : 
     480             :         // Example: [ATTRIBUTE_DATA:G1]
     481          30 :         osAttributeDataName = SECTION_ATTRIBUTE_DATA;
     482          30 :         osAttributeDataName.append(":");
     483          30 :         osAttributeDataName.append(osBandSectionValue.Trim());
     484             : 
     485             :         // Let's see if this band contains the expected name
     486             :         // or none (in monoband case)
     487          30 :         iState = MMRStateOfNomFitxerInSection(osLayerName, osAttributeDataName,
     488             :                                               osRELFile, true);
     489          30 :         if (iState == MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED)
     490           3 :             return osRELFile;
     491             : 
     492          27 :         else if (iState == MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED)
     493          27 :             continue;
     494             : 
     495             :         // If there is only one band is accepted NOMFITXER_NOT_FOUND/EMPTY iState result
     496           0 :         if (nTokenBands == 1)
     497           0 :             return osRELFile;
     498             :     }
     499             : 
     500          26 :     if (m_bIsAMiraMonFile)
     501             :     {
     502           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     503             :                  "REL search failed for all bands in %s file",
     504             :                  osRELFile.c_str());
     505             :     }
     506          26 :     return "";
     507             : }
     508             : 
     509             : // Finds the metadata filename associated to osFileName (usually an IMG file)
     510         241 : CPLString MMRRel::GetAssociatedMetadataFileName(const CPLString &osFileName)
     511             : {
     512         241 :     if (osFileName.empty())
     513             :     {
     514           0 :         if (m_bIsAMiraMonFile)
     515           0 :             CPLError(CE_Failure, CPLE_OpenFailed, "Expected File name.");
     516           0 :         return "";
     517             :     }
     518             : 
     519             :     // If the string finishes in "I.rel" we consider it can be
     520             :     // the associated file to all bands that are documented in this file.
     521         241 :     if (cpl::ends_with(osFileName, pszExtRasterREL))
     522             :     {
     523          54 :         m_bIsAMiraMonFile = true;
     524          54 :         return osFileName;
     525             :     }
     526             : 
     527             :     // If the file is not a REL file, let's try to find the associated REL
     528             :     // It must be a IMG file.
     529         374 :     CPLString osExtension = CPLString(CPLGetExtensionSafe(osFileName).c_str());
     530         187 :     if (!EQUAL(osExtension, pszExtRaster + 1))
     531           4 :         return "";
     532             : 
     533             :     // Converting FileName.img to FileNameI.rel
     534         366 :     CPLString osRELFile = MMRGetSimpleMetadataName(osFileName);
     535         183 :     if (osRELFile.empty())
     536             :     {
     537           0 :         if (m_bIsAMiraMonFile)
     538             :         {
     539           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     540             :                      "Failing in conversion from .img to I.rel for %s file",
     541             :                      osFileName.c_str());
     542             :         }
     543           0 :         return "";
     544             :     }
     545             : 
     546             :     // Checking if the file exists
     547             :     VSIStatBufL sStat;
     548         183 :     if (VSIStatExL(osRELFile.c_str(), &sStat, VSI_STAT_EXISTS_FLAG) == 0)
     549          14 :         return MMRGetAReferenceToIMGFile(osFileName, osRELFile);
     550             : 
     551             :     // If the file I.rel doesn't exist then it has to be found
     552             :     // in the same folder than the .img file.
     553         338 :     const CPLString osPath = CPLGetPathSafe(osFileName);
     554         338 :     const CPLStringList folder(VSIReadDir(osPath.c_str()));
     555         169 :     const int size = folder.size();
     556             : 
     557        1247 :     for (int nIFile = 0; nIFile < size; nIFile++)
     558             :     {
     559        1081 :         if (folder[nIFile][0] == '.' || !strstr(folder[nIFile], "I.rel"))
     560             :         {
     561        1044 :             continue;
     562             :         }
     563             : 
     564             :         const CPLString osFilePath =
     565          37 :             CPLFormFilenameSafe(osPath, folder[nIFile], nullptr);
     566             : 
     567          37 :         osRELFile = MMRGetAReferenceToIMGFile(osFileName, osFilePath);
     568          37 :         if (!osRELFile.empty())
     569           3 :             return osRELFile;
     570             :     }
     571             : 
     572         166 :     if (m_bIsAMiraMonFile)
     573             :     {
     574           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "REL search failed for %s file",
     575             :                  osFileName.c_str());
     576             :     }
     577             : 
     578         166 :     return "";
     579             : }
     580             : 
     581             : /************************************************************************/
     582             : /*                           CheckBandInRel()                           */
     583             : /************************************************************************/
     584          12 : CPLErr MMRRel::CheckBandInRel(const CPLString &osRELFileName,
     585             :                               const CPLString &osIMGFile)
     586             : 
     587             : {
     588          24 :     CPLString osFieldNames;
     589          12 :     if (!GetMetadataValueDirectly(osRELFileName, SECTION_ATTRIBUTE_DATA,
     590          24 :                                   Key_IndexesNomsCamps, osFieldNames) ||
     591          12 :         osFieldNames.empty())
     592           0 :         return CE_Failure;
     593             : 
     594             :     // Separator ,
     595          24 :     const CPLStringList aosTokens(CSLTokenizeString2(osFieldNames, ",", 0));
     596          12 :     const int nTokenCount = CSLCount(aosTokens);
     597             : 
     598          12 :     if (!nTokenCount)
     599           0 :         return CE_Failure;
     600             : 
     601          24 :     CPLString osBandSectionKey;
     602          24 :     CPLString osBandSectionValue;
     603          24 :     for (int nIBand = 0; nIBand < nTokenCount; nIBand++)
     604             :     {
     605          24 :         osBandSectionKey = KEY_NomCamp;
     606          24 :         osBandSectionKey.append("_");
     607          24 :         osBandSectionKey.append(aosTokens[nIBand]);
     608             : 
     609          24 :         if (!GetMetadataValueDirectly(osRELFileName, SECTION_ATTRIBUTE_DATA,
     610          48 :                                       osBandSectionKey, osBandSectionValue) ||
     611          24 :             osBandSectionValue.empty())
     612           0 :             return CE_Failure;
     613             : 
     614          24 :         CPLString osAttributeDataName;
     615          24 :         osAttributeDataName = SECTION_ATTRIBUTE_DATA;
     616          24 :         osAttributeDataName.append(":");
     617          24 :         osAttributeDataName.append(osBandSectionValue.Trim());
     618             : 
     619          24 :         CPLString osRawBandFileName;
     620             : 
     621          24 :         if (!GetMetadataValueDirectly(osRELFileName, osAttributeDataName,
     622          48 :                                       KEY_NomFitxer, osRawBandFileName) ||
     623          24 :             osRawBandFileName.empty())
     624             :         {
     625             :             CPLString osBandFileName =
     626           0 :                 MMRGetFileNameFromRelName(osRELFileName, pszExtRaster);
     627           0 :             if (osBandFileName.empty())
     628           0 :                 return CE_Failure;
     629             :         }
     630             :         else
     631             :         {
     632          24 :             if (!EQUAL(osRawBandFileName, osIMGFile))
     633          12 :                 continue;
     634          12 :             break;  // Found
     635             :         }
     636             :     }
     637             : 
     638          12 :     return CE_None;
     639             : }
     640             : 
     641       57983 : int MMRRel::IdentifySubdataSetFile(const CPLString &osFileName)
     642             : {
     643      115966 :     const CPLString osMMRPrefix = "MiraMonRaster:";
     644       57983 :     if (!STARTS_WITH(osFileName, osMMRPrefix))
     645       57971 :         return FALSE;
     646             : 
     647             :     // SUBDATASETS
     648          12 :     size_t nPos = osFileName.ifind(osMMRPrefix);
     649          12 :     if (nPos != 0)
     650           0 :         return GDAL_IDENTIFY_FALSE;
     651             : 
     652          24 :     CPLString osRELAndBandName = osFileName.substr(osMMRPrefix.size());
     653             : 
     654          24 :     const CPLStringList aosTokens(CSLTokenizeString2(osRELAndBandName, ",", 0));
     655          12 :     const int nTokens = CSLCount(aosTokens);
     656             :     // Getting the REL associated to the bands
     657             :     // We need the REL and at least one band (index + name).
     658          12 :     if (nTokens < 2)
     659           0 :         return GDAL_IDENTIFY_FALSE;
     660             : 
     661             :     // Let's remove "\"" if existent.
     662          24 :     CPLString osRELName = aosTokens[0];
     663          12 :     osRELName.replaceAll("\"", "");
     664             : 
     665             :     // It must be a I.rel file.
     666          12 :     if (!cpl::ends_with(osRELName, pszExtRasterREL))
     667           0 :         return GDAL_IDENTIFY_FALSE;
     668             : 
     669          12 :     if (MMCheck_REL_FILE(osRELName))
     670           0 :         return GDAL_IDENTIFY_FALSE;
     671             : 
     672             :     // Let's see if the specified bands are in the REL file
     673             :     // Getting the index + internal names of the bands
     674          24 :     for (int nIBand = 1; nIBand < nTokens; nIBand++)
     675             :     {
     676             :         // Let's check that this band (papszTokens[nIBand]) is in the REL file.
     677          12 :         CPLString osBandName = aosTokens[nIBand];
     678             : 
     679             :         // Let's remove "\"" if existent.
     680          12 :         osBandName.replaceAll("\"", "");
     681             : 
     682             :         // If it's not an IMG file return FALSE
     683             :         CPLString osExtension =
     684          12 :             CPLString(CPLGetExtensionSafe(osBandName).c_str());
     685          12 :         if (!EQUAL(osExtension, pszExtRaster + 1))
     686           0 :             return GDAL_IDENTIFY_FALSE;
     687             : 
     688          12 :         if (CE_None != CheckBandInRel(osRELName, osBandName))
     689           0 :             return GDAL_IDENTIFY_FALSE;
     690             :     }
     691          12 :     return GDAL_IDENTIFY_TRUE;
     692             : }
     693             : 
     694       57971 : int MMRRel::IdentifyFile(const GDALOpenInfo *poOpenInfo)
     695             : {
     696             :     // IMG files are shared for many drivers.
     697             :     // Identify will mark it as unknown.
     698             :     // Open function will try to open that, but as it has computation
     699             :     // cost is better avoid doing it here.
     700       57971 :     if (poOpenInfo->IsExtensionEqualToCI("IMG"))
     701         481 :         return GDAL_IDENTIFY_UNKNOWN;
     702             : 
     703       57490 :     if (!poOpenInfo->IsExtensionEqualToCI("REL"))
     704       57378 :         return GDAL_IDENTIFY_FALSE;
     705             : 
     706             :     // In fact, the file has to end with I.rel (pszExtRasterREL)
     707         112 :     if (!cpl::ends_with(std::string_view(poOpenInfo->pszFilename),
     708             :                         pszExtRasterREL))
     709           2 :         return GDAL_IDENTIFY_FALSE;
     710             : 
     711             :     // Some versions of REL files are not allowed.
     712         110 :     if (MMCheck_REL_FILE(poOpenInfo->pszFilename))
     713           2 :         return GDAL_IDENTIFY_FALSE;
     714             : 
     715         108 :     return GDAL_IDENTIFY_TRUE;
     716             : }
     717             : 
     718             : /************************************************************************/
     719             : /*                          GetMetadataValue()                          */
     720             : /************************************************************************/
     721         412 : bool MMRRel::GetMetadataValue(const CPLString &osMainSection,
     722             :                               const CPLString &osSubSection,
     723             :                               const CPLString &osSubSubSection,
     724             :                               const CPLString &osKey, CPLString &osValue)
     725             : {
     726         412 :     CPLAssert(
     727             :         isAMiraMonFile());  // Trying to access metadata from the wrong way
     728             : 
     729             :     // Searches in [pszMainSection:pszSubSection]
     730         824 :     CPLString osAttributeDataName;
     731         412 :     osAttributeDataName = osMainSection;
     732         412 :     osAttributeDataName.append(":");
     733         412 :     osAttributeDataName.append(osSubSection);
     734         412 :     osAttributeDataName.append(":");
     735         412 :     osAttributeDataName.append(osSubSubSection);
     736             : 
     737         412 :     addExcludedSectionKey(osAttributeDataName, osKey);
     738         412 :     osValue = GetValueFromSectionKeyFromREL(osAttributeDataName, osKey);
     739         412 :     if (osValue != m_szImprobableRELChain)
     740          14 :         return true;  // Found
     741             : 
     742             :     // If the value is not found then searches in [pszMainSection]
     743         398 :     addExcludedSectionKey(osSubSubSection, osKey);
     744         398 :     osValue = GetValueFromSectionKeyFromREL(osSubSubSection, osKey);
     745         398 :     if (osValue == m_szImprobableRELChain)
     746             :     {
     747          40 :         osValue = "";
     748          40 :         return false;  // Key not found
     749             :     }
     750         358 :     return true;  // Found
     751             : }
     752             : 
     753        1836 : bool MMRRel::GetMetadataValue(const CPLString &osMainSection,
     754             :                               const CPLString &osSubSection,
     755             :                               const CPLString &osKey, CPLString &osValue)
     756             : {
     757        1836 :     CPLAssert(
     758             :         isAMiraMonFile());  // Trying to access metadata from the wrong way
     759             : 
     760             :     // Searches in [pszMainSection:pszSubSection]
     761        3672 :     CPLString osAttributeDataName;
     762        1836 :     osAttributeDataName = osMainSection;
     763        1836 :     osAttributeDataName.append(":");
     764        1836 :     osAttributeDataName.append(osSubSection);
     765             : 
     766        1836 :     addExcludedSectionKey(osAttributeDataName, osKey);
     767        1836 :     osValue = GetValueFromSectionKeyFromREL(osAttributeDataName, osKey);
     768        1836 :     if (osValue != m_szImprobableRELChain)
     769         577 :         return true;  // Found
     770             : 
     771             :     // If the value is not found then searches in [pszMainSection]
     772        1259 :     addExcludedSectionKey(osMainSection, osKey);
     773        1259 :     osValue = GetValueFromSectionKeyFromREL(osMainSection, osKey);
     774        1259 :     if (osValue == m_szImprobableRELChain)
     775             :     {
     776         849 :         osValue = "";
     777         849 :         return false;  // Key not found
     778             :     }
     779         410 :     return true;  // Found
     780             : }
     781             : 
     782        1144 : bool MMRRel::GetMetadataValue(const CPLString &osSection,
     783             :                               const CPLString &osKey, CPLString &osValue)
     784             : {
     785        1144 :     CPLAssert(
     786             :         isAMiraMonFile());  // Trying to access metadata from the wrong way
     787             : 
     788        1144 :     addExcludedSectionKey(osSection, osKey);
     789        1144 :     osValue = GetValueFromSectionKeyFromREL(osSection, osKey);
     790        1144 :     if (osValue == m_szImprobableRELChain)
     791             :     {
     792         231 :         osValue = "";
     793         231 :         return false;  // Key not found
     794             :     }
     795         913 :     return true;  // Found
     796             : }
     797             : 
     798          81 : void MMRRel::UpdateRELNameChar(const CPLString &osRelFileNameIn)
     799             : {
     800          81 :     m_osRelFileName = osRelFileNameIn;
     801          81 : }
     802             : 
     803             : /************************************************************************/
     804             : /*                           ParseBandInfo()                            */
     805             : /************************************************************************/
     806          77 : CPLErr MMRRel::ParseBandInfo()
     807             : {
     808          77 :     m_nBands = 0;
     809             : 
     810         154 :     CPLString osFieldNames;
     811          77 :     if (!GetMetadataValue(SECTION_ATTRIBUTE_DATA, Key_IndexesNomsCamps,
     812         153 :                           osFieldNames) ||
     813          76 :         osFieldNames.empty())
     814             :     {
     815           2 :         CPLError(CE_Failure, CPLE_AssertionFailed,
     816             :                  "%s-%s section-key should exist in %s.",
     817             :                  SECTION_ATTRIBUTE_DATA, Key_IndexesNomsCamps,
     818             :                  m_osRelFileName.c_str());
     819           2 :         return CE_Failure;
     820             :     }
     821             : 
     822             :     // Separator ,
     823         150 :     const CPLStringList aosTokens(CSLTokenizeString2(osFieldNames, ",", 0));
     824          75 :     const int nMaxBands = CSLCount(aosTokens);
     825             : 
     826          75 :     if (!nMaxBands)
     827             :     {
     828           0 :         CPLError(CE_Failure, CPLE_AssertionFailed, "No bands in file %s.",
     829             :                  m_osRelFileName.c_str());
     830           0 :         return CE_Failure;
     831             :     }
     832             : 
     833         150 :     CPLString osBandSectionKey;
     834         150 :     CPLString osBandSectionValue;
     835         150 :     std::set<std::string> setProcessedTokens;
     836             : 
     837             :     int nNBand;
     838          75 :     if (m_papoSDSBands.size())
     839           6 :         nNBand = static_cast<int>(m_papoSDSBands.size());
     840             :     else
     841          69 :         nNBand = nMaxBands;
     842             : 
     843          75 :     m_oBands.reserve(nNBand);
     844             : 
     845         202 :     for (int nIBand = 0; nIBand < nMaxBands; nIBand++)
     846             :     {
     847             :         const std::string lowerCaseToken =
     848         133 :             CPLString(aosTokens[nIBand]).tolower();
     849         133 :         if (cpl::contains(setProcessedTokens, lowerCaseToken))
     850           0 :             continue;  // Repeated bands are ignored.
     851             : 
     852         133 :         setProcessedTokens.insert(lowerCaseToken);
     853             : 
     854         133 :         osBandSectionKey = KEY_NomCamp;
     855         133 :         osBandSectionKey.append("_");
     856         133 :         osBandSectionKey.append(aosTokens[nIBand]);
     857             : 
     858         133 :         if (!GetMetadataValue(SECTION_ATTRIBUTE_DATA, osBandSectionKey,
     859         265 :                               osBandSectionValue) ||
     860         132 :             osBandSectionValue.empty())
     861           1 :             continue;
     862             : 
     863         132 :         if (m_papoSDSBands.size())
     864             :         {
     865          30 :             CPLString osRawBandFileName;
     866          30 :             if (!GetMetadataValue(SECTION_ATTRIBUTE_DATA, osBandSectionValue,
     867          60 :                                   KEY_NomFitxer, osRawBandFileName) ||
     868          30 :                 osRawBandFileName.empty())
     869           0 :                 return CE_Failure;
     870             : 
     871             :             // I'm in a Subataset
     872             :             size_t nISDSBand;
     873          54 :             for (nISDSBand = 0; nISDSBand < m_papoSDSBands.size(); nISDSBand++)
     874             :             {
     875          30 :                 if (m_papoSDSBands[nISDSBand] == osRawBandFileName)
     876           6 :                     break;
     877             :             }
     878          30 :             if (nISDSBand == m_papoSDSBands.size())
     879          24 :                 continue;
     880             :         }
     881             : 
     882         108 :         if (m_nBands >= nNBand)
     883           0 :             break;
     884             : 
     885             :         // MMRBand constructor is called
     886         108 :         m_oBands.emplace_back(*this, osBandSectionValue.Trim());
     887             : 
     888         108 :         if (!m_oBands[m_nBands].IsValid())
     889             :         {
     890             :             // This band is not been completed
     891           6 :             return CE_Failure;
     892             :         }
     893             : 
     894         102 :         m_nBands++;
     895             :     }
     896             : 
     897          69 :     return CE_None;
     898             : }
     899             : 
     900          75 : int MMRRel::GetColumnsNumberFromREL()
     901             : {
     902             :     // Number of columns of the subdataset (if exist)
     903             :     // Section [OVERVIEW:ASPECTES_TECNICS] in rel file
     904         150 :     CPLString osValue;
     905             : 
     906         150 :     if (!GetMetadataValue(SECTION_OVVW_ASPECTES_TECNICS, "columns", osValue) ||
     907          75 :         osValue.empty())
     908           0 :         return 0;  // Default value
     909             : 
     910             :     int nValue;
     911          75 :     if (1 != sscanf(osValue, "%d", &nValue))
     912           0 :         return 0;  // Default value
     913             : 
     914          75 :     return nValue;
     915             : }
     916             : 
     917          75 : int MMRRel::GetRowsNumberFromREL()
     918             : {
     919             :     // Number of columns of the subdataset (if exist)
     920             :     // Section [OVERVIEW:ASPECTES_TECNICS] in rel file
     921             :     // Key raws
     922         150 :     CPLString osValue;
     923             : 
     924         150 :     if (!GetMetadataValue(SECTION_OVVW_ASPECTES_TECNICS, "rows", osValue) ||
     925          75 :         osValue.empty())
     926           0 :         return 0;  // Default value
     927             : 
     928             :     int nValue;
     929          75 :     if (1 != sscanf(osValue, "%d", &nValue))
     930           0 :         return 0;  // Default value
     931             : 
     932          75 :     return nValue;
     933             : }
     934             : 
     935             : /************************************************************************/
     936             : /*                         Preserving metadata                          */
     937             : /************************************************************************/
     938          61 : void MMRRel::RELToGDALMetadata(GDALDataset *poDS)
     939             : {
     940          61 :     if (!m_pRELFile)
     941             :     {
     942           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     943             :                  "REL file cannot be opened: \"%s\"", m_osRelFileName.c_str());
     944           0 :         return;
     945             :     }
     946             : 
     947         122 :     CPLString osCurrentSection;
     948         122 :     CPLString osPendingKey, osPendingValue;
     949             : 
     950        6734 :     auto isExcluded = [&](const CPLString &section, const CPLString &key)
     951             :     {
     952       19074 :         return GetExcludedMetadata().count({section, key}) ||
     953       19074 :                GetExcludedMetadata().count({section, ""});
     954          61 :     };
     955             : 
     956             :     const char *pszLine;
     957             : 
     958          61 :     VSIFSeekL(m_pRELFile, 0, SEEK_SET);
     959       10076 :     while ((pszLine = CPLReadLine2L(m_pRELFile, 10000, nullptr)) != nullptr)
     960             :     {
     961       10015 :         CPLString rawLine = pszLine;
     962             : 
     963       10015 :         rawLine.Recode(CPL_ENC_ISO8859_1, CPL_ENC_UTF8);
     964       10015 :         rawLine.Trim();
     965             : 
     966       10015 :         if (rawLine.empty() || rawLine[0] == ';' || rawLine[0] == '#')
     967        1611 :             continue;
     968             : 
     969        8404 :         if (rawLine[0] == '[' && rawLine[rawLine.size() - 1] == ']')
     970             :         {
     971             :             // Saves last key
     972        1670 :             if (!osPendingKey.empty())
     973             :             {
     974        1609 :                 if (!isExcluded(osCurrentSection, osPendingKey))
     975             :                 {
     976             :                     CPLString fullKey =
     977        4269 :                         osCurrentSection + m_SecKeySeparator + osPendingKey;
     978             : 
     979        1423 :                     poDS->SetMetadataItem(fullKey.c_str(),
     980        1423 :                                           osPendingValue.Trim().c_str(),
     981        1423 :                                           m_kMetadataDomain);
     982             :                 }
     983        1609 :                 osPendingKey.clear();
     984        1609 :                 osPendingValue.clear();
     985             :             }
     986             : 
     987        1670 :             osCurrentSection = rawLine.substr(1, rawLine.size() - 2);
     988        1670 :             osCurrentSection.Trim();
     989        1670 :             continue;
     990             :         }
     991             : 
     992        6734 :         size_t equalPos = rawLine.find('=');
     993        6734 :         if (equalPos != CPLString::npos)
     994             :         {
     995             :             // Desa clau anterior
     996        6734 :             if (!osPendingKey.empty())
     997             :             {
     998        5064 :                 if (!isExcluded(osCurrentSection, osPendingKey))
     999             :                 {
    1000             :                     CPLString fullKey =
    1001       12480 :                         osCurrentSection + m_SecKeySeparator + osPendingKey;
    1002             : 
    1003        4160 :                     poDS->SetMetadataItem(fullKey.c_str(),
    1004        4160 :                                           osPendingValue.Trim().c_str(),
    1005        4160 :                                           m_kMetadataDomain);
    1006             :                 }
    1007             :             }
    1008             : 
    1009        6734 :             osPendingKey = rawLine.substr(0, equalPos);
    1010        6734 :             osPendingValue = rawLine.substr(equalPos + 1);
    1011        6734 :             osPendingKey.Trim();
    1012        6734 :             osPendingValue.Trim();
    1013             :         }
    1014           0 :         else if (!osPendingKey.empty())
    1015             :         {
    1016           0 :             osPendingValue += "\n" + rawLine;
    1017             :         }
    1018             :     }
    1019             : 
    1020             :     // Saves last key
    1021          61 :     if (!osPendingKey.empty())
    1022             :     {
    1023         183 :         CPLString fullKey = osCurrentSection + m_SecKeySeparator + osPendingKey;
    1024          61 :         if (!isExcluded(osCurrentSection, osPendingKey))
    1025          23 :             poDS->SetMetadataItem(fullKey.c_str(),
    1026          23 :                                   osPendingValue.Trim().c_str(),
    1027          23 :                                   m_kMetadataDomain);
    1028             :     }
    1029             : }
    1030             : 
    1031           1 : CPLErr MMRRel::UpdateGDALColorEntryFromBand(CPLString m_osBandSection,
    1032             :                                             GDALColorEntry &m_sConstantColorRGB)
    1033             : {
    1034             :     // Example: Color_Smb=(255,0,255)
    1035           2 :     CPLString os_Color_Smb;
    1036           1 :     if (!GetMetadataValue(SECTION_COLOR_TEXT, m_osBandSection, "Color_Smb",
    1037             :                           os_Color_Smb))
    1038           0 :         return CE_None;
    1039             : 
    1040           1 :     os_Color_Smb.replaceAll(" ", "");
    1041           2 :     if (!os_Color_Smb.empty() && os_Color_Smb.size() >= 7 &&
    1042           2 :         os_Color_Smb[0] == '(' && os_Color_Smb[os_Color_Smb.size() - 1] == ')')
    1043             :     {
    1044           1 :         os_Color_Smb.replaceAll("(", "");
    1045           1 :         os_Color_Smb.replaceAll(")", "");
    1046           1 :         const CPLStringList aosTokens(CSLTokenizeString2(os_Color_Smb, ",", 0));
    1047           1 :         if (CSLCount(aosTokens) != 3)
    1048             :         {
    1049           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1050             :                      "Invalid constant color: \"%s\"", GetRELNameChar());
    1051           0 :             return CE_Failure;
    1052             :         }
    1053             : 
    1054             :         int nIColor0;
    1055           1 :         if (1 != sscanf(aosTokens[0], "%d", &nIColor0))
    1056             :         {
    1057           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1058             :                      "Invalid constant color: \"%s\"", GetRELNameChar());
    1059           0 :             return CE_Failure;
    1060             :         }
    1061             : 
    1062             :         int nIColor1;
    1063           1 :         if (1 != sscanf(aosTokens[1], "%d", &nIColor1))
    1064             :         {
    1065           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1066             :                      "Invalid constant color: \"%s\"", GetRELNameChar());
    1067           0 :             return CE_Failure;
    1068             :         }
    1069             : 
    1070             :         int nIColor2;
    1071           1 :         if (1 != sscanf(aosTokens[2], "%d", &nIColor2))
    1072             :         {
    1073           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1074             :                      "Invalid constant color: \"%s\"", GetRELNameChar());
    1075           0 :             return CE_Failure;
    1076             :         }
    1077             : 
    1078           1 :         m_sConstantColorRGB.c1 = static_cast<short>(nIColor0);
    1079           1 :         m_sConstantColorRGB.c2 = static_cast<short>(nIColor1);
    1080           1 :         m_sConstantColorRGB.c3 = static_cast<short>(nIColor2);
    1081             :     }
    1082           1 :     return CE_None;
    1083             : }

Generated by: LCOV version 1.14