LCOV - code coverage report
Current view: top level - frmts/miramon - miramon_rel.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 816 936 87.2 %
Date: 2026-03-05 10:33:42 Functions: 50 50 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 "cpl_time.h"
      19             : #include <set>
      20             : 
      21             : #include "miramon_rel.h"
      22             : #include "miramon_band.h"
      23             : 
      24             : #include "../miramon_common/mm_gdal_functions.h"  // For MMCheck_REL_FILE()
      25             : 
      26             : CPLString MMRRel::m_szImprobableRELChain = "@#&%$|``|$%&#@";
      27             : 
      28             : /************************************************************************/
      29             : /*                               MMRRel()                               */
      30             : /************************************************************************/
      31         396 : MMRRel::MMRRel(const CPLString &osRELFilenameIn, bool bIMGMustExist)
      32         396 :     : m_osRelFileName(osRELFilenameIn), m_szFileIdentifier("")
      33             : {
      34         792 :     CPLString osRelCandidate = osRELFilenameIn;
      35             : 
      36             :     // Getting the name of the REL
      37         792 :     const CPLString osMMRPrefix = "MiraMonRaster:";
      38         396 :     if (STARTS_WITH(osRelCandidate, osMMRPrefix))
      39             :     {
      40             :         // SUBDATASET case: gets the names of the bands in the subdataset
      41           6 :         size_t nPos = osRelCandidate.ifind(osMMRPrefix);
      42           6 :         if (nPos != 0)
      43           0 :             return;
      44             : 
      45           6 :         CPLString osSDSReL = osRelCandidate.substr(osMMRPrefix.size());
      46             : 
      47             :         // Getting the internal names of the bands
      48           6 :         const CPLStringList aosTokens(CSLTokenizeString2(osSDSReL, ",", 0));
      49           6 :         const int nTokens = CSLCount(aosTokens);
      50             : 
      51           6 :         if (nTokens < 1)
      52           0 :             return;
      53             : 
      54           6 :         osRelCandidate = aosTokens[0];
      55           6 :         osRelCandidate.replaceAll("\"", "");
      56             : 
      57             :         // Getting the list of bands in the subdataset
      58          12 :         for (int nIBand = 0; nIBand < nTokens - 1; nIBand++)
      59             :         {
      60             :             // Raw band name
      61          12 :             CPLString osBandName = aosTokens[nIBand + 1];
      62           6 :             osBandName.replaceAll("\"", "");
      63           6 :             m_papoSDSBands.emplace_back(osBandName);
      64             :         }
      65           6 :         m_bIsAMiraMonFile = true;
      66             :     }
      67             :     else
      68             :     {
      69             :         // Getting the metadata file name. If it's already a REL file,
      70             :         // then same name is returned.
      71         390 :         osRelCandidate = GetAssociatedMetadataFileName(m_osRelFileName.c_str());
      72         390 :         if (osRelCandidate.empty())
      73             :         {
      74         196 :             if (m_bIsAMiraMonFile)
      75             :             {
      76           0 :                 CPLError(CE_Failure, CPLE_OpenFailed,
      77             :                          "Metadata file for %s should exist.",
      78             :                          m_osRelFileName.c_str());
      79             :             }
      80         196 :             if (!bIMGMustExist)
      81             :             {
      82             :                 // Simulates that we have a MiraMon file
      83             :                 // and we can ask things to this Rel file.
      84          30 :                 UpdateRELNameChar(m_osRelFileName);
      85          30 :                 m_bIsAMiraMonFile = true;
      86          30 :                 if (!OpenRELFile("rb"))
      87           0 :                     return;
      88             :             }
      89         196 :             return;
      90             :         }
      91             :         else
      92             :         {
      93             :             // It's a REL and it's not empty, so it's a MiraMon file
      94         194 :             VSILFILE *pF = VSIFOpenL(osRelCandidate, "r");
      95         194 :             if (!pF)
      96             :             {
      97           0 :                 CPLError(CE_Failure, CPLE_OpenFailed,
      98             :                          "Metadata file %s could not be opened.",
      99             :                          m_osRelFileName.c_str());
     100           0 :                 return;
     101             :             }
     102         194 :             VSIFSeekL(pF, 0, SEEK_END);
     103         194 :             if (VSIFTellL(pF))
     104         194 :                 m_bIsAMiraMonFile = true;
     105             :             else
     106             :             {
     107           0 :                 CPLError(
     108             :                     CE_Failure, CPLE_OpenFailed,
     109             :                     "Metadata file for %s should have some information in.",
     110             :                     m_osRelFileName.c_str());
     111             : 
     112           0 :                 VSIFCloseL(pF);
     113           0 :                 return;
     114             :             }
     115         194 :             VSIFCloseL(pF);
     116             :         }
     117             :     }
     118             : 
     119             :     // If rel name was not a REL name, we update that
     120             :     // from the one found in the process of discovering it.
     121         200 :     UpdateRELNameChar(osRelCandidate);
     122             : 
     123             :     // We let it be opened
     124         200 :     if (!OpenRELFile("rb"))
     125           0 :         return;
     126             : 
     127             :     // Collect band information
     128         200 :     if (ParseBandInfo() != CE_None)
     129           8 :         return;
     130             : 
     131             :     // We have a valid object MMRREL.
     132         192 :     m_bIsValid = true;
     133             : 
     134         192 :     return;
     135             : }
     136             : 
     137         126 : MMRRel::MMRRel(const CPLString &osRELFilenameIn, bool bNeedOfNomFitxer,
     138             :                const CPLString &osEPSG, int nWidth, int nHeight, double dfMinX,
     139             :                double dfMaxX, double dfMinY, double dfMaxY,
     140         126 :                std::vector<MMRBand> &&oBands)
     141             :     : m_osRelFileName(osRELFilenameIn), m_szFileIdentifier(""),
     142         126 :       m_oBands(std::move(oBands)), m_bNeedOfNomFitxer(bNeedOfNomFitxer),
     143             :       m_osEPSG(osEPSG), m_nWidth(nWidth), m_nHeight(nHeight), m_dfMinX(dfMinX),
     144         126 :       m_dfMaxX(dfMaxX), m_dfMinY(dfMinY), m_dfMaxY(dfMaxY)
     145             : {
     146         126 :     m_nBands = static_cast<int>(m_oBands.size());
     147             : 
     148         126 :     if (!m_nBands)
     149           0 :         return;
     150             : 
     151             :     // Getting the title of the rel
     152         126 :     m_osTitle = MMRGetFileNameWithOutI(CPLGetBasenameSafe(osRELFilenameIn));
     153             : 
     154         126 :     m_bIsAMiraMonFile = true;
     155         126 :     m_bIsValid = true;
     156             : }
     157             : 
     158          27 : MMRRel::MMRRel(const CPLString &osRELFilenameIn)
     159             :     : m_osRelFileName(osRELFilenameIn), m_osTitle(""), m_szFileIdentifier(""),
     160          27 :       m_bIsValid(true), m_bIsAMiraMonFile(true), m_nBands(0)
     161             : {
     162          27 : }
     163             : 
     164             : /************************************************************************/
     165             : /*                              ~MMRRel()                               */
     166             : /************************************************************************/
     167             : 
     168         549 : MMRRel::~MMRRel()
     169             : {
     170         549 :     CloseRELFile();
     171         549 : }
     172             : 
     173             : /************************************************************************/
     174             : /*                      Getting section-key-value                       */
     175             : /************************************************************************/
     176             : // Used when the MMRREL is not yet constructed.
     177             : CPLString
     178         247 : MMRRel::GetValueFromSectionKeyPriorToREL(const CPLString &osPriorRelName,
     179             :                                          const CPLString &osSection,
     180             :                                          const CPLString &osKey)
     181             : {
     182         247 :     if (osPriorRelName.empty())
     183           0 :         return "";
     184             : 
     185         247 :     VSILFILE *pPriorRELFile = VSIFOpenL(osPriorRelName, "rb");
     186         247 :     if (!pPriorRELFile)
     187           0 :         return "";
     188             : 
     189         494 :     CPLString osValue = GetValueFromSectionKey(pPriorRELFile, osSection, osKey);
     190         247 :     VSIFCloseL(pPriorRELFile);
     191         247 :     return osValue;
     192             : }
     193             : 
     194             : // Used when the MMRREL is already constructed.
     195       12437 : CPLString MMRRel::GetValueFromSectionKeyFromREL(const CPLString &osSection,
     196             :                                                 const CPLString &osKey)
     197             : {
     198       12437 :     if (!GetRELFile())
     199             :     {
     200           0 :         CPLError(CE_Failure, CPLE_AppDefined, "REL file is not opened: \"%s\"",
     201             :                  m_osRelFileName.c_str());
     202           0 :         return "";
     203             :     }
     204             : 
     205       12437 :     return GetValueFromSectionKey(GetRELFile(), osSection, osKey);
     206             : }
     207             : 
     208             : // This function is the C++ equivalent of MMReturnValueFromSectionINIFile().
     209             : // It improves upon the original by using CPLString instead of raw char pointers,
     210             : // and by operating on an already opened file pointer rather than reopening the file
     211             : // on each invocation.
     212             : // MMReturnValueFromSectionINIFile() is retained in miramon_common because it is
     213             : // widely used by existing, already OGR tested code (and in the common code itself).
     214             : // At least in C++ code the modern version is used
     215       12684 : CPLString MMRRel::GetValueFromSectionKey(VSILFILE *pf,
     216             :                                          const CPLString &osSection,
     217             :                                          const CPLString &osKey)
     218             : {
     219       12684 :     if (!pf)
     220           0 :         return "";
     221             : 
     222       25368 :     CPLString osCurrentSection;
     223       25368 :     CPLString osCurrentKey, osCurrentValue;
     224       12684 :     bool bIAmInMySection = false;
     225             : 
     226             :     const char *pszLine;
     227             : 
     228       12684 :     VSIFSeekL(pf, 0, SEEK_SET);
     229     1119360 :     while ((pszLine = CPLReadLine2L(pf, 10000, nullptr)) != nullptr)
     230             :     {
     231     1116170 :         CPLString rawLine = pszLine;
     232             : 
     233     1116170 :         rawLine.Recode(CPL_ENC_ISO8859_1, CPL_ENC_UTF8);
     234     1116170 :         rawLine.Trim();
     235             : 
     236     1116170 :         if (rawLine.empty() || rawLine[0] == ';' || rawLine[0] == '#')
     237      179485 :             continue;
     238             : 
     239      936684 :         if (rawLine[0] == '[' && rawLine[rawLine.size() - 1] == ']')
     240             :         {
     241      189712 :             if (bIAmInMySection)
     242             :             {
     243             :                 // This is the next section to mine, so nothing to find here.
     244        3978 :                 return m_szImprobableRELChain;
     245             :             }
     246             : 
     247      185734 :             osCurrentSection = rawLine.substr(1, rawLine.size() - 2);
     248      185734 :             osCurrentSection.Trim();
     249             : 
     250      185734 :             if (!EQUAL(osCurrentSection, osSection))
     251      175892 :                 bIAmInMySection = false;
     252             :             else
     253        9842 :                 bIAmInMySection = true;
     254             : 
     255      185734 :             continue;
     256             :         }
     257             : 
     258      746972 :         if (!bIAmInMySection)
     259      700123 :             continue;
     260             : 
     261       46849 :         size_t equalPos = rawLine.find('=');
     262       46849 :         if (equalPos != CPLString::npos)
     263             :         {
     264       46849 :             osCurrentKey = rawLine.substr(0, equalPos);
     265       46849 :             osCurrentValue = rawLine.substr(equalPos + 1);
     266       46849 :             osCurrentKey.Trim();
     267       46849 :             osCurrentValue.Trim();
     268             : 
     269       46849 :             if (EQUAL(osCurrentKey, osKey))
     270        5517 :                 return osCurrentValue;
     271             :         }
     272             :     }
     273             : 
     274        3189 :     return m_szImprobableRELChain;  // Key not found
     275             : }
     276             : 
     277             : /************************************************************************/
     278             : /*                           Other functions                            */
     279             : /************************************************************************/
     280             : 
     281             : // Converts FileNameI.rel to FileName
     282         319 : CPLString MMRRel::MMRGetFileNameWithOutI(const CPLString &osRELFile)
     283             : {
     284         319 :     if (osRELFile.empty())
     285           0 :         return "";
     286             : 
     287         638 :     CPLString osFile = CPLString(CPLResetExtensionSafe(osRELFile, "").c_str());
     288             : 
     289         319 :     if (osFile.length() < 2)
     290           0 :         return "";
     291             : 
     292         319 :     osFile.resize(osFile.size() - 2);  // I.
     293             : 
     294         319 :     return osFile;
     295             : }
     296             : 
     297             : // Converts FileNameI.rel to FileName.xxx (where xxx is an extension)
     298         193 : CPLString MMRRel::MMRGetFileNameFromRelName(const CPLString &osRELFile,
     299             :                                             const CPLString &osExtension)
     300             : {
     301         193 :     if (osRELFile.empty())
     302           0 :         return "";
     303             : 
     304             :     // Extracts I.rel
     305             :     CPLString osFile =
     306         579 :         MMRGetFileNameWithOutI(CPLResetExtensionSafe(osRELFile, ""));
     307             : 
     308         193 :     if (!osExtension.empty())
     309             :     {
     310             :         // Adds extension (with the ".", ex: ".img")
     311         193 :         osFile += osExtension;
     312             :     }
     313             : 
     314         193 :     return osFile;
     315             : }
     316             : 
     317             : // Converts FileName.img to FileNameI.rel
     318         183 : CPLString MMRRel::MMRGetSimpleMetadataName(const CPLString &osLayerName)
     319             : {
     320         183 :     if (osLayerName.empty())
     321           0 :         return "";
     322             : 
     323             :     // Extract extension
     324             :     CPLString osRELFile =
     325         366 :         CPLString(CPLResetExtensionSafe(osLayerName, "").c_str());
     326             : 
     327         183 :     if (!osRELFile.length())
     328           0 :         return "";
     329             : 
     330             :     // Extract "."
     331         183 :     osRELFile.resize(osRELFile.size() - 1);
     332             :     // Add "I.rel"
     333         183 :     osRELFile += pszExtRasterREL;
     334             : 
     335         183 :     return osRELFile;
     336             : }
     337             : 
     338             : // Gets the value from a section-key accessing directly to the RELFile.
     339             : // It happens when MMRel is used to access a REL that is not an IMG sidecar
     340             : // or at the Identify() process, when we don't have already the MMRRel constructed.
     341         187 : bool MMRRel::GetAndExcludeMetadataValueDirectly(const CPLString &osRELFile,
     342             :                                                 const CPLString &osSection,
     343             :                                                 const CPLString &osKey,
     344             :                                                 CPLString &osValue)
     345             : {
     346         187 :     addExcludedSectionKey(osSection, osKey);
     347         187 :     return GetMetadataValueDirectly(osRELFile, osSection, osKey, osValue);
     348             : }
     349             : 
     350         247 : bool MMRRel::GetMetadataValueDirectly(const CPLString &osRELFile,
     351             :                                       const CPLString &osSection,
     352             :                                       const CPLString &osKey,
     353             :                                       CPLString &osValue)
     354             : {
     355         247 :     osValue = GetValueFromSectionKeyPriorToREL(osRELFile, osSection, osKey);
     356             : 
     357         247 :     if (osValue != m_szImprobableRELChain)
     358         130 :         return true;  // Found
     359             : 
     360         117 :     osValue = "";
     361         117 :     return false;  // Key not found
     362             : }
     363             : 
     364          81 : bool MMRRel::SameFile(const CPLString &osFile1, const CPLString &osFile2)
     365             : {
     366          81 :     if (EQUAL(osFile1, osFile2))
     367          17 :         return true;
     368             : 
     369             :     // Just to be more sure:
     370         128 :     CPLString osLayerName1 = osFile1;
     371          64 :     osLayerName1.replaceAll("\\", "/");
     372         128 :     CPLString osLayerName2 = osFile2;
     373          64 :     osLayerName2.replaceAll("\\", "/");
     374             : 
     375          64 :     if (EQUAL(osLayerName1, osLayerName2))
     376           0 :         return true;
     377             : 
     378          64 :     return false;
     379             : }
     380             : 
     381             : // Gets the state (enum class MMRNomFitxerState) of NomFitxer in the
     382             : // specified section
     383             : // [pszSection]
     384             : // NomFitxer=Value
     385          81 : MMRNomFitxerState MMRRel::MMRStateOfNomFitxerInSection(
     386             :     const CPLString &osLayerName, const CPLString &osSection,
     387             :     const CPLString &osRELFile, bool bNomFitxerMustExist)
     388             : {
     389         162 :     CPLString osDocumentedLayerName;
     390             : 
     391          81 :     if (!GetAndExcludeMetadataValueDirectly(osRELFile, osSection, KEY_NomFitxer,
     392          90 :                                             osDocumentedLayerName) ||
     393           9 :         osDocumentedLayerName.empty())
     394             :     {
     395             :         CPLString osIIMGFromREL =
     396         144 :             MMRGetFileNameFromRelName(osRELFile, pszExtRaster);
     397          72 :         if (SameFile(osIIMGFromREL, osLayerName))
     398          13 :             return MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED;
     399             : 
     400          59 :         if (bNomFitxerMustExist)
     401          22 :             return MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED;
     402             :         else
     403          37 :             return MMRNomFitxerState::NOMFITXER_NOT_FOUND;
     404             :     }
     405             : 
     406          18 :     CPLString osFileAux = CPLFormFilenameSafe(CPLGetPathSafe(osRELFile).c_str(),
     407          18 :                                               osDocumentedLayerName, "");
     408             : 
     409           9 :     osDocumentedLayerName.Trim();
     410           9 :     if (*osDocumentedLayerName == '*' || *osDocumentedLayerName == '?')
     411           0 :         return MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED;
     412             : 
     413           9 :     if (SameFile(osFileAux, osLayerName))
     414           4 :         return MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED;
     415             : 
     416           5 :     return MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED;
     417             : }
     418             : 
     419             : // Tries to find a reference to the IMG file 'pszLayerName'
     420             : // we are opening in the REL file 'pszRELFile'
     421          51 : CPLString MMRRel::MMRGetAReferenceToIMGFile(const CPLString &osLayerName,
     422             :                                             const CPLString &osRELFile)
     423             : {
     424          51 :     if (osRELFile.empty())
     425             :     {
     426           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "Expected File name.");
     427           0 :         return "";
     428             :     }
     429             : 
     430             :     // [ATTRIBUTE_DATA]
     431             :     // NomFitxer=
     432             :     // It should be empty but if it's not, at least,
     433             :     // the value has to be osLayerName
     434          51 :     MMRNomFitxerState iState = MMRStateOfNomFitxerInSection(
     435             :         osLayerName, SECTION_ATTRIBUTE_DATA, osRELFile, false);
     436             : 
     437          51 :     if (iState == MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED ||
     438             :         iState == MMRNomFitxerState::NOMFITXER_VALUE_EMPTY)
     439             :     {
     440          14 :         return osRELFile;
     441             :     }
     442          37 :     else if (iState == MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED)
     443             :     {
     444           0 :         if (m_bIsAMiraMonFile)
     445             :         {
     446           0 :             CPLError(
     447             :                 CE_Failure, CPLE_OpenFailed,
     448             :                 "Unexpected value for SECTION_ATTRIBUTE_DATA [NomFitxer] in "
     449             :                 "%s file.",
     450             :                 osRELFile.c_str());
     451             :         }
     452           0 :         return "";
     453             :     }
     454             : 
     455             :     // Discarting not supported via SDE (some files
     456             :     // could have this option)
     457          74 :     CPLString osVia;
     458          37 :     if (GetAndExcludeMetadataValueDirectly(osRELFile, SECTION_ATTRIBUTE_DATA,
     459             :                                            KEY_via, osVia))
     460             :     {
     461           0 :         if (!osVia.empty() && !EQUAL(osVia, "SDE"))
     462             :         {
     463           0 :             if (m_bIsAMiraMonFile)
     464             :             {
     465           0 :                 CPLError(CE_Failure, CPLE_OpenFailed,
     466             :                          "Unexpected Via in %s file", osRELFile.c_str());
     467             :             }
     468           0 :             return "";
     469             :         }
     470             :     }
     471             : 
     472          74 :     CPLString osFieldNames;
     473             : 
     474          37 :     if (!GetAndExcludeMetadataValueDirectly(osRELFile, SECTION_ATTRIBUTE_DATA,
     475             :                                             Key_IndexesNomsCamps,
     476          68 :                                             osFieldNames) ||
     477          31 :         osFieldNames.empty())
     478             :     {
     479           8 :         if (m_bIsAMiraMonFile)
     480             :         {
     481           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     482             :                      "IndexesNomsCamps not found in %s file",
     483             :                      osRELFile.c_str());
     484             :         }
     485           8 :         return "";
     486             :     }
     487             : 
     488             :     // Getting the internal names of the bands
     489          58 :     const CPLStringList aosTokens(CSLTokenizeString2(osFieldNames, ",", 0));
     490          29 :     const int nTokenBands = CSLCount(aosTokens);
     491             : 
     492          58 :     CPLString osBandSectionKey;
     493          58 :     CPLString osAttributeDataName;
     494          58 :     for (int nIBand = 0; nIBand < nTokenBands; nIBand++)
     495             :     {
     496          32 :         osBandSectionKey = KEY_NomCamp;
     497          32 :         osBandSectionKey.append("_");
     498          32 :         osBandSectionKey.append(aosTokens[nIBand]);
     499             : 
     500          32 :         CPLString osBandSectionValue;
     501             : 
     502          32 :         if (!GetAndExcludeMetadataValueDirectly(
     503             :                 osRELFile, SECTION_ATTRIBUTE_DATA, osBandSectionKey,
     504          62 :                 osBandSectionValue) ||
     505          30 :             osBandSectionValue.empty())
     506           2 :             continue;  // A band without name (·$· unexpected)
     507             : 
     508             :         // Example: [ATTRIBUTE_DATA:G1]
     509          30 :         osAttributeDataName = SECTION_ATTRIBUTE_DATA;
     510          30 :         osAttributeDataName.append(":");
     511          30 :         osAttributeDataName.append(osBandSectionValue.Trim());
     512             : 
     513             :         // Let's see if this band contains the expected name
     514             :         // or none (in monoband case)
     515          30 :         iState = MMRStateOfNomFitxerInSection(osLayerName, osAttributeDataName,
     516             :                                               osRELFile, true);
     517          30 :         if (iState == MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED)
     518           3 :             return osRELFile;
     519             : 
     520          27 :         else if (iState == MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED)
     521          27 :             continue;
     522             : 
     523             :         // If there is only one band is accepted NOMFITXER_NOT_FOUND/EMPTY iState result
     524           0 :         if (nTokenBands == 1)
     525           0 :             return osRELFile;
     526             :     }
     527             : 
     528          26 :     if (m_bIsAMiraMonFile)
     529             :     {
     530           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     531             :                  "REL search failed for all bands in %s file",
     532             :                  osRELFile.c_str());
     533             :     }
     534          26 :     return "";
     535             : }
     536             : 
     537             : // Finds the metadata filename associated to osFileName (usually an IMG file)
     538         390 : CPLString MMRRel::GetAssociatedMetadataFileName(const CPLString &osFileName)
     539             : {
     540         390 :     if (osFileName.empty())
     541             :     {
     542           0 :         if (m_bIsAMiraMonFile)
     543           0 :             CPLError(CE_Failure, CPLE_OpenFailed, "Expected File name.");
     544           0 :         return "";
     545             :     }
     546             : 
     547             :     // If the string finishes in "I.rel" we consider it can be
     548             :     // the associated file to all bands that are documented in this file.
     549         390 :     if (cpl::ends_with(osFileName, pszExtRasterREL))
     550             :     {
     551         177 :         m_bIsAMiraMonFile = true;
     552         177 :         return osFileName;
     553             :     }
     554             : 
     555             :     // If the file is not a REL file, let's try to find the associated REL
     556             :     // It must be a IMG file.
     557         426 :     CPLString osExtension = CPLString(CPLGetExtensionSafe(osFileName).c_str());
     558         213 :     if (!EQUAL(osExtension, pszExtRaster + 1))
     559          30 :         return "";
     560             : 
     561             :     // Converting FileName.img to FileNameI.rel
     562         366 :     CPLString osRELFile = MMRGetSimpleMetadataName(osFileName);
     563         183 :     if (osRELFile.empty())
     564             :     {
     565           0 :         if (m_bIsAMiraMonFile)
     566             :         {
     567           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     568             :                      "Failing in conversion from .img to I.rel for %s file",
     569             :                      osFileName.c_str());
     570             :         }
     571           0 :         return "";
     572             :     }
     573             : 
     574             :     // Checking if the file exists
     575             :     VSIStatBufL sStat;
     576         183 :     if (VSIStatExL(osRELFile.c_str(), &sStat, VSI_STAT_EXISTS_FLAG) == 0)
     577          14 :         return MMRGetAReferenceToIMGFile(osFileName, osRELFile);
     578             : 
     579             :     // If the file I.rel doesn't exist then it has to be found
     580             :     // in the same folder than the .img file.
     581         338 :     const CPLString osPath = CPLGetPathSafe(osFileName);
     582         338 :     const CPLStringList folder(VSIReadDir(osPath.c_str()));
     583         169 :     const int size = folder.size();
     584             : 
     585        1247 :     for (int nIFile = 0; nIFile < size; nIFile++)
     586             :     {
     587        1081 :         if (folder[nIFile][0] == '.' || !strstr(folder[nIFile], "I.rel"))
     588             :         {
     589        1044 :             continue;
     590             :         }
     591             : 
     592             :         const CPLString osFilePath =
     593          37 :             CPLFormFilenameSafe(osPath, folder[nIFile], nullptr);
     594             : 
     595          37 :         osRELFile = MMRGetAReferenceToIMGFile(osFileName, osFilePath);
     596          37 :         if (!osRELFile.empty())
     597           3 :             return osRELFile;
     598             :     }
     599             : 
     600         166 :     if (m_bIsAMiraMonFile)
     601             :     {
     602           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "REL search failed for %s file",
     603             :                  osFileName.c_str());
     604             :     }
     605             : 
     606         166 :     return "";
     607             : }
     608             : 
     609             : /************************************************************************/
     610             : /*                           CheckBandInRel()                           */
     611             : /************************************************************************/
     612          12 : CPLErr MMRRel::CheckBandInRel(const CPLString &osRELFileName,
     613             :                               const CPLString &osIMGFile)
     614             : 
     615             : {
     616          24 :     CPLString osFieldNames;
     617          12 :     if (!GetMetadataValueDirectly(osRELFileName, SECTION_ATTRIBUTE_DATA,
     618          24 :                                   Key_IndexesNomsCamps, osFieldNames) ||
     619          12 :         osFieldNames.empty())
     620           0 :         return CE_Failure;
     621             : 
     622             :     // Separator ,
     623          24 :     const CPLStringList aosTokens(CSLTokenizeString2(osFieldNames, ",", 0));
     624          12 :     const int nTokenCount = CSLCount(aosTokens);
     625             : 
     626          12 :     if (!nTokenCount)
     627           0 :         return CE_Failure;
     628             : 
     629          24 :     CPLString osBandSectionKey;
     630          24 :     CPLString osBandSectionValue;
     631          24 :     for (int nIBand = 0; nIBand < nTokenCount; nIBand++)
     632             :     {
     633          24 :         osBandSectionKey = KEY_NomCamp;
     634          24 :         osBandSectionKey.append("_");
     635          24 :         osBandSectionKey.append(aosTokens[nIBand]);
     636             : 
     637          24 :         if (!GetMetadataValueDirectly(osRELFileName, SECTION_ATTRIBUTE_DATA,
     638          48 :                                       osBandSectionKey, osBandSectionValue) ||
     639          24 :             osBandSectionValue.empty())
     640           0 :             return CE_Failure;
     641             : 
     642          24 :         CPLString osAttributeDataName;
     643          24 :         osAttributeDataName = SECTION_ATTRIBUTE_DATA;
     644          24 :         osAttributeDataName.append(":");
     645          24 :         osAttributeDataName.append(osBandSectionValue.Trim());
     646             : 
     647          24 :         CPLString osRawBandFileName;
     648             : 
     649          24 :         if (!GetMetadataValueDirectly(osRELFileName, osAttributeDataName,
     650          48 :                                       KEY_NomFitxer, osRawBandFileName) ||
     651          24 :             osRawBandFileName.empty())
     652             :         {
     653             :             CPLString osBandFileName =
     654           0 :                 MMRGetFileNameFromRelName(osRELFileName, pszExtRaster);
     655           0 :             if (osBandFileName.empty())
     656           0 :                 return CE_Failure;
     657             :         }
     658             :         else
     659             :         {
     660          24 :             if (!EQUAL(osRawBandFileName, osIMGFile))
     661          12 :                 continue;
     662          12 :             break;  // Found
     663             :         }
     664             :     }
     665             : 
     666          12 :     return CE_None;
     667             : }
     668             : 
     669       58825 : int MMRRel::IdentifySubdataSetFile(const CPLString &osFileName)
     670             : {
     671      117650 :     const CPLString osMMRPrefix = "MiraMonRaster:";
     672       58825 :     if (!STARTS_WITH(osFileName, osMMRPrefix))
     673       58813 :         return FALSE;
     674             : 
     675             :     // SUBDATASETS
     676          12 :     size_t nPos = osFileName.ifind(osMMRPrefix);
     677          12 :     if (nPos != 0)
     678           0 :         return GDAL_IDENTIFY_FALSE;
     679             : 
     680          24 :     CPLString osRELAndBandName = osFileName.substr(osMMRPrefix.size());
     681             : 
     682          24 :     const CPLStringList aosTokens(CSLTokenizeString2(osRELAndBandName, ",", 0));
     683          12 :     const int nTokens = CSLCount(aosTokens);
     684             :     // Getting the REL associated to the bands
     685             :     // We need the REL and at least one band (index + name).
     686          12 :     if (nTokens < 2)
     687           0 :         return GDAL_IDENTIFY_FALSE;
     688             : 
     689             :     // Let's remove "\"" if existent.
     690          24 :     CPLString osRELName = aosTokens[0];
     691          12 :     osRELName.replaceAll("\"", "");
     692             : 
     693             :     // It must be a I.rel file.
     694          12 :     if (!cpl::ends_with(osRELName, pszExtRasterREL))
     695           0 :         return GDAL_IDENTIFY_FALSE;
     696             : 
     697          12 :     if (MMCheck_REL_FILE(osRELName))
     698           0 :         return GDAL_IDENTIFY_FALSE;
     699             : 
     700             :     // Let's see if the specified bands are in the REL file
     701             :     // Getting the index + internal names of the bands
     702          24 :     for (int nIBand = 1; nIBand < nTokens; nIBand++)
     703             :     {
     704             :         // Let's check that this band (papszTokens[nIBand]) is in the REL file.
     705          12 :         CPLString osBandName = aosTokens[nIBand];
     706             : 
     707             :         // Let's remove "\"" if existent.
     708          12 :         osBandName.replaceAll("\"", "");
     709             : 
     710             :         // If it's not an IMG file return FALSE
     711             :         CPLString osExtension =
     712          12 :             CPLString(CPLGetExtensionSafe(osBandName).c_str());
     713          12 :         if (!EQUAL(osExtension, pszExtRaster + 1))
     714           0 :             return GDAL_IDENTIFY_FALSE;
     715             : 
     716          12 :         if (CE_None != CheckBandInRel(osRELName, osBandName))
     717           0 :             return GDAL_IDENTIFY_FALSE;
     718             :     }
     719          12 :     return GDAL_IDENTIFY_TRUE;
     720             : }
     721             : 
     722       58813 : int MMRRel::IdentifyFile(const GDALOpenInfo *poOpenInfo)
     723             : {
     724             :     // IMG files are shared for many drivers.
     725             :     // Identify will mark it as unknown.
     726             :     // Open function will try to open that, but as it has computation
     727             :     // cost is better avoid doing it here.
     728       58813 :     if (poOpenInfo->IsExtensionEqualToCI("IMG"))
     729         481 :         return GDAL_IDENTIFY_UNKNOWN;
     730             : 
     731       58332 :     if (!poOpenInfo->IsExtensionEqualToCI("REL"))
     732       57750 :         return GDAL_IDENTIFY_FALSE;
     733             : 
     734             :     // In fact, the file has to end with I.rel (pszExtRasterREL)
     735         582 :     if (!cpl::ends_with(std::string_view(poOpenInfo->pszFilename),
     736             :                         pszExtRasterREL))
     737           2 :         return GDAL_IDENTIFY_FALSE;
     738             : 
     739             :     // Some versions of REL files are not allowed.
     740         580 :     if (MMCheck_REL_FILE(poOpenInfo->pszFilename))
     741         226 :         return GDAL_IDENTIFY_FALSE;
     742             : 
     743         354 :     return GDAL_IDENTIFY_TRUE;
     744             : }
     745             : 
     746             : /************************************************************************/
     747             : /*                          GetMetadataValue()                          */
     748             : /************************************************************************/
     749        1008 : bool MMRRel::GetMetadataValue(const CPLString &osMainSection,
     750             :                               const CPLString &osSubSection,
     751             :                               const CPLString &osSubSubSection,
     752             :                               const CPLString &osKey, CPLString &osValue)
     753             : {
     754        1008 :     CPLAssert(
     755             :         isAMiraMonFile());  // Trying to access metadata from the wrong way
     756             : 
     757             :     // Searches in [pszMainSection:pszSubSection]
     758        2016 :     CPLString osAttributeDataName;
     759        1008 :     osAttributeDataName = osMainSection;
     760        1008 :     osAttributeDataName.append(":");
     761        1008 :     osAttributeDataName.append(osSubSection);
     762        1008 :     osAttributeDataName.append(":");
     763        1008 :     osAttributeDataName.append(osSubSubSection);
     764             : 
     765        1008 :     addExcludedSectionKey(osAttributeDataName, osKey);
     766        1008 :     osValue = GetValueFromSectionKeyFromREL(osAttributeDataName, osKey);
     767        1008 :     if (osValue != m_szImprobableRELChain)
     768          14 :         return true;  // Found
     769             : 
     770             :     // If the value is not found then searches in [pszMainSection]
     771         994 :     addExcludedSectionKey(osSubSubSection, osKey);
     772         994 :     osValue = GetValueFromSectionKeyFromREL(osSubSubSection, osKey);
     773         994 :     if (osValue == m_szImprobableRELChain)
     774             :     {
     775          96 :         osValue = "";
     776          96 :         return false;  // Key not found
     777             :     }
     778         898 :     return true;  // Found
     779             : }
     780             : 
     781        5062 : bool MMRRel::GetMetadataValue(const CPLString &osMainSection,
     782             :                               const CPLString &osSubSection,
     783             :                               const CPLString &osKey, CPLString &osValue)
     784             : {
     785        5062 :     CPLAssert(
     786             :         isAMiraMonFile());  // Trying to access metadata from the wrong way
     787             : 
     788             :     // Searches in [pszMainSection:pszSubSection]
     789       10124 :     CPLString osAttributeDataName;
     790        5062 :     osAttributeDataName = osMainSection;
     791        5062 :     osAttributeDataName.append(":");
     792        5062 :     osAttributeDataName.append(osSubSection);
     793             : 
     794        5062 :     addExcludedSectionKey(osAttributeDataName, osKey);
     795        5062 :     osValue = GetValueFromSectionKeyFromREL(osAttributeDataName, osKey);
     796        5062 :     if (osValue != m_szImprobableRELChain)
     797        1599 :         return true;  // Found
     798             : 
     799             :     // If the value is not found then searches in [pszMainSection]
     800        3463 :     addExcludedSectionKey(osMainSection, osKey);
     801        3463 :     osValue = GetValueFromSectionKeyFromREL(osMainSection, osKey);
     802        3463 :     if (osValue == m_szImprobableRELChain)
     803             :     {
     804        2272 :         osValue = "";
     805        2272 :         return false;  // Key not found
     806             :     }
     807        1191 :     return true;  // Found
     808             : }
     809             : 
     810        1910 : bool MMRRel::GetMetadataValue(const CPLString &osSection,
     811             :                               const CPLString &osKey, CPLString &osValue)
     812             : {
     813        1910 :     CPLAssert(
     814             :         isAMiraMonFile());  // Trying to access metadata from the wrong way
     815             : 
     816        1910 :     addExcludedSectionKey(osSection, osKey);
     817        1910 :     osValue = GetValueFromSectionKeyFromREL(osSection, osKey);
     818        1910 :     if (osValue == m_szImprobableRELChain)
     819             :     {
     820         225 :         osValue = "";
     821         225 :         return false;  // Key not found
     822             :     }
     823        1685 :     return true;  // Found
     824             : }
     825             : 
     826         230 : void MMRRel::UpdateRELNameChar(const CPLString &osRelFileNameIn)
     827             : {
     828         230 :     m_osRelFileName = osRelFileNameIn;
     829         230 : }
     830             : 
     831             : /************************************************************************/
     832             : /*                           ParseBandInfo()                            */
     833             : /************************************************************************/
     834         200 : CPLErr MMRRel::ParseBandInfo()
     835             : {
     836         200 :     m_nBands = 0;
     837             : 
     838         400 :     CPLString osFieldNames;
     839         200 :     if (!GetMetadataValue(SECTION_ATTRIBUTE_DATA, Key_IndexesNomsCamps,
     840         399 :                           osFieldNames) ||
     841         199 :         osFieldNames.empty())
     842             :     {
     843           2 :         CPLError(CE_Failure, CPLE_AssertionFailed,
     844             :                  "%s-%s section-key should exist in %s.",
     845             :                  SECTION_ATTRIBUTE_DATA, Key_IndexesNomsCamps,
     846             :                  m_osRelFileName.c_str());
     847           2 :         return CE_Failure;
     848             :     }
     849             : 
     850             :     // Separator ,
     851         396 :     const CPLStringList aosTokens(CSLTokenizeString2(osFieldNames, ",", 0));
     852         198 :     const int nMaxBands = CSLCount(aosTokens);
     853             : 
     854         198 :     if (!nMaxBands)
     855             :     {
     856           0 :         CPLError(CE_Failure, CPLE_AssertionFailed, "No bands in file %s.",
     857             :                  m_osRelFileName.c_str());
     858           0 :         return CE_Failure;
     859             :     }
     860             : 
     861         396 :     CPLString osBandSectionKey;
     862         396 :     CPLString osBandSectionValue;
     863         396 :     std::set<std::string> setProcessedTokens;
     864             : 
     865             :     int nNBand;
     866         198 :     if (m_papoSDSBands.size())
     867           6 :         nNBand = static_cast<int>(m_papoSDSBands.size());
     868             :     else
     869         192 :         nNBand = nMaxBands;
     870             : 
     871         198 :     m_oBands.reserve(nNBand);
     872             : 
     873         474 :     for (int nIBand = 0; nIBand < nMaxBands; nIBand++)
     874             :     {
     875             :         const std::string lowerCaseToken =
     876         282 :             CPLString(aosTokens[nIBand]).tolower();
     877         282 :         if (cpl::contains(setProcessedTokens, lowerCaseToken))
     878           0 :             continue;  // Repeated bands are ignored.
     879             : 
     880         282 :         setProcessedTokens.insert(lowerCaseToken);
     881             : 
     882         282 :         osBandSectionKey = KEY_NomCamp;
     883         282 :         osBandSectionKey.append("_");
     884         282 :         osBandSectionKey.append(aosTokens[nIBand]);
     885             : 
     886         282 :         if (!GetMetadataValue(SECTION_ATTRIBUTE_DATA, osBandSectionKey,
     887         563 :                               osBandSectionValue) ||
     888         281 :             osBandSectionValue.empty())
     889           1 :             continue;
     890             : 
     891         281 :         if (m_papoSDSBands.size())
     892             :         {
     893          30 :             CPLString osRawBandFileName;
     894          30 :             if (!GetMetadataValue(SECTION_ATTRIBUTE_DATA, osBandSectionValue,
     895          60 :                                   KEY_NomFitxer, osRawBandFileName) ||
     896          30 :                 osRawBandFileName.empty())
     897           0 :                 return CE_Failure;
     898             : 
     899             :             // I'm in a Subataset
     900             :             size_t nISDSBand;
     901          54 :             for (nISDSBand = 0; nISDSBand < m_papoSDSBands.size(); nISDSBand++)
     902             :             {
     903          30 :                 if (m_papoSDSBands[nISDSBand] == osRawBandFileName)
     904           6 :                     break;
     905             :             }
     906          30 :             if (nISDSBand == m_papoSDSBands.size())
     907          24 :                 continue;
     908             :         }
     909             : 
     910         257 :         if (m_nBands >= nNBand)
     911           0 :             break;
     912             : 
     913             :         // MMRBand constructor is called
     914         257 :         m_oBands.emplace_back(*this, osBandSectionValue.Trim());
     915             : 
     916         257 :         if (!m_oBands[m_nBands].IsValid())
     917             :         {
     918             :             // This band is not been completed
     919           6 :             return CE_Failure;
     920             :         }
     921             : 
     922         251 :         m_nBands++;
     923             :     }
     924             : 
     925         192 :     return CE_None;
     926             : }
     927             : 
     928         198 : int MMRRel::GetColumnsNumberFromREL()
     929             : {
     930             :     // Number of columns of the subdataset (if exist)
     931             :     // Section [OVERVIEW:ASPECTES_TECNICS] in rel file
     932         396 :     CPLString osValue;
     933             : 
     934         396 :     if (!GetMetadataValue(SECTION_OVVW_ASPECTES_TECNICS, "columns", osValue) ||
     935         198 :         osValue.empty())
     936           0 :         return 0;  // Default value
     937             : 
     938             :     int nValue;
     939         198 :     if (1 != sscanf(osValue, "%d", &nValue))
     940           0 :         return 0;  // Default value
     941             : 
     942         198 :     return nValue;
     943             : }
     944             : 
     945         198 : int MMRRel::GetRowsNumberFromREL()
     946             : {
     947             :     // Number of columns of the subdataset (if exist)
     948             :     // Section [OVERVIEW:ASPECTES_TECNICS] in rel file
     949             :     // Key raws
     950         396 :     CPLString osValue;
     951             : 
     952         396 :     if (!GetMetadataValue(SECTION_OVVW_ASPECTES_TECNICS, "rows", osValue) ||
     953         198 :         osValue.empty())
     954           0 :         return 0;  // Default value
     955             : 
     956             :     int nValue;
     957         198 :     if (1 != sscanf(osValue, "%d", &nValue))
     958           0 :         return 0;  // Default value
     959             : 
     960         198 :     return nValue;
     961             : }
     962             : 
     963             : /************************************************************************/
     964             : /*                         Preserving metadata                          */
     965             : /************************************************************************/
     966         184 : void MMRRel::RELToGDALMetadata(GDALDataset *poDS)
     967             : {
     968         184 :     if (!m_pRELFile || !poDS)
     969             :     {
     970           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     971             :                  "REL file cannot be opened: \"%s\"", m_osRelFileName.c_str());
     972           0 :         return;
     973             :     }
     974             : 
     975         368 :     CPLString osCurrentSection;
     976         368 :     CPLString osPendingKey, osPendingValue;
     977             : 
     978       16004 :     auto isExcluded = [&](const CPLString &osSection, const CPLString &osKey)
     979             :     {
     980       44920 :         return GetExcludedMetadata().count({osSection, osKey}) ||
     981       44920 :                GetExcludedMetadata().count({osSection, ""});
     982         184 :     };
     983             : 
     984             :     const char *pszLine;
     985             : 
     986         184 :     VSIFSeekL(m_pRELFile, 0, SEEK_SET);
     987       23677 :     while ((pszLine = CPLReadLine2L(m_pRELFile, 10000, nullptr)) != nullptr)
     988             :     {
     989       23493 :         CPLString rawLine = pszLine;
     990             : 
     991       23493 :         rawLine.Recode(CPL_ENC_ISO8859_1, CPL_ENC_UTF8);
     992       23493 :         rawLine.Trim();
     993             : 
     994       23493 :         if (rawLine.empty() || rawLine[0] == ';' || rawLine[0] == '#')
     995        3722 :             continue;
     996             : 
     997       19771 :         if (rawLine[0] == '[' && rawLine[rawLine.size() - 1] == ']')
     998             :         {
     999             :             // Saves last key
    1000        3767 :             if (!osPendingKey.empty())
    1001             :             {
    1002        3583 :                 if (!isExcluded(osCurrentSection, osPendingKey))
    1003             :                 {
    1004             :                     CPLString fullKey =
    1005        5992 :                         osCurrentSection.replaceAll(":", IntraSecKeySeparator) +
    1006        8988 :                         SecKeySeparator + osPendingKey;
    1007             : 
    1008        2996 :                     if (osPendingValue.Trim().empty())
    1009        1076 :                         osPendingValue = MMEmptyValue;
    1010        2996 :                     poDS->SetMetadataItem(fullKey.c_str(),
    1011        2996 :                                           osPendingValue.Trim().c_str(),
    1012        2996 :                                           MetadataDomain);
    1013             :                 }
    1014        3583 :                 osPendingKey.clear();
    1015        3583 :                 osPendingValue.clear();
    1016             :             }
    1017             : 
    1018        3767 :             osCurrentSection = rawLine.substr(1, rawLine.size() - 2);
    1019        3767 :             osCurrentSection.Trim();
    1020        3767 :             continue;
    1021             :         }
    1022             : 
    1023       16004 :         size_t equalPos = rawLine.find('=');
    1024       16004 :         if (equalPos != CPLString::npos)
    1025             :         {
    1026             :             // Saves last key
    1027       16004 :             if (!osPendingKey.empty())
    1028             :             {
    1029       12237 :                 if (!isExcluded(osCurrentSection, osPendingKey))
    1030             :                 {
    1031             :                     CPLString fullKey =
    1032       19532 :                         osCurrentSection.replaceAll(":", IntraSecKeySeparator) +
    1033       29298 :                         SecKeySeparator + osPendingKey;
    1034             : 
    1035        9766 :                     if (osPendingValue.Trim().empty())
    1036         525 :                         osPendingValue = MMEmptyValue;
    1037        9766 :                     poDS->SetMetadataItem(fullKey.c_str(),
    1038        9766 :                                           osPendingValue.Trim().c_str(),
    1039        9766 :                                           MetadataDomain);
    1040             :                 }
    1041             :             }
    1042             : 
    1043       16004 :             osPendingKey = rawLine.substr(0, equalPos);
    1044       16004 :             osPendingValue = rawLine.substr(equalPos + 1);
    1045       16004 :             osPendingKey.Trim();
    1046       16004 :             osPendingValue.Trim();
    1047             :         }
    1048           0 :         else if (!osPendingKey.empty())
    1049             :         {
    1050           0 :             osPendingValue += "\n" + rawLine;
    1051             :         }
    1052             :     }
    1053             : 
    1054             :     // Saves last key
    1055         184 :     if (!osPendingKey.empty())
    1056             :     {
    1057             :         CPLString fullKey =
    1058         368 :             osCurrentSection.replaceAll(":", IntraSecKeySeparator) +
    1059         552 :             SecKeySeparator + osPendingKey;
    1060         184 :         if (!isExcluded(osCurrentSection, osPendingKey))
    1061             :         {
    1062         150 :             if (osPendingValue.Trim().empty())
    1063           3 :                 osPendingValue = MMEmptyValue;
    1064         150 :             poDS->SetMetadataItem(
    1065         150 :                 fullKey.c_str(), osPendingValue.Trim().c_str(), MetadataDomain);
    1066             :         }
    1067             :     }
    1068             : }
    1069             : 
    1070           3 : CPLErr MMRRel::UpdateGDALColorEntryFromBand(const CPLString &m_osBandSection,
    1071             :                                             GDALColorEntry &m_sConstantColorRGB)
    1072             : {
    1073             :     // Example: Color_Smb=(255,0,255)
    1074           6 :     CPLString os_Color_Smb;
    1075           3 :     if (!GetMetadataValue(SECTION_COLOR_TEXT, m_osBandSection, "Color_Smb",
    1076             :                           os_Color_Smb))
    1077           0 :         return CE_None;
    1078             : 
    1079           3 :     os_Color_Smb.replaceAll(" ", "");
    1080           6 :     if (!os_Color_Smb.empty() && os_Color_Smb.size() >= 7 &&
    1081           6 :         os_Color_Smb[0] == '(' && os_Color_Smb[os_Color_Smb.size() - 1] == ')')
    1082             :     {
    1083           3 :         os_Color_Smb.replaceAll("(", "");
    1084           3 :         os_Color_Smb.replaceAll(")", "");
    1085           3 :         const CPLStringList aosTokens(CSLTokenizeString2(os_Color_Smb, ",", 0));
    1086           3 :         if (CSLCount(aosTokens) != 3)
    1087             :         {
    1088           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1089             :                      "Invalid constant color: \"%s\"", GetRELNameChar());
    1090           0 :             return CE_Failure;
    1091             :         }
    1092             : 
    1093             :         int nIColor0;
    1094           3 :         if (1 != sscanf(aosTokens[0], "%d", &nIColor0))
    1095             :         {
    1096           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1097             :                      "Invalid constant color: \"%s\"", GetRELNameChar());
    1098           0 :             return CE_Failure;
    1099             :         }
    1100             : 
    1101             :         int nIColor1;
    1102           3 :         if (1 != sscanf(aosTokens[1], "%d", &nIColor1))
    1103             :         {
    1104           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1105             :                      "Invalid constant color: \"%s\"", GetRELNameChar());
    1106           0 :             return CE_Failure;
    1107             :         }
    1108             : 
    1109             :         int nIColor2;
    1110           3 :         if (1 != sscanf(aosTokens[2], "%d", &nIColor2))
    1111             :         {
    1112           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1113             :                      "Invalid constant color: \"%s\"", GetRELNameChar());
    1114           0 :             return CE_Failure;
    1115             :         }
    1116             : 
    1117           3 :         m_sConstantColorRGB.c1 = static_cast<short>(nIColor0);
    1118           3 :         m_sConstantColorRGB.c2 = static_cast<short>(nIColor1);
    1119           3 :         m_sConstantColorRGB.c3 = static_cast<short>(nIColor2);
    1120             :     }
    1121           3 :     return CE_None;
    1122             : }
    1123             : 
    1124             : /************************************************************************/
    1125             : /*                             Writing part                             */
    1126             : /************************************************************************/
    1127             : 
    1128         126 : void MMRRel::UpdateLineage(CSLConstList papszOptions, GDALDataset &oSrcDS)
    1129             : {
    1130         126 :     m_osInFile = oSrcDS.GetDescription();
    1131         126 :     m_osOutFile = m_osRelFileName;
    1132             : 
    1133         126 :     m_aosOptions = papszOptions;
    1134         126 : }
    1135             : 
    1136         126 : bool MMRRel::Write(GDALDataset &oSrcDS)
    1137             : {
    1138             :     // REL File creation
    1139         126 :     if (!CreateRELFile())
    1140           3 :         return false;
    1141             : 
    1142         123 :     AddRELVersion();
    1143             : 
    1144             :     // Writing METADADES section
    1145         123 :     WriteMETADADES();  // It fills m_szFileIdentifier
    1146             : 
    1147             :     // Writing IDENTIFICATION section
    1148         123 :     WriteIDENTIFICATION();
    1149             : 
    1150             :     // Writing OVERVIEW:ASPECTES_TECNICS
    1151         123 :     WriteOVERVIEW_ASPECTES_TECNICS(oSrcDS);
    1152             : 
    1153             :     // Writing SPATIAL_REFERENCE_SYSTEM:HORIZONTAL
    1154         123 :     WriteSPATIAL_REFERENCE_SYSTEM_HORIZONTAL();
    1155             : 
    1156             :     // Writing EXTENT section
    1157         123 :     WriteEXTENT();
    1158             : 
    1159             :     // Writing OVERVIEW section
    1160         123 :     WriteOVERVIEW();
    1161             : 
    1162             :     // Writing ATTRIBUTE_DATA section
    1163         123 :     if (!WriteATTRIBUTE_DATA(oSrcDS))
    1164             :     {
    1165           0 :         CloseRELFile();
    1166           0 :         return false;
    1167             :     }
    1168             : 
    1169             :     // Writing visualization sections
    1170         123 :     WriteCOLOR_TEXTSection();
    1171         123 :     WriteVISU_LLEGENDASection();
    1172             : 
    1173             :     // Writing lineage
    1174         123 :     WriteLINEAGE(oSrcDS);
    1175             : 
    1176         123 :     CloseRELFile();
    1177         123 :     return true;
    1178             : }
    1179             : 
    1180             : // Writes METADADES section
    1181         123 : void MMRRel::WriteMETADADES()
    1182             : {
    1183         123 :     if (!GetRELFile())
    1184           0 :         return;
    1185             : 
    1186         123 :     AddSectionStart(SECTION_METADADES);
    1187         123 :     AddKeyValue(KEY_language, KEY_Value_eng);
    1188         123 :     AddKeyValue(KEY_MDIdiom, KEY_Value_eng);
    1189             : 
    1190             :     char aMessage[MM_MESSAGE_LENGTH];
    1191         246 :     CPLString osFileName = CPLGetBasenameSafe(m_osRelFileName);
    1192         123 :     CPLStrlcpy(aMessage, osFileName, sizeof(aMessage));
    1193         123 :     MMGenerateFileIdentifierFromMetadataFileName(aMessage, m_szFileIdentifier);
    1194             : 
    1195         123 :     AddKeyValue(KEY_FileIdentifier, m_szFileIdentifier);
    1196         123 :     AddKeyValue(KEY_characterSet, KEY_Value_characterSet);
    1197         123 :     AddSectionEnd();
    1198             : }
    1199             : 
    1200             : // Writes IDENTIFICATION section
    1201         123 : void MMRRel::WriteIDENTIFICATION()
    1202             : {
    1203         123 :     if (!GetRELFile())
    1204           0 :         return;
    1205             : 
    1206         123 :     AddSectionStart(SECTION_IDENTIFICATION);
    1207         123 :     AddKeyValue(KEY_code, m_szFileIdentifier);
    1208         123 :     AddKey(KEY_codeSpace);
    1209             : 
    1210         123 :     if (!m_osTitle.empty())
    1211         123 :         AddKeyValue(KEY_DatasetTitle, m_osTitle);
    1212         123 :     AddSectionEnd();
    1213             : }
    1214             : 
    1215             : // Writes OVERVIEW:ASPECTES_TECNICS section
    1216         123 : void MMRRel::WriteOVERVIEW_ASPECTES_TECNICS(GDALDataset &oSrcDS)
    1217             : {
    1218         123 :     if (!GetRELFile())
    1219           0 :         return;
    1220             : 
    1221         123 :     if (m_nWidth && m_nHeight)
    1222             :     {
    1223         123 :         AddSectionStart(SECTION_OVERVIEW, SECTION_ASPECTES_TECNICS);
    1224         123 :         AddKeyValue("columns", m_nWidth);
    1225         123 :         AddKeyValue("rows", m_nHeight);
    1226             : 
    1227         123 :         WriteMetadataInComments(oSrcDS);
    1228             : 
    1229         123 :         AddSectionEnd();
    1230         123 :         m_bDimAlreadyWritten = TRUE;
    1231             :     }
    1232             : }
    1233             : 
    1234         123 : void MMRRel::WriteMetadataInComments(GDALDataset &oSrcDS)
    1235             : {
    1236             :     // Writing domain MIRAMON metadata to a section in REL that is used to
    1237             :     // add comments. It's could be useful if some raster contains METADATA from
    1238             :     // MiraMon and an expert user wants to recover and use it.
    1239         123 :     const CSLConstList aosMiraMonMetaData(oSrcDS.GetMetadata(MetadataDomain));
    1240         123 :     int nComment = 1;
    1241         246 :     CPLString osValue;
    1242         246 :     CPLString osCommentValue;
    1243             :     size_t nPos, nPos2;
    1244         246 :     CPLString osRecoveredMD;
    1245         123 :     if (oSrcDS.GetDescription())
    1246             :         osRecoveredMD =
    1247             :             CPLSPrintf("Recovered MIRAMON domain metadata from '%s'",
    1248         123 :                        oSrcDS.GetDescription());
    1249             :     else
    1250           0 :         osRecoveredMD = "Recovered MIRAMON domain metadata";
    1251             : 
    1252         246 :     CPLString osQUALITYLINEAGE = "QUALITY";
    1253         123 :     osQUALITYLINEAGE.append(IntraSecKeySeparator);
    1254         123 :     osQUALITYLINEAGE.append("LINEAGE");
    1255             : 
    1256         246 :     CPLString osCommenti;
    1257         290 :     for (const auto &[pszKey, pszValue] :
    1258         413 :          cpl::IterateNameValue(aosMiraMonMetaData))
    1259             :     {
    1260         145 :         osCommenti = CPLSPrintf("comment%d", nComment);
    1261             : 
    1262         145 :         CPLString osAux = pszKey;
    1263         145 :         nPos = osAux.find(SecKeySeparator);
    1264         145 :         if (nPos == std::string::npos)
    1265           0 :             continue;
    1266             : 
    1267         145 :         CPLString osSection = osAux.substr(0, nPos);
    1268             : 
    1269             :         // Section lineage is written in another section
    1270         145 :         nPos2 = osSection.find(osQUALITYLINEAGE);
    1271         145 :         if (nPos2 != std::string::npos)
    1272         119 :             continue;
    1273             : 
    1274          26 :         osSection.replaceAll(IntraSecKeySeparator, ":");
    1275             :         osCommentValue = CPLSPrintf(
    1276             :             "[%s]->%s=%s", osSection.c_str(),
    1277          26 :             osAux.substr(nPos + strlen(SecKeySeparator)).c_str(), pszValue);
    1278             : 
    1279          26 :         if (!osRecoveredMD.empty())
    1280             :         {
    1281           1 :             AddKeyValue(osCommenti, osRecoveredMD);
    1282           1 :             osCommenti = CPLSPrintf("comment%d", ++nComment);
    1283           1 :             osRecoveredMD.clear();
    1284             :         }
    1285             : 
    1286          26 :         AddKeyValue(osCommenti, osCommentValue);
    1287          26 :         nComment++;
    1288             :     }
    1289         123 : }
    1290             : 
    1291             : // Writes SPATIAL_REFERENCE_SYSTEM:HORIZONTAL section
    1292         123 : void MMRRel::WriteSPATIAL_REFERENCE_SYSTEM_HORIZONTAL()
    1293             : {
    1294         123 :     if (!GetRELFile())
    1295           0 :         return;
    1296             : 
    1297         123 :     AddSectionStart(SECTION_SPATIAL_REFERENCE_SYSTEM, SECTION_HORIZONTAL);
    1298             :     char aMMIDSRS[MM_MAX_ID_SNY];
    1299         221 :     if (!ReturnMMIDSRSFromEPSGCodeSRS(m_osEPSG, aMMIDSRS) &&
    1300          98 :         !MMIsEmptyString(aMMIDSRS))
    1301             :     {
    1302          98 :         AddKeyValue(KEY_HorizontalSystemIdentifier, aMMIDSRS);
    1303             :     }
    1304             :     else
    1305             :     {
    1306          25 :         CPLError(CE_Warning, CPLE_NotSupported,
    1307             :                  "The MiraMon driver cannot assign any HRS.");
    1308             :         // Horizontal Reference System
    1309          25 :         AddKeyValue(KEY_HorizontalSystemIdentifier, "plane");
    1310          25 :         AddKeyValue(KEY_HorizontalSystemDefinition, "local");
    1311             :     }
    1312         123 :     AddSectionEnd();
    1313             : }
    1314             : 
    1315             : // Writes EXTENT section
    1316         123 : void MMRRel::WriteEXTENT()
    1317             : {
    1318         123 :     if (!GetRELFile())
    1319           0 :         return;
    1320             : 
    1321         123 :     AddSectionStart(SECTION_EXTENT);
    1322         123 :     if (m_dfMinX != MM_UNDEFINED_STATISTICAL_VALUE &&
    1323         108 :         m_dfMaxX != -MM_UNDEFINED_STATISTICAL_VALUE &&
    1324         108 :         m_dfMinY != MM_UNDEFINED_STATISTICAL_VALUE &&
    1325         108 :         m_dfMaxY != -MM_UNDEFINED_STATISTICAL_VALUE)
    1326             :     {
    1327         108 :         AddKeyValue(KEY_MinX, m_dfMinX);
    1328         108 :         AddKeyValue(KEY_MaxX, m_dfMaxX);
    1329         108 :         AddKeyValue(KEY_MinY, m_dfMinY);
    1330         108 :         AddKeyValue(KEY_MaxY, m_dfMaxY);
    1331             :     }
    1332         123 :     AddKeyValue(KEY_toler_env, 0);
    1333         123 :     AddSectionEnd();
    1334             : }
    1335             : 
    1336             : // Writes OVERVIEW section
    1337         123 : void MMRRel::WriteOVERVIEW()
    1338             : {
    1339         123 :     if (!GetRELFile())
    1340           0 :         return;
    1341             : 
    1342         123 :     AddSectionStart(SECTION_OVERVIEW);
    1343         123 :     time_t currentTime = time(nullptr);
    1344             :     struct tm ltime;
    1345         123 :     VSILocalTime(&currentTime, &ltime);
    1346             :     char aTimeString[200];
    1347         123 :     snprintf(aTimeString, sizeof(aTimeString), "%04d%02d%02d %02d%02d%02d%02d",
    1348         123 :              ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday,
    1349             :              ltime.tm_hour, ltime.tm_min, ltime.tm_sec, 0);
    1350         123 :     AddKeyValue(KEY_CreationDate, aTimeString);
    1351         123 :     AddKeyValue(KEY_CoverageContentType, "001");  // Raster image
    1352         123 :     AddSectionEnd();
    1353             : }
    1354             : 
    1355             : // Writes ATTRIBUTE_DATA section
    1356         123 : bool MMRRel::WriteATTRIBUTE_DATA(GDALDataset &oSrcDS)
    1357             : {
    1358         123 :     if (!GetRELFile())
    1359           0 :         return false;
    1360             : 
    1361         123 :     if (!m_nBands)
    1362           0 :         return false;
    1363             : 
    1364         123 :     AddSectionStart(SECTION_ATTRIBUTE_DATA);
    1365             : 
    1366         246 :     const CPLString osDSDataType = m_oBands[0].GetRELDataType();
    1367         123 :     if (osDSDataType.empty())
    1368           0 :         return false;
    1369             : 
    1370         123 :     AddKeyValue("TipusCompressio", osDSDataType);
    1371         123 :     AddKeyValue("TipusRelacio", "RELACIO_1_1_DICC");
    1372             : 
    1373             :     // TractamentVariable by default
    1374             :     m_osDefTractVariable =
    1375         123 :         m_oBands[0].IsCategorical() ? "Categoric" : "QuantitatiuContinu";
    1376         123 :     AddKeyValue(KEY_TractamentVariable, m_osDefTractVariable);
    1377             : 
    1378             :     // Units by default
    1379         123 :     m_osDefUnits = m_oBands[0].GetUnits();
    1380         123 :     if (m_osDefUnits.empty())
    1381         123 :         AddKeyValue(KEY_MostrarUnitats, "0");
    1382             :     else
    1383           0 :         AddKeyValue(KEY_unitats, m_osDefUnits);
    1384             : 
    1385             :     // Key_IndexesNomsCamps
    1386         246 :     CPLString osIndexsNomsCamps = "1";
    1387         246 :     CPLString osIndex;
    1388         159 :     for (int nIBand = 1; nIBand < m_nBands; nIBand++)
    1389             :     {
    1390          36 :         osIndex = CPLSPrintf(",%d", nIBand + 1);
    1391          36 :         osIndexsNomsCamps.append(osIndex);
    1392             :     }
    1393         123 :     AddKeyValue(Key_IndexesNomsCamps, osIndexsNomsCamps);
    1394             : 
    1395             :     // Writing bands names
    1396         246 :     CPLString osIndexKey, osIndexValue;
    1397         282 :     for (int nIBand = 0; nIBand < m_nBands; nIBand++)
    1398             :     {
    1399         159 :         osIndexKey = CPLSPrintf("NomCamp_%d", nIBand + 1);
    1400         159 :         osIndexValue = m_oBands[nIBand].GetBandSection();
    1401         159 :         AddKeyValue(osIndexKey, osIndexValue);
    1402             :     }
    1403         123 :     AddSectionEnd();
    1404             : 
    1405             :     // Writing bands sections: particular band information
    1406         282 :     for (int nIBand = 0; nIBand < m_nBands; nIBand++)
    1407             :     {
    1408             :         // Writing IMG binary file. This calculates min and max values of the band
    1409         159 :         if (!m_oBands[nIBand].WriteBandFile(oSrcDS, m_nBands, nIBand))
    1410           0 :             return false;
    1411             : 
    1412             :         // Adding band information to REL. This writes min max values of the band
    1413         159 :         WriteBandSection(m_oBands[nIBand], osDSDataType);
    1414             :     }
    1415             : 
    1416         123 :     return true;
    1417             : }
    1418             : 
    1419         159 : void MMRRel::WriteBandSection(const MMRBand &osBand,
    1420             :                               const CPLString &osDSDataType)
    1421             : {
    1422         159 :     if (osBand.GetBandSection().empty())
    1423           0 :         return;  // It's not an error.
    1424             : 
    1425         318 :     CPLString osSection = "ATTRIBUTE_DATA";
    1426         159 :     osSection.append(":");
    1427         159 :     osSection.append(osBand.GetBandSection());
    1428             : 
    1429         159 :     AddSectionStart(osSection);
    1430         318 :     CPLString osDataType = osBand.GetRELDataType();
    1431         159 :     if (!EQUAL(osDSDataType, osDataType))
    1432           0 :         AddKeyValue("TipusCompressio", osDataType);
    1433             : 
    1434             :     // TractamentVariable of the band (only written if different from default)
    1435             :     CPLString osTractVariable =
    1436         318 :         osBand.IsCategorical() ? "Categoric" : "QuantitatiuContinu";
    1437         159 :     if (!EQUAL(m_osDefTractVariable, osTractVariable))
    1438           0 :         AddKeyValue(KEY_TractamentVariable, osTractVariable);
    1439             : 
    1440             :     // Units of the band (only written if different from default)
    1441         318 :     CPLString osUnits = osBand.GetUnits();
    1442         159 :     if (!EQUAL(m_osDefUnits, osUnits))
    1443             :     {
    1444          24 :         if (osUnits.empty())
    1445           0 :             AddKeyValue(KEY_MostrarUnitats, "0");
    1446             :         else
    1447             :         {
    1448          24 :             AddKeyValue(KEY_MostrarUnitats, "1");
    1449          24 :             AddKeyValue(KEY_unitats, osUnits);
    1450             :         }
    1451             :     }
    1452             : 
    1453             :     // If there is need of NomFitxer this is the place to wrote it.
    1454         159 :     if (!osBand.GetRawBandFileName().empty() && m_bNeedOfNomFitxer)
    1455         101 :         AddKeyValue(KEY_NomFitxer, osBand.GetRawBandFileName());
    1456             : 
    1457             :     // Description
    1458         159 :     if (!osBand.GetFriendlyDescription().empty())
    1459           0 :         AddKeyValue(KEY_descriptor, osBand.GetFriendlyDescription());
    1460             : 
    1461         159 :     if (osBand.BandHasNoData())
    1462             :     {
    1463         120 :         AddKeyValue("NODATA", osBand.GetNoDataValue());
    1464         120 :         AddKeyValue("NODATADef", "NODATA");
    1465             : 
    1466         120 :         if (osBand.GetMin() == osBand.GetNoDataValue())
    1467           0 :             AddKeyValue("min", osBand.GetMin() + 1);
    1468             :         else
    1469         120 :             AddKeyValue("min", osBand.GetMin());
    1470             : 
    1471         120 :         if (osBand.GetMax() == osBand.GetNoDataValue())
    1472           0 :             AddKeyValue("max", osBand.GetMax() - 1);
    1473             :         else
    1474         120 :             AddKeyValue("max", osBand.GetMax());
    1475             :     }
    1476             :     else
    1477             :     {
    1478          39 :         AddKeyValue("min", osBand.GetMin());
    1479          39 :         AddKeyValue("max", osBand.GetMax());
    1480             :     }
    1481             : 
    1482         318 :     CPLString osIJT = "";
    1483         159 :     if (!osBand.GetAttributeTableDBFNameFile().empty())
    1484             :     {
    1485          26 :         osIJT = osBand.GetBandSection();
    1486          26 :         osIJT.append("_DBF");
    1487          26 :         AddKeyValue("IndexsJoinTaula", osIJT);
    1488             : 
    1489          52 :         CPLString osJT = "JoinTaula_";
    1490          26 :         osJT.append(osIJT);
    1491          26 :         AddKeyValue(osJT, osIJT);
    1492             :     }
    1493         159 :     AddSectionEnd();
    1494             : 
    1495         159 :     if (!osIJT.empty())
    1496             :     {
    1497          52 :         CPLString osTAULA_SECTION = "TAULA_";
    1498          26 :         osTAULA_SECTION.append(osIJT);
    1499          26 :         AddSectionStart(osTAULA_SECTION);
    1500          26 :         AddKeyValue(KEY_NomFitxer,
    1501          26 :                     CPLGetFilename(osBand.GetAttributeTableRELNameFile()));
    1502          26 :         AddSectionEnd();
    1503             :     }
    1504             : }
    1505             : 
    1506         159 : CPLString MMRRel::GetColor_TractamentVariable(int nIBand) const
    1507             : {
    1508         159 :     if (m_oBands[nIBand].IsCategorical())
    1509          27 :         return "Categoric";
    1510             :     else
    1511         132 :         return "QuantitatiuContinu";
    1512             : }
    1513             : 
    1514         159 : CPLString MMRRel::GetColor_Paleta(int nIBand) const
    1515             : {
    1516         159 :     if (!m_oBands[nIBand].GetColorTableNameFile().empty())
    1517             :     {
    1518          30 :         CPLString osRELPath = CPLGetPathSafe(m_osRelFileName);
    1519             :         return CPLExtractRelativePath(
    1520          15 :             osRELPath, m_oBands[nIBand].GetColorTableNameFile(), nullptr);
    1521             :     }
    1522             :     else
    1523         144 :         return "<Automatic>";
    1524             : }
    1525             : 
    1526         123 : void MMRRel::WriteCOLOR_TEXTSection()
    1527             : {
    1528         123 :     if (!GetRELFile())
    1529           0 :         return;
    1530             : 
    1531         123 :     if (!m_nBands)
    1532           0 :         return;
    1533             : 
    1534         123 :     AddSectionStart(SECTION_COLOR_TEXT);
    1535         123 :     AddCOLOR_TEXTVersion();
    1536         123 :     AddKeyValue("UnificVisCons", 1);
    1537         123 :     AddKeyValue("visualitzable", 1);
    1538         123 :     AddKeyValue("consultable", 1);
    1539         123 :     AddKeyValue("EscalaMaxima", 0);
    1540         123 :     AddKeyValue("EscalaMinima", 900000000);
    1541         123 :     AddKeyValue("Color_Const", 0);
    1542             : 
    1543             :     // Setting the default values of "DefaultTractamentVariable"
    1544             :     CPLString osDefaultColor_TractamentVariable =
    1545         246 :         GetColor_TractamentVariable(0);
    1546         123 :     AddKeyValue("Color_TractamentVariable", osDefaultColor_TractamentVariable);
    1547             : 
    1548             :     // Setting the default values of "Color_Paleta"
    1549         246 :     CPLString os_DefaultColor_Paleta = GetColor_Paleta(0);
    1550         123 :     AddKeyValue("Color_Paleta", os_DefaultColor_Paleta);
    1551             : 
    1552         123 :     AddKeyValue("Tooltips_Const", 1);
    1553         123 :     AddSectionEnd();
    1554             : 
    1555             :     // Different key values from the first one in a new section
    1556             :     // Band 0 has been already documented. Let's check other bands.
    1557         159 :     for (int nIBand = 1; nIBand < m_nBands; nIBand++)
    1558             :     {
    1559          72 :         CPLString osSection = SECTION_COLOR_TEXT;
    1560          36 :         osSection.append(":");
    1561          36 :         osSection.append(m_oBands[nIBand].GetBandSection());
    1562          36 :         bool bSectionStarted = false;
    1563             : 
    1564             :         CPLString osColor_TractamentVariable =
    1565          72 :             GetColor_TractamentVariable(nIBand);
    1566          36 :         if (!EQUAL(osDefaultColor_TractamentVariable,
    1567             :                    osColor_TractamentVariable))
    1568             :         {
    1569           0 :             AddSectionStart(osSection);
    1570           0 :             bSectionStarted = true;
    1571           0 :             AddKeyValue("Color_TractamentVariable", osColor_TractamentVariable);
    1572             :         }
    1573             : 
    1574          72 :         CPLString osColor_Paleta = GetColor_Paleta(nIBand);
    1575          36 :         if (!EQUAL(os_DefaultColor_Paleta, osColor_Paleta))
    1576             :         {
    1577           0 :             if (!bSectionStarted)
    1578           0 :                 AddSectionStart(osSection);
    1579           0 :             AddKeyValue("Color_Paleta", osColor_Paleta);
    1580             :         }
    1581             : 
    1582          36 :         AddSectionEnd();
    1583             :     }
    1584             : }
    1585             : 
    1586         123 : void MMRRel::WriteVISU_LLEGENDASection()
    1587             : {
    1588         123 :     AddSectionStart(SECTION_VISU_LLEGENDA);
    1589             : 
    1590         123 :     AddVISU_LLEGENDAVersion();
    1591         123 :     AddKeyValue("Color_VisibleALleg", 1);
    1592         123 :     AddKeyValue("Color_TitolLlegenda", "");
    1593         123 :     AddKeyValue("Color_CategAMostrar", "N");
    1594         123 :     AddKeyValue("Color_InvertOrdPresentColorLleg", 0);
    1595         123 :     AddKeyValue("Color_MostrarIndColorLleg", 0);
    1596         123 :     AddKeyValue("Color_MostrarValColorLleg", 0);
    1597         123 :     AddKeyValue("Color_MostrarCatColorLleg", 1);
    1598         123 :     AddKeyValue("Color_MostrarNODATA", 0);
    1599         123 :     AddKeyValue("Color_MostrarEntradesBuides", 0);
    1600         123 :     AddKeyValue("Color_NovaColumnaLlegImpresa", 0);
    1601             : 
    1602         123 :     AddSectionEnd();
    1603         123 : }
    1604             : 
    1605         123 : void MMRRel::WriteLINEAGE(GDALDataset &oSrcDS)
    1606             : {
    1607         123 :     ImportAndWriteLineageSection(oSrcDS);
    1608         123 :     WriteCurrentProcess();
    1609         123 :     EndProcessesSection();
    1610         123 : }
    1611             : 
    1612         123 : void MMRRel::EndProcessesSection()
    1613             : {
    1614         123 :     if (!m_nNProcesses)
    1615           0 :         return;
    1616         123 :     AddSectionStart(SECTION_QUALITY_LINEAGE);
    1617         123 :     AddKeyValue("processes", m_osListOfProcesses);
    1618         123 :     AddSectionEnd();
    1619             : }
    1620             : 
    1621         123 : void MMRRel::WriteCurrentProcess()
    1622             : {
    1623             :     // Checking the current processes. If it's too much,
    1624             :     // then we cannot write lineage information.
    1625         123 :     if (nILastProcess >= INT_MAX - 1)
    1626           0 :         return;
    1627             : 
    1628         123 :     nILastProcess++;
    1629         246 :     CPLString osCurrentProcess = CPLSPrintf("%d", nILastProcess);
    1630             : 
    1631         123 :     CPLString osSection = SECTION_QUALITY_LINEAGE;
    1632         123 :     osSection.append(":PROCESS");
    1633         123 :     osSection.append(osCurrentProcess);
    1634             : 
    1635         123 :     AddSectionStart(osSection);
    1636         123 :     AddKeyValue("purpose", "GDAL process");
    1637             : 
    1638             :     struct tm ltime;
    1639             :     char aTimeString[200];
    1640         123 :     time_t currentTime = time(nullptr);
    1641             : 
    1642         123 :     VSILocalTime(&currentTime, &ltime);
    1643         123 :     snprintf(aTimeString, sizeof(aTimeString), "%04d%02d%02d %02d%02d%02d%02d",
    1644         123 :              ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday,
    1645             :              ltime.tm_hour, ltime.tm_min, ltime.tm_sec, 0);
    1646             : 
    1647         123 :     AddKeyValue("date", aTimeString);
    1648         123 :     AddKeyValue("NomFitxer", "gdal");
    1649         123 :     AddSectionEnd();
    1650             : 
    1651         123 :     int nInOut = 1;
    1652         123 :     if (!m_osInFile.empty())
    1653             :     {
    1654           1 :         WriteINOUTSection(osSection, nInOut, "InFile", "", "C", m_osInFile);
    1655           1 :         nInOut++;
    1656             :     }
    1657             : 
    1658         123 :     if (!m_osOutFile.empty())
    1659             :     {
    1660         123 :         WriteINOUTSection(osSection, nInOut, "OutFile", "1", "C", m_osOutFile);
    1661         123 :         nInOut++;
    1662             :     }
    1663             : 
    1664         283 :     for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(m_aosOptions))
    1665             :     {
    1666         160 :         CPLString osIdentifierValue = "-co ";
    1667         160 :         osIdentifierValue.append(pszKey);
    1668         160 :         WriteINOUTSection(osSection, nInOut, osIdentifierValue, "", "C",
    1669             :                           pszValue);
    1670         160 :         nInOut++;
    1671             :     }
    1672         123 :     if (m_nNProcesses)
    1673           1 :         m_osListOfProcesses.append(",");
    1674             : 
    1675         123 :     m_osListOfProcesses.append(osCurrentProcess);
    1676         123 :     m_nNProcesses++;
    1677             : }
    1678             : 
    1679         284 : void MMRRel::WriteINOUTSection(const CPLString &osSection, int nInOut,
    1680             :                                const CPLString &osIdentifierValue,
    1681             :                                const CPLString &osSentitValue,
    1682             :                                const CPLString &osTypeValuesValue,
    1683             :                                const CPLString &osResultValueValue)
    1684             : {
    1685         568 :     CPLString osSectionIn = osSection;
    1686         284 :     osSectionIn.append(":INOUT");
    1687         284 :     osSectionIn.append(CPLSPrintf("%d", nInOut));
    1688             : 
    1689         284 :     AddSectionStart(osSectionIn);
    1690         284 :     if (!osIdentifierValue.empty())
    1691         284 :         AddKeyValue("identifier", osIdentifierValue);
    1692         284 :     if (!osSentitValue.empty())
    1693         123 :         AddKeyValue("sentit", osSentitValue);
    1694         284 :     if (!osTypeValuesValue.empty())
    1695         284 :         AddKeyValue("TypeValues", osTypeValuesValue);
    1696         284 :     if (!osResultValueValue.empty())
    1697         284 :         AddKeyValue("ResultValue", osResultValueValue);
    1698         284 :     AddKeyValue("ResultUnits", "");
    1699         284 :     AddSectionEnd();
    1700         284 : }
    1701             : 
    1702             : // This function imports lineage information from the source dataset
    1703             : // and writes it in rel file (QUALITY:LINEAGE section).
    1704         123 : void MMRRel::ImportAndWriteLineageSection(GDALDataset &oSrcDS)
    1705             : {
    1706         123 :     CPLStringList aosMiraMonMetaData(oSrcDS.GetMetadata(MetadataDomain));
    1707         123 :     if (aosMiraMonMetaData.empty())
    1708         122 :         return;
    1709             : 
    1710           1 :     m_nNProcesses = 0;
    1711             : 
    1712             :     CPLString osLineageProcessesKey = CPLSPrintf(
    1713           1 :         "QUALITY%sLINEAGE%sprocesses", IntraSecKeySeparator, SecKeySeparator);
    1714             :     CPLString osListOfProcesses =
    1715           1 :         CSLFetchNameValueDef(aosMiraMonMetaData, osLineageProcessesKey, "");
    1716           1 :     if (osListOfProcesses.empty())
    1717           0 :         return;
    1718             : 
    1719             :     const CPLStringList aosTokens(
    1720           1 :         CSLTokenizeString2(osListOfProcesses, ",", 0));
    1721           1 :     const int nTokens = CSLCount(aosTokens);
    1722             :     // No processes to import
    1723           1 :     if (nTokens == 0)
    1724           0 :         return;
    1725             : 
    1726             :     // Too much processes, it's not possible.
    1727           1 :     if (nTokens >= INT_MAX)
    1728           0 :         return;
    1729             : 
    1730             :     // Getting the list of metadata in MIRAMON domain and
    1731             :     // converting to CPLStringList for sorting (necessary to write into the REL)
    1732           2 :     CPLStringList aosMiraMonSortedMetaData(oSrcDS.GetMetadata(MetadataDomain));
    1733           1 :     aosMiraMonSortedMetaData.Sort();
    1734             : 
    1735             :     int nIProcess;
    1736           1 :     m_nNProcesses = 0;
    1737           1 :     int nLastValidIndex = -1;
    1738           4 :     for (nIProcess = 0; nIProcess < nTokens; nIProcess++)
    1739             :     {
    1740             :         CPLString osProcessSection =
    1741             :             CPLSPrintf("QUALITY%sLINEAGE%sPROCESS%s", IntraSecKeySeparator,
    1742           3 :                        IntraSecKeySeparator, aosTokens[nIProcess]);
    1743           3 :         if (!ProcessProcessSection(aosMiraMonSortedMetaData, osProcessSection))
    1744           0 :             break;  // If some section have a problem, we stop reading the lineage.
    1745             : 
    1746           3 :         nLastValidIndex = nIProcess;
    1747           3 :         if (m_nNProcesses)
    1748           2 :             m_osListOfProcesses.append(",");
    1749           3 :         m_osListOfProcesses.append(CPLSPrintf("%s", aosTokens[nIProcess]));
    1750           3 :         m_nNProcesses++;
    1751             :     }
    1752             : 
    1753           1 :     if (nLastValidIndex >= 0)
    1754             :     {
    1755           1 :         if (1 != sscanf(aosTokens[nLastValidIndex], "%d", &nILastProcess))
    1756           0 :             nILastProcess = 0;
    1757             :     }
    1758             : }
    1759             : 
    1760             : // This function processes a process section and its eventual subsections.
    1761             : // It returns true if it has found the section and processed it, false otherwise.
    1762           3 : bool MMRRel::ProcessProcessSection(
    1763             :     const CPLStringList &aosMiraMonSortedMetaData,
    1764             :     const CPLString &osProcessSection)
    1765             : {
    1766           6 :     CPLString osProcess = osProcessSection;
    1767           3 :     osProcess.append(SecKeySeparator);
    1768           3 :     osProcess.append("");
    1769             : 
    1770           6 :     CPLString osExpectedProcessKey;
    1771           3 :     const size_t nStart = strlen(osProcessSection);
    1772           3 :     const size_t nSecLen = strlen(SecKeySeparator);
    1773           3 :     const size_t nIntraSecLen = strlen(IntraSecKeySeparator);
    1774             : 
    1775             :     // Main section
    1776           3 :     bool bStartSectionDone = false;
    1777           3 :     bool bSomethingInSection = false;
    1778         870 :     for (const auto &[pszKey, pszValue] :
    1779         873 :          cpl::IterateNameValue(aosMiraMonSortedMetaData))
    1780             :     {
    1781         435 :         if (pszKey && STARTS_WITH(pszKey, osProcessSection.c_str()))
    1782             :         {
    1783         118 :             CPLString osKey = pszKey;
    1784         118 :             size_t nPos = osKey.find(SecKeySeparator);
    1785         118 :             if (nPos == std::string::npos)
    1786           0 :                 continue;
    1787             : 
    1788         118 :             if (osKey.size() < nStart + nSecLen)
    1789           0 :                 continue;
    1790         118 :             if (osKey.compare(nStart, nSecLen, SecKeySeparator) != 0)
    1791         105 :                 continue;
    1792             : 
    1793             :             // We are in the section we are looking for
    1794          13 :             if (!bStartSectionDone)
    1795             :             {
    1796           3 :                 bStartSectionDone = true;
    1797           3 :                 bSomethingInSection = true;
    1798           6 :                 CPLString osFinalSection = osKey.substr(0, nStart);
    1799           3 :                 osFinalSection.replaceAll(IntraSecKeySeparator, ":");
    1800           3 :                 AddSectionStart(osFinalSection);
    1801             :             }
    1802          13 :             AddKeyValue(osKey.substr(nStart + nSecLen), pszValue);
    1803             :         }
    1804             :     }
    1805           3 :     if (!bSomethingInSection)
    1806           0 :         return false;
    1807             : 
    1808           3 :     AddSectionEnd();
    1809             : 
    1810             :     // Subsections
    1811           3 :     bool bCloseLastSubSection = false;
    1812           3 :     CPLString osFinalSection = "";
    1813         870 :     for (const auto &[pszKey, pszValue] :
    1814         873 :          cpl::IterateNameValue(aosMiraMonSortedMetaData))
    1815             :     {
    1816         435 :         if (pszKey && STARTS_WITH(pszKey, osProcessSection.c_str()))
    1817             :         {
    1818         118 :             CPLString osKey = pszKey;
    1819         118 :             size_t nPos = osKey.find(SecKeySeparator);
    1820         118 :             if (nPos == std::string::npos)
    1821           0 :                 continue;
    1822             : 
    1823         118 :             if (osKey.size() < nStart + nIntraSecLen)
    1824           0 :                 continue;
    1825         118 :             if (osKey.compare(nStart, nIntraSecLen, IntraSecKeySeparator) != 0)
    1826           0 :                 continue;
    1827             : 
    1828             :             // It cannot be the main section
    1829         236 :             if (osKey.size() >= nStart + nSecLen &&
    1830         118 :                 osKey.compare(nStart, nSecLen, SecKeySeparator) == 0)
    1831          13 :                 continue;
    1832             : 
    1833             :             // We are in the subsection we are looking for
    1834         105 :             if (!STARTS_WITH(osFinalSection.c_str(),
    1835             :                              osKey.substr(0, nPos).c_str()))
    1836             :             {
    1837          28 :                 if (bCloseLastSubSection)
    1838          25 :                     AddSectionEnd();
    1839          28 :                 bCloseLastSubSection = true;
    1840          56 :                 CPLString osFinalSectionDecod = osKey.substr(0, nPos);
    1841          28 :                 osFinalSectionDecod.replaceAll(IntraSecKeySeparator, ":");
    1842          28 :                 AddSectionStart(osFinalSectionDecod);
    1843             :             }
    1844         105 :             AddKeyValue(osKey.substr(nPos + nSecLen), pszValue);
    1845             : 
    1846         105 :             osFinalSection = osKey.substr(0, nPos);
    1847             :         }
    1848             :     }
    1849           3 :     if (bCloseLastSubSection)
    1850           3 :         AddSectionEnd();
    1851             : 
    1852           3 :     return true;
    1853             : }

Generated by: LCOV version 1.14