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 474 : NASAKeywordHandler::NASAKeywordHandler()
56 474 : : pszHeaderNext(nullptr), m_bStripSurroundingQuotes(false)
57 : {
58 474 : oJSon.Deinit();
59 474 : }
60 :
61 : /************************************************************************/
62 : /* ~NASAKeywordHandler() */
63 : /************************************************************************/
64 :
65 474 : NASAKeywordHandler::~NASAKeywordHandler()
66 :
67 : {
68 474 : }
69 :
70 : /************************************************************************/
71 : /* Ingest() */
72 : /************************************************************************/
73 :
74 312 : 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 312 : if (VSIFSeekL(fp, nOffset, SEEK_SET) != 0)
81 0 : return false;
82 :
83 624 : std::string osHeaderText;
84 : for (; true;)
85 : {
86 : char szChunk[513];
87 :
88 3654 : int nBytesRead = static_cast<int>(VSIFReadL(szChunk, 1, 512, fp));
89 :
90 3654 : szChunk[nBytesRead] = '\0';
91 3654 : osHeaderText += szChunk;
92 :
93 3654 : if (nBytesRead < 512)
94 165 : break;
95 :
96 3489 : const char *pszCheck = nullptr;
97 3489 : if (osHeaderText.size() > 520)
98 499 : pszCheck = osHeaderText.c_str() + (osHeaderText.size() - 520);
99 : else
100 2990 : pszCheck = szChunk;
101 :
102 3489 : if (strstr(pszCheck, "\r\nEND\r\n") != nullptr ||
103 3474 : strstr(pszCheck, "\nEND\n") != nullptr ||
104 3465 : strstr(pszCheck, "\r\nEnd\r\n") != nullptr ||
105 3465 : strstr(pszCheck, "\nEnd\n") != nullptr)
106 : break;
107 3342 : }
108 :
109 312 : return Parse(osHeaderText.c_str());
110 : }
111 :
112 : /************************************************************************/
113 : /* Parse() */
114 : /************************************************************************/
115 :
116 343 : bool NASAKeywordHandler::Parse(const char *pszStr)
117 :
118 : {
119 343 : pszHeaderNext = pszStr;
120 :
121 : /* -------------------------------------------------------------------- */
122 : /* Process name/value pairs, keeping track of a "path stack". */
123 : /* -------------------------------------------------------------------- */
124 343 : oJSon = CPLJSONObject();
125 343 : return ReadGroup("", oJSon, 0);
126 : }
127 :
128 : /************************************************************************/
129 : /* ReadGroup() */
130 : /************************************************************************/
131 :
132 2785 : bool NASAKeywordHandler::ReadGroup(const std::string &osPathPrefix,
133 : CPLJSONObject &oCur, int nRecLevel)
134 :
135 : {
136 2785 : if (osPathPrefix.size() > 256)
137 : {
138 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big prefix for GROUP");
139 0 : return false;
140 : }
141 2785 : if (nRecLevel == 100)
142 0 : return false;
143 : for (; true;)
144 : {
145 14690 : CPLString osName, osValue;
146 14690 : if (!ReadPair(osName, osValue, oCur))
147 3 : return false;
148 :
149 14687 : if (EQUAL(osName, "OBJECT") || EQUAL(osName, "GROUP"))
150 : {
151 2442 : CPLJSONObject oNewGroup;
152 2442 : oNewGroup.Add("_type",
153 2442 : EQUAL(osName, "OBJECT") ? "object" : "group");
154 2442 : if (!ReadGroup((osPathPrefix + osValue + ".").c_str(), oNewGroup,
155 : nRecLevel + 1))
156 : {
157 0 : return false;
158 : }
159 7326 : CPLJSONObject oName = oNewGroup["Name"];
160 2442 : if (oName.GetType() == CPLJSONObject::Type::String)
161 : {
162 285 : oCur.Add(osValue + "_" + oName.ToString(), oNewGroup);
163 285 : oNewGroup.Add("_container_name", osValue);
164 : }
165 2157 : else if (oCur[osValue].IsValid())
166 : {
167 4 : int nIter = 2;
168 6 : while (oCur[osValue + CPLSPrintf("_%d", nIter)].IsValid())
169 : {
170 2 : nIter++;
171 : }
172 4 : oCur.Add(osValue + CPLSPrintf("_%d", nIter), oNewGroup);
173 4 : oNewGroup.Add("_container_name", osValue);
174 : }
175 : else
176 : {
177 2153 : oCur.Add(osValue, oNewGroup);
178 : }
179 : }
180 23115 : else if (EQUAL(osName, "END") || EQUAL(osName, "END_GROUP") ||
181 10870 : EQUAL(osName, "END_OBJECT"))
182 : {
183 2782 : return true;
184 : }
185 : else
186 : {
187 9463 : osName = osPathPrefix + osName;
188 9463 : aosKeywordList.AddNameValue(osName, osValue);
189 : }
190 11905 : }
191 : }
192 :
193 : /************************************************************************/
194 : /* StripQuotesIfNeeded() */
195 : /************************************************************************/
196 :
197 5191 : static CPLString StripQuotesIfNeeded(const CPLString &osWord,
198 : bool bQuotesAlreadyRemoved)
199 : {
200 5191 : if (bQuotesAlreadyRemoved || osWord.size() < 2 || osWord[0] != '"')
201 3372 : return osWord;
202 3638 : return osWord.substr(1, osWord.size() - 2);
203 : }
204 :
205 : /************************************************************************/
206 : /* ReadPair() */
207 : /* */
208 : /* Read a name/value pair from the input stream. Strip off */
209 : /* white space, ignore comments, split on '='. */
210 : /* Returns TRUE on success. */
211 : /************************************************************************/
212 :
213 14690 : bool NASAKeywordHandler::ReadPair(CPLString &osName, CPLString &osValue,
214 : CPLJSONObject &oCur)
215 :
216 : {
217 14690 : osName = "";
218 14690 : osValue = "";
219 :
220 14690 : if (!ReadWord(osName))
221 0 : return false;
222 :
223 14690 : SkipWhite();
224 :
225 14690 : if (EQUAL(osName, "END"))
226 340 : return true;
227 :
228 14350 : if (*pszHeaderNext != '=')
229 : {
230 : // ISIS3 does not have anything after the end group/object keyword.
231 1792 : if (EQUAL(osName, "End_Group") || EQUAL(osName, "End_Object"))
232 1791 : return true;
233 :
234 1 : return false;
235 : }
236 :
237 12558 : pszHeaderNext++;
238 :
239 12558 : SkipWhite();
240 :
241 12558 : osValue = "";
242 12558 : bool bIsString = true;
243 :
244 : // Handle value lists like:
245 : // Name = (Red, Red) or {Red, Red} or even ({Red, Red}, {Red, Red})
246 25116 : CPLJSONArray oArray;
247 12558 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
248 : {
249 438 : std::vector<char> oStackArrayBeginChar;
250 438 : CPLString osWord;
251 :
252 438 : oStackArrayBeginChar.push_back(*pszHeaderNext);
253 438 : osValue += *pszHeaderNext;
254 438 : pszHeaderNext++;
255 :
256 1335 : while (ReadWord(osWord, false, true, &bIsString))
257 : {
258 1335 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
259 : {
260 3 : oStackArrayBeginChar.push_back(*pszHeaderNext);
261 3 : osValue += *pszHeaderNext;
262 3 : pszHeaderNext++;
263 : }
264 :
265 : // TODO: we could probably do better with nested json arrays
266 : // instead of flattening when there are (( )) or ({ }) constructs
267 1335 : if (bIsString)
268 : {
269 973 : if (!(osWord.empty() &&
270 8 : (*pszHeaderNext == '(' || *pszHeaderNext == '{' ||
271 8 : *pszHeaderNext == ')' || *pszHeaderNext == '}')))
272 : {
273 : std::string osValueInArray =
274 1922 : StripQuotesIfNeeded(osWord, false);
275 1214 : if (!osValueInArray.empty() && osValueInArray == osWord &&
276 253 : osValueInArray.back() == '>')
277 : {
278 50 : const auto nPosLeftBracket = osValueInArray.rfind('<');
279 50 : if (nPosLeftBracket != std::string::npos)
280 : {
281 : const std::string osUnit = osValueInArray.substr(
282 50 : nPosLeftBracket + 1, osValueInArray.size() - 1 -
283 100 : (nPosLeftBracket + 1));
284 50 : osValueInArray.resize(nPosLeftBracket);
285 200 : while (!osValueInArray.empty() &&
286 100 : osValueInArray.back() == ' ')
287 50 : osValueInArray.pop_back();
288 :
289 100 : CPLJSONObject newObject;
290 50 : if (CPLGetValueType(osValueInArray.c_str()) ==
291 : CPL_VALUE_STRING)
292 : {
293 0 : newObject.Add("value", osValueInArray);
294 : }
295 50 : else if (CPLGetValueType(osValueInArray.c_str()) ==
296 : CPL_VALUE_INTEGER)
297 : {
298 6 : newObject.Add("value",
299 : atoi(osValueInArray.c_str()));
300 : }
301 : else
302 : {
303 44 : newObject.Add("value",
304 : CPLAtof(osValueInArray.c_str()));
305 : }
306 50 : newObject.Add("unit", osUnit);
307 50 : oArray.Add(newObject);
308 : }
309 : else
310 : {
311 0 : oArray.Add(osValueInArray);
312 : }
313 : }
314 : else
315 : {
316 911 : oArray.Add(osValueInArray);
317 : }
318 : }
319 : }
320 370 : else if (CPLGetValueType(osWord) == CPL_VALUE_INTEGER)
321 : {
322 235 : oArray.Add(atoi(osWord));
323 : }
324 : else
325 : {
326 135 : oArray.Add(CPLAtof(osWord));
327 : }
328 :
329 1335 : osValue += osWord;
330 1348 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
331 : {
332 13 : pszHeaderNext++;
333 : }
334 :
335 1335 : if (*pszHeaderNext == ')')
336 : {
337 428 : osValue += *pszHeaderNext;
338 856 : if (oStackArrayBeginChar.empty() ||
339 428 : oStackArrayBeginChar.back() != '(')
340 : {
341 1 : CPLDebug("PDS", "Unpaired ( ) for %s", osName.c_str());
342 1 : return false;
343 : }
344 427 : oStackArrayBeginChar.pop_back();
345 427 : pszHeaderNext++;
346 427 : if (oStackArrayBeginChar.empty())
347 426 : break;
348 : }
349 907 : else if (*pszHeaderNext == '}')
350 : {
351 13 : osValue += *pszHeaderNext;
352 26 : if (oStackArrayBeginChar.empty() ||
353 13 : oStackArrayBeginChar.back() != '{')
354 : {
355 1 : CPLDebug("PDS", "Unpaired { } for %s", osName.c_str());
356 1 : return false;
357 : }
358 12 : oStackArrayBeginChar.pop_back();
359 12 : pszHeaderNext++;
360 12 : if (oStackArrayBeginChar.empty())
361 10 : break;
362 : }
363 894 : else if (*pszHeaderNext == ',')
364 : {
365 891 : osValue += *pszHeaderNext;
366 891 : pszHeaderNext++;
367 : // Do not use SkipWhite() here to avoid being confuse by
368 : // constructs like
369 : // FOO = (#123456,
370 : // #123456)
371 : // where we could confuse the second line with a comment.
372 3248 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
373 : {
374 2357 : pszHeaderNext++;
375 : }
376 : }
377 897 : SkipWhite();
378 436 : }
379 : }
380 :
381 : else // Handle more normal "single word" values.
382 : {
383 12120 : if (!ReadWord(osValue, m_bStripSurroundingQuotes, false, &bIsString))
384 0 : return false;
385 : }
386 :
387 12556 : SkipWhite();
388 :
389 20234 : const auto AddToCur = [&oCur, &osName](const CPLJSONObject &o)
390 : {
391 20228 : auto oExistingObjForName = oCur[osName];
392 10114 : if (oExistingObjForName.IsValid())
393 : {
394 10 : if (oExistingObjForName["values"].GetType() ==
395 : CPLJSONObject::Type::Array)
396 : {
397 2 : oExistingObjForName["values"].ToArray().Add(o);
398 : }
399 : else
400 : {
401 16 : CPLJSONArray ar;
402 8 : ar.Add(oExistingObjForName);
403 8 : ar.Add(o);
404 8 : CPLJSONObject oObj;
405 8 : oObj.Add("values", ar);
406 8 : oCur.Delete(osName);
407 8 : oCur[osName] = oObj;
408 : }
409 : }
410 : else
411 : {
412 10104 : oCur.Add(osName, o);
413 : }
414 10114 : };
415 :
416 : // No units keyword?
417 12556 : if (*pszHeaderNext != '<')
418 : {
419 11806 : if (!EQUAL(osName, "OBJECT") && !EQUAL(osName, "GROUP"))
420 : {
421 9364 : if (oArray.Size() > 0)
422 : {
423 432 : AddToCur(oArray);
424 : }
425 : else
426 : {
427 17864 : CPLJSONObject oObj;
428 8932 : if (bIsString)
429 : {
430 8460 : oObj = CPLJSONObject(StripQuotesIfNeeded(
431 8460 : osValue, m_bStripSurroundingQuotes));
432 : }
433 4702 : else if (CPLGetValueType(osValue) == CPL_VALUE_INTEGER)
434 : {
435 2931 : oObj = CPLJSONObject(atoi(osValue));
436 : }
437 : else
438 : {
439 1771 : oObj = CPLJSONObject(CPLAtof(osValue));
440 : }
441 8932 : AddToCur(oObj);
442 : }
443 : }
444 11806 : return true;
445 : }
446 :
447 1500 : CPLString osValueNoUnit(osValue);
448 : // Append units keyword. For lines that like like this:
449 : // MAP_RESOLUTION = 4.0 <PIXEL/DEGREE>
450 :
451 750 : osValue += " ";
452 :
453 1500 : CPLString osWord;
454 1500 : CPLString osUnit;
455 750 : while (ReadWord(osWord))
456 : {
457 750 : SkipWhite();
458 :
459 750 : osValue += osWord;
460 750 : osUnit = osWord;
461 750 : if (osWord.back() == '>')
462 750 : break;
463 : }
464 :
465 750 : if (osUnit[0] == '<')
466 750 : osUnit = osUnit.substr(1);
467 750 : if (!osUnit.empty() && osUnit.back() == '>')
468 750 : osUnit = osUnit.substr(0, osUnit.size() - 1);
469 :
470 750 : CPLJSONObject newObject;
471 :
472 750 : if (oArray.Size() > 0)
473 : {
474 2 : newObject.Add("value", oArray);
475 : }
476 : else
477 : {
478 748 : if (bIsString)
479 : {
480 10 : newObject.Add("value", osValueNoUnit);
481 : }
482 738 : else if (CPLGetValueType(osValueNoUnit) == CPL_VALUE_INTEGER)
483 : {
484 42 : newObject.Add("value", atoi(osValueNoUnit));
485 : }
486 : else
487 : {
488 696 : newObject.Add("value", CPLAtof(osValueNoUnit));
489 : }
490 : }
491 750 : newObject.Add("unit", osUnit);
492 750 : AddToCur(newObject);
493 :
494 750 : return true;
495 : }
496 :
497 : /************************************************************************/
498 : /* ReadWord() */
499 : /* Returns TRUE on success */
500 : /************************************************************************/
501 :
502 28895 : bool NASAKeywordHandler::ReadWord(CPLString &osWord,
503 : bool bStripSurroundingQuotes, bool bParseList,
504 : bool *pbIsString)
505 :
506 : {
507 28895 : if (pbIsString)
508 13455 : *pbIsString = false;
509 28895 : osWord = "";
510 :
511 28895 : SkipWhite();
512 :
513 28895 : if (!(*pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
514 28895 : !isspace(static_cast<unsigned char>(*pszHeaderNext))))
515 0 : return false;
516 :
517 : /* Extract a text string delimited by '\"' */
518 : /* Convert newlines (CR or LF) within quotes. While text strings
519 : support them as per ODL, the keyword list doesn't want them */
520 28895 : if (*pszHeaderNext == '"')
521 : {
522 1841 : if (pbIsString)
523 1841 : *pbIsString = true;
524 1841 : if (!bStripSurroundingQuotes)
525 1821 : osWord += *(pszHeaderNext);
526 1841 : pszHeaderNext++;
527 27500 : while (*pszHeaderNext != '"')
528 : {
529 25659 : if (*pszHeaderNext == '\0')
530 0 : return false;
531 25659 : if (*pszHeaderNext == '\n')
532 : {
533 121 : osWord += "\\n";
534 121 : pszHeaderNext++;
535 121 : continue;
536 : }
537 25538 : if (*pszHeaderNext == '\r')
538 : {
539 118 : osWord += "\\r";
540 118 : pszHeaderNext++;
541 118 : continue;
542 : }
543 25420 : osWord += *(pszHeaderNext++);
544 : }
545 1841 : if (!bStripSurroundingQuotes)
546 1821 : osWord += *(pszHeaderNext);
547 1841 : pszHeaderNext++;
548 :
549 1841 : return true;
550 : }
551 :
552 : /* Extract a symbol string */
553 : /* These are expected to not have
554 : '\'' (delimiters),
555 : format effectors (should fit on a single line) or
556 : control characters.
557 : */
558 27054 : if (*pszHeaderNext == '\'')
559 : {
560 8 : if (pbIsString)
561 8 : *pbIsString = true;
562 8 : if (!bStripSurroundingQuotes)
563 8 : osWord += *(pszHeaderNext);
564 8 : pszHeaderNext++;
565 32 : while (*pszHeaderNext != '\'')
566 : {
567 24 : if (*pszHeaderNext == '\0')
568 0 : return false;
569 :
570 24 : osWord += *(pszHeaderNext++);
571 : }
572 8 : if (!bStripSurroundingQuotes)
573 8 : osWord += *(pszHeaderNext);
574 8 : pszHeaderNext++;
575 8 : return true;
576 : }
577 :
578 : /*
579 : * Extract normal text. Terminated by '=' or whitespace.
580 : *
581 : * A special exception is that a line may terminate with a '-'
582 : * which is taken as a line extender, and we suck up white space to new
583 : * text.
584 : */
585 241111 : while (
586 268157 : *pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
587 266152 : ((bParseList && *pszHeaderNext != ',' && *pszHeaderNext != '(' &&
588 5375 : *pszHeaderNext != ')' && *pszHeaderNext != '{' &&
589 266152 : *pszHeaderNext != '}') ||
590 260283 : (!bParseList && !isspace(static_cast<unsigned char>(*pszHeaderNext)))))
591 : {
592 241111 : osWord += *pszHeaderNext;
593 241111 : pszHeaderNext++;
594 :
595 241111 : if (*pszHeaderNext == '-' &&
596 145 : (pszHeaderNext[1] == 10 || pszHeaderNext[1] == 13))
597 : {
598 4 : pszHeaderNext += 2;
599 4 : SkipWhite();
600 : }
601 : }
602 :
603 27046 : if (pbIsString)
604 11606 : *pbIsString = CPLGetValueType(osWord) == CPL_VALUE_STRING;
605 :
606 27046 : return true;
607 : }
608 :
609 : /************************************************************************/
610 : /* SkipWhite() */
611 : /* Skip white spaces and C style comments */
612 : /************************************************************************/
613 :
614 227055 : void NASAKeywordHandler::SkipWhite()
615 :
616 : {
617 : for (; true;)
618 : {
619 : // Skip C style comments
620 227055 : if (*pszHeaderNext == '/' && pszHeaderNext[1] == '*')
621 : {
622 333 : pszHeaderNext += 2;
623 :
624 17886 : while (*pszHeaderNext != '\0' &&
625 17886 : (*pszHeaderNext != '*' || pszHeaderNext[1] != '/'))
626 : {
627 17553 : pszHeaderNext++;
628 : }
629 333 : if (*pszHeaderNext == '\0')
630 0 : return;
631 :
632 333 : pszHeaderNext += 2;
633 :
634 : // consume till end of line.
635 : // reduce sensibility to a label error
636 333 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
637 294 : *pszHeaderNext != 13)
638 : {
639 0 : pszHeaderNext++;
640 : }
641 333 : continue;
642 : }
643 :
644 : // Skip # style comments
645 226722 : if ((*pszHeaderNext == 10 || *pszHeaderNext == 13 ||
646 207235 : *pszHeaderNext == ' ' || *pszHeaderNext == '\t') &&
647 156372 : pszHeaderNext[1] == '#')
648 : {
649 83 : pszHeaderNext += 2;
650 :
651 : // consume till end of line.
652 4138 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
653 4055 : *pszHeaderNext != 13)
654 : {
655 4055 : pszHeaderNext++;
656 : }
657 83 : continue;
658 : }
659 :
660 : // Skip white space (newline, space, tab, etc )
661 226639 : if (isspace(static_cast<unsigned char>(*pszHeaderNext)))
662 : {
663 156289 : pszHeaderNext++;
664 156289 : continue;
665 : }
666 :
667 : // not white space, return.
668 70350 : return;
669 : }
670 : }
671 :
672 : /************************************************************************/
673 : /* GetKeyword() */
674 : /************************************************************************/
675 :
676 8259 : const char *NASAKeywordHandler::GetKeyword(const char *pszPath,
677 : const char *pszDefault)
678 :
679 : {
680 8259 : return aosKeywordList.FetchNameValueDef(pszPath, pszDefault);
681 : }
682 :
683 : /************************************************************************/
684 : /* GetKeywordList() */
685 : /************************************************************************/
686 :
687 0 : char **NASAKeywordHandler::GetKeywordList()
688 : {
689 0 : return aosKeywordList.List();
690 : }
691 :
692 : /************************************************************************/
693 : /* StealJSon() */
694 : /************************************************************************/
695 :
696 331 : CPLJSONObject NASAKeywordHandler::GetJsonObject() const
697 : {
698 331 : return oJSon;
699 : }
700 :
701 : //! @endcond
|