/src/gdal/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * Name: mitab_imapinfo |
4 | | * Project: MapInfo mid/mif Tab Read/Write library |
5 | | * Language: C++ |
6 | | * Purpose: Implementation of the IMapInfoFile class, super class of |
7 | | * of MIFFile and TABFile |
8 | | * Author: Daniel Morissette, dmorissette@dmsolutions.ca |
9 | | * |
10 | | ********************************************************************** |
11 | | * Copyright (c) 1999-2008, Daniel Morissette |
12 | | * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com> |
13 | | * |
14 | | * SPDX-License-Identifier: MIT |
15 | | **********************************************************************/ |
16 | | |
17 | | #include "cpl_port.h" |
18 | | #include "mitab.h" |
19 | | |
20 | | #include <cassert> |
21 | | #include <cctype> |
22 | | #include <cstring> |
23 | | #include <algorithm> |
24 | | |
25 | | #include "cpl_conv.h" |
26 | | #include "cpl_error.h" |
27 | | #include "cpl_vsi.h" |
28 | | #include "mitab_priv.h" |
29 | | #include "mitab_utils.h" |
30 | | #include "ogr_core.h" |
31 | | #include "ogr_feature.h" |
32 | | #include "ogr_geometry.h" |
33 | | |
34 | | #ifdef __HP_aCC |
35 | | #include <wchar.h> /* iswspace() */ |
36 | | #else |
37 | | #include <wctype.h> /* iswspace() */ |
38 | | #endif |
39 | | |
40 | | /********************************************************************** |
41 | | * IMapInfoFile::IMapInfoFile() |
42 | | * |
43 | | * Constructor. |
44 | | **********************************************************************/ |
45 | | IMapInfoFile::IMapInfoFile(GDALDataset *poDS) |
46 | 51.4k | : m_poDS(poDS), m_nCurFeatureId(0), m_poCurFeature(nullptr), |
47 | 51.4k | m_bBoundsSet(FALSE), m_pszCharset(nullptr) |
48 | 51.4k | { |
49 | 51.4k | } |
50 | | |
51 | | /********************************************************************** |
52 | | * IMapInfoFile::~IMapInfoFile() |
53 | | * |
54 | | * Destructor. |
55 | | **********************************************************************/ |
56 | | IMapInfoFile::~IMapInfoFile() |
57 | 51.4k | { |
58 | 51.4k | if (m_poCurFeature) |
59 | 0 | { |
60 | 0 | delete m_poCurFeature; |
61 | 0 | m_poCurFeature = nullptr; |
62 | 0 | } |
63 | | |
64 | 51.4k | CPLFree(m_pszCharset); |
65 | 51.4k | m_pszCharset = nullptr; |
66 | 51.4k | } |
67 | | |
68 | | /********************************************************************** |
69 | | * IMapInfoFile::Open() |
70 | | * |
71 | | * Compatibility layer with new interface. |
72 | | * Return 0 on success, -1 in case of failure. |
73 | | **********************************************************************/ |
74 | | |
75 | | int IMapInfoFile::Open(const char *pszFname, const char *pszAccess, |
76 | | GBool bTestOpenNoError, const char *pszCharset) |
77 | 0 | { |
78 | | // cppcheck-suppress nullPointer |
79 | 0 | if (STARTS_WITH_CI(pszAccess, "r")) |
80 | 0 | return Open(pszFname, TABRead, bTestOpenNoError, pszCharset); |
81 | 0 | else if (STARTS_WITH_CI(pszAccess, "w")) |
82 | 0 | return Open(pszFname, TABWrite, bTestOpenNoError, pszCharset); |
83 | 0 | else |
84 | 0 | { |
85 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
86 | 0 | "Open() failed: access mode \"%s\" not supported", pszAccess); |
87 | 0 | return -1; |
88 | 0 | } |
89 | 0 | } |
90 | | |
91 | | /********************************************************************** |
92 | | * IMapInfoFile::SmartOpen() |
93 | | * |
94 | | * Use this static method to automatically open any flavor of MapInfo |
95 | | * dataset. This method will detect the file type, create an object |
96 | | * of the right type, and open the file. |
97 | | * |
98 | | * Call GetFileClass() on the returned object if you need to find out |
99 | | * its exact type. (To access format-specific methods for instance) |
100 | | * |
101 | | * Returns the new object ptr. , or NULL if the open failed. |
102 | | **********************************************************************/ |
103 | | IMapInfoFile *IMapInfoFile::SmartOpen(GDALDataset *poDS, const char *pszFname, |
104 | | GBool bUpdate, |
105 | | GBool bTestOpenNoError /*=FALSE*/) |
106 | 51.3k | { |
107 | 51.3k | IMapInfoFile *poFile = nullptr; |
108 | 51.3k | int nLen = 0; |
109 | | |
110 | 51.3k | if (pszFname) |
111 | 51.3k | nLen = static_cast<int>(strlen(pszFname)); |
112 | | |
113 | 51.3k | if (nLen > 4 && (EQUAL(pszFname + nLen - 4, ".MIF") || |
114 | 5.38k | EQUAL(pszFname + nLen - 4, ".MID"))) |
115 | 46.0k | { |
116 | | /*------------------------------------------------------------- |
117 | | * MIF/MID file |
118 | | *------------------------------------------------------------*/ |
119 | 46.0k | poFile = new MIFFile(poDS); |
120 | 46.0k | } |
121 | 5.38k | else if (nLen > 4 && EQUAL(pszFname + nLen - 4, ".TAB")) |
122 | 5.38k | { |
123 | | /*------------------------------------------------------------- |
124 | | * .TAB file ... is it a TABFileView or a TABFile? |
125 | | * We have to read the .tab header to find out. |
126 | | *------------------------------------------------------------*/ |
127 | 5.38k | char *pszAdjFname = CPLStrdup(pszFname); |
128 | 5.38k | GBool bFoundFields = FALSE; |
129 | 5.38k | GBool bFoundView = FALSE; |
130 | 5.38k | GBool bFoundSeamless = FALSE; |
131 | | |
132 | 5.38k | TABAdjustFilenameExtension(pszAdjFname); |
133 | 5.38k | VSILFILE *fp = VSIFOpenL(pszAdjFname, "r"); |
134 | 5.38k | const char *pszLine = nullptr; |
135 | 3.53M | while (fp && (pszLine = CPLReadLineL(fp)) != nullptr) |
136 | 3.53M | { |
137 | 3.87M | while (isspace(static_cast<unsigned char>(*pszLine))) |
138 | 340k | pszLine++; |
139 | 3.53M | if (STARTS_WITH_CI(pszLine, "Fields")) |
140 | 8.54k | bFoundFields = TRUE; |
141 | 3.52M | else if (STARTS_WITH_CI(pszLine, "create view")) |
142 | 9.46k | bFoundView = TRUE; |
143 | 3.51M | else if (STARTS_WITH_CI(pszLine, "\"\\IsSeamless\" = \"TRUE\"")) |
144 | 713 | bFoundSeamless = TRUE; |
145 | 3.53M | } |
146 | | |
147 | 5.38k | if (bFoundView) |
148 | 1.16k | poFile = new TABView(poDS); |
149 | 4.21k | else if (bFoundFields && bFoundSeamless) |
150 | 105 | poFile = new TABSeamless(poDS); |
151 | 4.11k | else if (bFoundFields) |
152 | 2.60k | poFile = new TABFile(poDS); |
153 | | |
154 | 5.38k | if (fp) |
155 | 4.98k | VSIFCloseL(fp); |
156 | | |
157 | 5.38k | CPLFree(pszAdjFname); |
158 | 5.38k | } |
159 | | |
160 | | /*----------------------------------------------------------------- |
161 | | * Perform the open() call |
162 | | *----------------------------------------------------------------*/ |
163 | 51.3k | if (poFile && poFile->Open(pszFname, bUpdate ? TABReadWrite : TABRead, |
164 | 49.8k | bTestOpenNoError) != 0) |
165 | 22.2k | { |
166 | 22.2k | delete poFile; |
167 | 22.2k | poFile = nullptr; |
168 | 22.2k | } |
169 | | |
170 | 51.3k | if (!bTestOpenNoError && poFile == nullptr) |
171 | 0 | { |
172 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
173 | 0 | "%s could not be opened as a MapInfo dataset.", pszFname); |
174 | 0 | } |
175 | | |
176 | 51.3k | return poFile; |
177 | 51.3k | } |
178 | | |
179 | | /********************************************************************** |
180 | | * IMapInfoFile::GetNextFeature() |
181 | | * |
182 | | * Standard OGR GetNextFeature implementation. This method is used |
183 | | * to retrieve the next OGRFeature. |
184 | | **********************************************************************/ |
185 | | OGRFeature *IMapInfoFile::GetNextFeature() |
186 | 588k | { |
187 | 588k | GIntBig nFeatureId = 0; |
188 | | |
189 | 588k | while ((nFeatureId = GetNextFeatureId(m_nCurFeatureId)) != -1) |
190 | 577k | { |
191 | 577k | OGRGeometry *poGeom = nullptr; |
192 | 577k | OGRFeature *poFeatureRef = GetFeatureRef(nFeatureId); |
193 | 577k | if (poFeatureRef == nullptr) |
194 | 10.2k | return nullptr; |
195 | 567k | else if ((m_poFilterGeom == nullptr || |
196 | 0 | ((poGeom = poFeatureRef->GetGeometryRef()) != nullptr && |
197 | 0 | FilterGeometry(poGeom))) && |
198 | 567k | (m_poAttrQuery == nullptr || |
199 | 0 | m_poAttrQuery->Evaluate(poFeatureRef))) |
200 | 567k | { |
201 | | // Avoid cloning feature... return the copy owned by the class |
202 | 567k | CPLAssert(poFeatureRef == m_poCurFeature); |
203 | 567k | m_poCurFeature = nullptr; |
204 | 567k | if (poFeatureRef->GetGeometryRef() != nullptr) |
205 | 350k | poFeatureRef->GetGeometryRef()->assignSpatialReference( |
206 | 350k | GetSpatialRef()); |
207 | 567k | return poFeatureRef; |
208 | 567k | } |
209 | 577k | } |
210 | 11.4k | return nullptr; |
211 | 588k | } |
212 | | |
213 | | /********************************************************************** |
214 | | * IMapInfoFile::CreateTABFeature() |
215 | | * |
216 | | * Instantiate a TABFeature* from a OGRFeature* (or NULL on error) |
217 | | **********************************************************************/ |
218 | | |
219 | | TABFeature *IMapInfoFile::CreateTABFeature(OGRFeature *poFeature) |
220 | 372k | { |
221 | 372k | TABFeature *poTABFeature = nullptr; |
222 | 372k | OGRwkbGeometryType eGType; |
223 | 372k | TABPoint *poTABPointFeature = nullptr; |
224 | 372k | TABRegion *poTABRegionFeature = nullptr; |
225 | 372k | TABPolyline *poTABPolylineFeature = nullptr; |
226 | | |
227 | | /*----------------------------------------------------------------- |
228 | | * MITAB won't accept new features unless they are in a type derived |
229 | | * from TABFeature... so we have to do our best to map to the right |
230 | | * feature type based on the geometry type. |
231 | | *----------------------------------------------------------------*/ |
232 | 372k | OGRGeometry *poGeom = poFeature->GetGeometryRef(); |
233 | 372k | if (poGeom != nullptr) |
234 | 24.4k | eGType = poGeom->getGeometryType(); |
235 | 347k | else |
236 | 347k | eGType = wkbNone; |
237 | | |
238 | 372k | switch (wkbFlatten(eGType)) |
239 | 372k | { |
240 | | /*------------------------------------------------------------- |
241 | | * POINT |
242 | | *------------------------------------------------------------*/ |
243 | 4.58k | case wkbPoint: |
244 | 4.58k | { |
245 | 4.58k | const char *pszStyleString = poFeature->GetStyleString(); |
246 | 4.58k | if (pszStyleString) |
247 | 0 | { |
248 | 0 | if (strstr(pszStyleString, "LABEL(")) |
249 | 0 | { |
250 | 0 | auto poText = new TABText(poFeature->GetDefnRef()); |
251 | 0 | poText->SetLabelFromStyleString(pszStyleString); |
252 | 0 | poTABFeature = poText; |
253 | |
|
254 | 0 | if (strstr(pszStyleString, "SYMBOL(")) |
255 | 0 | { |
256 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
257 | 0 | "OGR style string contains both " |
258 | 0 | "Label and Symbol parts. " |
259 | 0 | "Only Label taken into account due to " |
260 | 0 | "MapInfo TAB/MIF format limitations."); |
261 | 0 | } |
262 | 0 | } |
263 | 0 | else |
264 | 0 | { |
265 | 0 | TABFeatureClass featureClass = |
266 | 0 | ITABFeatureSymbol::GetSymbolFeatureClass( |
267 | 0 | pszStyleString); |
268 | 0 | if (featureClass == TABFCFontPoint) |
269 | 0 | { |
270 | 0 | poTABFeature = |
271 | 0 | new TABFontPoint(poFeature->GetDefnRef()); |
272 | 0 | } |
273 | 0 | else if (featureClass == TABFCCustomPoint) |
274 | 0 | { |
275 | 0 | poTABFeature = |
276 | 0 | new TABCustomPoint(poFeature->GetDefnRef()); |
277 | 0 | } |
278 | 0 | else |
279 | 0 | { |
280 | 0 | poTABFeature = new TABPoint(poFeature->GetDefnRef()); |
281 | 0 | } |
282 | 0 | poTABPointFeature = |
283 | 0 | cpl::down_cast<TABPoint *>(poTABFeature); |
284 | 0 | poTABPointFeature->SetSymbolFromStyleString( |
285 | 0 | poFeature->GetStyleString()); |
286 | 0 | } |
287 | 0 | } |
288 | 4.58k | else |
289 | 4.58k | { |
290 | 4.58k | poTABFeature = new TABPoint(poFeature->GetDefnRef()); |
291 | 4.58k | } |
292 | 4.58k | break; |
293 | 0 | } |
294 | | |
295 | | /*------------------------------------------------------------- |
296 | | * REGION |
297 | | *------------------------------------------------------------*/ |
298 | 3.05k | case wkbPolygon: |
299 | 3.82k | case wkbMultiPolygon: |
300 | 3.82k | { |
301 | 3.82k | poTABFeature = new TABRegion(poFeature->GetDefnRef()); |
302 | 3.82k | if (poFeature->GetStyleString()) |
303 | 0 | { |
304 | 0 | poTABRegionFeature = cpl::down_cast<TABRegion *>(poTABFeature); |
305 | 0 | poTABRegionFeature->SetPenFromStyleString( |
306 | 0 | poFeature->GetStyleString()); |
307 | |
|
308 | 0 | poTABRegionFeature->SetBrushFromStyleString( |
309 | 0 | poFeature->GetStyleString()); |
310 | 0 | } |
311 | 3.82k | break; |
312 | 3.05k | } |
313 | | /*------------------------------------------------------------- |
314 | | * LINE/PLINE/MULTIPLINE |
315 | | *------------------------------------------------------------*/ |
316 | 6.78k | case wkbLineString: |
317 | 12.0k | case wkbMultiLineString: |
318 | 12.0k | { |
319 | 12.0k | poTABFeature = new TABPolyline(poFeature->GetDefnRef()); |
320 | 12.0k | if (poFeature->GetStyleString()) |
321 | 1 | { |
322 | 1 | poTABPolylineFeature = |
323 | 1 | cpl::down_cast<TABPolyline *>(poTABFeature); |
324 | 1 | poTABPolylineFeature->SetPenFromStyleString( |
325 | 1 | poFeature->GetStyleString()); |
326 | 1 | } |
327 | 12.0k | break; |
328 | 6.78k | } |
329 | | /*------------------------------------------------------------- |
330 | | * Collection types that are not directly supported... convert |
331 | | * to multiple features in output file through recursive calls. |
332 | | *------------------------------------------------------------*/ |
333 | 2.15k | case wkbGeometryCollection: |
334 | 2.42k | case wkbMultiPoint: |
335 | 2.42k | { |
336 | 2.42k | OGRErr eStatus = OGRERR_NONE; |
337 | 2.42k | assert(poGeom); // for clang static analyzer |
338 | 2.42k | OGRGeometryCollection *poColl = poGeom->toGeometryCollection(); |
339 | 2.42k | auto poTmpFeature = std::unique_ptr<OGRFeature>(poFeature->Clone()); |
340 | | |
341 | 5.54k | for (int i = 0; eStatus == OGRERR_NONE && poColl != nullptr && |
342 | 4.70k | i < poColl->getNumGeometries(); |
343 | 3.12k | i++) |
344 | 3.12k | { |
345 | 3.12k | poTmpFeature->SetFID(OGRNullFID); |
346 | 3.12k | poTmpFeature->SetGeometry(poColl->getGeometryRef(i)); |
347 | 3.12k | eStatus = ICreateFeature(poTmpFeature.get()); |
348 | 3.12k | } |
349 | 2.42k | break; |
350 | 2.42k | } |
351 | | |
352 | | /*------------------------------------------------------------- |
353 | | * Unsupported type.... convert to MapInfo geometry NONE |
354 | | *------------------------------------------------------------*/ |
355 | 349k | default: |
356 | 349k | { |
357 | 349k | poTABFeature = new TABFeature(poFeature->GetDefnRef()); |
358 | 349k | break; |
359 | 2.42k | } |
360 | 372k | } |
361 | | |
362 | 372k | if (poTABFeature) |
363 | 369k | { |
364 | 369k | if (poGeom != nullptr) |
365 | 22.0k | poTABFeature->SetGeometryDirectly(poGeom->clone()); |
366 | | |
367 | 1.49M | for (int i = 0; i < poFeature->GetDefnRef()->GetFieldCount(); i++) |
368 | 1.12M | { |
369 | 1.12M | poTABFeature->SetField(i, poFeature->GetRawFieldRef(i)); |
370 | 1.12M | } |
371 | | |
372 | 369k | poTABFeature->SetFID(poFeature->GetFID()); |
373 | 369k | } |
374 | | |
375 | 372k | return poTABFeature; |
376 | 372k | } |
377 | | |
378 | | /********************************************************************** |
379 | | * IMapInfoFile::ICreateFeature() |
380 | | * |
381 | | * Standard OGR CreateFeature implementation. This method is used |
382 | | * to create a new feature in current dataset |
383 | | **********************************************************************/ |
384 | | OGRErr IMapInfoFile::ICreateFeature(OGRFeature *poFeature) |
385 | 372k | { |
386 | 372k | TABFeature *poTABFeature = CreateTABFeature(poFeature); |
387 | 372k | if (poTABFeature == nullptr) /* MultiGeometry */ |
388 | 2.42k | return OGRERR_NONE; |
389 | | |
390 | 369k | OGRErr eErr = CreateFeature(poTABFeature); |
391 | 369k | if (eErr == OGRERR_NONE) |
392 | 363k | poFeature->SetFID(poTABFeature->GetFID()); |
393 | | |
394 | 369k | delete poTABFeature; |
395 | | |
396 | 369k | return eErr; |
397 | 372k | } |
398 | | |
399 | | /********************************************************************** |
400 | | * IMapInfoFile::GetFeature() |
401 | | * |
402 | | * Standard OGR GetFeature implementation. This method is used |
403 | | * to get the wanted (nFeatureId) feature, a NULL value will be |
404 | | * returned on error. |
405 | | **********************************************************************/ |
406 | | OGRFeature *IMapInfoFile::GetFeature(GIntBig nFeatureId) |
407 | 0 | { |
408 | | /*fprintf(stderr, "GetFeature(%ld)\n", nFeatureId);*/ |
409 | |
|
410 | 0 | OGRFeature *poFeatureRef = GetFeatureRef(nFeatureId); |
411 | 0 | if (poFeatureRef) |
412 | 0 | { |
413 | | // Avoid cloning feature... return the copy owned by the class |
414 | 0 | CPLAssert(poFeatureRef == m_poCurFeature); |
415 | 0 | m_poCurFeature = nullptr; |
416 | |
|
417 | 0 | return poFeatureRef; |
418 | 0 | } |
419 | 0 | else |
420 | 0 | return nullptr; |
421 | 0 | } |
422 | | |
423 | | /************************************************************************/ |
424 | | /* GetTABType() */ |
425 | | /* */ |
426 | | /* Create a native field based on a generic OGR definition. */ |
427 | | /************************************************************************/ |
428 | | |
429 | | int IMapInfoFile::GetTABType(const OGRFieldDefn *poField, |
430 | | TABFieldType *peTABType, int *pnWidth, |
431 | | int *pnPrecision) |
432 | 32.2k | { |
433 | 32.2k | TABFieldType eTABType; |
434 | 32.2k | int nWidth = poField->GetWidth(); |
435 | 32.2k | int nPrecision = |
436 | 32.2k | poField->GetType() == OFTReal ? poField->GetPrecision() : 0; |
437 | | |
438 | 32.2k | if (poField->GetType() == OFTInteger) |
439 | 13 | { |
440 | 13 | if (poField->GetSubType() == OFSTBoolean) |
441 | 0 | { |
442 | 0 | eTABType = TABFLogical; |
443 | 0 | nWidth = 1; |
444 | 0 | } |
445 | 13 | else |
446 | 13 | { |
447 | 13 | eTABType = TABFInteger; |
448 | 13 | if (nWidth == 0) |
449 | 12 | nWidth = 12; |
450 | 13 | } |
451 | 13 | } |
452 | 32.2k | else if (poField->GetType() == OFTInteger64) |
453 | 0 | { |
454 | 0 | eTABType = TABFLargeInt; |
455 | 0 | if (nWidth == 0) |
456 | 0 | nWidth = 20; |
457 | 0 | } |
458 | 32.2k | else if (poField->GetType() == OFTReal) |
459 | 9 | { |
460 | 9 | if (nWidth == 0 && poField->GetPrecision() == 0) |
461 | 9 | { |
462 | 9 | eTABType = TABFFloat; |
463 | 9 | nWidth = 32; |
464 | 9 | } |
465 | 0 | else |
466 | 0 | { |
467 | 0 | eTABType = TABFDecimal; |
468 | | // Enforce Mapinfo limits, otherwise MapInfo will crash (#6392) |
469 | 0 | if (nWidth > 20 || nWidth - nPrecision < 2 || nPrecision > 16) |
470 | 0 | { |
471 | 0 | if (nWidth > 20) |
472 | 0 | nWidth = 20; |
473 | 0 | if (nWidth - nPrecision < 2) |
474 | 0 | nPrecision = nWidth - 2; |
475 | 0 | if (nPrecision > 16) |
476 | 0 | nPrecision = 16; |
477 | 0 | CPLDebug("MITAB", |
478 | 0 | "Adjusting initial width,precision of %s from %d,%d " |
479 | 0 | "to %d,%d", |
480 | 0 | poField->GetNameRef(), poField->GetWidth(), |
481 | 0 | poField->GetPrecision(), nWidth, nPrecision); |
482 | 0 | } |
483 | 0 | } |
484 | 9 | } |
485 | 32.2k | else if (poField->GetType() == OFTDate) |
486 | 0 | { |
487 | 0 | eTABType = TABFDate; |
488 | 0 | if (nWidth == 0) |
489 | 0 | nWidth = 10; |
490 | 0 | } |
491 | 32.2k | else if (poField->GetType() == OFTTime) |
492 | 0 | { |
493 | 0 | eTABType = TABFTime; |
494 | 0 | if (nWidth == 0) |
495 | 0 | nWidth = 9; |
496 | 0 | } |
497 | 32.2k | else if (poField->GetType() == OFTDateTime) |
498 | 0 | { |
499 | 0 | eTABType = TABFDateTime; |
500 | 0 | if (nWidth == 0) |
501 | 0 | nWidth = 19; |
502 | 0 | } |
503 | 32.2k | else if (poField->GetType() == OFTString) |
504 | 32.2k | { |
505 | 32.2k | eTABType = TABFChar; |
506 | 32.2k | if (nWidth == 0) |
507 | 32.2k | nWidth = 254; |
508 | 6 | else |
509 | 6 | nWidth = std::min(254, nWidth); |
510 | 32.2k | } |
511 | 0 | else |
512 | 0 | { |
513 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
514 | 0 | "IMapInfoFile::CreateField() called with unsupported field" |
515 | 0 | " type %d.\n" |
516 | 0 | "Note that Mapinfo files don't support list field types.\n", |
517 | 0 | poField->GetType()); |
518 | |
|
519 | 0 | return -1; |
520 | 0 | } |
521 | | |
522 | 32.2k | if (peTABType) |
523 | 32.2k | *peTABType = eTABType; |
524 | 32.2k | if (pnWidth) |
525 | 32.2k | *pnWidth = nWidth; |
526 | 32.2k | if (pnPrecision) |
527 | 32.2k | *pnPrecision = nPrecision; |
528 | | |
529 | 32.2k | return 0; |
530 | 32.2k | } |
531 | | |
532 | | /************************************************************************/ |
533 | | /* CreateField() */ |
534 | | /* */ |
535 | | /* Create a native field based on a generic OGR definition. */ |
536 | | /************************************************************************/ |
537 | | |
538 | | OGRErr IMapInfoFile::CreateField(const OGRFieldDefn *poField, int bApproxOK) |
539 | | |
540 | 32.2k | { |
541 | 32.2k | TABFieldType eTABType; |
542 | 32.2k | int nWidth = 0; |
543 | 32.2k | int nPrecision = 0; |
544 | | |
545 | 32.2k | if (GetTABType(poField, &eTABType, &nWidth, &nPrecision) < 0) |
546 | 0 | return OGRERR_FAILURE; |
547 | | |
548 | 32.2k | if (AddFieldNative(poField->GetNameRef(), eTABType, nWidth, nPrecision, |
549 | 32.2k | FALSE, FALSE, bApproxOK) > -1) |
550 | 32.2k | return OGRERR_NONE; |
551 | | |
552 | 3 | return OGRERR_FAILURE; |
553 | 32.2k | } |
554 | | |
555 | | /********************************************************************** |
556 | | * IMapInfoFile::SetCharset() |
557 | | * |
558 | | * Set the charset for the tab header. |
559 | | * |
560 | | * |
561 | | * Returns 0 on success, -1 on error. |
562 | | **********************************************************************/ |
563 | | int IMapInfoFile::SetCharset(const char *pszCharset) |
564 | 49.6k | { |
565 | 49.6k | if (pszCharset && strlen(pszCharset) > 0) |
566 | 49.6k | { |
567 | 49.6k | if (pszCharset == m_pszCharset) |
568 | 0 | { |
569 | 0 | return 0; |
570 | 0 | } |
571 | 49.6k | CPLFree(m_pszCharset); |
572 | 49.6k | m_pszCharset = CPLStrdup(pszCharset); |
573 | 49.6k | return 0; |
574 | 49.6k | } |
575 | 0 | return -1; |
576 | 49.6k | } |
577 | | |
578 | | const char *IMapInfoFile::GetCharset() const |
579 | 1.11M | { |
580 | 1.11M | return m_pszCharset; |
581 | 1.11M | } |
582 | | |
583 | | // Table is adopted from |
584 | | // http://www.i-signum.com/Formation/download/MB_ReferenceGuide.pdf pp. 127-128 |
585 | | // NOTE: if modifying this table, please keep doc/source/drivers/vector/mapinfo_encodings.csv in sync |
586 | | static const char *const apszCharsets[][2] = { |
587 | | {"Neutral", ""}, // No character conversions performed. |
588 | | {"ISO8859_1", "ISO-8859-1"}, // ISO 8859-1 (UNIX) |
589 | | {"ISO8859_2", "ISO-8859-2"}, // ISO 8859-2 (UNIX) |
590 | | {"ISO8859_3", "ISO-8859-3"}, // ISO 8859-3 (UNIX) |
591 | | {"ISO8859_4", "ISO-8859-4"}, // ISO 8859-4 (UNIX) |
592 | | {"ISO8859_5", "ISO-8859-5"}, // ISO 8859-5 (UNIX) |
593 | | {"ISO8859_6", "ISO-8859-6"}, // ISO 8859-6 (UNIX) |
594 | | {"ISO8859_7", "ISO-8859-7"}, // ISO 8859-7 (UNIX) |
595 | | {"ISO8859_8", "ISO-8859-8"}, // ISO 8859-8 (UNIX) |
596 | | {"ISO8859_9", "ISO-8859-9"}, // ISO 8859-9 (UNIX) |
597 | | {"PackedEUCJapaese", "EUC-JP"}, // UNIX, standard Japanese implementation. |
598 | | {"WindowsLatin1", "CP1252"}, |
599 | | {"WindowsLatin2", "CP1250"}, |
600 | | {"WindowsArabic", "CP1256"}, |
601 | | {"WindowsCyrillic", "CP1251"}, |
602 | | {"WindowsBalticRim", "CP1257"}, |
603 | | {"WindowsGreek", "CP1253"}, |
604 | | {"WindowsHebrew", "CP1255"}, |
605 | | {"WindowsTurkish", "CP1254"}, // Windows Eastern Europe |
606 | | {"WindowsTradChinese", "CP950"}, // Windows Traditional Chinese |
607 | | {"WindowsSimpChinese", "CP936"}, // Windows Simplified Chinese |
608 | | {"WindowsJapanese", "CP932"}, |
609 | | {"WindowsKorean", "CP949"}, |
610 | | {"CodePage437", "CP437"}, // DOS Code Page 437 = IBM Extended ASCII |
611 | | {"CodePage850", "CP850"}, // DOS Code Page 850 = Multilingual |
612 | | {"CodePage852", "CP852"}, // DOS Code Page 852 = Eastern Europe |
613 | | {"CodePage855", "CP855"}, // DOS Code Page 855 = Cyrillic |
614 | | {"CodePage857", "CP857"}, |
615 | | {"CodePage860", "CP860"}, // DOS Code Page 860 = Portuguese |
616 | | {"CodePage861", "CP861"}, // DOS Code Page 861 = Icelandic |
617 | | {"CodePage863", "CP863"}, // DOS Code Page 863 = French Canadian |
618 | | {"CodePage864", "CP864"}, // DOS Code Page 864 = Arabic |
619 | | {"CodePage865", "CP865"}, // DOS Code Page 865 = Nordic |
620 | | {"CodePage869", "CP869"}, // DOS Code Page 869 = Modern Greek |
621 | | {"LICS", ""}, // Lotus worksheet release 1,2 character set |
622 | | {"LMBCS", ""}, // Lotus worksheet release 3,4 character set |
623 | | {"UTF-8", "UTF-8"}, |
624 | | {nullptr, nullptr}}; |
625 | | |
626 | | const char *IMapInfoFile::CharsetToEncoding(const char *pszCharset) |
627 | 1.19M | { |
628 | 1.19M | if (pszCharset == nullptr) |
629 | 446k | { |
630 | 446k | return apszCharsets[0][1]; |
631 | 446k | } |
632 | | |
633 | 9.82M | for (size_t i = 0; apszCharsets[i][0] != nullptr; ++i) |
634 | 9.65M | { |
635 | 9.65M | if (EQUAL(pszCharset, apszCharsets[i][0])) |
636 | 587k | { |
637 | 587k | return apszCharsets[i][1]; |
638 | 587k | } |
639 | 9.65M | } |
640 | | |
641 | 164k | CPLError(CE_Warning, CPLE_NotSupported, |
642 | 164k | "Cannot find iconv encoding corresponding to MapInfo %s charset", |
643 | 164k | pszCharset); |
644 | 164k | return apszCharsets[0][1]; |
645 | 751k | } |
646 | | |
647 | | const char *IMapInfoFile::EncodingToCharset(const char *pszEncoding) |
648 | 676 | { |
649 | 676 | if (pszEncoding == nullptr) |
650 | 676 | { |
651 | 676 | return apszCharsets[0][0]; |
652 | 676 | } |
653 | | |
654 | 0 | for (size_t i = 0; apszCharsets[i][1] != nullptr; ++i) |
655 | 0 | { |
656 | 0 | if (EQUAL(pszEncoding, apszCharsets[i][1])) |
657 | 0 | { |
658 | 0 | return apszCharsets[i][0]; |
659 | 0 | } |
660 | 0 | } |
661 | | |
662 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
663 | 0 | "Cannot find MapInfo charset corresponding to iconv %s encoding", |
664 | 0 | pszEncoding); |
665 | 0 | return apszCharsets[0][0]; |
666 | 0 | } |
667 | | |
668 | | const char *IMapInfoFile::GetEncoding() const |
669 | 1.09M | { |
670 | 1.09M | return CharsetToEncoding(GetCharset()); |
671 | 1.09M | } |
672 | | |
673 | | void IMapInfoFile::SetEncoding(const char *pszEncoding) |
674 | 0 | { |
675 | 0 | SetCharset(EncodingToCharset(pszEncoding)); |
676 | 0 | } |
677 | | |
678 | | void IMapInfoFile::SetStrictLaundering(bool bStrictLaundering) |
679 | 676 | { |
680 | 676 | m_bStrictLaundering = bStrictLaundering; |
681 | 676 | } |
682 | | |
683 | | int IMapInfoFile::TestUtf8Capability() const |
684 | 0 | { |
685 | 0 | const char *pszEncoding(GetEncoding()); |
686 | 0 | if (strlen(pszEncoding) == 0) |
687 | 0 | { |
688 | 0 | return FALSE; |
689 | 0 | } |
690 | | |
691 | 0 | return CPLCanRecode("test", GetEncoding(), CPL_ENC_UTF8); |
692 | 0 | } |
693 | | |
694 | | CPLString IMapInfoFile::NormalizeFieldName(const char *pszName) const |
695 | 260k | { |
696 | 260k | CPLString osName(pszName); |
697 | 260k | if (strlen(GetEncoding()) > 0) |
698 | 37.0k | osName.Recode(CPL_ENC_UTF8, GetEncoding()); |
699 | | |
700 | 260k | char szNewFieldName[31 + 1]; /* 31 is the max characters for a field name*/ |
701 | 260k | unsigned int nRenameNum = 1; |
702 | | |
703 | 260k | strncpy(szNewFieldName, osName.c_str(), sizeof(szNewFieldName) - 1); |
704 | 260k | szNewFieldName[sizeof(szNewFieldName) - 1] = '\0'; |
705 | | |
706 | 1.11M | while (m_oSetFields.find(CPLString(szNewFieldName).toupper()) != |
707 | 1.11M | m_oSetFields.end() && |
708 | 919k | nRenameNum < 10) |
709 | 852k | { |
710 | 852k | CPLsnprintf(szNewFieldName, sizeof(szNewFieldName), "%.29s_%.1u", |
711 | 852k | osName.c_str(), nRenameNum); |
712 | 852k | nRenameNum++; |
713 | 852k | } |
714 | | |
715 | 2.64M | while (m_oSetFields.find(CPLString(szNewFieldName).toupper()) != |
716 | 2.64M | m_oSetFields.end() && |
717 | 2.39M | nRenameNum < 100) |
718 | 2.38M | { |
719 | 2.38M | CPLsnprintf(szNewFieldName, sizeof(szNewFieldName), "%.29s%.2u", |
720 | 2.38M | osName.c_str(), nRenameNum); |
721 | 2.38M | nRenameNum++; |
722 | 2.38M | } |
723 | | |
724 | 260k | if (m_oSetFields.find(CPLString(szNewFieldName).toupper()) != |
725 | 260k | m_oSetFields.end()) |
726 | 11.0k | { |
727 | 11.0k | CPLError(CE_Failure, CPLE_NotSupported, |
728 | 11.0k | "Too many field names like '%s' when truncated to 31 letters " |
729 | 11.0k | "for MapInfo format.", |
730 | 11.0k | pszName); |
731 | 11.0k | } |
732 | | |
733 | 260k | CPLString osNewFieldName(szNewFieldName); |
734 | 260k | if (strlen(GetEncoding()) > 0) |
735 | 37.0k | osNewFieldName.Recode(GetEncoding(), CPL_ENC_UTF8); |
736 | | |
737 | 260k | if (!EQUAL(pszName, osNewFieldName.c_str())) |
738 | 145k | { |
739 | 145k | CPLError(CE_Warning, CPLE_NotSupported, |
740 | 145k | "Normalized/laundered field name: '%s' to '%s'", pszName, |
741 | 145k | osNewFieldName.c_str()); |
742 | 145k | } |
743 | | |
744 | 260k | return osNewFieldName; |
745 | 260k | } |