/src/gdal/frmts/ers/ershdrnode.cpp
Line | Count | Source |
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 | | ERSHdrNode::~ERSHdrNode() |
22 | | |
23 | 96.8k | { |
24 | 3.07M | for (int i = 0; i < nItemCount; i++) |
25 | 2.98M | { |
26 | 2.98M | if (papoItemChild[i] != nullptr) |
27 | 66.2k | delete papoItemChild[i]; |
28 | 2.98M | if (papszItemValue[i] != nullptr) |
29 | 2.91M | CPLFree(papszItemValue[i]); |
30 | 2.98M | CPLFree(papszItemName[i]); |
31 | 2.98M | } |
32 | | |
33 | 96.8k | CPLFree(papszItemName); |
34 | 96.8k | CPLFree(papszItemValue); |
35 | 96.8k | CPLFree(papoItemChild); |
36 | 96.8k | } |
37 | | |
38 | | /************************************************************************/ |
39 | | /* MakeSpace() */ |
40 | | /* */ |
41 | | /* Ensure we have room for at least one more entry in our item */ |
42 | | /* lists. */ |
43 | | /************************************************************************/ |
44 | | |
45 | | void ERSHdrNode::MakeSpace() |
46 | | |
47 | 2.98M | { |
48 | 2.98M | if (nItemCount == nItemMax) |
49 | 95.2k | { |
50 | 95.2k | nItemMax = nItemMax + nItemMax / 3 + 10; |
51 | 95.2k | papszItemName = static_cast<char **>( |
52 | 95.2k | CPLRealloc(papszItemName, sizeof(char *) * nItemMax)); |
53 | 95.2k | papszItemValue = static_cast<char **>( |
54 | 95.2k | CPLRealloc(papszItemValue, sizeof(char *) * nItemMax)); |
55 | 95.2k | papoItemChild = static_cast<ERSHdrNode **>( |
56 | 95.2k | CPLRealloc(papoItemChild, sizeof(ERSHdrNode *) * nItemMax)); |
57 | 95.2k | } |
58 | 2.98M | } |
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 | | int ERSHdrNode::ReadLine(VSILFILE *fp, CPLString &osLine) |
68 | | |
69 | 4.23M | { |
70 | 4.23M | int nBracketLevel = 0; |
71 | 4.23M | bool bInQuote = false; |
72 | 4.23M | size_t i = 0; |
73 | 4.23M | bool bLastCharWasSlashInQuote = false; |
74 | | |
75 | 4.23M | osLine = ""; |
76 | 4.23M | do |
77 | 4.82M | { |
78 | 4.82M | const char *pszNewLine = CPLReadLineL(fp); |
79 | | |
80 | 4.82M | if (pszNewLine == nullptr) |
81 | 1.97k | return FALSE; |
82 | | |
83 | 4.82M | osLine += pszNewLine; |
84 | | |
85 | 170M | for (; i < osLine.length(); i++) |
86 | 165M | { |
87 | 165M | const char ch = osLine[i]; |
88 | 165M | if (bLastCharWasSlashInQuote) |
89 | 38.1k | { |
90 | 38.1k | bLastCharWasSlashInQuote = false; |
91 | 38.1k | } |
92 | 165M | else if (ch == '"') |
93 | 395k | bInQuote = !bInQuote; |
94 | 164M | else if (ch == '{' && !bInQuote) |
95 | 35.8k | nBracketLevel++; |
96 | 164M | else if (ch == '}' && !bInQuote) |
97 | 47.4k | nBracketLevel--; |
98 | | // We have to ignore escaped quotes and backslashes in strings. |
99 | 164M | else if (ch == '\\' && bInQuote) |
100 | 38.1k | { |
101 | 38.1k | bLastCharWasSlashInQuote = true; |
102 | 38.1k | } |
103 | | // A comment is a '#' up to the end of the line. |
104 | 164M | else if (ch == '#' && !bInQuote) |
105 | 90.2k | { |
106 | 90.2k | osLine = osLine.substr(0, i) + "\n"; |
107 | 90.2k | } |
108 | 165M | } |
109 | 4.82M | } while (nBracketLevel > 0); |
110 | | |
111 | 4.23M | return TRUE; |
112 | 4.23M | } |
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 | | int ERSHdrNode::ParseHeader(VSILFILE *fp) |
123 | | |
124 | 30.6k | { |
125 | 980k | while (true) |
126 | 980k | { |
127 | | /* -------------------------------------------------------------------- |
128 | | */ |
129 | | /* Read the next line */ |
130 | | /* -------------------------------------------------------------------- |
131 | | */ |
132 | 980k | CPLString osLine; |
133 | 980k | size_t iOff; |
134 | | |
135 | 980k | if (!ReadLine(fp, osLine)) |
136 | 1.55k | return FALSE; |
137 | | |
138 | | /* -------------------------------------------------------------------- |
139 | | */ |
140 | | /* Got a DatasetHeader Begin */ |
141 | | /* -------------------------------------------------------------------- |
142 | | */ |
143 | 978k | else if ((iOff = osLine.ifind(" Begin")) != std::string::npos) |
144 | 67.4k | { |
145 | 67.4k | CPLString osName = osLine.substr(0, iOff); |
146 | 67.4k | osName.Trim(); |
147 | | |
148 | 67.4k | if (osName.tolower() == CPLString("DatasetHeader").tolower()) |
149 | 29.0k | { |
150 | 29.0k | return ParseChildren(fp); |
151 | 29.0k | } |
152 | 67.4k | } |
153 | 980k | } |
154 | 30.6k | } |
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 | | int ERSHdrNode::ParseChildren(VSILFILE *fp, int nRecLevel) |
169 | | |
170 | 95.2k | { |
171 | 95.2k | if (nRecLevel == 100) // arbitrary limit |
172 | 34 | { |
173 | 34 | CPLError(CE_Failure, CPLE_AppDefined, |
174 | 34 | "Too many recursion level while parsing .ers header"); |
175 | 34 | return FALSE; |
176 | 34 | } |
177 | | |
178 | 3.25M | while (true) |
179 | 3.25M | { |
180 | | /* -------------------------------------------------------------------- |
181 | | */ |
182 | | /* Read the next line (or multi-line for bracketed value). */ |
183 | | /* -------------------------------------------------------------------- |
184 | | */ |
185 | 3.25M | CPLString osLine; |
186 | | |
187 | 3.25M | if (!ReadLine(fp, osLine)) |
188 | 428 | return FALSE; |
189 | | |
190 | | /* -------------------------------------------------------------------- |
191 | | */ |
192 | | /* Got a Name=Value. */ |
193 | | /* -------------------------------------------------------------------- |
194 | | */ |
195 | 3.25M | size_t iOff; |
196 | | |
197 | 3.25M | if ((iOff = osLine.find_first_of('=')) != std::string::npos) |
198 | 2.91M | { |
199 | 2.91M | CPLString osName = |
200 | 2.91M | iOff == 0 ? std::string() : osLine.substr(0, iOff); |
201 | 2.91M | osName.Trim(); |
202 | | |
203 | 2.91M | CPLString osValue = osLine.c_str() + iOff + 1; |
204 | 2.91M | osValue.Trim(); |
205 | | |
206 | 2.91M | MakeSpace(); |
207 | 2.91M | papszItemName[nItemCount] = CPLStrdup(osName); |
208 | 2.91M | papszItemValue[nItemCount] = CPLStrdup(osValue); |
209 | 2.91M | papoItemChild[nItemCount] = nullptr; |
210 | | |
211 | 2.91M | nItemCount++; |
212 | 2.91M | } |
213 | | |
214 | | /* -------------------------------------------------------------------- |
215 | | */ |
216 | | /* Got a Begin for an object. */ |
217 | | /* -------------------------------------------------------------------- |
218 | | */ |
219 | 343k | else if ((iOff = osLine.ifind(" Begin")) != std::string::npos) |
220 | 66.2k | { |
221 | 66.2k | CPLString osName = osLine.substr(0, iOff); |
222 | 66.2k | osName.Trim(); |
223 | | |
224 | 66.2k | MakeSpace(); |
225 | 66.2k | papszItemName[nItemCount] = CPLStrdup(osName); |
226 | 66.2k | papszItemValue[nItemCount] = nullptr; |
227 | 66.2k | papoItemChild[nItemCount] = new ERSHdrNode(); |
228 | | |
229 | 66.2k | nItemCount++; |
230 | | |
231 | 66.2k | if (!papoItemChild[nItemCount - 1]->ParseChildren(fp, |
232 | 66.2k | nRecLevel + 1)) |
233 | 10.0k | return FALSE; |
234 | 66.2k | } |
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 | 277k | else if (osLine.ifind(" End") != std::string::npos) |
243 | 83.4k | { |
244 | 83.4k | return TRUE; |
245 | 83.4k | } |
246 | | |
247 | | /* -------------------------------------------------------------------- |
248 | | */ |
249 | | /* Error? */ |
250 | | /* -------------------------------------------------------------------- |
251 | | */ |
252 | 194k | else if (osLine.Trim().length() > 0) |
253 | 1.28k | { |
254 | 1.28k | CPLError(CE_Failure, CPLE_AppDefined, |
255 | 1.28k | "Unexpected line parsing .ecw:\n%s", osLine.c_str()); |
256 | 1.28k | return FALSE; |
257 | 1.28k | } |
258 | 3.25M | } |
259 | 95.2k | } |
260 | | |
261 | | /************************************************************************/ |
262 | | /* WriteSelf() */ |
263 | | /* */ |
264 | | /* Recursively write self and children to file. */ |
265 | | /************************************************************************/ |
266 | | |
267 | | int ERSHdrNode::WriteSelf(VSILFILE *fp, int nIndent) |
268 | | |
269 | 0 | { |
270 | 0 | CPLString oIndent; |
271 | |
|
272 | 0 | oIndent.assign(nIndent, '\t'); |
273 | |
|
274 | 0 | for (int i = 0; i < nItemCount; i++) |
275 | 0 | { |
276 | 0 | if (papszItemValue[i] != nullptr) |
277 | 0 | { |
278 | 0 | if (VSIFPrintfL(fp, "%s%s\t= %s\n", oIndent.c_str(), |
279 | 0 | papszItemName[i], papszItemValue[i]) < 1) |
280 | 0 | return FALSE; |
281 | 0 | } |
282 | 0 | else |
283 | 0 | { |
284 | 0 | VSIFPrintfL(fp, "%s%s Begin\n", oIndent.c_str(), papszItemName[i]); |
285 | 0 | if (!papoItemChild[i]->WriteSelf(fp, nIndent + 1)) |
286 | 0 | return FALSE; |
287 | 0 | if (VSIFPrintfL(fp, "%s%s End\n", oIndent.c_str(), |
288 | 0 | papszItemName[i]) < 1) |
289 | 0 | return FALSE; |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | 0 | return TRUE; |
294 | 0 | } |
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 | | const char *ERSHdrNode::Find(const char *pszPath, const char *pszDefault) |
304 | | |
305 | 2.33M | { |
306 | | /* -------------------------------------------------------------------- */ |
307 | | /* If this is the final component of the path, search for a */ |
308 | | /* matching child and return the value. */ |
309 | | /* -------------------------------------------------------------------- */ |
310 | 2.33M | if (strchr(pszPath, '.') == nullptr) |
311 | 1.16M | { |
312 | 14.6M | for (int i = 0; i < nItemCount; i++) |
313 | 13.8M | { |
314 | 13.8M | if (EQUAL(pszPath, papszItemName[i])) |
315 | 442k | { |
316 | 442k | if (papszItemValue[i] != nullptr) |
317 | 442k | { |
318 | 442k | if (papszItemValue[i][0] == '"') |
319 | 192k | { |
320 | | // strip off quotes. |
321 | 192k | osTempReturn = papszItemValue[i]; |
322 | 192k | if (osTempReturn.length() < 2) |
323 | 2.37k | osTempReturn.clear(); |
324 | 189k | else |
325 | 189k | osTempReturn = osTempReturn.substr( |
326 | 189k | 1, osTempReturn.length() - 2); |
327 | 192k | return osTempReturn; |
328 | 192k | } |
329 | 250k | else |
330 | 250k | return papszItemValue[i]; |
331 | 442k | } |
332 | 300 | else |
333 | 300 | return pszDefault; |
334 | 442k | } |
335 | 13.8M | } |
336 | 726k | return pszDefault; |
337 | 1.16M | } |
338 | | |
339 | | /* -------------------------------------------------------------------- */ |
340 | | /* This is a dot path - extract the first element, find a match */ |
341 | | /* and recurse. */ |
342 | | /* -------------------------------------------------------------------- */ |
343 | 1.16M | CPLString osPathFirst, osPathRest, osPath = pszPath; |
344 | | |
345 | 1.16M | size_t iDot = osPath.find_first_of('.'); |
346 | 1.16M | osPathFirst = osPath.substr(0, iDot); |
347 | 1.16M | osPathRest = osPath.substr(iDot + 1); |
348 | | |
349 | 8.59M | for (int i = 0; i < nItemCount; i++) |
350 | 8.49M | { |
351 | 8.49M | if (EQUAL(osPathFirst, papszItemName[i])) |
352 | 1.07M | { |
353 | 1.07M | if (papoItemChild[i] != nullptr) |
354 | 1.07M | return papoItemChild[i]->Find(osPathRest, pszDefault); |
355 | | |
356 | 71 | return pszDefault; |
357 | 1.07M | } |
358 | 8.49M | } |
359 | | |
360 | 95.3k | return pszDefault; |
361 | 1.16M | } |
362 | | |
363 | | /************************************************************************/ |
364 | | /* FindElem() */ |
365 | | /* */ |
366 | | /* Find a particular element from an array valued item. */ |
367 | | /************************************************************************/ |
368 | | |
369 | | const char *ERSHdrNode::FindElem(const char *pszPath, int iElem, |
370 | | const char *pszDefault) |
371 | | |
372 | 737k | { |
373 | 737k | const char *pszArray = Find(pszPath, nullptr); |
374 | | |
375 | 737k | if (pszArray == nullptr) |
376 | 547k | return pszDefault; |
377 | | |
378 | 190k | bool bDefault = true; |
379 | 190k | char **papszTokens = |
380 | 190k | CSLTokenizeStringComplex(pszArray, "{ \t}", TRUE, FALSE); |
381 | 190k | if (iElem >= 0 && iElem < CSLCount(papszTokens)) |
382 | 5.00k | { |
383 | 5.00k | osTempReturn = papszTokens[iElem]; |
384 | 5.00k | bDefault = false; |
385 | 5.00k | } |
386 | | |
387 | 190k | CSLDestroy(papszTokens); |
388 | | |
389 | 190k | if (bDefault) |
390 | 185k | return pszDefault; |
391 | | |
392 | 5.00k | return osTempReturn; |
393 | 190k | } |
394 | | |
395 | | /************************************************************************/ |
396 | | /* FindNode() */ |
397 | | /* */ |
398 | | /* Find the desired node. */ |
399 | | /************************************************************************/ |
400 | | |
401 | | ERSHdrNode *ERSHdrNode::FindNode(const char *pszPath) |
402 | | |
403 | 68.5k | { |
404 | 68.5k | std::string osPathFirst, osPathRest; |
405 | 68.5k | std::string osPath = pszPath; |
406 | 68.5k | const size_t iDot = osPath.find('.'); |
407 | 68.5k | if (iDot == std::string::npos) |
408 | 46.0k | { |
409 | 46.0k | osPathFirst = std::move(osPath); |
410 | 46.0k | } |
411 | 22.5k | else |
412 | 22.5k | { |
413 | 22.5k | osPathFirst = osPath.substr(0, iDot); |
414 | 22.5k | osPathRest = osPath.substr(iDot + 1); |
415 | 22.5k | } |
416 | | |
417 | 2.93M | for (int i = 0; i < nItemCount; i++) |
418 | 2.91M | { |
419 | 2.91M | if (EQUAL(osPathFirst.c_str(), papszItemName[i])) |
420 | 45.4k | { |
421 | 45.4k | if (papoItemChild[i] != nullptr) |
422 | 45.4k | { |
423 | 45.4k | if (osPathRest.length() > 0) |
424 | 22.5k | return papoItemChild[i]->FindNode(osPathRest.c_str()); |
425 | 22.8k | else |
426 | 22.8k | return papoItemChild[i]; |
427 | 45.4k | } |
428 | 19 | else |
429 | 19 | return nullptr; |
430 | 45.4k | } |
431 | 2.91M | } |
432 | | |
433 | 23.1k | return nullptr; |
434 | 68.5k | } |
435 | | |
436 | | /************************************************************************/ |
437 | | /* Set() */ |
438 | | /* */ |
439 | | /* Set a value item. */ |
440 | | /************************************************************************/ |
441 | | |
442 | | void ERSHdrNode::Set(const char *pszPath, const char *pszValue) |
443 | | |
444 | 0 | { |
445 | 0 | CPLString osPath = pszPath; |
446 | 0 | size_t iDot = osPath.find_first_of('.'); |
447 | | |
448 | | /* -------------------------------------------------------------------- */ |
449 | | /* We have an intermediate node, find or create it and */ |
450 | | /* recurse. */ |
451 | | /* -------------------------------------------------------------------- */ |
452 | 0 | if (iDot != std::string::npos) |
453 | 0 | { |
454 | 0 | CPLString osPathFirst = osPath.substr(0, iDot); |
455 | 0 | CPLString osPathRest = osPath.substr(iDot + 1); |
456 | 0 | ERSHdrNode *poFirst = FindNode(osPathFirst); |
457 | |
|
458 | 0 | if (poFirst == nullptr) |
459 | 0 | { |
460 | 0 | poFirst = new ERSHdrNode(); |
461 | |
|
462 | 0 | MakeSpace(); |
463 | 0 | papszItemName[nItemCount] = CPLStrdup(osPathFirst); |
464 | 0 | papszItemValue[nItemCount] = nullptr; |
465 | 0 | papoItemChild[nItemCount] = poFirst; |
466 | 0 | nItemCount++; |
467 | 0 | } |
468 | |
|
469 | 0 | poFirst->Set(osPathRest, pszValue); |
470 | 0 | return; |
471 | 0 | } |
472 | | |
473 | | /* -------------------------------------------------------------------- */ |
474 | | /* This is the final item name. Find or create it. */ |
475 | | /* -------------------------------------------------------------------- */ |
476 | 0 | for (int i = 0; i < nItemCount; i++) |
477 | 0 | { |
478 | 0 | if (EQUAL(osPath, papszItemName[i]) && papszItemValue[i] != nullptr) |
479 | 0 | { |
480 | 0 | CPLFree(papszItemValue[i]); |
481 | 0 | papszItemValue[i] = CPLStrdup(pszValue); |
482 | 0 | return; |
483 | 0 | } |
484 | 0 | } |
485 | | |
486 | 0 | MakeSpace(); |
487 | 0 | papszItemName[nItemCount] = CPLStrdup(osPath); |
488 | 0 | papszItemValue[nItemCount] = CPLStrdup(pszValue); |
489 | 0 | papoItemChild[nItemCount] = nullptr; |
490 | 0 | nItemCount++; |
491 | 0 | } |