Line data Source code
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 2398 : IMapInfoFile::IMapInfoFile(GDALDataset *poDS)
46 : : m_poDS(poDS), m_nCurFeatureId(0), m_poCurFeature(nullptr),
47 2398 : m_bBoundsSet(FALSE), m_pszCharset(nullptr)
48 : {
49 2398 : }
50 :
51 : /**********************************************************************
52 : * IMapInfoFile::~IMapInfoFile()
53 : *
54 : * Destructor.
55 : **********************************************************************/
56 2398 : IMapInfoFile::~IMapInfoFile()
57 : {
58 2398 : if (m_poCurFeature)
59 : {
60 0 : delete m_poCurFeature;
61 0 : m_poCurFeature = nullptr;
62 : }
63 :
64 2398 : CPLFree(m_pszCharset);
65 2398 : m_pszCharset = nullptr;
66 2398 : }
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 0 : int IMapInfoFile::Open(const char *pszFname, const char *pszAccess,
76 : GBool bTestOpenNoError, const char *pszCharset)
77 : {
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 : else
84 : {
85 0 : CPLError(CE_Failure, CPLE_FileIO,
86 : "Open() failed: access mode \"%s\" not supported", pszAccess);
87 0 : return -1;
88 : }
89 : }
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 2178 : IMapInfoFile *IMapInfoFile::SmartOpen(GDALDataset *poDS, const char *pszFname,
104 : GBool bUpdate,
105 : GBool bTestOpenNoError /*=FALSE*/)
106 : {
107 2178 : IMapInfoFile *poFile = nullptr;
108 2178 : int nLen = 0;
109 :
110 2178 : if (pszFname)
111 2178 : nLen = static_cast<int>(strlen(pszFname));
112 :
113 2178 : if (nLen > 4 && (EQUAL(pszFname + nLen - 4, ".MIF") ||
114 1303 : EQUAL(pszFname + nLen - 4, ".MID")))
115 : {
116 : /*-------------------------------------------------------------
117 : * MIF/MID file
118 : *------------------------------------------------------------*/
119 875 : poFile = new MIFFile(poDS);
120 : }
121 1303 : else if (nLen > 4 && EQUAL(pszFname + nLen - 4, ".TAB"))
122 : {
123 : /*-------------------------------------------------------------
124 : * .TAB file ... is it a TABFileView or a TABFile?
125 : * We have to read the .tab header to find out.
126 : *------------------------------------------------------------*/
127 1303 : char *pszAdjFname = CPLStrdup(pszFname);
128 1303 : GBool bFoundFields = FALSE;
129 1303 : GBool bFoundView = FALSE;
130 1303 : GBool bFoundSeamless = FALSE;
131 :
132 1303 : TABAdjustFilenameExtension(pszAdjFname);
133 1303 : VSILFILE *fp = VSIFOpenL(pszAdjFname, "r");
134 1303 : const char *pszLine = nullptr;
135 12038 : while (fp && (pszLine = CPLReadLineL(fp)) != nullptr)
136 : {
137 22037 : while (isspace(static_cast<unsigned char>(*pszLine)))
138 11302 : pszLine++;
139 10735 : if (STARTS_WITH_CI(pszLine, "Fields"))
140 1300 : bFoundFields = TRUE;
141 9435 : else if (STARTS_WITH_CI(pszLine, "create view"))
142 2 : bFoundView = TRUE;
143 9433 : else if (STARTS_WITH_CI(pszLine, "\"\\IsSeamless\" = \"TRUE\""))
144 1 : bFoundSeamless = TRUE;
145 : }
146 :
147 1303 : if (bFoundView)
148 2 : poFile = new TABView(poDS);
149 1301 : else if (bFoundFields && bFoundSeamless)
150 1 : poFile = new TABSeamless(poDS);
151 1300 : else if (bFoundFields)
152 1299 : poFile = new TABFile(poDS);
153 :
154 1303 : if (fp)
155 1303 : VSIFCloseL(fp);
156 :
157 1303 : CPLFree(pszAdjFname);
158 : }
159 :
160 : /*-----------------------------------------------------------------
161 : * Perform the open() call
162 : *----------------------------------------------------------------*/
163 4355 : if (poFile && poFile->Open(pszFname, bUpdate ? TABReadWrite : TABRead,
164 2177 : bTestOpenNoError) != 0)
165 : {
166 75 : delete poFile;
167 75 : poFile = nullptr;
168 : }
169 :
170 2178 : if (!bTestOpenNoError && poFile == nullptr)
171 : {
172 0 : CPLError(CE_Failure, CPLE_FileIO,
173 : "%s could not be opened as a MapInfo dataset.", pszFname);
174 : }
175 :
176 2178 : return poFile;
177 : }
178 :
179 : /**********************************************************************
180 : * IMapInfoFile::GetNextFeature()
181 : *
182 : * Standard OGR GetNextFeature implementation. This method is used
183 : * to retrieve the next OGRFeature.
184 : **********************************************************************/
185 223072 : OGRFeature *IMapInfoFile::GetNextFeature()
186 : {
187 223072 : GIntBig nFeatureId = 0;
188 :
189 544068 : while ((nFeatureId = GetNextFeatureId(m_nCurFeatureId)) != -1)
190 : {
191 533257 : OGRGeometry *poGeom = nullptr;
192 533257 : OGRFeature *poFeatureRef = GetFeatureRef(nFeatureId);
193 533257 : if (poFeatureRef == nullptr)
194 328 : return nullptr;
195 1441220 : else if ((m_poFilterGeom == nullptr ||
196 375363 : ((poGeom = poFeatureRef->GetGeometryRef()) != nullptr &&
197 1122430 : FilterGeometry(poGeom))) &&
198 214137 : (m_poAttrQuery == nullptr ||
199 4894 : m_poAttrQuery->Evaluate(poFeatureRef)))
200 : {
201 : // Avoid cloning feature... return the copy owned by the class
202 211933 : CPLAssert(poFeatureRef == m_poCurFeature);
203 211933 : m_poCurFeature = nullptr;
204 211933 : if (poFeatureRef->GetGeometryRef() != nullptr)
205 211763 : poFeatureRef->GetGeometryRef()->assignSpatialReference(
206 211763 : GetSpatialRef());
207 211933 : return poFeatureRef;
208 : }
209 : }
210 10811 : return nullptr;
211 : }
212 :
213 : /**********************************************************************
214 : * IMapInfoFile::CreateTABFeature()
215 : *
216 : * Instantiate a TABFeature* from a OGRFeature* (or NULL on error)
217 : **********************************************************************/
218 :
219 15224 : TABFeature *IMapInfoFile::CreateTABFeature(OGRFeature *poFeature)
220 : {
221 15224 : TABFeature *poTABFeature = nullptr;
222 : OGRwkbGeometryType eGType;
223 15224 : TABPoint *poTABPointFeature = nullptr;
224 15224 : TABRegion *poTABRegionFeature = nullptr;
225 15224 : 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 15224 : OGRGeometry *poGeom = poFeature->GetGeometryRef();
233 15224 : if (poGeom != nullptr)
234 15146 : eGType = poGeom->getGeometryType();
235 : else
236 78 : eGType = wkbNone;
237 :
238 15224 : switch (wkbFlatten(eGType))
239 : {
240 : /*-------------------------------------------------------------
241 : * POINT
242 : *------------------------------------------------------------*/
243 14779 : case wkbPoint:
244 : {
245 14779 : const char *pszStyleString = poFeature->GetStyleString();
246 14779 : if (pszStyleString)
247 : {
248 113 : if (strstr(pszStyleString, "LABEL("))
249 : {
250 4 : auto poText = new TABText(poFeature->GetDefnRef());
251 4 : poText->SetLabelFromStyleString(pszStyleString);
252 4 : poTABFeature = poText;
253 :
254 4 : if (strstr(pszStyleString, "SYMBOL("))
255 : {
256 0 : CPLError(CE_Warning, CPLE_AppDefined,
257 : "OGR style string contains both "
258 : "Label and Symbol parts. "
259 : "Only Label taken into account due to "
260 : "MapInfo TAB/MIF format limitations.");
261 : }
262 : }
263 : else
264 : {
265 : TABFeatureClass featureClass =
266 109 : ITABFeatureSymbol::GetSymbolFeatureClass(
267 : pszStyleString);
268 109 : if (featureClass == TABFCFontPoint)
269 : {
270 2 : poTABFeature =
271 2 : new TABFontPoint(poFeature->GetDefnRef());
272 : }
273 107 : else if (featureClass == TABFCCustomPoint)
274 : {
275 2 : poTABFeature =
276 2 : new TABCustomPoint(poFeature->GetDefnRef());
277 : }
278 : else
279 : {
280 105 : poTABFeature = new TABPoint(poFeature->GetDefnRef());
281 : }
282 : poTABPointFeature =
283 109 : cpl::down_cast<TABPoint *>(poTABFeature);
284 109 : poTABPointFeature->SetSymbolFromStyleString(
285 109 : poFeature->GetStyleString());
286 : }
287 : }
288 : else
289 : {
290 14666 : poTABFeature = new TABPoint(poFeature->GetDefnRef());
291 : }
292 14779 : break;
293 : }
294 :
295 : /*-------------------------------------------------------------
296 : * REGION
297 : *------------------------------------------------------------*/
298 111 : case wkbPolygon:
299 : case wkbMultiPolygon:
300 : {
301 111 : poTABFeature = new TABRegion(poFeature->GetDefnRef());
302 111 : if (poFeature->GetStyleString())
303 : {
304 19 : poTABRegionFeature = cpl::down_cast<TABRegion *>(poTABFeature);
305 19 : poTABRegionFeature->SetPenFromStyleString(
306 19 : poFeature->GetStyleString());
307 :
308 19 : poTABRegionFeature->SetBrushFromStyleString(
309 19 : poFeature->GetStyleString());
310 : }
311 111 : break;
312 : }
313 : /*-------------------------------------------------------------
314 : * LINE/PLINE/MULTIPLINE
315 : *------------------------------------------------------------*/
316 238 : case wkbLineString:
317 : case wkbMultiLineString:
318 : {
319 238 : poTABFeature = new TABPolyline(poFeature->GetDefnRef());
320 238 : if (poFeature->GetStyleString())
321 : {
322 : poTABPolylineFeature =
323 13 : cpl::down_cast<TABPolyline *>(poTABFeature);
324 13 : poTABPolylineFeature->SetPenFromStyleString(
325 13 : poFeature->GetStyleString());
326 : }
327 238 : break;
328 : }
329 : /*-------------------------------------------------------------
330 : * Collection types that are not directly supported... convert
331 : * to multiple features in output file through recursive calls.
332 : *------------------------------------------------------------*/
333 18 : case wkbGeometryCollection:
334 : case wkbMultiPoint:
335 : {
336 18 : OGRErr eStatus = OGRERR_NONE;
337 18 : assert(poGeom); // for clang static analyzer
338 18 : OGRGeometryCollection *poColl = poGeom->toGeometryCollection();
339 36 : auto poTmpFeature = std::unique_ptr<OGRFeature>(poFeature->Clone());
340 :
341 112 : for (int i = 0; eStatus == OGRERR_NONE && poColl != nullptr &&
342 56 : i < poColl->getNumGeometries();
343 : i++)
344 : {
345 38 : poTmpFeature->SetFID(OGRNullFID);
346 38 : poTmpFeature->SetGeometry(poColl->getGeometryRef(i));
347 38 : eStatus = ICreateFeature(poTmpFeature.get());
348 : }
349 18 : break;
350 : }
351 :
352 : /*-------------------------------------------------------------
353 : * Unsupported type.... convert to MapInfo geometry NONE
354 : *------------------------------------------------------------*/
355 78 : default:
356 : {
357 78 : poTABFeature = new TABFeature(poFeature->GetDefnRef());
358 78 : break;
359 : }
360 : }
361 :
362 15224 : if (poTABFeature)
363 : {
364 15206 : if (poGeom != nullptr)
365 15128 : poTABFeature->SetGeometryDirectly(poGeom->clone());
366 :
367 31169 : for (int i = 0; i < poFeature->GetDefnRef()->GetFieldCount(); i++)
368 : {
369 15963 : poTABFeature->SetField(i, poFeature->GetRawFieldRef(i));
370 : }
371 :
372 15206 : poTABFeature->SetFID(poFeature->GetFID());
373 : }
374 :
375 15224 : return poTABFeature;
376 : }
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 15008 : OGRErr IMapInfoFile::ICreateFeature(OGRFeature *poFeature)
385 : {
386 15008 : TABFeature *poTABFeature = CreateTABFeature(poFeature);
387 15008 : if (poTABFeature == nullptr) /* MultiGeometry */
388 18 : return OGRERR_NONE;
389 :
390 14990 : OGRErr eErr = CreateFeature(poTABFeature);
391 14990 : if (eErr == OGRERR_NONE)
392 14989 : poFeature->SetFID(poTABFeature->GetFID());
393 :
394 14990 : delete poTABFeature;
395 :
396 14990 : return eErr;
397 : }
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 292 : OGRFeature *IMapInfoFile::GetFeature(GIntBig nFeatureId)
407 : {
408 : /*fprintf(stderr, "GetFeature(%ld)\n", nFeatureId);*/
409 :
410 292 : OGRFeature *poFeatureRef = GetFeatureRef(nFeatureId);
411 292 : if (poFeatureRef)
412 : {
413 : // Avoid cloning feature... return the copy owned by the class
414 274 : CPLAssert(poFeatureRef == m_poCurFeature);
415 274 : m_poCurFeature = nullptr;
416 :
417 274 : return poFeatureRef;
418 : }
419 : else
420 18 : return nullptr;
421 : }
422 :
423 : /************************************************************************/
424 : /* GetTABType() */
425 : /* */
426 : /* Create a native field based on a generic OGR definition. */
427 : /************************************************************************/
428 :
429 352 : int IMapInfoFile::GetTABType(const OGRFieldDefn *poField,
430 : TABFieldType *peTABType, int *pnWidth,
431 : int *pnPrecision)
432 : {
433 : TABFieldType eTABType;
434 352 : int nWidth = poField->GetWidth();
435 : int nPrecision =
436 352 : poField->GetType() == OFTReal ? poField->GetPrecision() : 0;
437 :
438 352 : if (poField->GetType() == OFTInteger)
439 : {
440 103 : if (poField->GetSubType() == OFSTBoolean)
441 : {
442 2 : eTABType = TABFLogical;
443 2 : nWidth = 1;
444 : }
445 : else
446 : {
447 101 : eTABType = TABFInteger;
448 101 : if (nWidth == 0)
449 86 : nWidth = 12;
450 : }
451 : }
452 249 : else if (poField->GetType() == OFTInteger64)
453 : {
454 4 : eTABType = TABFLargeInt;
455 4 : if (nWidth == 0)
456 4 : nWidth = 20;
457 : }
458 245 : else if (poField->GetType() == OFTReal)
459 : {
460 41 : if (nWidth == 0 && poField->GetPrecision() == 0)
461 : {
462 29 : eTABType = TABFFloat;
463 29 : nWidth = 32;
464 : }
465 : else
466 : {
467 12 : eTABType = TABFDecimal;
468 : // Enforce Mapinfo limits, otherwise MapInfo will crash (#6392)
469 12 : if (nWidth > 20 || nWidth - nPrecision < 2 || nPrecision > 16)
470 : {
471 4 : if (nWidth > 20)
472 4 : nWidth = 20;
473 4 : if (nWidth - nPrecision < 2)
474 1 : nPrecision = nWidth - 2;
475 4 : if (nPrecision > 16)
476 1 : nPrecision = 16;
477 4 : CPLDebug("MITAB",
478 : "Adjusting initial width,precision of %s from %d,%d "
479 : "to %d,%d",
480 : poField->GetNameRef(), poField->GetWidth(),
481 : poField->GetPrecision(), nWidth, nPrecision);
482 : }
483 : }
484 : }
485 204 : else if (poField->GetType() == OFTDate)
486 : {
487 19 : eTABType = TABFDate;
488 19 : if (nWidth == 0)
489 17 : nWidth = 10;
490 : }
491 185 : else if (poField->GetType() == OFTTime)
492 : {
493 3 : eTABType = TABFTime;
494 3 : if (nWidth == 0)
495 1 : nWidth = 9;
496 : }
497 182 : else if (poField->GetType() == OFTDateTime)
498 : {
499 19 : eTABType = TABFDateTime;
500 19 : if (nWidth == 0)
501 17 : nWidth = 19;
502 : }
503 163 : else if (poField->GetType() == OFTString)
504 : {
505 163 : eTABType = TABFChar;
506 163 : if (nWidth == 0)
507 118 : nWidth = 254;
508 : else
509 45 : nWidth = std::min(254, nWidth);
510 : }
511 : else
512 : {
513 0 : CPLError(CE_Failure, CPLE_AppDefined,
514 : "IMapInfoFile::CreateField() called with unsupported field"
515 : " type %d.\n"
516 : "Note that Mapinfo files don't support list field types.\n",
517 0 : poField->GetType());
518 :
519 0 : return -1;
520 : }
521 :
522 352 : if (peTABType)
523 333 : *peTABType = eTABType;
524 352 : if (pnWidth)
525 341 : *pnWidth = nWidth;
526 352 : if (pnPrecision)
527 341 : *pnPrecision = nPrecision;
528 :
529 352 : return 0;
530 : }
531 :
532 : /************************************************************************/
533 : /* CreateField() */
534 : /* */
535 : /* Create a native field based on a generic OGR definition. */
536 : /************************************************************************/
537 :
538 322 : OGRErr IMapInfoFile::CreateField(const OGRFieldDefn *poField, int bApproxOK)
539 :
540 : {
541 : TABFieldType eTABType;
542 322 : int nWidth = 0;
543 322 : int nPrecision = 0;
544 :
545 322 : if (GetTABType(poField, &eTABType, &nWidth, &nPrecision) < 0)
546 0 : return OGRERR_FAILURE;
547 :
548 322 : if (AddFieldNative(poField->GetNameRef(), eTABType, nWidth, nPrecision,
549 644 : FALSE, FALSE, bApproxOK) > -1)
550 322 : return OGRERR_NONE;
551 :
552 0 : return OGRERR_FAILURE;
553 : }
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 2375 : int IMapInfoFile::SetCharset(const char *pszCharset)
564 : {
565 2375 : if (pszCharset && strlen(pszCharset) > 0)
566 : {
567 2375 : if (pszCharset == m_pszCharset)
568 : {
569 0 : return 0;
570 : }
571 2375 : CPLFree(m_pszCharset);
572 2375 : m_pszCharset = CPLStrdup(pszCharset);
573 2375 : return 0;
574 : }
575 0 : return -1;
576 : }
577 :
578 5330 : const char *IMapInfoFile::GetCharset() const
579 : {
580 5330 : return m_pszCharset;
581 : }
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 7533 : const char *IMapInfoFile::CharsetToEncoding(const char *pszCharset)
627 : {
628 7533 : if (pszCharset == nullptr)
629 : {
630 874 : return apszCharsets[0][1];
631 : }
632 :
633 18052 : for (size_t i = 0; apszCharsets[i][0] != nullptr; ++i)
634 : {
635 18046 : if (EQUAL(pszCharset, apszCharsets[i][0]))
636 : {
637 6653 : return apszCharsets[i][1];
638 : }
639 : }
640 :
641 6 : CPLError(CE_Warning, CPLE_NotSupported,
642 : "Cannot find iconv encoding corresponding to MapInfo %s charset",
643 : pszCharset);
644 6 : return apszCharsets[0][1];
645 : }
646 :
647 361 : const char *IMapInfoFile::EncodingToCharset(const char *pszEncoding)
648 : {
649 361 : if (pszEncoding == nullptr)
650 : {
651 352 : return apszCharsets[0][0];
652 : }
653 :
654 179 : for (size_t i = 0; apszCharsets[i][1] != nullptr; ++i)
655 : {
656 179 : if (EQUAL(pszEncoding, apszCharsets[i][1]))
657 : {
658 9 : return apszCharsets[i][0];
659 : }
660 : }
661 :
662 0 : CPLError(CE_Warning, CPLE_NotSupported,
663 : "Cannot find MapInfo charset corresponding to iconv %s encoding",
664 : pszEncoding);
665 0 : return apszCharsets[0][0];
666 : }
667 :
668 3922 : const char *IMapInfoFile::GetEncoding() const
669 : {
670 3922 : return CharsetToEncoding(GetCharset());
671 : }
672 :
673 0 : void IMapInfoFile::SetEncoding(const char *pszEncoding)
674 : {
675 0 : SetCharset(EncodingToCharset(pszEncoding));
676 0 : }
677 :
678 360 : void IMapInfoFile::SetStrictLaundering(bool bStrictLaundering)
679 : {
680 360 : m_bStrictLaundering = bStrictLaundering;
681 360 : }
682 :
683 69 : int IMapInfoFile::TestUtf8Capability() const
684 : {
685 69 : const char *pszEncoding(GetEncoding());
686 69 : if (strlen(pszEncoding) == 0)
687 : {
688 55 : return FALSE;
689 : }
690 :
691 14 : return CPLCanRecode("test", GetEncoding(), CPL_ENC_UTF8);
692 : }
693 :
694 493 : CPLString IMapInfoFile::NormalizeFieldName(const char *pszName) const
695 : {
696 986 : CPLString osName(pszName);
697 493 : if (strlen(GetEncoding()) > 0)
698 84 : osName.Recode(CPL_ENC_UTF8, GetEncoding());
699 :
700 : char szNewFieldName[31 + 1]; /* 31 is the max characters for a field name*/
701 493 : unsigned int nRenameNum = 1;
702 :
703 493 : strncpy(szNewFieldName, osName.c_str(), sizeof(szNewFieldName) - 1);
704 493 : szNewFieldName[sizeof(szNewFieldName) - 1] = '\0';
705 :
706 986 : while (m_oSetFields.find(CPLString(szNewFieldName).toupper()) !=
707 1479 : m_oSetFields.end() &&
708 : nRenameNum < 10)
709 : {
710 0 : CPLsnprintf(szNewFieldName, sizeof(szNewFieldName), "%.29s_%.1u",
711 : osName.c_str(), nRenameNum);
712 0 : nRenameNum++;
713 : }
714 :
715 986 : while (m_oSetFields.find(CPLString(szNewFieldName).toupper()) !=
716 1479 : m_oSetFields.end() &&
717 : nRenameNum < 100)
718 : {
719 0 : CPLsnprintf(szNewFieldName, sizeof(szNewFieldName), "%.29s%.2u",
720 : osName.c_str(), nRenameNum);
721 0 : nRenameNum++;
722 : }
723 :
724 493 : if (m_oSetFields.find(CPLString(szNewFieldName).toupper()) !=
725 986 : m_oSetFields.end())
726 : {
727 0 : CPLError(CE_Failure, CPLE_NotSupported,
728 : "Too many field names like '%s' when truncated to 31 letters "
729 : "for MapInfo format.",
730 : pszName);
731 : }
732 :
733 493 : CPLString osNewFieldName(szNewFieldName);
734 493 : if (strlen(GetEncoding()) > 0)
735 84 : osNewFieldName.Recode(GetEncoding(), CPL_ENC_UTF8);
736 :
737 493 : if (!EQUAL(pszName, osNewFieldName.c_str()))
738 : {
739 0 : CPLError(CE_Warning, CPLE_NotSupported,
740 : "Normalized/laundered field name: '%s' to '%s'", pszName,
741 : osNewFieldName.c_str());
742 : }
743 :
744 986 : return osNewFieldName;
745 : }
|