/src/gdal/frmts/iso8211/ddffield.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: ISO 8211 Access |
4 | | * Purpose: Implements the DDFField class. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 1999, Frank Warmerdam |
9 | | * Copyright (c) 2026, Even Rouault |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include "cpl_port.h" |
15 | | #include "iso8211.h" |
16 | | |
17 | | #include <cstdio> |
18 | | #include <cstdlib> |
19 | | |
20 | | #include <algorithm> |
21 | | |
22 | | #include "cpl_conv.h" |
23 | | #include "cpl_enumerate.h" |
24 | | |
25 | | // Note, we implement no constructor for this class to make instantiation |
26 | | // cheaper. It is required that the Initialize() be called before anything |
27 | | // else. |
28 | | |
29 | | /************************************************************************/ |
30 | | /* Initialize() */ |
31 | | /************************************************************************/ |
32 | | |
33 | | bool DDFField::Initialize(const DDFFieldDefn *poDefnIn, const char *pachDataIn, |
34 | | int nDataSizeIn, bool bInitializeParts) |
35 | | |
36 | 394k | { |
37 | 394k | pachData = pachDataIn; |
38 | 394k | nDataSize = nDataSizeIn; |
39 | 394k | poDefn = poDefnIn; |
40 | | |
41 | 394k | return bInitializeParts ? InitializeParts() : true; |
42 | 394k | } |
43 | | |
44 | | /************************************************************************/ |
45 | | /* InitializeParts() */ |
46 | | /************************************************************************/ |
47 | | |
48 | | bool DDFField::InitializeParts() |
49 | 357k | { |
50 | 357k | const bool bCreateParts = apoFieldParts.empty(); |
51 | 357k | const size_t nDefnPartsCount = poDefn->GetParts().size(); |
52 | 357k | CPLAssert(bCreateParts || apoFieldParts.size() == nDefnPartsCount); |
53 | | |
54 | 357k | int iOffset = 0; |
55 | 357k | for (const auto &[iPart, poFieldDefnPart] : |
56 | 357k | cpl::enumerate(poDefn->GetParts())) |
57 | 35.9k | { |
58 | 35.9k | if (iOffset > nDataSize) |
59 | 19 | { |
60 | 19 | CPLError(CE_Failure, CPLE_AppDefined, |
61 | 19 | "Not enough bytes for part %d of field %s", |
62 | 19 | static_cast<int>(iPart), poDefn->GetName()); |
63 | 19 | return false; |
64 | 19 | } |
65 | 35.9k | const int iOffsetBefore = iOffset; |
66 | 35.9k | if (nDataSize > 0) |
67 | 35.3k | { |
68 | 35.3k | if (iPart + 1 < nDefnPartsCount) |
69 | 17.6k | { |
70 | 17.6k | for (const auto &poThisSFDefn : poFieldDefnPart->GetSubfields()) |
71 | 142k | { |
72 | 142k | int nBytesConsumed = 0; |
73 | 142k | poThisSFDefn->GetDataLength(pachData + iOffset, |
74 | 142k | nDataSize - iOffset, |
75 | 142k | &nBytesConsumed); |
76 | | |
77 | 142k | iOffset += nBytesConsumed; |
78 | 142k | } |
79 | 17.6k | } |
80 | 17.6k | else |
81 | 17.6k | { |
82 | 17.6k | iOffset = nDataSize; |
83 | 17.6k | if (pachData[nDataSize - 1] == DDF_FIELD_TERMINATOR) |
84 | 14.9k | --iOffset; |
85 | 17.6k | } |
86 | 35.3k | } |
87 | | |
88 | 35.9k | if (bCreateParts) |
89 | 32.4k | { |
90 | 32.4k | auto poFieldPart = std::make_unique<DDFField>(); |
91 | 32.4k | poFieldPart->Initialize(poFieldDefnPart.get(), |
92 | 32.4k | pachData + iOffsetBefore, |
93 | 32.4k | iOffset - iOffsetBefore, false); |
94 | 32.4k | apoFieldParts.push_back(std::move(poFieldPart)); |
95 | 32.4k | } |
96 | 3.46k | else |
97 | 3.46k | { |
98 | 3.46k | apoFieldParts[iPart]->Initialize(poFieldDefnPart.get(), |
99 | 3.46k | pachData + iOffsetBefore, |
100 | 3.46k | iOffset - iOffsetBefore, false); |
101 | 3.46k | } |
102 | 35.9k | } |
103 | | |
104 | 357k | return true; |
105 | 357k | } |
106 | | |
107 | | /************************************************************************/ |
108 | | /* Dump() */ |
109 | | /************************************************************************/ |
110 | | |
111 | | /** |
112 | | * Write out field contents to debugging file. |
113 | | * |
114 | | * A variety of information about this field, and all its |
115 | | * subfields is written to the given debugging file handle. Note that |
116 | | * field definition information (ala DDFFieldDefn) isn't written. |
117 | | * |
118 | | * @param fp The standard IO file handle to write to. i.e. stderr |
119 | | */ |
120 | | |
121 | | void DDFField::Dump(FILE *fp, int nNestingLevel) const |
122 | | |
123 | 0 | { |
124 | 0 | std::string osIndent; |
125 | 0 | for (int i = 0; i < nNestingLevel; ++i) |
126 | 0 | osIndent += " "; |
127 | |
|
128 | 0 | #define Print(...) \ |
129 | 0 | do \ |
130 | 0 | { \ |
131 | 0 | fprintf(fp, "%s", osIndent.c_str()); \ |
132 | 0 | fprintf(fp, __VA_ARGS__); \ |
133 | 0 | } while (0) |
134 | |
|
135 | 0 | int nMaxRepeat = 8; |
136 | |
|
137 | 0 | const char *pszDDF_MAXDUMP = getenv("DDF_MAXDUMP"); |
138 | 0 | if (pszDDF_MAXDUMP != nullptr) |
139 | 0 | nMaxRepeat = atoi(pszDDF_MAXDUMP); |
140 | |
|
141 | 0 | Print("DDFField:\n"); |
142 | 0 | Print(" Tag = `%s'\n", poDefn->GetName()); |
143 | 0 | Print(" DataSize = %d\n", nDataSize); |
144 | |
|
145 | 0 | if (!apoFieldParts.empty()) |
146 | 0 | { |
147 | 0 | for (const auto &poPart : apoFieldParts) |
148 | 0 | { |
149 | 0 | poPart->Dump(fp, nNestingLevel + 1); |
150 | 0 | } |
151 | 0 | return; |
152 | 0 | } |
153 | | |
154 | 0 | Print(" Data = `"); |
155 | 0 | for (int i = 0; i < std::min(nDataSize, 40); i++) |
156 | 0 | { |
157 | 0 | if (pachData[i] < 32 || pachData[i] > 126) |
158 | 0 | fprintf(fp, "\\%02X", |
159 | 0 | reinterpret_cast<const unsigned char *>(pachData)[i]); |
160 | 0 | else |
161 | 0 | fprintf(fp, "%c", pachData[i]); |
162 | 0 | } |
163 | |
|
164 | 0 | if (nDataSize > 40) |
165 | 0 | fprintf(fp, "..."); |
166 | 0 | fprintf(fp, "'\n"); |
167 | | |
168 | | /* -------------------------------------------------------------------- */ |
169 | | /* dump the data of the subfields. */ |
170 | | /* -------------------------------------------------------------------- */ |
171 | 0 | int iOffset = 0; |
172 | 0 | const int nRepeatCount = GetRepeatCount(); |
173 | 0 | for (int nLoopCount = 0; nLoopCount < nRepeatCount; nLoopCount++) |
174 | 0 | { |
175 | 0 | if (nLoopCount > nMaxRepeat) |
176 | 0 | { |
177 | 0 | Print(" ...\n"); |
178 | 0 | break; |
179 | 0 | } |
180 | | |
181 | 0 | for (const auto &poThisSFDefn : poDefn->GetSubfields()) |
182 | 0 | { |
183 | 0 | poThisSFDefn->DumpData(pachData + iOffset, nDataSize - iOffset, fp); |
184 | |
|
185 | 0 | int nBytesConsumed = 0; |
186 | 0 | poThisSFDefn->GetDataLength(pachData + iOffset, nDataSize - iOffset, |
187 | 0 | &nBytesConsumed); |
188 | |
|
189 | 0 | iOffset += nBytesConsumed; |
190 | 0 | } |
191 | 0 | } |
192 | 0 | } |
193 | | |
194 | | /************************************************************************/ |
195 | | /* GetSubfieldData() */ |
196 | | /************************************************************************/ |
197 | | |
198 | | /** |
199 | | * Fetch raw data pointer for a particular subfield of this field. |
200 | | * |
201 | | * The passed DDFSubfieldDefn (poSFDefn) should be acquired from the |
202 | | * DDFFieldDefn corresponding with this field. This is normally done |
203 | | * once before reading any records. This method involves a series of |
204 | | * calls to DDFSubfield::GetDataLength() in order to track through the |
205 | | * DDFField data to that belonging to the requested subfield. This can |
206 | | * be relatively expensive.<p> |
207 | | * |
208 | | * @param poSFDefn The definition of the subfield for which the raw |
209 | | * data pointer is desired. |
210 | | * @param pnMaxBytes The maximum number of bytes that can be accessed from |
211 | | * the returned data pointer is placed in this int, unless it is NULL. |
212 | | * @param iSubfieldIndex The instance of this subfield to fetch. Use zero |
213 | | * (the default) for the first instance. |
214 | | * |
215 | | * @return A pointer into the DDFField's data that belongs to the subfield. |
216 | | * This returned pointer is invalidated by the next record read |
217 | | * (DDFRecord::ReadRecord()) and the returned pointer should not be freed |
218 | | * by the application. |
219 | | */ |
220 | | |
221 | | const char *DDFField::GetSubfieldData(const DDFSubfieldDefn *poSFDefn, |
222 | | int *pnMaxBytes, int iSubfieldIndex) const |
223 | | |
224 | 1.32M | { |
225 | 1.32M | if (poSFDefn == nullptr) |
226 | 0 | return nullptr; |
227 | | |
228 | 1.32M | int iOffset = 0; |
229 | 1.32M | if (iSubfieldIndex > 0 && poDefn->GetFixedWidth() > 0) |
230 | 206k | { |
231 | 206k | iOffset = poDefn->GetFixedWidth() * iSubfieldIndex; |
232 | 206k | iSubfieldIndex = 0; |
233 | 206k | } |
234 | | |
235 | 2.10M | while (iSubfieldIndex >= 0) |
236 | 2.10M | { |
237 | 2.10M | for (const auto &poThisSFDefn : poDefn->GetSubfields()) |
238 | 7.28M | { |
239 | 7.28M | if (nDataSize <= iOffset) |
240 | 235k | { |
241 | 235k | CPLError(CE_Failure, CPLE_AppDefined, |
242 | 235k | "Invalid data size for subfield %s of %s", |
243 | 235k | poThisSFDefn->GetName(), poDefn->GetName()); |
244 | 235k | return nullptr; |
245 | 235k | } |
246 | | |
247 | 7.05M | if (poThisSFDefn.get() == poSFDefn && iSubfieldIndex == 0) |
248 | 1.09M | { |
249 | 1.09M | if (pnMaxBytes != nullptr) |
250 | 1.09M | *pnMaxBytes = nDataSize - iOffset; |
251 | | |
252 | 1.09M | return pachData + iOffset; |
253 | 1.09M | } |
254 | | |
255 | 5.95M | int nBytesConsumed = 0; |
256 | 5.95M | poThisSFDefn->GetDataLength(pachData + iOffset, nDataSize - iOffset, |
257 | 5.95M | &nBytesConsumed); |
258 | 5.95M | iOffset += nBytesConsumed; |
259 | 5.95M | } |
260 | | |
261 | 772k | iSubfieldIndex--; |
262 | 772k | } |
263 | | |
264 | | // We didn't find our target subfield or instance! |
265 | 97 | return nullptr; |
266 | 1.32M | } |
267 | | |
268 | | /************************************************************************/ |
269 | | /* GetRepeatCount() */ |
270 | | /************************************************************************/ |
271 | | |
272 | | /** |
273 | | * How many times do the subfields of this record repeat? This |
274 | | * will always be one for non-repeating fields. |
275 | | * |
276 | | * @return The number of times that the subfields of this record occur |
277 | | * in this record. This will be one for non-repeating fields. |
278 | | * |
279 | | * @see <a href="example.html">8211view example program</a> |
280 | | * for a demonstration of handling repeated fields properly. |
281 | | */ |
282 | | |
283 | | int DDFField::GetRepeatCount() const |
284 | | |
285 | 328k | { |
286 | 328k | if (!apoFieldParts.empty()) |
287 | 0 | return 0; |
288 | | |
289 | 328k | if (!poDefn->IsRepeating()) |
290 | 196k | return 1; |
291 | | |
292 | | /* -------------------------------------------------------------------- */ |
293 | | /* The occurrence count depends on how many copies of this */ |
294 | | /* field's list of subfields can fit into the data space. */ |
295 | | /* -------------------------------------------------------------------- */ |
296 | 132k | if (poDefn->GetFixedWidth()) |
297 | 87.3k | { |
298 | 87.3k | return nDataSize / poDefn->GetFixedWidth(); |
299 | 87.3k | } |
300 | | |
301 | | /* -------------------------------------------------------------------- */ |
302 | | /* Note that it may be legal to have repeating variable width */ |
303 | | /* subfields, but I don't have any samples, so I ignore it for */ |
304 | | /* now. */ |
305 | | /* */ |
306 | | /* The file data/cape_royal_AZ_DEM/1183XREF.DDF has a repeating */ |
307 | | /* variable length field, but the count is one, so it isn't */ |
308 | | /* much value for testing. */ |
309 | | /* -------------------------------------------------------------------- */ |
310 | 44.7k | int iOffset = 0; |
311 | 44.7k | int iRepeatCount = 1; |
312 | | |
313 | 128k | while (true) |
314 | 128k | { |
315 | 128k | const int iOffsetBefore = iOffset; |
316 | 128k | for (const auto &poThisSFDefn : poDefn->GetSubfields()) |
317 | 335k | { |
318 | 335k | int nBytesConsumed = 0; |
319 | 335k | if (poThisSFDefn->GetWidth() > nDataSize - iOffset) |
320 | 5.80k | nBytesConsumed = poThisSFDefn->GetWidth(); |
321 | 330k | else |
322 | 330k | poThisSFDefn->GetDataLength( |
323 | 330k | pachData + iOffset, nDataSize - iOffset, &nBytesConsumed); |
324 | | |
325 | 335k | iOffset += nBytesConsumed; |
326 | 335k | if (iOffset > nDataSize) |
327 | 25.3k | return iRepeatCount - 1; |
328 | 335k | } |
329 | 103k | if (iOffset == iOffsetBefore) |
330 | 3.37k | { |
331 | | // Should probably emit error |
332 | 3.37k | return iRepeatCount - 1; |
333 | 3.37k | } |
334 | | |
335 | 100k | if (iOffset > nDataSize - 2) |
336 | 16.0k | return iRepeatCount; |
337 | | |
338 | 84.0k | iRepeatCount++; |
339 | 84.0k | } |
340 | 44.7k | } |
341 | | |
342 | | /************************************************************************/ |
343 | | /* GetInstanceData() */ |
344 | | /************************************************************************/ |
345 | | |
346 | | /** |
347 | | * Get field instance data and size. |
348 | | * |
349 | | * The returned data pointer and size values are suitable for use with |
350 | | * DDFRecord::SetFieldRaw(). |
351 | | * |
352 | | * @param nInstance a value from 0 to GetRepeatCount()-1. |
353 | | * @param pnInstanceSize a location to put the size (in bytes) of the |
354 | | * field instance data returned. This size will include the unit terminator |
355 | | * (if any), but not the field terminator. This size pointer may be NULL |
356 | | * if not needed. |
357 | | * |
358 | | * @return the data pointer, or NULL on error. |
359 | | */ |
360 | | |
361 | | const char *DDFField::GetInstanceData(int nInstance, int *pnInstanceSize) |
362 | | |
363 | 4 | { |
364 | 4 | const int nRepeatCount = GetRepeatCount(); |
365 | 4 | if (!apoFieldParts.empty() && nInstance == 0) |
366 | 0 | { |
367 | 0 | const char *pachWrkData = GetData(); |
368 | 0 | if (pnInstanceSize != nullptr) |
369 | 0 | *pnInstanceSize = GetDataSize(); |
370 | 0 | return pachWrkData; |
371 | 0 | } |
372 | | |
373 | 4 | if (nInstance < 0 || nInstance >= nRepeatCount) |
374 | 0 | return nullptr; |
375 | | |
376 | | /* -------------------------------------------------------------------- */ |
377 | | /* Special case for fields without subfields (like "0001"). We */ |
378 | | /* don't currently handle repeating simple fields. */ |
379 | | /* -------------------------------------------------------------------- */ |
380 | 4 | if (poDefn->GetSubfieldCount() == 0) |
381 | 0 | { |
382 | 0 | const char *pachWrkData = GetData(); |
383 | 0 | if (pnInstanceSize != nullptr) |
384 | 0 | *pnInstanceSize = GetDataSize(); |
385 | 0 | return pachWrkData; |
386 | 0 | } |
387 | | |
388 | | /* -------------------------------------------------------------------- */ |
389 | | /* Get a pointer to the start of the existing data for this */ |
390 | | /* iteration of the field. */ |
391 | | /* -------------------------------------------------------------------- */ |
392 | 4 | int nBytesRemaining1 = 0; |
393 | 4 | int nBytesRemaining2 = 0; |
394 | 4 | const DDFSubfieldDefn *poFirstSubfield = |
395 | 4 | poDefn->GetSubfields().front().get(); |
396 | | |
397 | 4 | const char *pachWrkData = |
398 | 4 | GetSubfieldData(poFirstSubfield, &nBytesRemaining1, nInstance); |
399 | 4 | if (pachWrkData == nullptr) |
400 | 0 | return nullptr; |
401 | | |
402 | | /* -------------------------------------------------------------------- */ |
403 | | /* Figure out the size of the entire field instance, including */ |
404 | | /* unit terminators, but not any trailing field terminator. */ |
405 | | /* -------------------------------------------------------------------- */ |
406 | 4 | if (pnInstanceSize != nullptr) |
407 | 0 | { |
408 | 0 | const DDFSubfieldDefn *poLastSubfield = |
409 | 0 | poDefn->GetSubfields().back().get(); |
410 | |
|
411 | 0 | const char *pachLastData = |
412 | 0 | GetSubfieldData(poLastSubfield, &nBytesRemaining2, nInstance); |
413 | 0 | if (pachLastData == nullptr) |
414 | 0 | return nullptr; |
415 | | |
416 | 0 | int nLastSubfieldWidth = 0; |
417 | 0 | poLastSubfield->GetDataLength(pachLastData, nBytesRemaining2, |
418 | 0 | &nLastSubfieldWidth); |
419 | |
|
420 | 0 | *pnInstanceSize = |
421 | 0 | nBytesRemaining1 - (nBytesRemaining2 - nLastSubfieldWidth); |
422 | 0 | } |
423 | | |
424 | 4 | return pachWrkData; |
425 | 4 | } |