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 458 : NASAKeywordHandler::NASAKeywordHandler()
56 458 : : pszHeaderNext(nullptr), m_bStripSurroundingQuotes(false)
57 : {
58 458 : oJSon.Deinit();
59 458 : }
60 :
61 : /************************************************************************/
62 : /* ~NASAKeywordHandler() */
63 : /************************************************************************/
64 :
65 458 : NASAKeywordHandler::~NASAKeywordHandler()
66 :
67 : {
68 458 : }
69 :
70 : /************************************************************************/
71 : /* Ingest() */
72 : /************************************************************************/
73 :
74 307 : 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 307 : if (VSIFSeekL(fp, nOffset, SEEK_SET) != 0)
81 0 : return false;
82 :
83 614 : std::string osHeaderText;
84 : for (; true;)
85 : {
86 : char szChunk[513];
87 :
88 4151 : int nBytesRead = static_cast<int>(VSIFReadL(szChunk, 1, 512, fp));
89 :
90 4151 : szChunk[nBytesRead] = '\0';
91 4151 : osHeaderText += szChunk;
92 :
93 4151 : if (nBytesRead < 512)
94 165 : break;
95 :
96 3986 : const char *pszCheck = nullptr;
97 3986 : if (osHeaderText.size() > 520)
98 489 : pszCheck = osHeaderText.c_str() + (osHeaderText.size() - 520);
99 : else
100 3497 : pszCheck = szChunk;
101 :
102 3986 : if (strstr(pszCheck, "\r\nEND\r\n") != nullptr ||
103 3971 : strstr(pszCheck, "\nEND\n") != nullptr ||
104 3962 : strstr(pszCheck, "\r\nEnd\r\n") != nullptr ||
105 3962 : strstr(pszCheck, "\nEnd\n") != nullptr)
106 : break;
107 3844 : }
108 :
109 307 : return Parse(osHeaderText.c_str());
110 : }
111 :
112 : /************************************************************************/
113 : /* Parse() */
114 : /************************************************************************/
115 :
116 333 : bool NASAKeywordHandler::Parse(const char *pszStr)
117 :
118 : {
119 333 : pszHeaderNext = pszStr;
120 :
121 : /* -------------------------------------------------------------------- */
122 : /* Process name/value pairs, keeping track of a "path stack". */
123 : /* -------------------------------------------------------------------- */
124 333 : oJSon = CPLJSONObject();
125 333 : return ReadGroup("", oJSon, 0);
126 : }
127 :
128 : /************************************************************************/
129 : /* ReadGroup() */
130 : /************************************************************************/
131 :
132 2672 : bool NASAKeywordHandler::ReadGroup(const std::string &osPathPrefix,
133 : CPLJSONObject &oCur, int nRecLevel)
134 :
135 : {
136 2672 : if (osPathPrefix.size() > 256)
137 : {
138 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big prefix for GROUP");
139 0 : return false;
140 : }
141 2672 : if (nRecLevel == 100)
142 0 : return false;
143 : for (; true;)
144 : {
145 14187 : CPLString osName, osValue;
146 14187 : if (!ReadPair(osName, osValue, oCur))
147 3 : return false;
148 :
149 14184 : if (EQUAL(osName, "OBJECT") || EQUAL(osName, "GROUP"))
150 : {
151 2339 : CPLJSONObject oNewGroup;
152 2339 : oNewGroup.Add("_type",
153 2339 : EQUAL(osName, "OBJECT") ? "object" : "group");
154 2339 : if (!ReadGroup((osPathPrefix + osValue + ".").c_str(), oNewGroup,
155 : nRecLevel + 1))
156 : {
157 0 : return false;
158 : }
159 7017 : CPLJSONObject oName = oNewGroup["Name"];
160 2359 : 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 2319 : 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 2315 : oCur.Add(osValue, oNewGroup);
179 : }
180 : }
181 22388 : else if (EQUAL(osName, "END") || EQUAL(osName, "END_GROUP") ||
182 10543 : EQUAL(osName, "END_OBJECT"))
183 : {
184 2669 : return true;
185 : }
186 : else
187 : {
188 9176 : osName = osPathPrefix + osName;
189 9176 : aosKeywordList.AddNameValue(osName, osValue);
190 : }
191 11515 : }
192 : }
193 :
194 : /************************************************************************/
195 : /* StripQuotesIfNeeded() */
196 : /************************************************************************/
197 :
198 4970 : static CPLString StripQuotesIfNeeded(const CPLString &osWord,
199 : bool bQuotesAlreadyRemoved)
200 : {
201 4970 : if (bQuotesAlreadyRemoved || osWord.size() < 2 || osWord[0] != '"')
202 3238 : return osWord;
203 3464 : 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 14187 : bool NASAKeywordHandler::ReadPair(CPLString &osName, CPLString &osValue,
215 : CPLJSONObject &oCur)
216 :
217 : {
218 14187 : osName = "";
219 14187 : osValue = "";
220 :
221 14187 : if (!ReadWord(osName))
222 0 : return false;
223 :
224 14187 : SkipWhite();
225 :
226 14187 : if (EQUAL(osName, "END"))
227 330 : return true;
228 :
229 13857 : 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 12104 : pszHeaderNext++;
239 :
240 12104 : SkipWhite();
241 :
242 12104 : osValue = "";
243 12104 : bool bIsString = true;
244 :
245 : // Handle value lists like:
246 : // Name = (Red, Red) or {Red, Red} or even ({Red, Red}, {Red, Red})
247 24208 : CPLJSONArray oArray;
248 12104 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
249 : {
250 401 : std::vector<char> oStackArrayBeginChar;
251 401 : CPLString osWord;
252 :
253 401 : oStackArrayBeginChar.push_back(*pszHeaderNext);
254 401 : osValue += *pszHeaderNext;
255 401 : pszHeaderNext++;
256 :
257 1224 : while (ReadWord(osWord, m_bStripSurroundingQuotes, true, &bIsString))
258 : {
259 1224 : 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 1224 : if (bIsString)
269 : {
270 919 : if (!(osWord.empty() &&
271 8 : (*pszHeaderNext == '(' || *pszHeaderNext == '{' ||
272 8 : *pszHeaderNext == ')' || *pszHeaderNext == '}')))
273 : {
274 907 : oArray.Add(
275 1814 : StripQuotesIfNeeded(osWord, m_bStripSurroundingQuotes));
276 : }
277 : }
278 313 : else if (CPLGetValueType(osWord) == CPL_VALUE_INTEGER)
279 : {
280 193 : oArray.Add(atoi(osWord));
281 : }
282 : else
283 : {
284 120 : oArray.Add(CPLAtof(osWord));
285 : }
286 :
287 1224 : osValue += osWord;
288 1237 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
289 : {
290 13 : pszHeaderNext++;
291 : }
292 :
293 1224 : if (*pszHeaderNext == ')')
294 : {
295 391 : osValue += *pszHeaderNext;
296 782 : if (oStackArrayBeginChar.empty() ||
297 391 : oStackArrayBeginChar.back() != '(')
298 : {
299 1 : CPLDebug("PDS", "Unpaired ( ) for %s", osName.c_str());
300 1 : return false;
301 : }
302 390 : oStackArrayBeginChar.pop_back();
303 390 : pszHeaderNext++;
304 390 : if (oStackArrayBeginChar.empty())
305 389 : break;
306 : }
307 833 : 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 820 : else if (*pszHeaderNext == ',')
322 : {
323 817 : osValue += *pszHeaderNext;
324 817 : 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 3164 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
331 : {
332 2347 : pszHeaderNext++;
333 : }
334 : }
335 823 : SkipWhite();
336 399 : }
337 : }
338 :
339 : else // Handle more normal "single word" values.
340 : {
341 11703 : if (!ReadWord(osValue, m_bStripSurroundingQuotes, false, &bIsString))
342 0 : return false;
343 : }
344 :
345 12102 : SkipWhite();
346 :
347 : // No units keyword?
348 12102 : if (*pszHeaderNext != '<')
349 : {
350 11376 : if (!EQUAL(osName, "OBJECT") && !EQUAL(osName, "GROUP"))
351 : {
352 9037 : if (oArray.Size() > 0)
353 : {
354 395 : oCur.Add(osName, oArray);
355 : }
356 : else
357 : {
358 8642 : if (bIsString)
359 : {
360 4063 : oCur.Add(osName, StripQuotesIfNeeded(
361 4063 : osValue, m_bStripSurroundingQuotes));
362 : }
363 4579 : else if (CPLGetValueType(osValue) == CPL_VALUE_INTEGER)
364 : {
365 2863 : oCur.Add(osName, atoi(osValue));
366 : }
367 : else
368 : {
369 1716 : oCur.Add(osName, CPLAtof(osValue));
370 : }
371 : }
372 : }
373 11376 : 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 27840 : bool NASAKeywordHandler::ReadWord(CPLString &osWord,
432 : bool bStripSurroundingQuotes, bool bParseList,
433 : bool *pbIsString)
434 :
435 : {
436 27840 : if (pbIsString)
437 12927 : *pbIsString = false;
438 27840 : osWord = "";
439 :
440 27840 : SkipWhite();
441 :
442 27840 : if (!(*pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
443 27840 : !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 27840 : if (*pszHeaderNext == '"')
450 : {
451 1760 : if (pbIsString)
452 1760 : *pbIsString = true;
453 1760 : if (!bStripSurroundingQuotes)
454 1734 : osWord += *(pszHeaderNext);
455 1760 : pszHeaderNext++;
456 26332 : while (*pszHeaderNext != '"')
457 : {
458 24572 : if (*pszHeaderNext == '\0')
459 0 : return false;
460 24572 : if (*pszHeaderNext == '\n')
461 : {
462 121 : osWord += "\\n";
463 121 : pszHeaderNext++;
464 121 : continue;
465 : }
466 24451 : if (*pszHeaderNext == '\r')
467 : {
468 118 : osWord += "\\r";
469 118 : pszHeaderNext++;
470 118 : continue;
471 : }
472 24333 : osWord += *(pszHeaderNext++);
473 : }
474 1760 : if (!bStripSurroundingQuotes)
475 1734 : osWord += *(pszHeaderNext);
476 1760 : pszHeaderNext++;
477 :
478 1760 : 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 26080 : 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 232453 : while (
515 258525 : *pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
516 256736 : ((bParseList && *pszHeaderNext != ',' && *pszHeaderNext != '(' &&
517 5091 : *pszHeaderNext != ')' && *pszHeaderNext != '{' &&
518 256736 : *pszHeaderNext != '}') ||
519 251201 : (!bParseList && !isspace(static_cast<unsigned char>(*pszHeaderNext)))))
520 : {
521 232453 : osWord += *pszHeaderNext;
522 232453 : pszHeaderNext++;
523 :
524 232453 : if (*pszHeaderNext == '-' &&
525 141 : (pszHeaderNext[1] == 10 || pszHeaderNext[1] == 13))
526 : {
527 4 : pszHeaderNext += 2;
528 4 : SkipWhite();
529 : }
530 : }
531 :
532 26072 : if (pbIsString)
533 11159 : *pbIsString = CPLGetValueType(osWord) == CPL_VALUE_STRING;
534 :
535 26072 : return true;
536 : }
537 :
538 : /************************************************************************/
539 : /* SkipWhite() */
540 : /* Skip white spaces and C style comments */
541 : /************************************************************************/
542 :
543 220568 : void NASAKeywordHandler::SkipWhite()
544 :
545 : {
546 : for (; true;)
547 : {
548 : // Skip C style comments
549 220568 : if (*pszHeaderNext == '/' && pszHeaderNext[1] == '*')
550 : {
551 333 : pszHeaderNext += 2;
552 :
553 17886 : while (*pszHeaderNext != '\0' &&
554 17886 : (*pszHeaderNext != '*' || pszHeaderNext[1] != '/'))
555 : {
556 17553 : pszHeaderNext++;
557 : }
558 333 : if (*pszHeaderNext == '\0')
559 0 : return;
560 :
561 333 : pszHeaderNext += 2;
562 :
563 : // consume till end of line.
564 : // reduce sensibility to a label error
565 333 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
566 294 : *pszHeaderNext != 13)
567 : {
568 0 : pszHeaderNext++;
569 : }
570 333 : continue;
571 : }
572 :
573 : // Skip # style comments
574 220235 : if ((*pszHeaderNext == 10 || *pszHeaderNext == 13 ||
575 201276 : *pszHeaderNext == ' ' || *pszHeaderNext == '\t') &&
576 152449 : 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 220152 : if (isspace(static_cast<unsigned char>(*pszHeaderNext)))
591 : {
592 152366 : pszHeaderNext++;
593 152366 : continue;
594 : }
595 :
596 : // not white space, return.
597 67786 : return;
598 : }
599 : }
600 :
601 : /************************************************************************/
602 : /* GetKeyword() */
603 : /************************************************************************/
604 :
605 8144 : const char *NASAKeywordHandler::GetKeyword(const char *pszPath,
606 : const char *pszDefault)
607 :
608 : {
609 8144 : 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
|