Line data Source code
1 : /****************************************************************************** 2 : * 3 : * Project: Common Portability Library 4 : * Purpose: Implementation of CPLKeywordParser - a class for parsing 5 : * the keyword format used for files like QuickBird .RPB files. 6 : * This is a slight variation on the NASAKeywordParser used for 7 : * the PDS/ISIS2/ISIS3 formats. 8 : * Author: Frank Warmerdam <warmerdam@pobox.com 9 : * 10 : ****************************************************************************** 11 : * Copyright (c) 2008, Frank Warmerdam <warmerdam@pobox.com> 12 : * Copyright (c) 2009-2010, Even Rouault <even dot rouault at spatialys.com> 13 : * 14 : * SPDX-License-Identifier: MIT 15 : ****************************************************************************/ 16 : 17 : //! @cond Doxygen_Suppress 18 : 19 : #include "cpl_port.h" 20 : #include "cplkeywordparser.h" 21 : 22 : #include <cctype> 23 : #include <cstring> 24 : #include <string> 25 : 26 : #include "cpl_string.h" 27 : #include "cpl_vsi.h" 28 : 29 : /************************************************************************/ 30 : /* ==================================================================== */ 31 : /* CPLKeywordParser */ 32 : /* ==================================================================== */ 33 : /************************************************************************/ 34 : 35 : /************************************************************************/ 36 : /* CPLKeywordParser() */ 37 : /************************************************************************/ 38 : 39 : CPLKeywordParser::CPLKeywordParser() = default; 40 : 41 : /************************************************************************/ 42 : /* ~CPLKeywordParser() */ 43 : /************************************************************************/ 44 : 45 159 : CPLKeywordParser::~CPLKeywordParser() 46 : 47 : { 48 53 : CSLDestroy(papszKeywordList); 49 53 : papszKeywordList = nullptr; 50 53 : } 51 : 52 : /************************************************************************/ 53 : /* Ingest() */ 54 : /************************************************************************/ 55 : 56 212 : int CPLKeywordParser::Ingest(VSILFILE *fp) 57 : 58 : { 59 : /* -------------------------------------------------------------------- */ 60 : /* Read in buffer till we find END all on its own line. */ 61 : /* -------------------------------------------------------------------- */ 62 : for (; true;) 63 : { 64 212 : char szChunk[513] = {}; 65 212 : const size_t nBytesRead = VSIFReadL(szChunk, 1, 512, fp); 66 : 67 212 : szChunk[nBytesRead] = '\0'; 68 212 : osHeaderText += szChunk; 69 : 70 212 : if (nBytesRead < 512) 71 53 : break; 72 : 73 159 : const char *pszCheck = nullptr; 74 159 : if (osHeaderText.size() > 520) 75 117 : pszCheck = osHeaderText.c_str() + (osHeaderText.size() - 520); 76 : else 77 42 : pszCheck = szChunk; 78 : 79 159 : if (strstr(pszCheck, "\r\nEND;\r\n") != nullptr || 80 159 : strstr(pszCheck, "\nEND;\n") != nullptr) 81 : break; 82 159 : } 83 : 84 53 : pszHeaderNext = osHeaderText.c_str(); 85 : 86 : /* -------------------------------------------------------------------- */ 87 : /* Process name/value pairs, keeping track of a "path stack". */ 88 : /* -------------------------------------------------------------------- */ 89 53 : return ReadGroup("", 0); 90 : } 91 : 92 : /************************************************************************/ 93 : /* ReadGroup() */ 94 : /************************************************************************/ 95 : 96 161 : bool CPLKeywordParser::ReadGroup(const char *pszPathPrefix, int nRecLevel) 97 : 98 : { 99 322 : CPLString osName; 100 322 : CPLString osValue; 101 : 102 : // Arbitrary threshold to avoid stack overflow 103 161 : if (nRecLevel == 100) 104 0 : return false; 105 : 106 : for (; true;) 107 : { 108 2749 : if (!ReadPair(osName, osValue)) 109 4 : return false; 110 : 111 2745 : if (EQUAL(osName, "BEGIN_GROUP") || EQUAL(osName, "GROUP")) 112 : { 113 108 : if (!ReadGroup((CPLString(pszPathPrefix) + osValue + ".").c_str(), 114 : nRecLevel + 1)) 115 4 : return false; 116 : } 117 2637 : else if (STARTS_WITH_CI(osName, "END")) 118 : { 119 153 : return true; 120 : } 121 : else 122 : { 123 2484 : osName = pszPathPrefix + osName; 124 2484 : papszKeywordList = 125 2484 : CSLSetNameValue(papszKeywordList, osName, osValue); 126 : } 127 : } 128 : } 129 : 130 : /************************************************************************/ 131 : /* ReadPair() */ 132 : /* */ 133 : /* Read a name/value pair from the input stream. Strip off */ 134 : /* white space, ignore comments, split on '='. */ 135 : /************************************************************************/ 136 : 137 2749 : bool CPLKeywordParser::ReadPair(CPLString &osName, CPLString &osValue) 138 : 139 : { 140 2749 : osName = ""; 141 2749 : osValue = ""; 142 : 143 2749 : if (!ReadWord(osName)) 144 0 : return false; 145 : 146 2749 : SkipWhite(); 147 : 148 2749 : if (EQUAL(osName, "END")) 149 49 : return TRUE; 150 : 151 2700 : if (*pszHeaderNext != '=') 152 : { 153 : // ISIS3 does not have anything after the end group/object keyword. 154 5 : return EQUAL(osName, "End_Group") || EQUAL(osName, "End_Object"); 155 : } 156 : 157 2695 : pszHeaderNext++; 158 : 159 2695 : SkipWhite(); 160 : 161 2695 : osValue = ""; 162 : 163 : // Handle value lists like: Name = (Red, Red) 164 : // or list of lists like: TLCList = ( (0, 0.000000), (8299, 4.811014) ); 165 2695 : if (*pszHeaderNext == '(') 166 : { 167 184 : CPLString osWord; 168 92 : int nDepth = 0; 169 92 : const char *pszLastPos = pszHeaderNext; 170 : 171 1456 : while (ReadWord(osWord) && pszLastPos != pszHeaderNext) 172 : { 173 1456 : SkipWhite(); 174 1456 : pszLastPos = pszHeaderNext; 175 : 176 1456 : osValue += osWord; 177 1456 : const char *pszIter = osWord.c_str(); 178 1456 : bool bInQuote = false; 179 18799 : while (*pszIter != '\0') 180 : { 181 17435 : if (*pszIter == '"') 182 0 : bInQuote = !bInQuote; 183 17435 : else if (!bInQuote) 184 : { 185 17435 : if (*pszIter == '(') 186 92 : nDepth++; 187 17343 : else if (*pszIter == ')') 188 : { 189 92 : nDepth--; 190 92 : if (nDepth == 0) 191 92 : break; 192 : } 193 : } 194 17343 : pszIter++; 195 : } 196 1456 : if (*pszIter == ')' && nDepth == 0) 197 92 : break; 198 : } 199 : } 200 : 201 : else // Handle more normal "single word" values. 202 : { 203 : // Special case to handle non-conformant IMD files generated by 204 : // previous GDAL version where we omit to surround values that have 205 : // spaces with double quotes. 206 : // So we use a heuristics to handle things like: 207 : // key = value with spaces without single or double quotes at 208 : // beginning of value;[\r]\n 209 2603 : const char *pszNextLF = strchr(pszHeaderNext, '\n'); 210 2603 : if (pszNextLF) 211 : { 212 2603 : std::string osTxt(pszHeaderNext, pszNextLF - pszHeaderNext); 213 2603 : const auto nCRPos = osTxt.find('\r'); 214 2603 : const auto nSemiColonPos = osTxt.find(';'); 215 2603 : const auto nQuotePos = osTxt.find('\''); 216 2603 : const auto nDoubleQuotePos = osTxt.find('"'); 217 2603 : const auto nLTPos = osTxt.find('<'); 218 2354 : if (nSemiColonPos != std::string::npos && 219 2354 : (nCRPos == std::string::npos || (nCRPos + 1 == osTxt.size())) && 220 1 : ((nCRPos != std::string::npos && 221 2354 : (nSemiColonPos + 1 == nCRPos)) || 222 2353 : (nCRPos == std::string::npos && 223 4704 : (nSemiColonPos + 1 == osTxt.size()))) && 224 2351 : (nQuotePos == std::string::npos || nQuotePos != 0) && 225 595 : (nDoubleQuotePos == std::string::npos || 226 4957 : nDoubleQuotePos != 0) && 227 0 : (nLTPos == std::string::npos || 228 0 : osTxt.find('>') == std::string::npos)) 229 : { 230 1741 : pszHeaderNext = pszNextLF; 231 1741 : osTxt.resize(nSemiColonPos); 232 1741 : osValue = osTxt; 233 1742 : while (!osValue.empty() && osValue.back() == ' ') 234 1 : osValue.pop_back(); 235 1741 : return true; 236 : } 237 : } 238 : 239 862 : if (!ReadWord(osValue)) 240 0 : return false; 241 : } 242 : 243 954 : SkipWhite(); 244 : 245 : // No units keyword? 246 954 : if (*pszHeaderNext != '<') 247 954 : return true; 248 : 249 : // Append units keyword. For lines that like like this: 250 : // MAP_RESOLUTION = 4.0 <PIXEL/DEGREE> 251 : 252 0 : CPLString osWord; 253 : 254 0 : osValue += " "; 255 : 256 0 : while (ReadWord(osWord)) 257 : { 258 0 : SkipWhite(); 259 : 260 0 : osValue += osWord; 261 0 : if (osWord.back() == '>') 262 0 : break; 263 : } 264 : 265 0 : return true; 266 : } 267 : 268 : /************************************************************************/ 269 : /* ReadWord() */ 270 : /************************************************************************/ 271 : 272 5067 : bool CPLKeywordParser::ReadWord(CPLString &osWord) 273 : 274 : { 275 5067 : osWord = ""; 276 : 277 5067 : SkipWhite(); 278 : 279 5067 : if (*pszHeaderNext == '\0' || *pszHeaderNext == '=') 280 0 : return false; 281 : 282 98965 : while (*pszHeaderNext != '\0' && *pszHeaderNext != '=' && 283 104032 : *pszHeaderNext != ';' && 284 51264 : !isspace(static_cast<unsigned char>(*pszHeaderNext))) 285 : { 286 46949 : if (*pszHeaderNext == '"') 287 : { 288 608 : osWord += *(pszHeaderNext++); 289 4152 : while (*pszHeaderNext != '"') 290 : { 291 3544 : if (*pszHeaderNext == '\0') 292 0 : return false; 293 : 294 3544 : osWord += *(pszHeaderNext++); 295 : } 296 608 : osWord += *(pszHeaderNext++); 297 : } 298 46341 : else if (*pszHeaderNext == '\'') 299 : { 300 15 : osWord += *(pszHeaderNext++); 301 264 : while (*pszHeaderNext != '\'') 302 : { 303 249 : if (*pszHeaderNext == '\0') 304 0 : return false; 305 : 306 249 : osWord += *(pszHeaderNext++); 307 : } 308 15 : osWord += *(pszHeaderNext++); 309 : } 310 : else 311 : { 312 46326 : osWord += *pszHeaderNext; 313 46326 : pszHeaderNext++; 314 : } 315 : } 316 : 317 5067 : if (*pszHeaderNext == ';') 318 752 : pszHeaderNext++; 319 : 320 5067 : return true; 321 : } 322 : 323 : /************************************************************************/ 324 : /* SkipWhite() */ 325 : /************************************************************************/ 326 : 327 29395 : void CPLKeywordParser::SkipWhite() 328 : 329 : { 330 : for (; true;) 331 : { 332 : // Skip white space (newline, space, tab, etc ) 333 29395 : if (isspace(static_cast<unsigned char>(*pszHeaderNext))) 334 : { 335 16474 : pszHeaderNext++; 336 16474 : continue; 337 : } 338 : 339 : // Skip C style comments 340 12921 : if (*pszHeaderNext == '/' && pszHeaderNext[1] == '*') 341 : { 342 0 : pszHeaderNext += 2; 343 : 344 0 : while (*pszHeaderNext != '\0' && 345 0 : (*pszHeaderNext != '*' || pszHeaderNext[1] != '/')) 346 : { 347 0 : pszHeaderNext++; 348 : } 349 0 : if (*pszHeaderNext == '\0') 350 0 : break; 351 : 352 0 : pszHeaderNext += 2; 353 0 : continue; 354 : } 355 : 356 : // Skip # style comments 357 12921 : if (*pszHeaderNext == '#') 358 : { 359 0 : pszHeaderNext += 1; 360 : 361 : // consume till end of line. 362 0 : while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 && 363 0 : *pszHeaderNext != 13) 364 : { 365 0 : pszHeaderNext++; 366 : } 367 0 : continue; 368 : } 369 : 370 : // not white space, return. 371 12921 : return; 372 : } 373 : } 374 : 375 : /************************************************************************/ 376 : /* GetKeyword() */ 377 : /************************************************************************/ 378 : 379 160 : const char *CPLKeywordParser::GetKeyword(const char *pszPath, 380 : const char *pszDefault) 381 : 382 : { 383 160 : const char *pszResult = CSLFetchNameValue(papszKeywordList, pszPath); 384 160 : if (pszResult == nullptr) 385 0 : return pszDefault; 386 : 387 160 : return pszResult; 388 : } 389 : 390 : //! @endcond