Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: PDS Driver; Planetary Data System Format
4 : * Purpose: Implementation of NASAKeywordHandler - a class to read
5 : * keyword data from PDS, ISIS2 and ISIS3 data products.
6 : * Author: Frank Warmerdam <warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2006, Frank Warmerdam <warmerdam@pobox.com>
10 : * Copyright (c) 2008-2010, Even Rouault <even dot rouault at spatialys.com>
11 : * Copyright (c) 2017 Hobu Inc
12 : * Copyright (c) 2017, Dmitry Baryshnikov <polimax@mail.ru>
13 : * Copyright (c) 2017, NextGIS <info@nextgis.com>
14 : *
15 : * SPDX-License-Identifier: MIT
16 : ****************************************************************************
17 : * Object Description Language (ODL) is used to encode data labels for PDS
18 : * and other NASA data systems. Refer to Chapter 12 of "PDS Standards
19 : * Reference" at http://pds.jpl.nasa.gov/tools/standards-reference.shtml for
20 : * further details about ODL.
21 : *
22 : * This is also known as PVL (Parameter Value Language) which is written
23 : * about at http://www.orrery.us/node/44 where it notes:
24 : *
25 : * The PVL syntax that the PDS uses is specified by the Consultative Committee
26 : * for Space Data Systems in their Blue Book publication: "Parameter Value
27 : * Language Specification (CCSD0006 and CCSD0008)", June 2000
28 : * [CCSDS 641.0-B-2], and Green Book publication: "Parameter Value Language -
29 : * A Tutorial", June 2000 [CCSDS 641.0-G-2]. PVL has also been accepted by the
30 : * International Standards Organization (ISO), as a Final Draft International
31 : * Standard (ISO 14961:2002) keyword value type language for naming and
32 : * expressing data values.
33 : * --
34 : * also of interest, on PDS ODL:
35 : * http://pds.jpl.nasa.gov/documents/sr/Chapter12.pdf
36 : *
37 : ****************************************************************************/
38 :
39 : #include "nasakeywordhandler.h"
40 : #include "ogrlibjsonutils.h"
41 : #include <vector>
42 :
43 : //! @cond Doxygen_Suppress
44 :
45 : /************************************************************************/
46 : /* ==================================================================== */
47 : /* NASAKeywordHandler */
48 : /* ==================================================================== */
49 : /************************************************************************/
50 :
51 : /************************************************************************/
52 : /* NASAKeywordHandler() */
53 : /************************************************************************/
54 :
55 477 : NASAKeywordHandler::NASAKeywordHandler()
56 477 : : pszHeaderNext(nullptr), m_bStripSurroundingQuotes(false)
57 : {
58 477 : oJSon.Deinit();
59 477 : }
60 :
61 : /************************************************************************/
62 : /* ~NASAKeywordHandler() */
63 : /************************************************************************/
64 :
65 477 : NASAKeywordHandler::~NASAKeywordHandler()
66 :
67 : {
68 477 : }
69 :
70 : /************************************************************************/
71 : /* Ingest() */
72 : /************************************************************************/
73 :
74 314 : bool NASAKeywordHandler::Ingest(VSILFILE *fp, vsi_l_offset nOffset)
75 :
76 : {
77 : /* -------------------------------------------------------------------- */
78 : /* Read in buffer till we find END all on its own line. */
79 : /* -------------------------------------------------------------------- */
80 314 : if (VSIFSeekL(fp, nOffset, SEEK_SET) != 0)
81 0 : return false;
82 :
83 628 : std::string osHeaderText;
84 : for (; true;)
85 : {
86 : char szChunk[513];
87 :
88 3658 : int nBytesRead = static_cast<int>(VSIFReadL(szChunk, 1, 512, fp));
89 :
90 3658 : szChunk[nBytesRead] = '\0';
91 3658 : osHeaderText += szChunk;
92 :
93 3658 : if (nBytesRead < 512)
94 167 : break;
95 :
96 3491 : const char *pszCheck = nullptr;
97 3491 : if (osHeaderText.size() > 520)
98 500 : pszCheck = osHeaderText.c_str() + (osHeaderText.size() - 520);
99 : else
100 2991 : pszCheck = szChunk;
101 :
102 3491 : if (strstr(pszCheck, "\r\nEND\r\n") != nullptr ||
103 3476 : strstr(pszCheck, "\nEND\n") != nullptr ||
104 3467 : strstr(pszCheck, "\r\nEnd\r\n") != nullptr ||
105 3467 : strstr(pszCheck, "\nEnd\n") != nullptr)
106 : break;
107 3344 : }
108 :
109 314 : return Parse(osHeaderText.c_str());
110 : }
111 :
112 : /************************************************************************/
113 : /* Parse() */
114 : /************************************************************************/
115 :
116 346 : bool NASAKeywordHandler::Parse(const char *pszStr)
117 :
118 : {
119 346 : pszHeaderNext = pszStr;
120 :
121 : /* -------------------------------------------------------------------- */
122 : /* Process name/value pairs, keeping track of a "path stack". */
123 : /* -------------------------------------------------------------------- */
124 346 : oJSon = CPLJSONObject();
125 346 : return ReadGroup("", oJSon, 0);
126 : }
127 :
128 : /************************************************************************/
129 : /* ReadGroup() */
130 : /************************************************************************/
131 :
132 2830 : bool NASAKeywordHandler::ReadGroup(const std::string &osPathPrefix,
133 : CPLJSONObject &oCur, int nRecLevel)
134 :
135 : {
136 2830 : if (osPathPrefix.size() > 256)
137 : {
138 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big prefix for GROUP");
139 0 : return false;
140 : }
141 2830 : if (nRecLevel == 100)
142 0 : return false;
143 : for (; true;)
144 : {
145 14935 : CPLString osName, osValue;
146 14935 : if (!ReadPair(osName, osValue, oCur))
147 3 : return false;
148 :
149 14932 : if (EQUAL(osName, "OBJECT") || EQUAL(osName, "GROUP"))
150 : {
151 2484 : CPLJSONObject oNewGroup;
152 2484 : oNewGroup.Add("_type",
153 2484 : EQUAL(osName, "OBJECT") ? "object" : "group");
154 2484 : if (!ReadGroup((osPathPrefix + osValue + ".").c_str(), oNewGroup,
155 : nRecLevel + 1))
156 : {
157 0 : return false;
158 : }
159 7452 : CPLJSONObject oName = oNewGroup["Name"];
160 2484 : if (oName.GetType() == CPLJSONObject::Type::String)
161 : {
162 285 : oCur.AddNoSplitName(osValue + "_" + oName.ToString(),
163 : oNewGroup);
164 285 : oNewGroup.Add("_container_name", osValue);
165 : }
166 2199 : else if (oCur[osValue].IsValid())
167 : {
168 4 : int nIter = 2;
169 6 : while (oCur[osValue + CPLSPrintf("_%d", nIter)].IsValid())
170 : {
171 2 : nIter++;
172 : }
173 4 : oCur.AddNoSplitName(osValue + CPLSPrintf("_%d", nIter),
174 : oNewGroup);
175 4 : oNewGroup.Add("_container_name", osValue);
176 : }
177 : else
178 : {
179 2195 : oCur.AddNoSplitName(osValue, oNewGroup);
180 : }
181 : }
182 23503 : else if (EQUAL(osName, "END") || EQUAL(osName, "END_GROUP") ||
183 11055 : EQUAL(osName, "END_OBJECT"))
184 : {
185 2827 : return true;
186 : }
187 : else
188 : {
189 9621 : osName = osPathPrefix + osName;
190 9621 : aosKeywordList.AddNameValue(osName, osValue);
191 : }
192 12105 : }
193 : }
194 :
195 : /************************************************************************/
196 : /* StripQuotesIfNeeded() */
197 : /************************************************************************/
198 :
199 5373 : static CPLString StripQuotesIfNeeded(const CPLString &osWord,
200 : bool bQuotesAlreadyRemoved)
201 : {
202 5373 : if (bQuotesAlreadyRemoved || osWord.size() < 2 || osWord[0] != '"')
203 3447 : return osWord;
204 3852 : return osWord.substr(1, osWord.size() - 2);
205 : }
206 :
207 : /************************************************************************/
208 : /* ReadPair() */
209 : /* */
210 : /* Read a name/value pair from the input stream. Strip off */
211 : /* white space, ignore comments, split on '='. */
212 : /* Returns TRUE on success. */
213 : /************************************************************************/
214 :
215 14935 : bool NASAKeywordHandler::ReadPair(CPLString &osName, CPLString &osValue,
216 : CPLJSONObject &oCur)
217 :
218 : {
219 14935 : osName = "";
220 14935 : osValue = "";
221 :
222 14935 : if (!ReadWord(osName))
223 0 : return false;
224 :
225 14935 : SkipWhite();
226 :
227 14935 : if (EQUAL(osName, "END"))
228 343 : return true;
229 :
230 14592 : if (*pszHeaderNext != '=')
231 : {
232 : // ISIS3 does not have anything after the end group/object keyword.
233 1797 : if (EQUAL(osName, "End_Group") || EQUAL(osName, "End_Object"))
234 1796 : return true;
235 :
236 1 : return false;
237 : }
238 :
239 12795 : pszHeaderNext++;
240 :
241 12795 : SkipWhite();
242 :
243 12795 : osValue = "";
244 12795 : bool bIsString = true;
245 :
246 : // Handle value lists like:
247 : // Name = (Red, Red) or {Red, Red} or even ({Red, Red}, {Red, Red})
248 25590 : CPLJSONArray oArray;
249 12795 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
250 : {
251 484 : std::vector<char> oStackArrayBeginChar;
252 484 : CPLString osWord;
253 :
254 484 : oStackArrayBeginChar.push_back(*pszHeaderNext);
255 484 : osValue += *pszHeaderNext;
256 484 : pszHeaderNext++;
257 :
258 1481 : while (ReadWord(osWord, false, true, &bIsString))
259 : {
260 1481 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
261 : {
262 3 : oStackArrayBeginChar.push_back(*pszHeaderNext);
263 3 : osValue += *pszHeaderNext;
264 3 : pszHeaderNext++;
265 : }
266 :
267 : // TODO: we could probably do better with nested json arrays
268 : // instead of flattening when there are (( )) or ({ }) constructs
269 1481 : if (bIsString)
270 : {
271 1049 : if (!(osWord.empty() &&
272 8 : (*pszHeaderNext == '(' || *pszHeaderNext == '{' ||
273 8 : *pszHeaderNext == ')' || *pszHeaderNext == '}')))
274 : {
275 : std::string osValueInArray =
276 2074 : StripQuotesIfNeeded(osWord, false);
277 1290 : if (!osValueInArray.empty() && osValueInArray == osWord &&
278 253 : osValueInArray.back() == '>')
279 : {
280 50 : const auto nPosLeftBracket = osValueInArray.rfind('<');
281 50 : if (nPosLeftBracket != std::string::npos)
282 : {
283 : const std::string osUnit = osValueInArray.substr(
284 50 : nPosLeftBracket + 1, osValueInArray.size() - 1 -
285 100 : (nPosLeftBracket + 1));
286 50 : osValueInArray.resize(nPosLeftBracket);
287 200 : while (!osValueInArray.empty() &&
288 100 : osValueInArray.back() == ' ')
289 50 : osValueInArray.pop_back();
290 :
291 100 : CPLJSONObject newObject;
292 50 : if (CPLGetValueType(osValueInArray.c_str()) ==
293 : CPL_VALUE_STRING)
294 : {
295 0 : newObject.Add("value", osValueInArray);
296 : }
297 50 : else if (CPLGetValueType(osValueInArray.c_str()) ==
298 : CPL_VALUE_INTEGER)
299 : {
300 6 : newObject.Add("value",
301 : atoi(osValueInArray.c_str()));
302 : }
303 : else
304 : {
305 44 : newObject.Add("value",
306 : CPLAtof(osValueInArray.c_str()));
307 : }
308 50 : newObject.Add("unit", osUnit);
309 50 : oArray.Add(newObject);
310 : }
311 : else
312 : {
313 0 : oArray.Add(osValueInArray);
314 : }
315 : }
316 : else
317 : {
318 987 : oArray.Add(osValueInArray);
319 : }
320 : }
321 : }
322 440 : else if (CPLGetValueType(osWord) == CPL_VALUE_INTEGER)
323 : {
324 295 : oArray.Add(atoi(osWord));
325 : }
326 : else
327 : {
328 145 : oArray.Add(CPLAtof(osWord));
329 : }
330 :
331 1481 : osValue += osWord;
332 1494 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
333 : {
334 13 : pszHeaderNext++;
335 : }
336 :
337 1481 : if (*pszHeaderNext == ')')
338 : {
339 474 : osValue += *pszHeaderNext;
340 948 : if (oStackArrayBeginChar.empty() ||
341 474 : oStackArrayBeginChar.back() != '(')
342 : {
343 1 : CPLDebug("PDS", "Unpaired ( ) for %s", osName.c_str());
344 1 : return false;
345 : }
346 473 : oStackArrayBeginChar.pop_back();
347 473 : pszHeaderNext++;
348 473 : if (oStackArrayBeginChar.empty())
349 472 : break;
350 : }
351 1007 : else if (*pszHeaderNext == '}')
352 : {
353 13 : osValue += *pszHeaderNext;
354 26 : if (oStackArrayBeginChar.empty() ||
355 13 : oStackArrayBeginChar.back() != '{')
356 : {
357 1 : CPLDebug("PDS", "Unpaired { } for %s", osName.c_str());
358 1 : return false;
359 : }
360 12 : oStackArrayBeginChar.pop_back();
361 12 : pszHeaderNext++;
362 12 : if (oStackArrayBeginChar.empty())
363 10 : break;
364 : }
365 994 : else if (*pszHeaderNext == ',')
366 : {
367 991 : osValue += *pszHeaderNext;
368 991 : pszHeaderNext++;
369 : // Do not use SkipWhite() here to avoid being confuse by
370 : // constructs like
371 : // FOO = (#123456,
372 : // #123456)
373 : // where we could confuse the second line with a comment.
374 3348 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
375 : {
376 2357 : pszHeaderNext++;
377 : }
378 : }
379 997 : SkipWhite();
380 482 : }
381 : }
382 :
383 : else // Handle more normal "single word" values.
384 : {
385 12311 : if (!ReadWord(osValue, m_bStripSurroundingQuotes, false, &bIsString))
386 0 : return false;
387 : }
388 :
389 12793 : SkipWhite();
390 :
391 20632 : const auto AddToCur = [&oCur, &osName](const CPLJSONObject &o)
392 : {
393 20618 : auto oExistingObjForName = oCur.GetObjNoSplitName(osName);
394 10309 : if (oExistingObjForName.IsValid())
395 : {
396 10 : if (oExistingObjForName["values"].GetType() ==
397 : CPLJSONObject::Type::Array)
398 : {
399 2 : oExistingObjForName["values"].ToArray().Add(o);
400 : }
401 : else
402 : {
403 16 : CPLJSONArray ar;
404 8 : ar.Add(oExistingObjForName);
405 8 : ar.Add(o);
406 16 : CPLJSONObject oObj;
407 8 : oObj.Add("values", ar);
408 8 : oCur.DeleteNoSplitName(osName);
409 8 : oCur.AddNoSplitName(osName, std::move(oObj));
410 : }
411 : }
412 : else
413 : {
414 10299 : oCur.AddNoSplitName(osName, o);
415 : }
416 10309 : };
417 :
418 : // No units keyword?
419 12793 : if (*pszHeaderNext != '<')
420 : {
421 12041 : if (!EQUAL(osName, "OBJECT") && !EQUAL(osName, "GROUP"))
422 : {
423 9557 : if (oArray.Size() > 0)
424 : {
425 478 : AddToCur(oArray);
426 : }
427 : else
428 : {
429 18158 : CPLJSONObject oObj;
430 9079 : if (bIsString)
431 : {
432 8672 : oObj = CPLJSONObject(StripQuotesIfNeeded(
433 8672 : osValue, m_bStripSurroundingQuotes));
434 : }
435 4743 : else if (CPLGetValueType(osValue) == CPL_VALUE_INTEGER)
436 : {
437 2968 : oObj = CPLJSONObject(atoi(osValue));
438 : }
439 : else
440 : {
441 1775 : oObj = CPLJSONObject(CPLAtof(osValue));
442 : }
443 9079 : AddToCur(oObj);
444 : }
445 : }
446 12041 : return true;
447 : }
448 :
449 1504 : CPLString osValueNoUnit(osValue);
450 : // Append units keyword. For lines that like like this:
451 : // MAP_RESOLUTION = 4.0 <PIXEL/DEGREE>
452 :
453 752 : osValue += " ";
454 :
455 1504 : CPLString osWord;
456 1504 : CPLString osUnit;
457 752 : while (ReadWord(osWord))
458 : {
459 752 : SkipWhite();
460 :
461 752 : osValue += osWord;
462 752 : osUnit = osWord;
463 752 : if (osWord.back() == '>')
464 752 : break;
465 : }
466 :
467 752 : if (osUnit[0] == '<')
468 752 : osUnit = osUnit.substr(1);
469 752 : if (!osUnit.empty() && osUnit.back() == '>')
470 752 : osUnit = osUnit.substr(0, osUnit.size() - 1);
471 :
472 752 : CPLJSONObject newObject;
473 :
474 752 : if (oArray.Size() > 0)
475 : {
476 2 : newObject.Add("value", oArray);
477 : }
478 : else
479 : {
480 750 : if (bIsString)
481 : {
482 10 : newObject.Add("value", osValueNoUnit);
483 : }
484 740 : else if (CPLGetValueType(osValueNoUnit) == CPL_VALUE_INTEGER)
485 : {
486 44 : newObject.Add("value", atoi(osValueNoUnit));
487 : }
488 : else
489 : {
490 696 : newObject.Add("value", CPLAtof(osValueNoUnit));
491 : }
492 : }
493 752 : newObject.Add("unit", osUnit);
494 752 : AddToCur(newObject);
495 :
496 752 : return true;
497 : }
498 :
499 : /************************************************************************/
500 : /* ReadWord() */
501 : /* Returns TRUE on success */
502 : /************************************************************************/
503 :
504 29479 : bool NASAKeywordHandler::ReadWord(CPLString &osWord,
505 : bool bStripSurroundingQuotes, bool bParseList,
506 : bool *pbIsString)
507 :
508 : {
509 29479 : if (pbIsString)
510 13792 : *pbIsString = false;
511 29479 : osWord = "";
512 :
513 29479 : SkipWhite();
514 :
515 29479 : if (!(*pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
516 29479 : !isspace(static_cast<unsigned char>(*pszHeaderNext))))
517 0 : return false;
518 :
519 : /* Extract a text string delimited by '\"' */
520 : /* Convert newlines (CR or LF) within quotes. While text strings
521 : support them as per ODL, the keyword list doesn't want them */
522 29479 : if (*pszHeaderNext == '"')
523 : {
524 1948 : if (pbIsString)
525 1948 : *pbIsString = true;
526 1948 : if (!bStripSurroundingQuotes)
527 1928 : osWord += *(pszHeaderNext);
528 1948 : pszHeaderNext++;
529 28375 : while (*pszHeaderNext != '"')
530 : {
531 26427 : if (*pszHeaderNext == '\0')
532 0 : return false;
533 26427 : if (*pszHeaderNext == '\n')
534 : {
535 121 : osWord += "\\n";
536 121 : pszHeaderNext++;
537 121 : continue;
538 : }
539 26306 : if (*pszHeaderNext == '\r')
540 : {
541 118 : osWord += "\\r";
542 118 : pszHeaderNext++;
543 118 : continue;
544 : }
545 26188 : osWord += *(pszHeaderNext++);
546 : }
547 1948 : if (!bStripSurroundingQuotes)
548 1928 : osWord += *(pszHeaderNext);
549 1948 : pszHeaderNext++;
550 :
551 1948 : return true;
552 : }
553 :
554 : /* Extract a symbol string */
555 : /* These are expected to not have
556 : '\'' (delimiters),
557 : format effectors (should fit on a single line) or
558 : control characters.
559 : */
560 27531 : if (*pszHeaderNext == '\'')
561 : {
562 8 : if (pbIsString)
563 8 : *pbIsString = true;
564 8 : if (!bStripSurroundingQuotes)
565 8 : osWord += *(pszHeaderNext);
566 8 : pszHeaderNext++;
567 32 : while (*pszHeaderNext != '\'')
568 : {
569 24 : if (*pszHeaderNext == '\0')
570 0 : return false;
571 :
572 24 : osWord += *(pszHeaderNext++);
573 : }
574 8 : if (!bStripSurroundingQuotes)
575 8 : osWord += *(pszHeaderNext);
576 8 : pszHeaderNext++;
577 8 : return true;
578 : }
579 :
580 : /*
581 : * Extract normal text. Terminated by '=' or whitespace.
582 : *
583 : * A special exception is that a line may terminate with a '-'
584 : * which is taken as a line extender, and we suck up white space to new
585 : * text.
586 : */
587 245283 : while (
588 272806 : *pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
589 270607 : ((bParseList && *pszHeaderNext != ',' && *pszHeaderNext != '(' &&
590 5661 : *pszHeaderNext != ')' && *pszHeaderNext != '{' &&
591 270607 : *pszHeaderNext != '}') ||
592 264400 : (!bParseList && !isspace(static_cast<unsigned char>(*pszHeaderNext)))))
593 : {
594 245283 : osWord += *pszHeaderNext;
595 245283 : pszHeaderNext++;
596 :
597 245283 : if (*pszHeaderNext == '-' &&
598 145 : (pszHeaderNext[1] == 10 || pszHeaderNext[1] == 13))
599 : {
600 4 : pszHeaderNext += 2;
601 4 : SkipWhite();
602 : }
603 : }
604 :
605 27523 : if (pbIsString)
606 11836 : *pbIsString = CPLGetValueType(osWord) == CPL_VALUE_STRING;
607 :
608 27523 : return true;
609 : }
610 :
611 : /************************************************************************/
612 : /* SkipWhite() */
613 : /* Skip white spaces and C style comments */
614 : /************************************************************************/
615 :
616 229882 : void NASAKeywordHandler::SkipWhite()
617 :
618 : {
619 : for (; true;)
620 : {
621 : // Skip C style comments
622 229882 : if (*pszHeaderNext == '/' && pszHeaderNext[1] == '*')
623 : {
624 336 : pszHeaderNext += 2;
625 :
626 17999 : while (*pszHeaderNext != '\0' &&
627 17999 : (*pszHeaderNext != '*' || pszHeaderNext[1] != '/'))
628 : {
629 17663 : pszHeaderNext++;
630 : }
631 336 : if (*pszHeaderNext == '\0')
632 0 : return;
633 :
634 336 : pszHeaderNext += 2;
635 :
636 : // consume till end of line.
637 : // reduce sensibility to a label error
638 336 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
639 294 : *pszHeaderNext != 13)
640 : {
641 0 : pszHeaderNext++;
642 : }
643 336 : continue;
644 : }
645 :
646 : // Skip # style comments
647 229546 : if ((*pszHeaderNext == 10 || *pszHeaderNext == 13 ||
648 209808 : *pszHeaderNext == ' ' || *pszHeaderNext == '\t') &&
649 157791 : pszHeaderNext[1] == '#')
650 : {
651 83 : pszHeaderNext += 2;
652 :
653 : // consume till end of line.
654 4138 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
655 4055 : *pszHeaderNext != 13)
656 : {
657 4055 : pszHeaderNext++;
658 : }
659 83 : continue;
660 : }
661 :
662 : // Skip white space (newline, space, tab, etc )
663 229463 : if (isspace(static_cast<unsigned char>(*pszHeaderNext)))
664 : {
665 157708 : pszHeaderNext++;
666 157708 : continue;
667 : }
668 :
669 : // not white space, return.
670 71755 : return;
671 : }
672 : }
673 :
674 : /************************************************************************/
675 : /* GetKeyword() */
676 : /************************************************************************/
677 :
678 8338 : const char *NASAKeywordHandler::GetKeyword(const char *pszPath,
679 : const char *pszDefault)
680 :
681 : {
682 8338 : return aosKeywordList.FetchNameValueDef(pszPath, pszDefault);
683 : }
684 :
685 : /************************************************************************/
686 : /* GetKeywordList() */
687 : /************************************************************************/
688 :
689 0 : char **NASAKeywordHandler::GetKeywordList()
690 : {
691 0 : return aosKeywordList.List();
692 : }
693 :
694 : /************************************************************************/
695 : /* StealJSon() */
696 : /************************************************************************/
697 :
698 334 : CPLJSONObject NASAKeywordHandler::GetJsonObject() const
699 : {
700 334 : return oJSon;
701 : }
702 :
703 : //! @endcond
|