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 : * Permission is hereby granted, free of charge, to any person obtaining a
16 : * copy of this software and associated documentation files (the "Software"),
17 : * to deal in the Software without restriction, including without limitation
18 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
19 : * and/or sell copies of the Software, and to permit persons to whom the
20 : * Software is furnished to do so, subject to the following conditions:
21 : *
22 : * The above copyright notice and this permission notice shall be included
23 : * in all copies or substantial portions of the Software.
24 : *
25 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
26 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
28 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
30 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
31 : * DEALINGS IN THE SOFTWARE.
32 : ****************************************************************************
33 : * Object Description Language (ODL) is used to encode data labels for PDS
34 : * and other NASA data systems. Refer to Chapter 12 of "PDS Standards
35 : * Reference" at http://pds.jpl.nasa.gov/tools/standards-reference.shtml for
36 : * further details about ODL.
37 : *
38 : * This is also known as PVL (Parameter Value Language) which is written
39 : * about at http://www.orrery.us/node/44 where it notes:
40 : *
41 : * The PVL syntax that the PDS uses is specified by the Consultative Committee
42 : * for Space Data Systems in their Blue Book publication: "Parameter Value
43 : * Language Specification (CCSD0006 and CCSD0008)", June 2000
44 : * [CCSDS 641.0-B-2], and Green Book publication: "Parameter Value Language -
45 : * A Tutorial", June 2000 [CCSDS 641.0-G-2]. PVL has also been accepted by the
46 : * International Standards Organization (ISO), as a Final Draft International
47 : * Standard (ISO 14961:2002) keyword value type language for naming and
48 : * expressing data values.
49 : * --
50 : * also of interest, on PDS ODL:
51 : * http://pds.jpl.nasa.gov/documents/sr/Chapter12.pdf
52 : *
53 : ****************************************************************************/
54 :
55 : #include "nasakeywordhandler.h"
56 : #include "ogrgeojsonreader.h"
57 : #include <vector>
58 :
59 : //! @cond Doxygen_Suppress
60 :
61 : /************************************************************************/
62 : /* ==================================================================== */
63 : /* NASAKeywordHandler */
64 : /* ==================================================================== */
65 : /************************************************************************/
66 :
67 : /************************************************************************/
68 : /* NASAKeywordHandler() */
69 : /************************************************************************/
70 :
71 499 : NASAKeywordHandler::NASAKeywordHandler()
72 499 : : pszHeaderNext(nullptr), m_bStripSurroundingQuotes(false)
73 : {
74 499 : oJSon.Deinit();
75 499 : }
76 :
77 : /************************************************************************/
78 : /* ~NASAKeywordHandler() */
79 : /************************************************************************/
80 :
81 499 : NASAKeywordHandler::~NASAKeywordHandler()
82 :
83 : {
84 499 : }
85 :
86 : /************************************************************************/
87 : /* Ingest() */
88 : /************************************************************************/
89 :
90 348 : bool NASAKeywordHandler::Ingest(VSILFILE *fp, int nOffset)
91 :
92 : {
93 : /* -------------------------------------------------------------------- */
94 : /* Read in buffer till we find END all on its own line. */
95 : /* -------------------------------------------------------------------- */
96 348 : if (VSIFSeekL(fp, nOffset, SEEK_SET) != 0)
97 0 : return false;
98 :
99 696 : std::string osHeaderText;
100 : for (; true;)
101 : {
102 : char szChunk[513];
103 :
104 4241 : int nBytesRead = static_cast<int>(VSIFReadL(szChunk, 1, 512, fp));
105 :
106 4241 : szChunk[nBytesRead] = '\0';
107 4241 : osHeaderText += szChunk;
108 :
109 4241 : if (nBytesRead < 512)
110 165 : break;
111 :
112 4076 : const char *pszCheck = nullptr;
113 4076 : if (osHeaderText.size() > 520)
114 538 : pszCheck = osHeaderText.c_str() + (osHeaderText.size() - 520);
115 : else
116 3538 : pszCheck = szChunk;
117 :
118 4076 : if (strstr(pszCheck, "\r\nEND\r\n") != nullptr ||
119 4061 : strstr(pszCheck, "\nEND\n") != nullptr ||
120 4011 : strstr(pszCheck, "\r\nEnd\r\n") != nullptr ||
121 4011 : strstr(pszCheck, "\nEnd\n") != nullptr)
122 : break;
123 3893 : }
124 :
125 348 : return Parse(osHeaderText.c_str());
126 : }
127 :
128 : /************************************************************************/
129 : /* Parse() */
130 : /************************************************************************/
131 :
132 374 : bool NASAKeywordHandler::Parse(const char *pszStr)
133 :
134 : {
135 374 : pszHeaderNext = pszStr;
136 :
137 : /* -------------------------------------------------------------------- */
138 : /* Process name/value pairs, keeping track of a "path stack". */
139 : /* -------------------------------------------------------------------- */
140 374 : oJSon = CPLJSONObject();
141 374 : return ReadGroup("", oJSon, 0);
142 : }
143 :
144 : /************************************************************************/
145 : /* ReadGroup() */
146 : /************************************************************************/
147 :
148 2754 : bool NASAKeywordHandler::ReadGroup(const std::string &osPathPrefix,
149 : CPLJSONObject &oCur, int nRecLevel)
150 :
151 : {
152 2754 : if (osPathPrefix.size() > 256)
153 : {
154 0 : CPLError(CE_Failure, CPLE_NotSupported, "Too big prefix for GROUP");
155 0 : return false;
156 : }
157 2754 : if (nRecLevel == 100)
158 0 : return false;
159 : for (; true;)
160 : {
161 15026 : CPLString osName, osValue;
162 15026 : if (!ReadPair(osName, osValue, oCur))
163 3 : return false;
164 :
165 15023 : if (EQUAL(osName, "OBJECT") || EQUAL(osName, "GROUP"))
166 : {
167 2380 : CPLJSONObject oNewGroup;
168 2380 : oNewGroup.Add("_type",
169 2380 : EQUAL(osName, "OBJECT") ? "object" : "group");
170 2380 : if (!ReadGroup((osPathPrefix + osValue + ".").c_str(), oNewGroup,
171 : nRecLevel + 1))
172 : {
173 0 : return false;
174 : }
175 7140 : CPLJSONObject oName = oNewGroup["Name"];
176 2400 : if ((osValue == "Table" || osValue == "Field") &&
177 20 : (oName.GetType() == CPLJSONObject::Type::String))
178 : {
179 20 : oCur.Add(osValue + "_" + oName.ToString(), oNewGroup);
180 20 : oNewGroup.Add("_container_name", osValue);
181 : }
182 2360 : else if (oCur[osValue].IsValid())
183 : {
184 4 : int nIter = 2;
185 6 : while (oCur[osValue + CPLSPrintf("_%d", nIter)].IsValid())
186 : {
187 2 : nIter++;
188 : }
189 4 : oCur.Add(osValue + CPLSPrintf("_%d", nIter), oNewGroup);
190 4 : oNewGroup.Add("_container_name", osValue);
191 : }
192 : else
193 : {
194 2356 : oCur.Add(osValue, oNewGroup);
195 : }
196 : }
197 23943 : else if (EQUAL(osName, "END") || EQUAL(osName, "END_GROUP") ||
198 11300 : EQUAL(osName, "END_OBJECT"))
199 : {
200 2751 : return true;
201 : }
202 : else
203 : {
204 9892 : osName = osPathPrefix + osName;
205 9892 : aosKeywordList.AddNameValue(osName, osValue);
206 : }
207 12272 : }
208 : }
209 :
210 : /************************************************************************/
211 : /* StripQuotesIfNeeded() */
212 : /************************************************************************/
213 :
214 5354 : static CPLString StripQuotesIfNeeded(const CPLString &osWord,
215 : bool bQuotesAlreadyRemoved)
216 : {
217 5354 : if (bQuotesAlreadyRemoved || osWord.size() < 2 || osWord[0] != '"')
218 3537 : return osWord;
219 3634 : return osWord.substr(1, osWord.size() - 2);
220 : }
221 :
222 : /************************************************************************/
223 : /* ReadPair() */
224 : /* */
225 : /* Read a name/value pair from the input stream. Strip off */
226 : /* white space, ignore comments, split on '='. */
227 : /* Returns TRUE on success. */
228 : /************************************************************************/
229 :
230 15026 : bool NASAKeywordHandler::ReadPair(CPLString &osName, CPLString &osValue,
231 : CPLJSONObject &oCur)
232 :
233 : {
234 15026 : osName = "";
235 15026 : osValue = "";
236 :
237 15026 : if (!ReadWord(osName))
238 0 : return false;
239 :
240 15026 : SkipWhite();
241 :
242 15026 : if (EQUAL(osName, "END"))
243 371 : return true;
244 :
245 14655 : if (*pszHeaderNext != '=')
246 : {
247 : // ISIS3 does not have anything after the end group/object keyword.
248 1753 : if (EQUAL(osName, "End_Group") || EQUAL(osName, "End_Object"))
249 1752 : return true;
250 :
251 1 : return false;
252 : }
253 :
254 12902 : pszHeaderNext++;
255 :
256 12902 : SkipWhite();
257 :
258 12902 : osValue = "";
259 12902 : bool bIsString = true;
260 :
261 : // Handle value lists like:
262 : // Name = (Red, Red) or {Red, Red} or even ({Red, Red}, {Red, Red})
263 25804 : CPLJSONArray oArray;
264 12902 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
265 : {
266 533 : std::vector<char> oStackArrayBeginChar;
267 533 : CPLString osWord;
268 :
269 533 : oStackArrayBeginChar.push_back(*pszHeaderNext);
270 533 : osValue += *pszHeaderNext;
271 533 : pszHeaderNext++;
272 :
273 1617 : while (ReadWord(osWord, m_bStripSurroundingQuotes, true, &bIsString))
274 : {
275 1617 : if (*pszHeaderNext == '(' || *pszHeaderNext == '{')
276 : {
277 3 : oStackArrayBeginChar.push_back(*pszHeaderNext);
278 3 : osValue += *pszHeaderNext;
279 3 : pszHeaderNext++;
280 : }
281 :
282 : // TODO: we could probably do better with nested json arrays
283 : // instead of flattening when there are (( )) or ({ }) constructs
284 1617 : if (bIsString)
285 : {
286 1051 : if (!(osWord.empty() &&
287 8 : (*pszHeaderNext == '(' || *pszHeaderNext == '{' ||
288 8 : *pszHeaderNext == ')' || *pszHeaderNext == '}')))
289 : {
290 1039 : oArray.Add(
291 2078 : StripQuotesIfNeeded(osWord, m_bStripSurroundingQuotes));
292 : }
293 : }
294 574 : else if (CPLGetValueType(osWord) == CPL_VALUE_INTEGER)
295 : {
296 454 : oArray.Add(atoi(osWord));
297 : }
298 : else
299 : {
300 120 : oArray.Add(CPLAtof(osWord));
301 : }
302 :
303 1617 : osValue += osWord;
304 1630 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
305 : {
306 13 : pszHeaderNext++;
307 : }
308 :
309 1617 : if (*pszHeaderNext == ')')
310 : {
311 523 : osValue += *pszHeaderNext;
312 1046 : if (oStackArrayBeginChar.empty() ||
313 523 : oStackArrayBeginChar.back() != '(')
314 : {
315 1 : CPLDebug("PDS", "Unpaired ( ) for %s", osName.c_str());
316 1 : return false;
317 : }
318 522 : oStackArrayBeginChar.pop_back();
319 522 : pszHeaderNext++;
320 522 : if (oStackArrayBeginChar.empty())
321 521 : break;
322 : }
323 1094 : else if (*pszHeaderNext == '}')
324 : {
325 13 : osValue += *pszHeaderNext;
326 26 : if (oStackArrayBeginChar.empty() ||
327 13 : oStackArrayBeginChar.back() != '{')
328 : {
329 1 : CPLDebug("PDS", "Unpaired { } for %s", osName.c_str());
330 1 : return false;
331 : }
332 12 : oStackArrayBeginChar.pop_back();
333 12 : pszHeaderNext++;
334 12 : if (oStackArrayBeginChar.empty())
335 10 : break;
336 : }
337 1081 : else if (*pszHeaderNext == ',')
338 : {
339 1078 : osValue += *pszHeaderNext;
340 1078 : pszHeaderNext++;
341 : // Do not use SkipWhite() here to avoid being confuse by
342 : // constructs like
343 : // FOO = (#123456,
344 : // #123456)
345 : // where we could confuse the second line with a comment.
346 3511 : while (isspace(static_cast<unsigned char>(*pszHeaderNext)))
347 : {
348 2433 : pszHeaderNext++;
349 : }
350 : }
351 1084 : SkipWhite();
352 531 : }
353 : }
354 :
355 : else // Handle more normal "single word" values.
356 : {
357 12369 : if (!ReadWord(osValue, m_bStripSurroundingQuotes, false, &bIsString))
358 0 : return false;
359 : }
360 :
361 12900 : SkipWhite();
362 :
363 : // No units keyword?
364 12900 : if (*pszHeaderNext != '<')
365 : {
366 12174 : if (!EQUAL(osName, "OBJECT") && !EQUAL(osName, "GROUP"))
367 : {
368 9794 : if (oArray.Size() > 0)
369 : {
370 527 : oCur.Add(osName, oArray);
371 : }
372 : else
373 : {
374 9267 : if (bIsString)
375 : {
376 4315 : oCur.Add(osName, StripQuotesIfNeeded(
377 4315 : osValue, m_bStripSurroundingQuotes));
378 : }
379 4952 : else if (CPLGetValueType(osValue) == CPL_VALUE_INTEGER)
380 : {
381 3150 : oCur.Add(osName, atoi(osValue));
382 : }
383 : else
384 : {
385 1802 : oCur.Add(osName, CPLAtof(osValue));
386 : }
387 : }
388 : }
389 12174 : return true;
390 : }
391 :
392 1452 : CPLString osValueNoUnit(osValue);
393 : // Append units keyword. For lines that like like this:
394 : // MAP_RESOLUTION = 4.0 <PIXEL/DEGREE>
395 :
396 726 : osValue += " ";
397 :
398 1452 : CPLString osWord;
399 1452 : CPLString osUnit;
400 726 : while (ReadWord(osWord))
401 : {
402 726 : SkipWhite();
403 :
404 726 : osValue += osWord;
405 726 : osUnit = osWord;
406 726 : if (osWord.back() == '>')
407 726 : break;
408 : }
409 :
410 726 : if (osUnit[0] == '<')
411 726 : osUnit = osUnit.substr(1);
412 726 : if (!osUnit.empty() && osUnit.back() == '>')
413 726 : osUnit = osUnit.substr(0, osUnit.size() - 1);
414 :
415 726 : CPLJSONObject newObject;
416 726 : oCur.Add(osName, newObject);
417 :
418 726 : if (oArray.Size() > 0)
419 : {
420 2 : newObject.Add("value", oArray);
421 : }
422 : else
423 : {
424 724 : if (bIsString)
425 : {
426 8 : newObject.Add("value", osValueNoUnit);
427 : }
428 716 : else if (CPLGetValueType(osValueNoUnit) == CPL_VALUE_INTEGER)
429 : {
430 38 : newObject.Add("value", atoi(osValueNoUnit));
431 : }
432 : else
433 : {
434 678 : newObject.Add("value", CPLAtof(osValueNoUnit));
435 : }
436 : }
437 726 : newObject.Add("unit", osUnit);
438 :
439 726 : return true;
440 : }
441 :
442 : /************************************************************************/
443 : /* ReadWord() */
444 : /* Returns TRUE on success */
445 : /************************************************************************/
446 :
447 29738 : bool NASAKeywordHandler::ReadWord(CPLString &osWord,
448 : bool bStripSurroundingQuotes, bool bParseList,
449 : bool *pbIsString)
450 :
451 : {
452 29738 : if (pbIsString)
453 13986 : *pbIsString = false;
454 29738 : osWord = "";
455 :
456 29738 : SkipWhite();
457 :
458 29738 : if (!(*pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
459 29738 : !isspace(static_cast<unsigned char>(*pszHeaderNext))))
460 0 : return false;
461 :
462 : /* Extract a text string delimited by '\"' */
463 : /* Convert newlines (CR or LF) within quotes. While text strings
464 : support them as per ODL, the keyword list doesn't want them */
465 29738 : if (*pszHeaderNext == '"')
466 : {
467 1845 : if (pbIsString)
468 1845 : *pbIsString = true;
469 1845 : if (!bStripSurroundingQuotes)
470 1819 : osWord += *(pszHeaderNext);
471 1845 : pszHeaderNext++;
472 27198 : while (*pszHeaderNext != '"')
473 : {
474 25353 : if (*pszHeaderNext == '\0')
475 0 : return false;
476 25353 : if (*pszHeaderNext == '\n')
477 : {
478 121 : osWord += "\\n";
479 121 : pszHeaderNext++;
480 121 : continue;
481 : }
482 25232 : if (*pszHeaderNext == '\r')
483 : {
484 118 : osWord += "\\r";
485 118 : pszHeaderNext++;
486 118 : continue;
487 : }
488 25114 : osWord += *(pszHeaderNext++);
489 : }
490 1845 : if (!bStripSurroundingQuotes)
491 1819 : osWord += *(pszHeaderNext);
492 1845 : pszHeaderNext++;
493 :
494 1845 : return true;
495 : }
496 :
497 : /* Extract a symbol string */
498 : /* These are expected to not have
499 : '\'' (delimiters),
500 : format effectors (should fit on a single line) or
501 : control characters.
502 : */
503 27893 : if (*pszHeaderNext == '\'')
504 : {
505 8 : if (pbIsString)
506 8 : *pbIsString = true;
507 8 : if (!bStripSurroundingQuotes)
508 8 : osWord += *(pszHeaderNext);
509 8 : pszHeaderNext++;
510 32 : while (*pszHeaderNext != '\'')
511 : {
512 24 : if (*pszHeaderNext == '\0')
513 0 : return false;
514 :
515 24 : osWord += *(pszHeaderNext++);
516 : }
517 8 : if (!bStripSurroundingQuotes)
518 8 : osWord += *(pszHeaderNext);
519 8 : pszHeaderNext++;
520 8 : return true;
521 : }
522 :
523 : /*
524 : * Extract normal text. Terminated by '=' or whitespace.
525 : *
526 : * A special exception is that a line may terminate with a '-'
527 : * which is taken as a line extender, and we suck up white space to new
528 : * text.
529 : */
530 245967 : while (
531 273852 : *pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
532 271243 : ((bParseList && *pszHeaderNext != ',' && *pszHeaderNext != '(' &&
533 6228 : *pszHeaderNext != ')' && *pszHeaderNext != '{' &&
534 271243 : *pszHeaderNext != '}') ||
535 264313 : (!bParseList && !isspace(static_cast<unsigned char>(*pszHeaderNext)))))
536 : {
537 245967 : osWord += *pszHeaderNext;
538 245967 : pszHeaderNext++;
539 :
540 245967 : if (*pszHeaderNext == '-' &&
541 141 : (pszHeaderNext[1] == 10 || pszHeaderNext[1] == 13))
542 : {
543 4 : pszHeaderNext += 2;
544 4 : SkipWhite();
545 : }
546 : }
547 :
548 27885 : if (pbIsString)
549 12133 : *pbIsString = CPLGetValueType(osWord) == CPL_VALUE_STRING;
550 :
551 27885 : return true;
552 : }
553 :
554 : /************************************************************************/
555 : /* SkipWhite() */
556 : /* Skip white spaces and C style comments */
557 : /************************************************************************/
558 :
559 248363 : void NASAKeywordHandler::SkipWhite()
560 :
561 : {
562 : for (; true;)
563 : {
564 : // Skip C style comments
565 248363 : if (*pszHeaderNext == '/' && pszHeaderNext[1] == '*')
566 : {
567 548 : pszHeaderNext += 2;
568 :
569 23046 : while (*pszHeaderNext != '\0' &&
570 23046 : (*pszHeaderNext != '*' || pszHeaderNext[1] != '/'))
571 : {
572 22498 : pszHeaderNext++;
573 : }
574 548 : if (*pszHeaderNext == '\0')
575 0 : return;
576 :
577 548 : pszHeaderNext += 2;
578 :
579 : // consume till end of line.
580 : // reduce sensibility to a label error
581 548 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
582 294 : *pszHeaderNext != 13)
583 : {
584 0 : pszHeaderNext++;
585 : }
586 548 : continue;
587 : }
588 :
589 : // Skip # style comments
590 247815 : if ((*pszHeaderNext == 10 || *pszHeaderNext == 13 ||
591 227673 : *pszHeaderNext == ' ' || *pszHeaderNext == '\t') &&
592 175435 : pszHeaderNext[1] == '#')
593 : {
594 83 : pszHeaderNext += 2;
595 :
596 : // consume till end of line.
597 4138 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
598 4055 : *pszHeaderNext != 13)
599 : {
600 4055 : pszHeaderNext++;
601 : }
602 83 : continue;
603 : }
604 :
605 : // Skip white space (newline, space, tab, etc )
606 247732 : if (isspace(static_cast<unsigned char>(*pszHeaderNext)))
607 : {
608 175352 : pszHeaderNext++;
609 175352 : continue;
610 : }
611 :
612 : // not white space, return.
613 72380 : return;
614 : }
615 : }
616 :
617 : /************************************************************************/
618 : /* GetKeyword() */
619 : /************************************************************************/
620 :
621 9312 : const char *NASAKeywordHandler::GetKeyword(const char *pszPath,
622 : const char *pszDefault)
623 :
624 : {
625 9312 : return aosKeywordList.FetchNameValueDef(pszPath, pszDefault);
626 : }
627 :
628 : /************************************************************************/
629 : /* GetKeywordList() */
630 : /************************************************************************/
631 :
632 0 : char **NASAKeywordHandler::GetKeywordList()
633 : {
634 0 : return aosKeywordList.List();
635 : }
636 :
637 : /************************************************************************/
638 : /* StealJSon() */
639 : /************************************************************************/
640 :
641 321 : CPLJSONObject NASAKeywordHandler::GetJsonObject() const
642 : {
643 321 : return oJSon;
644 : }
645 :
646 : //! @endcond
|