Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: ERMapper .ers Driver
4 : * Purpose: Implementation of ERSHdrNode class for parsing/accessing .ers hdr.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2007, Frank Warmerdam <warmerdam@pobox.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_conv.h"
14 : #include "cpl_string.h"
15 : #include "ershdrnode.h"
16 :
17 : /************************************************************************/
18 : /* ERSHdrNode() */
19 : /************************************************************************/
20 :
21 284 : ERSHdrNode::ERSHdrNode()
22 : : nItemMax(0), nItemCount(0), papszItemName(nullptr),
23 284 : papszItemValue(nullptr), papoItemChild(nullptr)
24 : {
25 284 : }
26 :
27 : /************************************************************************/
28 : /* ~ERSHdrNode() */
29 : /************************************************************************/
30 :
31 284 : ERSHdrNode::~ERSHdrNode()
32 :
33 : {
34 1562 : for (int i = 0; i < nItemCount; i++)
35 : {
36 1278 : if (papoItemChild[i] != nullptr)
37 222 : delete papoItemChild[i];
38 1278 : if (papszItemValue[i] != nullptr)
39 1056 : CPLFree(papszItemValue[i]);
40 1278 : CPLFree(papszItemName[i]);
41 : }
42 :
43 284 : CPLFree(papszItemName);
44 284 : CPLFree(papszItemValue);
45 284 : CPLFree(papoItemChild);
46 284 : }
47 :
48 : /************************************************************************/
49 : /* MakeSpace() */
50 : /* */
51 : /* Ensure we have room for at least one more entry in our item */
52 : /* lists. */
53 : /************************************************************************/
54 :
55 1278 : void ERSHdrNode::MakeSpace()
56 :
57 : {
58 1278 : if (nItemCount == nItemMax)
59 : {
60 287 : nItemMax = (int)(nItemMax * 1.3) + 10;
61 287 : papszItemName =
62 287 : (char **)CPLRealloc(papszItemName, sizeof(char *) * nItemMax);
63 287 : papszItemValue =
64 287 : (char **)CPLRealloc(papszItemValue, sizeof(char *) * nItemMax);
65 287 : papoItemChild =
66 287 : (ERSHdrNode **)CPLRealloc(papoItemChild, sizeof(void *) * nItemMax);
67 : }
68 1278 : }
69 :
70 : /************************************************************************/
71 : /* ReadLine() */
72 : /* */
73 : /* Read one virtual line from the input source. Multiple lines */
74 : /* will be appended for objects enclosed in {}. */
75 : /************************************************************************/
76 :
77 1128 : int ERSHdrNode::ReadLine(VSILFILE *fp, CPLString &osLine)
78 :
79 : {
80 1128 : int nBracketLevel = 0;
81 1128 : bool bInQuote = false;
82 1128 : size_t i = 0;
83 1128 : bool bLastCharWasSlashInQuote = false;
84 :
85 1128 : osLine = "";
86 19 : do
87 : {
88 1147 : const char *pszNewLine = CPLReadLineL(fp);
89 :
90 1147 : if (pszNewLine == nullptr)
91 0 : return FALSE;
92 :
93 1147 : osLine += pszNewLine;
94 :
95 24171 : for (; i < osLine.length(); i++)
96 : {
97 23024 : const char ch = osLine[i];
98 23024 : if (bLastCharWasSlashInQuote)
99 : {
100 12 : bLastCharWasSlashInQuote = false;
101 : }
102 23012 : else if (ch == '"')
103 398 : bInQuote = !bInQuote;
104 22614 : else if (ch == '{' && !bInQuote)
105 20 : nBracketLevel++;
106 22594 : else if (ch == '}' && !bInQuote)
107 20 : nBracketLevel--;
108 : // We have to ignore escaped quotes and backslashes in strings.
109 22574 : else if (ch == '\\' && bInQuote)
110 : {
111 12 : bLastCharWasSlashInQuote = true;
112 : }
113 : // A comment is a '#' up to the end of the line.
114 22562 : else if (ch == '#' && !bInQuote)
115 : {
116 12 : osLine = osLine.substr(0, i) + "\n";
117 : }
118 : }
119 1147 : } while (nBracketLevel > 0);
120 :
121 1128 : return TRUE;
122 : }
123 :
124 : /************************************************************************/
125 : /* ParseHeader() */
126 : /* */
127 : /* We receive the FILE * positioned at the start of the file */
128 : /* and read all children. This allows reading comment lines */
129 : /* at the start of the file. */
130 : /************************************************************************/
131 :
132 65 : int ERSHdrNode::ParseHeader(VSILFILE *fp)
133 :
134 : {
135 : while (true)
136 : {
137 : /* --------------------------------------------------------------------
138 : */
139 : /* Read the next line */
140 : /* --------------------------------------------------------------------
141 : */
142 65 : CPLString osLine;
143 : size_t iOff;
144 :
145 65 : if (!ReadLine(fp, osLine))
146 0 : return FALSE;
147 :
148 : /* --------------------------------------------------------------------
149 : */
150 : /* Got a DatasetHeader Begin */
151 : /* --------------------------------------------------------------------
152 : */
153 65 : else if ((iOff = osLine.ifind(" Begin")) != std::string::npos)
154 : {
155 62 : CPLString osName = osLine.substr(0, iOff);
156 62 : osName.Trim();
157 :
158 62 : if (osName.tolower() == CPLString("DatasetHeader").tolower())
159 : {
160 62 : return ParseChildren(fp);
161 : }
162 : }
163 3 : }
164 : }
165 :
166 : /************************************************************************/
167 : /* ParseChildren() */
168 : /* */
169 : /* We receive the FILE * positioned after the "Object Begin" */
170 : /* line for this object, and are responsible for reading all */
171 : /* children. We should return after consuming the */
172 : /* corresponding End line for this object. Really the first */
173 : /* unmatched End since we don't know what object we are. */
174 : /* */
175 : /* This function is used recursively to read sub-objects. */
176 : /************************************************************************/
177 :
178 184 : int ERSHdrNode::ParseChildren(VSILFILE *fp, int nRecLevel)
179 :
180 : {
181 184 : if (nRecLevel == 100) // arbitrary limit
182 : {
183 0 : CPLError(CE_Failure, CPLE_AppDefined,
184 : "Too many recursion level while parsing .ers header");
185 0 : return FALSE;
186 : }
187 :
188 : while (true)
189 : {
190 : /* --------------------------------------------------------------------
191 : */
192 : /* Read the next line (or multi-line for bracketed value). */
193 : /* --------------------------------------------------------------------
194 : */
195 1063 : CPLString osLine;
196 :
197 1063 : if (!ReadLine(fp, osLine))
198 0 : return FALSE;
199 :
200 : /* --------------------------------------------------------------------
201 : */
202 : /* Got a Name=Value. */
203 : /* --------------------------------------------------------------------
204 : */
205 : size_t iOff;
206 :
207 1063 : if ((iOff = osLine.find_first_of('=')) != std::string::npos)
208 : {
209 : CPLString osName =
210 1496 : iOff == 0 ? std::string() : osLine.substr(0, iOff);
211 748 : osName.Trim();
212 :
213 748 : CPLString osValue = osLine.c_str() + iOff + 1;
214 748 : osValue.Trim();
215 :
216 748 : MakeSpace();
217 748 : papszItemName[nItemCount] = CPLStrdup(osName);
218 748 : papszItemValue[nItemCount] = CPLStrdup(osValue);
219 748 : papoItemChild[nItemCount] = nullptr;
220 :
221 748 : nItemCount++;
222 : }
223 :
224 : /* --------------------------------------------------------------------
225 : */
226 : /* Got a Begin for an object. */
227 : /* --------------------------------------------------------------------
228 : */
229 315 : else if ((iOff = osLine.ifind(" Begin")) != std::string::npos)
230 : {
231 122 : CPLString osName = osLine.substr(0, iOff);
232 122 : osName.Trim();
233 :
234 122 : MakeSpace();
235 122 : papszItemName[nItemCount] = CPLStrdup(osName);
236 122 : papszItemValue[nItemCount] = nullptr;
237 122 : papoItemChild[nItemCount] = new ERSHdrNode();
238 :
239 122 : nItemCount++;
240 :
241 122 : if (!papoItemChild[nItemCount - 1]->ParseChildren(fp,
242 : nRecLevel + 1))
243 0 : return FALSE;
244 : }
245 :
246 : /* --------------------------------------------------------------------
247 : */
248 : /* Got an End for our object. Well, at least we *assume* it */
249 : /* must be for our object. */
250 : /* --------------------------------------------------------------------
251 : */
252 193 : else if (osLine.ifind(" End") != std::string::npos)
253 : {
254 184 : return TRUE;
255 : }
256 :
257 : /* --------------------------------------------------------------------
258 : */
259 : /* Error? */
260 : /* --------------------------------------------------------------------
261 : */
262 9 : else if (osLine.Trim().length() > 0)
263 : {
264 0 : CPLError(CE_Failure, CPLE_AppDefined,
265 : "Unexpected line parsing .ecw:\n%s", osLine.c_str());
266 0 : return FALSE;
267 : }
268 879 : }
269 : }
270 :
271 : /************************************************************************/
272 : /* WriteSelf() */
273 : /* */
274 : /* Recursively write self and children to file. */
275 : /************************************************************************/
276 :
277 175 : int ERSHdrNode::WriteSelf(VSILFILE *fp, int nIndent)
278 :
279 : {
280 350 : CPLString oIndent;
281 :
282 175 : oIndent.assign(nIndent, '\t');
283 :
284 959 : for (int i = 0; i < nItemCount; i++)
285 : {
286 784 : if (papszItemValue[i] != nullptr)
287 : {
288 646 : if (VSIFPrintfL(fp, "%s%s\t= %s\n", oIndent.c_str(),
289 1292 : papszItemName[i], papszItemValue[i]) < 1)
290 0 : return FALSE;
291 : }
292 : else
293 : {
294 138 : VSIFPrintfL(fp, "%s%s Begin\n", oIndent.c_str(), papszItemName[i]);
295 138 : if (!papoItemChild[i]->WriteSelf(fp, nIndent + 1))
296 0 : return FALSE;
297 138 : if (VSIFPrintfL(fp, "%s%s End\n", oIndent.c_str(),
298 276 : papszItemName[i]) < 1)
299 0 : return FALSE;
300 : }
301 : }
302 :
303 175 : return TRUE;
304 : }
305 :
306 : /************************************************************************/
307 : /* Find() */
308 : /* */
309 : /* Find the desired entry value. The input is a path with */
310 : /* components separated by dots, relative to the current node. */
311 : /************************************************************************/
312 :
313 2393 : const char *ERSHdrNode::Find(const char *pszPath, const char *pszDefault)
314 :
315 : {
316 : /* -------------------------------------------------------------------- */
317 : /* If this is the final component of the path, search for a */
318 : /* matching child and return the value. */
319 : /* -------------------------------------------------------------------- */
320 2393 : if (strchr(pszPath, '.') == nullptr)
321 : {
322 4573 : for (int i = 0; i < nItemCount; i++)
323 : {
324 4201 : if (EQUAL(pszPath, papszItemName[i]))
325 : {
326 785 : if (papszItemValue[i] != nullptr)
327 : {
328 785 : if (papszItemValue[i][0] == '"')
329 : {
330 : // strip off quotes.
331 67 : osTempReturn = papszItemValue[i];
332 67 : if (osTempReturn.length() < 2)
333 0 : osTempReturn.clear();
334 : else
335 134 : osTempReturn = osTempReturn.substr(
336 134 : 1, osTempReturn.length() - 2);
337 67 : return osTempReturn;
338 : }
339 : else
340 718 : return papszItemValue[i];
341 : }
342 : else
343 0 : return pszDefault;
344 : }
345 : }
346 372 : return pszDefault;
347 : }
348 :
349 : /* -------------------------------------------------------------------- */
350 : /* This is a dot path - extract the first element, find a match */
351 : /* and recurse. */
352 : /* -------------------------------------------------------------------- */
353 2472 : CPLString osPathFirst, osPathRest, osPath = pszPath;
354 :
355 1236 : size_t iDot = osPath.find_first_of('.');
356 1236 : osPathFirst = osPath.substr(0, iDot);
357 1236 : osPathRest = osPath.substr(iDot + 1);
358 :
359 7785 : for (int i = 0; i < nItemCount; i++)
360 : {
361 7550 : if (EQUAL(osPathFirst, papszItemName[i]))
362 : {
363 1001 : if (papoItemChild[i] != nullptr)
364 1001 : return papoItemChild[i]->Find(osPathRest, pszDefault);
365 :
366 0 : return pszDefault;
367 : }
368 : }
369 :
370 235 : return pszDefault;
371 : }
372 :
373 : /************************************************************************/
374 : /* FindElem() */
375 : /* */
376 : /* Find a particular element from an array valued item. */
377 : /************************************************************************/
378 :
379 12 : const char *ERSHdrNode::FindElem(const char *pszPath, int iElem,
380 : const char *pszDefault)
381 :
382 : {
383 12 : const char *pszArray = Find(pszPath, nullptr);
384 :
385 12 : if (pszArray == nullptr)
386 0 : return pszDefault;
387 :
388 12 : bool bDefault = true;
389 : char **papszTokens =
390 12 : CSLTokenizeStringComplex(pszArray, "{ \t}", TRUE, FALSE);
391 12 : if (iElem >= 0 && iElem < CSLCount(papszTokens))
392 : {
393 12 : osTempReturn = papszTokens[iElem];
394 12 : bDefault = false;
395 : }
396 :
397 12 : CSLDestroy(papszTokens);
398 :
399 12 : if (bDefault)
400 0 : return pszDefault;
401 :
402 12 : return osTempReturn;
403 : }
404 :
405 : /************************************************************************/
406 : /* FindNode() */
407 : /* */
408 : /* Find the desired node. */
409 : /************************************************************************/
410 :
411 646 : ERSHdrNode *ERSHdrNode::FindNode(const char *pszPath)
412 :
413 : {
414 1292 : std::string osPathFirst, osPathRest;
415 1292 : const std::string osPath = pszPath;
416 646 : size_t iDot = osPath.find_first_of('.');
417 646 : if (iDot == std::string::npos)
418 : {
419 585 : osPathFirst = osPath;
420 : }
421 : else
422 : {
423 61 : osPathFirst = osPath.substr(0, iDot);
424 61 : osPathRest = osPath.substr(iDot + 1);
425 : }
426 :
427 3995 : for (int i = 0; i < nItemCount; i++)
428 : {
429 3836 : if (EQUAL(osPathFirst.c_str(), papszItemName[i]))
430 : {
431 487 : if (papoItemChild[i] != nullptr)
432 : {
433 487 : if (osPathRest.length() > 0)
434 61 : return papoItemChild[i]->FindNode(osPathRest.c_str());
435 : else
436 426 : return papoItemChild[i];
437 : }
438 : else
439 0 : return nullptr;
440 : }
441 : }
442 :
443 159 : return nullptr;
444 : }
445 :
446 : /************************************************************************/
447 : /* Set() */
448 : /* */
449 : /* Set a value item. */
450 : /************************************************************************/
451 :
452 778 : void ERSHdrNode::Set(const char *pszPath, const char *pszValue)
453 :
454 : {
455 778 : CPLString osPath = pszPath;
456 778 : size_t iDot = osPath.find_first_of('.');
457 :
458 : /* -------------------------------------------------------------------- */
459 : /* We have an intermediate node, find or create it and */
460 : /* recurse. */
461 : /* -------------------------------------------------------------------- */
462 778 : if (iDot != std::string::npos)
463 : {
464 920 : CPLString osPathFirst = osPath.substr(0, iDot);
465 460 : CPLString osPathRest = osPath.substr(iDot + 1);
466 460 : ERSHdrNode *poFirst = FindNode(osPathFirst);
467 :
468 460 : if (poFirst == nullptr)
469 : {
470 100 : poFirst = new ERSHdrNode();
471 :
472 100 : MakeSpace();
473 100 : papszItemName[nItemCount] = CPLStrdup(osPathFirst);
474 100 : papszItemValue[nItemCount] = nullptr;
475 100 : papoItemChild[nItemCount] = poFirst;
476 100 : nItemCount++;
477 : }
478 :
479 460 : poFirst->Set(osPathRest, pszValue);
480 460 : return;
481 : }
482 :
483 : /* -------------------------------------------------------------------- */
484 : /* This is the final item name. Find or create it. */
485 : /* -------------------------------------------------------------------- */
486 763 : for (int i = 0; i < nItemCount; i++)
487 : {
488 455 : if (EQUAL(osPath, papszItemName[i]) && papszItemValue[i] != nullptr)
489 : {
490 10 : CPLFree(papszItemValue[i]);
491 10 : papszItemValue[i] = CPLStrdup(pszValue);
492 10 : return;
493 : }
494 : }
495 :
496 308 : MakeSpace();
497 308 : papszItemName[nItemCount] = CPLStrdup(osPath);
498 308 : papszItemValue[nItemCount] = CPLStrdup(pszValue);
499 308 : papoItemChild[nItemCount] = nullptr;
500 308 : nItemCount++;
501 : }
|