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 501 : NASAKeywordHandler::NASAKeywordHandler()
56 501 : : pszHeaderNext(nullptr), m_bStripSurroundingQuotes(false)
57 : {
58 501 : oJSon.Deinit();
59 501 : }
60 :
61 : /************************************************************************/
62 : /* ~NASAKeywordHandler() */
63 : /************************************************************************/
64 :
65 501 : NASAKeywordHandler::~NASAKeywordHandler()
66 :
67 : {
68 501 : }
69 :
70 : /************************************************************************/
71 : /* Ingest() */
72 : /************************************************************************/
73 :
74 350 : bool NASAKeywordHandler::Ingest(VSILFILE *fp, int nOffset)
75 :
76 : {
77 : /* -------------------------------------------------------------------- */
78 : /* Read in buffer till we find END all on its own line. */
79 : /* -------------------------------------------------------------------- */
80 350 : if (VSIFSeekL(fp, nOffset, SEEK_SET) != 0)
81 0 : return false;
82 :
83 700 : std::string osHeaderText;
84 : for (; true;)
85 : {
86 : char szChunk[513];
87 :
88 4237 : int nBytesRead = static_cast<int>(VSIFReadL(szChunk, 1, 512, fp));
89 :
90 4237 : szChunk[nBytesRead] = '\0';
91 4237 : osHeaderText += szChunk;
92 :
93 4237 : if (nBytesRead < 512)
94 165 : break;
95 :
96 4072 : const char *pszCheck = nullptr;
97 4072 : if (osHeaderText.size() > 520)
98 532 : pszCheck = osHeaderText.c_str() + (osHeaderText.size() - 520);
99 : else
100 3540 : pszCheck = szChunk;
101 :
102 4072 : if (strstr(pszCheck, "\r\nEND\r\n") != nullptr ||
103 4057 : strstr(pszCheck, "\nEND\n") != nullptr ||
104 4005 : strstr(pszCheck, "\r\nEnd\r\n") != nullptr ||
105 4005 : strstr(pszCheck, "\nEnd\n") != nullptr)
106 : break;
107 3887 : }
108 :
109 350 : return Parse(osHeaderText.c_str());
110 : }
111 :
112 : /************************************************************************/
113 : /* Parse() */
114 : /************************************************************************/
115 :
116 376 : bool NASAKeywordHandler::Parse(const char *pszStr)
117 :
118 : {
119 376 : pszHeaderNext = pszStr;
120 :
121 : /* -------------------------------------------------------------------- */
122 : /* Process name/value pairs, keeping track of a "path stack". */
123 : /* -------------------------------------------------------------------- */
124 376 : oJSon = CPLJSONObject();
125 376 : return ReadGroup("", oJSon, 0);
126 : }
127 :
128 : /************************************************************************/
129 : /* ReadGroup() */
130 : /************************************************************************/
131 :
132 2758 : bool NASAKeywordHandler::ReadGroup(const std::string &osPathPrefix,
133 : CPLJSONObject &oCur, int nRecLevel)
134 :
135 : {
136 2758 : if (osPathPrefix.size() > 256)
137 : {
138 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big prefix for GROUP");
139 0 : return false;
140 : }
141 2758 : if (nRecLevel == 100)
142 0 : return false;
143 : for (; true;)
144 : {
145 15050 : CPLString osName, osValue;
146 15050 : if (!ReadPair(osName, osValue, oCur))
147 3 : return false;
148 :
149 15047 : if (EQUAL(osName, "OBJECT") || EQUAL(osName, "GROUP"))
150 : {
151 2382 : CPLJSONObject oNewGroup;
152 2382 : oNewGroup.Add("_type",
153 2382 : EQUAL(osName, "OBJECT") ? "object" : "group");
154 2382 : if (!ReadGroup((osPathPrefix + osValue + ".").c_str(), oNewGroup,
155 : nRecLevel + 1))
156 : {
157 0 : return false;
158 : }
159 7146 : CPLJSONObject oName = oNewGroup["Name"];
160 2402 : if ((osValue == "Table" || osValue == "Field") &&
161 20 : (oName.GetType() == CPLJSONObject::Type::String))
162 : {
163 20 : oCur.Add(osValue + "_" + oName.ToString(), oNewGroup);
164 20 : oNewGroup.Add("_container_name", osValue);
165 : }
166 2362 : 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.Add(osValue + CPLSPrintf("_%d", nIter), oNewGroup);
174 4 : oNewGroup.Add("_container_name", osValue);
175 : }
176 : else
177 : {
178 2358 : oCur.Add(osValue, oNewGroup);
179 : }
180 : }
181 23985 : else if (EQUAL(osName, "END") || EQUAL(osName, "END_GROUP") ||
182 11320 : EQUAL(osName, "END_OBJECT"))
183 : {
184 2755 : return true;
185 : }
186 : else
187 : {
188 9910 : osName = osPathPrefix + osName;
189 9910 : aosKeywordList.AddNameValue(osName, osValue);
190 : }
191 12292 : }
192 : }
193 :
194 : /************************************************************************/
195 : /* StripQuotesIfNeeded() */
196 : /************************************************************************/
197 :
198 5363 : static CPLString StripQuotesIfNeeded(const CPLString &osWord,
199 : bool bQuotesAlreadyRemoved)
200 : {
201 5363 : if (bQuotesAlreadyRemoved || osWord.size() < 2 || osWord[0] != '"')
202 3542 : return osWord;
203 3642 : return osWord.substr(1, osWord.size() - 2);
204 : }
205 :
206 : /************************************************************************/
207 : /* ReadPair() */
208 : /* */
209 : /* Read a name/value pair from the input stream. Strip off */
210 : /* white space, ignore comments, split on '='. */
211 : /* Returns TRUE on success. */
212 : /************************************************************************/
213 :
214 15050 : bool NASAKeywordHandler::ReadPair(CPLString &osName, CPLString &osValue,
215 : CPLJSONObject &oCur)
216 :
217 : {
218 15050 : osName = "";
219 15050 : osValue = "";
220 :
221 15050 : if (!ReadWord(osName))
222 0 : return false;
223 :
224 15050 : SkipWhite();
225 :
226 15050 : if (EQUAL(osName, "END"))
227 373 : return true;
228 :
229 14677 : if (*pszHeaderNext != '=')
230 : {
231 : // ISIS3 does not have anything after the end group/object keyword.
232 1753 : if (EQUAL(osName, "End_Group") || EQUAL(osName, "End_Object"))
233 1752 : return true;
234 :
235 1 : return false;
236 : }
237 :
238 12924 : pszHeaderNext++;
239 :
240 12924 : SkipWhite();
241 :
242 12924 : osValue = "";
243 12924 : bool bIsString = true;
244 :
245 : // Handle value lists like:
246 : // Name = (Red, Red) or {Red, Red} or even ({Red, Red}, {Red, Red})
247 25848 : CPLJSONArray oArray;
248 12924 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
249 : {
250 533 : std::vector<char> oStackArrayBeginChar;
251 533 : CPLString osWord;
252 :
253 533 : oStackArrayBeginChar.push_back(*pszHeaderNext);
254 533 : osValue += *pszHeaderNext;
255 533 : pszHeaderNext++;
256 :
257 1617 : while (ReadWord(osWord, m_bStripSurroundingQuotes, true, &bIsString))
258 : {
259 1617 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
260 : {
261 3 : oStackArrayBeginChar.push_back(*pszHeaderNext);
262 3 : osValue += *pszHeaderNext;
263 3 : pszHeaderNext++;
264 : }
265 :
266 : // TODO: we could probably do better with nested json arrays
267 : // instead of flattening when there are (( )) or ({ }) constructs
268 1617 : if (bIsString)
269 : {
270 1051 : if (!(osWord.empty() &&
271 8 : (*pszHeaderNext == '(' || *pszHeaderNext == '{' ||
272 8 : *pszHeaderNext == ')' || *pszHeaderNext == '}')))
273 : {
274 1039 : oArray.Add(
275 2078 : StripQuotesIfNeeded(osWord, m_bStripSurroundingQuotes));
276 : }
277 : }
278 574 : else if (CPLGetValueType(osWord) == CPL_VALUE_INTEGER)
279 : {
280 454 : oArray.Add(atoi(osWord));
281 : }
282 : else
283 : {
284 120 : oArray.Add(CPLAtof(osWord));
285 : }
286 :
287 1617 : osValue += osWord;
288 1630 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
289 : {
290 13 : pszHeaderNext++;
291 : }
292 :
293 1617 : if (*pszHeaderNext == ')')
294 : {
295 523 : osValue += *pszHeaderNext;
296 1046 : if (oStackArrayBeginChar.empty() ||
297 523 : oStackArrayBeginChar.back() != '(')
298 : {
299 1 : CPLDebug("PDS", "Unpaired ( ) for %s", osName.c_str());
300 1 : return false;
301 : }
302 522 : oStackArrayBeginChar.pop_back();
303 522 : pszHeaderNext++;
304 522 : if (oStackArrayBeginChar.empty())
305 521 : break;
306 : }
307 1094 : else if (*pszHeaderNext == '}')
308 : {
309 13 : osValue += *pszHeaderNext;
310 26 : if (oStackArrayBeginChar.empty() ||
311 13 : oStackArrayBeginChar.back() != '{')
312 : {
313 1 : CPLDebug("PDS", "Unpaired { } for %s", osName.c_str());
314 1 : return false;
315 : }
316 12 : oStackArrayBeginChar.pop_back();
317 12 : pszHeaderNext++;
318 12 : if (oStackArrayBeginChar.empty())
319 10 : break;
320 : }
321 1081 : else if (*pszHeaderNext == ',')
322 : {
323 1078 : osValue += *pszHeaderNext;
324 1078 : pszHeaderNext++;
325 : // Do not use SkipWhite() here to avoid being confuse by
326 : // constructs like
327 : // FOO = (#123456,
328 : // #123456)
329 : // where we could confuse the second line with a comment.
330 3511 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
331 : {
332 2433 : pszHeaderNext++;
333 : }
334 : }
335 1084 : SkipWhite();
336 531 : }
337 : }
338 :
339 : else // Handle more normal "single word" values.
340 : {
341 12391 : if (!ReadWord(osValue, m_bStripSurroundingQuotes, false, &bIsString))
342 0 : return false;
343 : }
344 :
345 12922 : SkipWhite();
346 :
347 : // No units keyword?
348 12922 : if (*pszHeaderNext != '<')
349 : {
350 12196 : if (!EQUAL(osName, "OBJECT") && !EQUAL(osName, "GROUP"))
351 : {
352 9814 : if (oArray.Size() > 0)
353 : {
354 527 : oCur.Add(osName, oArray);
355 : }
356 : else
357 : {
358 9287 : if (bIsString)
359 : {
360 4324 : oCur.Add(osName, StripQuotesIfNeeded(
361 4324 : osValue, m_bStripSurroundingQuotes));
362 : }
363 4963 : else if (CPLGetValueType(osValue) == CPL_VALUE_INTEGER)
364 : {
365 3161 : oCur.Add(osName, atoi(osValue));
366 : }
367 : else
368 : {
369 1802 : oCur.Add(osName, CPLAtof(osValue));
370 : }
371 : }
372 : }
373 12196 : return true;
374 : }
375 :
376 1452 : CPLString osValueNoUnit(osValue);
377 : // Append units keyword. For lines that like like this:
378 : // MAP_RESOLUTION = 4.0 <PIXEL/DEGREE>
379 :
380 726 : osValue += " ";
381 :
382 1452 : CPLString osWord;
383 1452 : CPLString osUnit;
384 726 : while (ReadWord(osWord))
385 : {
386 726 : SkipWhite();
387 :
388 726 : osValue += osWord;
389 726 : osUnit = osWord;
390 726 : if (osWord.back() == '>')
391 726 : break;
392 : }
393 :
394 726 : if (osUnit[0] == '<')
395 726 : osUnit = osUnit.substr(1);
396 726 : if (!osUnit.empty() && osUnit.back() == '>')
397 726 : osUnit = osUnit.substr(0, osUnit.size() - 1);
398 :
399 726 : CPLJSONObject newObject;
400 726 : oCur.Add(osName, newObject);
401 :
402 726 : if (oArray.Size() > 0)
403 : {
404 2 : newObject.Add("value", oArray);
405 : }
406 : else
407 : {
408 724 : if (bIsString)
409 : {
410 10 : newObject.Add("value", osValueNoUnit);
411 : }
412 714 : else if (CPLGetValueType(osValueNoUnit) == CPL_VALUE_INTEGER)
413 : {
414 38 : newObject.Add("value", atoi(osValueNoUnit));
415 : }
416 : else
417 : {
418 676 : newObject.Add("value", CPLAtof(osValueNoUnit));
419 : }
420 : }
421 726 : newObject.Add("unit", osUnit);
422 :
423 726 : return true;
424 : }
425 :
426 : /************************************************************************/
427 : /* ReadWord() */
428 : /* Returns TRUE on success */
429 : /************************************************************************/
430 :
431 29784 : bool NASAKeywordHandler::ReadWord(CPLString &osWord,
432 : bool bStripSurroundingQuotes, bool bParseList,
433 : bool *pbIsString)
434 :
435 : {
436 29784 : if (pbIsString)
437 14008 : *pbIsString = false;
438 29784 : osWord = "";
439 :
440 29784 : SkipWhite();
441 :
442 29784 : if (!(*pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
443 29784 : !isspace(static_cast<unsigned char>(*pszHeaderNext))))
444 0 : return false;
445 :
446 : /* Extract a text string delimited by '\"' */
447 : /* Convert newlines (CR or LF) within quotes. While text strings
448 : support them as per ODL, the keyword list doesn't want them */
449 29784 : if (*pszHeaderNext == '"')
450 : {
451 1849 : if (pbIsString)
452 1849 : *pbIsString = true;
453 1849 : if (!bStripSurroundingQuotes)
454 1823 : osWord += *(pszHeaderNext);
455 1849 : pszHeaderNext++;
456 27240 : while (*pszHeaderNext != '"')
457 : {
458 25391 : if (*pszHeaderNext == '\0')
459 0 : return false;
460 25391 : if (*pszHeaderNext == '\n')
461 : {
462 121 : osWord += "\\n";
463 121 : pszHeaderNext++;
464 121 : continue;
465 : }
466 25270 : if (*pszHeaderNext == '\r')
467 : {
468 118 : osWord += "\\r";
469 118 : pszHeaderNext++;
470 118 : continue;
471 : }
472 25152 : osWord += *(pszHeaderNext++);
473 : }
474 1849 : if (!bStripSurroundingQuotes)
475 1823 : osWord += *(pszHeaderNext);
476 1849 : pszHeaderNext++;
477 :
478 1849 : return true;
479 : }
480 :
481 : /* Extract a symbol string */
482 : /* These are expected to not have
483 : '\'' (delimiters),
484 : format effectors (should fit on a single line) or
485 : control characters.
486 : */
487 27935 : if (*pszHeaderNext == '\'')
488 : {
489 8 : if (pbIsString)
490 8 : *pbIsString = true;
491 8 : if (!bStripSurroundingQuotes)
492 8 : osWord += *(pszHeaderNext);
493 8 : pszHeaderNext++;
494 32 : while (*pszHeaderNext != '\'')
495 : {
496 24 : if (*pszHeaderNext == '\0')
497 0 : return false;
498 :
499 24 : osWord += *(pszHeaderNext++);
500 : }
501 8 : if (!bStripSurroundingQuotes)
502 8 : osWord += *(pszHeaderNext);
503 8 : pszHeaderNext++;
504 8 : return true;
505 : }
506 :
507 : /*
508 : * Extract normal text. Terminated by '=' or whitespace.
509 : *
510 : * A special exception is that a line may terminate with a '-'
511 : * which is taken as a line extender, and we suck up white space to new
512 : * text.
513 : */
514 244628 : while (
515 272555 : *pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
516 269946 : ((bParseList && *pszHeaderNext != ',' && *pszHeaderNext != '(' &&
517 6228 : *pszHeaderNext != ')' && *pszHeaderNext != '{' &&
518 269946 : *pszHeaderNext != '}') ||
519 263016 : (!bParseList && !isspace(static_cast<unsigned char>(*pszHeaderNext)))))
520 : {
521 244628 : osWord += *pszHeaderNext;
522 244628 : pszHeaderNext++;
523 :
524 244628 : if (*pszHeaderNext == '-' &&
525 141 : (pszHeaderNext[1] == 10 || pszHeaderNext[1] == 13))
526 : {
527 4 : pszHeaderNext += 2;
528 4 : SkipWhite();
529 : }
530 : }
531 :
532 27927 : if (pbIsString)
533 12151 : *pbIsString = CPLGetValueType(osWord) == CPL_VALUE_STRING;
534 :
535 27927 : return true;
536 : }
537 :
538 : /************************************************************************/
539 : /* SkipWhite() */
540 : /* Skip white spaces and C style comments */
541 : /************************************************************************/
542 :
543 249115 : void NASAKeywordHandler::SkipWhite()
544 :
545 : {
546 : for (; true;)
547 : {
548 : // Skip C style comments
549 249115 : if (*pszHeaderNext == '/' && pszHeaderNext[1] == '*')
550 : {
551 548 : pszHeaderNext += 2;
552 :
553 23046 : while (*pszHeaderNext != '\0' &&
554 23046 : (*pszHeaderNext != '*' || pszHeaderNext[1] != '/'))
555 : {
556 22498 : pszHeaderNext++;
557 : }
558 548 : if (*pszHeaderNext == '\0')
559 0 : return;
560 :
561 548 : pszHeaderNext += 2;
562 :
563 : // consume till end of line.
564 : // reduce sensibility to a label error
565 548 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
566 294 : *pszHeaderNext != 13)
567 : {
568 0 : pszHeaderNext++;
569 : }
570 548 : continue;
571 : }
572 :
573 : // Skip # style comments
574 248567 : if ((*pszHeaderNext == 10 || *pszHeaderNext == 13 ||
575 228401 : *pszHeaderNext == ' ' || *pszHeaderNext == '\t') &&
576 176073 : pszHeaderNext[1] == '#')
577 : {
578 83 : pszHeaderNext += 2;
579 :
580 : // consume till end of line.
581 4138 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
582 4055 : *pszHeaderNext != 13)
583 : {
584 4055 : pszHeaderNext++;
585 : }
586 83 : continue;
587 : }
588 :
589 : // Skip white space (newline, space, tab, etc )
590 248484 : if (isspace(static_cast<unsigned char>(*pszHeaderNext)))
591 : {
592 175990 : pszHeaderNext++;
593 175990 : continue;
594 : }
595 :
596 : // not white space, return.
597 72494 : return;
598 : }
599 : }
600 :
601 : /************************************************************************/
602 : /* GetKeyword() */
603 : /************************************************************************/
604 :
605 9316 : const char *NASAKeywordHandler::GetKeyword(const char *pszPath,
606 : const char *pszDefault)
607 :
608 : {
609 9316 : return aosKeywordList.FetchNameValueDef(pszPath, pszDefault);
610 : }
611 :
612 : /************************************************************************/
613 : /* GetKeywordList() */
614 : /************************************************************************/
615 :
616 0 : char **NASAKeywordHandler::GetKeywordList()
617 : {
618 0 : return aosKeywordList.List();
619 : }
620 :
621 : /************************************************************************/
622 : /* StealJSon() */
623 : /************************************************************************/
624 :
625 321 : CPLJSONObject NASAKeywordHandler::GetJsonObject() const
626 : {
627 321 : return oJSon;
628 : }
629 :
630 : //! @endcond
|