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