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(¤tTime, <ime);
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(¤tTime, <ime);
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 : }
|