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 467 : NASAKeywordHandler::NASAKeywordHandler()
56 467 : : pszHeaderNext(nullptr), m_bStripSurroundingQuotes(false)
57 : {
58 467 : oJSon.Deinit();
59 467 : }
60 :
61 : /************************************************************************/
62 : /* ~NASAKeywordHandler() */
63 : /************************************************************************/
64 :
65 467 : NASAKeywordHandler::~NASAKeywordHandler()
66 :
67 : {
68 467 : }
69 :
70 : /************************************************************************/
71 : /* Ingest() */
72 : /************************************************************************/
73 :
74 308 : 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 308 : if (VSIFSeekL(fp, nOffset, SEEK_SET) != 0)
81 0 : return false;
82 :
83 616 : std::string osHeaderText;
84 : for (; true;)
85 : {
86 : char szChunk[513];
87 :
88 3648 : int nBytesRead = static_cast<int>(VSIFReadL(szChunk, 1, 512, fp));
89 :
90 3648 : szChunk[nBytesRead] = '\0';
91 3648 : osHeaderText += szChunk;
92 :
93 3648 : if (nBytesRead < 512)
94 161 : break;
95 :
96 3487 : const char *pszCheck = nullptr;
97 3487 : if (osHeaderText.size() > 520)
98 499 : pszCheck = osHeaderText.c_str() + (osHeaderText.size() - 520);
99 : else
100 2988 : pszCheck = szChunk;
101 :
102 3487 : if (strstr(pszCheck, "\r\nEND\r\n") != nullptr ||
103 3472 : strstr(pszCheck, "\nEND\n") != nullptr ||
104 3463 : strstr(pszCheck, "\r\nEnd\r\n") != nullptr ||
105 3463 : strstr(pszCheck, "\nEnd\n") != nullptr)
106 : break;
107 3340 : }
108 :
109 308 : return Parse(osHeaderText.c_str());
110 : }
111 :
112 : /************************************************************************/
113 : /* Parse() */
114 : /************************************************************************/
115 :
116 338 : bool NASAKeywordHandler::Parse(const char *pszStr)
117 :
118 : {
119 338 : pszHeaderNext = pszStr;
120 :
121 : /* -------------------------------------------------------------------- */
122 : /* Process name/value pairs, keeping track of a "path stack". */
123 : /* -------------------------------------------------------------------- */
124 338 : oJSon = CPLJSONObject();
125 338 : return ReadGroup("", oJSon, 0);
126 : }
127 :
128 : /************************************************************************/
129 : /* ReadGroup() */
130 : /************************************************************************/
131 :
132 2743 : bool NASAKeywordHandler::ReadGroup(const std::string &osPathPrefix,
133 : CPLJSONObject &oCur, int nRecLevel)
134 :
135 : {
136 2743 : if (osPathPrefix.size() > 256)
137 : {
138 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big prefix for GROUP");
139 0 : return false;
140 : }
141 2743 : if (nRecLevel == 100)
142 0 : return false;
143 : for (; true;)
144 : {
145 14532 : CPLString osName, osValue;
146 14532 : if (!ReadPair(osName, osValue, oCur))
147 3 : return false;
148 :
149 14529 : if (EQUAL(osName, "OBJECT") || EQUAL(osName, "GROUP"))
150 : {
151 2405 : CPLJSONObject oNewGroup;
152 2405 : oNewGroup.Add("_type",
153 2405 : EQUAL(osName, "OBJECT") ? "object" : "group");
154 2405 : if (!ReadGroup((osPathPrefix + osValue + ".").c_str(), oNewGroup,
155 : nRecLevel + 1))
156 : {
157 0 : return false;
158 : }
159 7215 : CPLJSONObject oName = oNewGroup["Name"];
160 2405 : if (oName.GetType() == CPLJSONObject::Type::String)
161 : {
162 285 : oCur.Add(osValue + "_" + oName.ToString(), oNewGroup);
163 285 : oNewGroup.Add("_container_name", osValue);
164 : }
165 2120 : 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 2116 : oCur.Add(osValue, oNewGroup);
178 : }
179 : }
180 22900 : else if (EQUAL(osName, "END") || EQUAL(osName, "END_GROUP") ||
181 10776 : EQUAL(osName, "END_OBJECT"))
182 : {
183 2740 : return true;
184 : }
185 : else
186 : {
187 9384 : osName = osPathPrefix + osName;
188 9384 : aosKeywordList.AddNameValue(osName, osValue);
189 : }
190 11789 : }
191 : }
192 :
193 : /************************************************************************/
194 : /* StripQuotesIfNeeded() */
195 : /************************************************************************/
196 :
197 5124 : static CPLString StripQuotesIfNeeded(const CPLString &osWord,
198 : bool bQuotesAlreadyRemoved)
199 : {
200 5124 : if (bQuotesAlreadyRemoved || osWord.size() < 2 || osWord[0] != '"')
201 3347 : return osWord;
202 3554 : 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 14532 : bool NASAKeywordHandler::ReadPair(CPLString &osName, CPLString &osValue,
214 : CPLJSONObject &oCur)
215 :
216 : {
217 14532 : osName = "";
218 14532 : osValue = "";
219 :
220 14532 : if (!ReadWord(osName))
221 0 : return false;
222 :
223 14532 : SkipWhite();
224 :
225 14532 : if (EQUAL(osName, "END"))
226 335 : return true;
227 :
228 14197 : if (*pszHeaderNext != '=')
229 : {
230 : // ISIS3 does not have anything after the end group/object keyword.
231 1772 : if (EQUAL(osName, "End_Group") || EQUAL(osName, "End_Object"))
232 1771 : return true;
233 :
234 1 : return false;
235 : }
236 :
237 12425 : pszHeaderNext++;
238 :
239 12425 : SkipWhite();
240 :
241 12425 : osValue = "";
242 12425 : bool bIsString = true;
243 :
244 : // Handle value lists like:
245 : // Name = (Red, Red) or {Red, Red} or even ({Red, Red}, {Red, Red})
246 24850 : CPLJSONArray oArray;
247 12425 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
248 : {
249 424 : std::vector<char> oStackArrayBeginChar;
250 424 : CPLString osWord;
251 :
252 424 : oStackArrayBeginChar.push_back(*pszHeaderNext);
253 424 : osValue += *pszHeaderNext;
254 424 : pszHeaderNext++;
255 :
256 1303 : while (ReadWord(osWord, m_bStripSurroundingQuotes, true, &bIsString))
257 : {
258 1303 : 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 1303 : if (bIsString)
268 : {
269 947 : if (!(osWord.empty() &&
270 8 : (*pszHeaderNext == '(' || *pszHeaderNext == '{' ||
271 8 : *pszHeaderNext == ')' || *pszHeaderNext == '}')))
272 : {
273 935 : oArray.Add(
274 1870 : StripQuotesIfNeeded(osWord, m_bStripSurroundingQuotes));
275 : }
276 : }
277 364 : else if (CPLGetValueType(osWord) == CPL_VALUE_INTEGER)
278 : {
279 229 : oArray.Add(atoi(osWord));
280 : }
281 : else
282 : {
283 135 : oArray.Add(CPLAtof(osWord));
284 : }
285 :
286 1303 : osValue += osWord;
287 1316 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
288 : {
289 13 : pszHeaderNext++;
290 : }
291 :
292 1303 : if (*pszHeaderNext == ')')
293 : {
294 414 : osValue += *pszHeaderNext;
295 828 : if (oStackArrayBeginChar.empty() ||
296 414 : oStackArrayBeginChar.back() != '(')
297 : {
298 1 : CPLDebug("PDS", "Unpaired ( ) for %s", osName.c_str());
299 1 : return false;
300 : }
301 413 : oStackArrayBeginChar.pop_back();
302 413 : pszHeaderNext++;
303 413 : if (oStackArrayBeginChar.empty())
304 412 : break;
305 : }
306 889 : else if (*pszHeaderNext == '}')
307 : {
308 13 : osValue += *pszHeaderNext;
309 26 : if (oStackArrayBeginChar.empty() ||
310 13 : oStackArrayBeginChar.back() != '{')
311 : {
312 1 : CPLDebug("PDS", "Unpaired { } for %s", osName.c_str());
313 1 : return false;
314 : }
315 12 : oStackArrayBeginChar.pop_back();
316 12 : pszHeaderNext++;
317 12 : if (oStackArrayBeginChar.empty())
318 10 : break;
319 : }
320 876 : else if (*pszHeaderNext == ',')
321 : {
322 873 : osValue += *pszHeaderNext;
323 873 : pszHeaderNext++;
324 : // Do not use SkipWhite() here to avoid being confuse by
325 : // constructs like
326 : // FOO = (#123456,
327 : // #123456)
328 : // where we could confuse the second line with a comment.
329 3220 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
330 : {
331 2347 : pszHeaderNext++;
332 : }
333 : }
334 879 : SkipWhite();
335 422 : }
336 : }
337 :
338 : else // Handle more normal "single word" values.
339 : {
340 12001 : if (!ReadWord(osValue, m_bStripSurroundingQuotes, false, &bIsString))
341 0 : return false;
342 : }
343 :
344 12423 : SkipWhite();
345 :
346 : // No units keyword?
347 12423 : if (*pszHeaderNext != '<')
348 : {
349 11677 : if (!EQUAL(osName, "OBJECT") && !EQUAL(osName, "GROUP"))
350 : {
351 9272 : if (oArray.Size() > 0)
352 : {
353 418 : oCur.Add(osName, oArray);
354 : }
355 : else
356 : {
357 8854 : if (bIsString)
358 : {
359 4189 : oCur.Add(osName, StripQuotesIfNeeded(
360 4189 : osValue, m_bStripSurroundingQuotes));
361 : }
362 4665 : else if (CPLGetValueType(osValue) == CPL_VALUE_INTEGER)
363 : {
364 2902 : oCur.Add(osName, atoi(osValue));
365 : }
366 : else
367 : {
368 1763 : oCur.Add(osName, CPLAtof(osValue));
369 : }
370 : }
371 : }
372 11677 : return true;
373 : }
374 :
375 1492 : CPLString osValueNoUnit(osValue);
376 : // Append units keyword. For lines that like like this:
377 : // MAP_RESOLUTION = 4.0 <PIXEL/DEGREE>
378 :
379 746 : osValue += " ";
380 :
381 1492 : CPLString osWord;
382 1492 : CPLString osUnit;
383 746 : while (ReadWord(osWord))
384 : {
385 746 : SkipWhite();
386 :
387 746 : osValue += osWord;
388 746 : osUnit = osWord;
389 746 : if (osWord.back() == '>')
390 746 : break;
391 : }
392 :
393 746 : if (osUnit[0] == '<')
394 746 : osUnit = osUnit.substr(1);
395 746 : if (!osUnit.empty() && osUnit.back() == '>')
396 746 : osUnit = osUnit.substr(0, osUnit.size() - 1);
397 :
398 746 : CPLJSONObject newObject;
399 746 : oCur.Add(osName, newObject);
400 :
401 746 : if (oArray.Size() > 0)
402 : {
403 2 : newObject.Add("value", oArray);
404 : }
405 : else
406 : {
407 744 : if (bIsString)
408 : {
409 10 : newObject.Add("value", osValueNoUnit);
410 : }
411 734 : else if (CPLGetValueType(osValueNoUnit) == CPL_VALUE_INTEGER)
412 : {
413 38 : newObject.Add("value", atoi(osValueNoUnit));
414 : }
415 : else
416 : {
417 696 : newObject.Add("value", CPLAtof(osValueNoUnit));
418 : }
419 : }
420 746 : newObject.Add("unit", osUnit);
421 :
422 746 : return true;
423 : }
424 :
425 : /************************************************************************/
426 : /* ReadWord() */
427 : /* Returns TRUE on success */
428 : /************************************************************************/
429 :
430 28582 : bool NASAKeywordHandler::ReadWord(CPLString &osWord,
431 : bool bStripSurroundingQuotes, bool bParseList,
432 : bool *pbIsString)
433 :
434 : {
435 28582 : if (pbIsString)
436 13304 : *pbIsString = false;
437 28582 : osWord = "";
438 :
439 28582 : SkipWhite();
440 :
441 28582 : if (!(*pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
442 28582 : !isspace(static_cast<unsigned char>(*pszHeaderNext))))
443 0 : return false;
444 :
445 : /* Extract a text string delimited by '\"' */
446 : /* Convert newlines (CR or LF) within quotes. While text strings
447 : support them as per ODL, the keyword list doesn't want them */
448 28582 : if (*pszHeaderNext == '"')
449 : {
450 1813 : if (pbIsString)
451 1813 : *pbIsString = true;
452 1813 : if (!bStripSurroundingQuotes)
453 1779 : osWord += *(pszHeaderNext);
454 1813 : pszHeaderNext++;
455 27179 : while (*pszHeaderNext != '"')
456 : {
457 25366 : if (*pszHeaderNext == '\0')
458 0 : return false;
459 25366 : if (*pszHeaderNext == '\n')
460 : {
461 121 : osWord += "\\n";
462 121 : pszHeaderNext++;
463 121 : continue;
464 : }
465 25245 : if (*pszHeaderNext == '\r')
466 : {
467 118 : osWord += "\\r";
468 118 : pszHeaderNext++;
469 118 : continue;
470 : }
471 25127 : osWord += *(pszHeaderNext++);
472 : }
473 1813 : if (!bStripSurroundingQuotes)
474 1779 : osWord += *(pszHeaderNext);
475 1813 : pszHeaderNext++;
476 :
477 1813 : return true;
478 : }
479 :
480 : /* Extract a symbol string */
481 : /* These are expected to not have
482 : '\'' (delimiters),
483 : format effectors (should fit on a single line) or
484 : control characters.
485 : */
486 26769 : if (*pszHeaderNext == '\'')
487 : {
488 8 : if (pbIsString)
489 8 : *pbIsString = true;
490 8 : if (!bStripSurroundingQuotes)
491 8 : osWord += *(pszHeaderNext);
492 8 : pszHeaderNext++;
493 32 : while (*pszHeaderNext != '\'')
494 : {
495 24 : if (*pszHeaderNext == '\0')
496 0 : return false;
497 :
498 24 : osWord += *(pszHeaderNext++);
499 : }
500 8 : if (!bStripSurroundingQuotes)
501 8 : osWord += *(pszHeaderNext);
502 8 : pszHeaderNext++;
503 8 : return true;
504 : }
505 :
506 : /*
507 : * Extract normal text. Terminated by '=' or whitespace.
508 : *
509 : * A special exception is that a line may terminate with a '-'
510 : * which is taken as a line extender, and we suck up white space to new
511 : * text.
512 : */
513 239048 : while (
514 265809 : *pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
515 263861 : ((bParseList && *pszHeaderNext != ',' && *pszHeaderNext != '(' &&
516 5331 : *pszHeaderNext != ')' && *pszHeaderNext != '{' &&
517 263861 : *pszHeaderNext != '}') ||
518 258044 : (!bParseList && !isspace(static_cast<unsigned char>(*pszHeaderNext)))))
519 : {
520 239048 : osWord += *pszHeaderNext;
521 239048 : pszHeaderNext++;
522 :
523 239048 : if (*pszHeaderNext == '-' &&
524 145 : (pszHeaderNext[1] == 10 || pszHeaderNext[1] == 13))
525 : {
526 4 : pszHeaderNext += 2;
527 4 : SkipWhite();
528 : }
529 : }
530 :
531 26761 : if (pbIsString)
532 11483 : *pbIsString = CPLGetValueType(osWord) == CPL_VALUE_STRING;
533 :
534 26761 : return true;
535 : }
536 :
537 : /************************************************************************/
538 : /* SkipWhite() */
539 : /* Skip white spaces and C style comments */
540 : /************************************************************************/
541 :
542 224874 : void NASAKeywordHandler::SkipWhite()
543 :
544 : {
545 : for (; true;)
546 : {
547 : // Skip C style comments
548 224874 : if (*pszHeaderNext == '/' && pszHeaderNext[1] == '*')
549 : {
550 333 : pszHeaderNext += 2;
551 :
552 17886 : while (*pszHeaderNext != '\0' &&
553 17886 : (*pszHeaderNext != '*' || pszHeaderNext[1] != '/'))
554 : {
555 17553 : pszHeaderNext++;
556 : }
557 333 : if (*pszHeaderNext == '\0')
558 0 : return;
559 :
560 333 : pszHeaderNext += 2;
561 :
562 : // consume till end of line.
563 : // reduce sensibility to a label error
564 333 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
565 294 : *pszHeaderNext != 13)
566 : {
567 0 : pszHeaderNext++;
568 : }
569 333 : continue;
570 : }
571 :
572 : // Skip # style comments
573 224541 : if ((*pszHeaderNext == 10 || *pszHeaderNext == 13 ||
574 205224 : *pszHeaderNext == ' ' || *pszHeaderNext == '\t') &&
575 154950 : pszHeaderNext[1] == '#')
576 : {
577 83 : pszHeaderNext += 2;
578 :
579 : // consume till end of line.
580 4138 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
581 4055 : *pszHeaderNext != 13)
582 : {
583 4055 : pszHeaderNext++;
584 : }
585 83 : continue;
586 : }
587 :
588 : // Skip white space (newline, space, tab, etc )
589 224458 : if (isspace(static_cast<unsigned char>(*pszHeaderNext)))
590 : {
591 154867 : pszHeaderNext++;
592 154867 : continue;
593 : }
594 :
595 : // not white space, return.
596 69591 : return;
597 : }
598 : }
599 :
600 : /************************************************************************/
601 : /* GetKeyword() */
602 : /************************************************************************/
603 :
604 8167 : const char *NASAKeywordHandler::GetKeyword(const char *pszPath,
605 : const char *pszDefault)
606 :
607 : {
608 8167 : return aosKeywordList.FetchNameValueDef(pszPath, pszDefault);
609 : }
610 :
611 : /************************************************************************/
612 : /* GetKeywordList() */
613 : /************************************************************************/
614 :
615 0 : char **NASAKeywordHandler::GetKeywordList()
616 : {
617 0 : return aosKeywordList.List();
618 : }
619 :
620 : /************************************************************************/
621 : /* StealJSon() */
622 : /************************************************************************/
623 :
624 326 : CPLJSONObject NASAKeywordHandler::GetJsonObject() const
625 : {
626 326 : return oJSon;
627 : }
628 :
629 : //! @endcond
|