From 0735c5ffb7cd75a94c01f28a79a9f476d6b0f059 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Aug 2024 15:18:52 +0200 Subject: [PATCH] DGN: add ENCODING open option and creation option Refs #10630 --- autotest/ogr/ogr_dgn.py | 29 +++++++++++++ doc/source/drivers/vector/dgn.rst | 33 +++++++++++++++ ogr/ogrsf_frmts/dgn/ogr_dgn.h | 30 ++++++++------ ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp | 53 +++++++----------------- ogr/ogrsf_frmts/dgn/ogrdgndriver.cpp | 15 +++++-- ogr/ogrsf_frmts/dgn/ogrdgnlayer.cpp | 50 ++++++++++++++++++---- 6 files changed, 148 insertions(+), 62 deletions(-) diff --git a/autotest/ogr/ogr_dgn.py b/autotest/ogr/ogr_dgn.py index 6d5551aa1873..5cf724fc168c 100755 --- a/autotest/ogr/ogr_dgn.py +++ b/autotest/ogr/ogr_dgn.py @@ -315,3 +315,32 @@ def test_ogr_dgn_open_dgnv8_not_supported(): finally: if dgnv8_drv: dgnv8_drv.Register() + + +############################################################################### +# Test ENCODING creation option and open option + + +def test_ogr_dgn_encoding(tmp_path): + + filename = tmp_path / "test.dgn" + with ogr.GetDriverByName("DGN").CreateDataSource( + filename, options=["ENCODING=ISO-8859-1"] + ) as ds: + lyr = ds.CreateLayer("elements") + f = ogr.Feature(lyr.GetLayerDefn()) + f["Text"] = "\xc3\xa9ven" # UTF-8 encoded + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(0 0)")) + lyr.CreateFeature(f) + + with ogr.Open(filename) as ds: + lyr = ds.GetLayer(0) + assert lyr.TestCapability(ogr.OLCStringsAsUTF8) == 0 + f = lyr.GetNextFeature() + assert f["Text"] == "\xe9ven" # ISO-8859-1 + + with gdal.OpenEx(filename, open_options=["ENCODING=ISO-8859-1"]) as ds: + lyr = ds.GetLayer(0) + assert lyr.TestCapability(ogr.OLCStringsAsUTF8) == 1 + f = lyr.GetNextFeature() + assert f["Text"] == "\xc3\xa9ven" # UTF-8 diff --git a/doc/source/drivers/vector/dgn.rst b/doc/source/drivers/vector/dgn.rst index cfc920916ca1..877855117037 100644 --- a/doc/source/drivers/vector/dgn.rst +++ b/doc/source/drivers/vector/dgn.rst @@ -122,6 +122,25 @@ Creation Issues - DGN files can only have one layer. Attempts to create more than one layer in a DGN file will fail. +Open options +------------ + +.. versionadded:: 3.10 + +|about-open-options| +The following open options are supported: + +- .. oo:: ENCODING + :since: 3.10 + :choices: + + An encoding name supported by :cpp:func:`CPLRecode` (i.e. an + `iconv `__ name) + that indicates the encoding used by Text elements in the DGN file, to + recode them to UTF-8. If not specified (or specified to UTF-8), no + recoding will be done. + + Dataset creation options ------------------------ @@ -180,6 +199,20 @@ The following dataset-creation options are supported: Override the origin of the design plane. By default the origin from the seed file is used. +- .. dsco:: ENCODING + :since: 3.10 + :choices: + + An encoding name supported by :cpp:func:`CPLRecode` (i.e. an + `iconv `__ name) + that indicates the encoding used by Text elements in the DGN file, to + recode them from UTF-8. If not specified (or specified to UTF-8), no + recoding will be done. "ISO-8859-1" (ISO Latin1) is supported even on + builds without iconv support. + Note that no fields in the DGN file itself contain the encoding name, + hence it is the responsibility of the reader to open the file with the + same value for the ENCODING open option. + -------------- - `Dgnlib Page `__ diff --git a/ogr/ogrsf_frmts/dgn/ogr_dgn.h b/ogr/ogrsf_frmts/dgn/ogr_dgn.h index f9359e8cfdec..868bc7df35e3 100644 --- a/ogr/ogrsf_frmts/dgn/ogr_dgn.h +++ b/ogr/ogrsf_frmts/dgn/ogr_dgn.h @@ -37,9 +37,11 @@ /* OGRDGNLayer */ /************************************************************************/ +class OGRDGNDataSource; + class OGRDGNLayer final : public OGRLayer { - GDALDataset *m_poDS = nullptr; + OGRDGNDataSource *m_poDS = nullptr; OGRFeatureDefn *poFeatureDefn; int iNextShapeId; @@ -64,7 +66,7 @@ class OGRDGNLayer final : public OGRLayer OGRErr CreateFeatureWithGeom(OGRFeature *, const OGRGeometry *); public: - OGRDGNLayer(GDALDataset *poDS, const char *pszName, DGNHandle hDGN, + OGRDGNLayer(OGRDGNDataSource *poDS, const char *pszName, DGNHandle hDGN, int bUpdate); virtual ~OGRDGNLayer(); @@ -97,10 +99,7 @@ class OGRDGNLayer final : public OGRLayer OGRErr ICreateFeature(OGRFeature *poFeature) override; - GDALDataset *GetDataset() override - { - return m_poDS; - } + GDALDataset *GetDataset() override; }; /************************************************************************/ @@ -109,19 +108,21 @@ class OGRDGNLayer final : public OGRLayer class OGRDGNDataSource final : public OGRDataSource { - OGRDGNLayer **papoLayers; - int nLayers; + OGRDGNLayer **papoLayers = nullptr; + int nLayers = 0; - char *pszName; - DGNHandle hDGN; + char *pszName = nullptr; + DGNHandle hDGN = nullptr; - char **papszOptions; + char **papszOptions = nullptr; + + std::string m_osEncoding{}; public: OGRDGNDataSource(); ~OGRDGNDataSource(); - int Open(const char *, int bTestOpen, int bUpdate); + bool Open(GDALOpenInfo *poOpenInfo); bool PreCreate(const char *, char **); OGRLayer *ICreateLayer(const char *pszName, @@ -141,6 +142,11 @@ class OGRDGNDataSource final : public OGRDataSource OGRLayer *GetLayer(int) override; int TestCapability(const char *) override; + + const std::string &GetEncoding() const + { + return m_osEncoding; + } }; #endif /* ndef OGR_DGN_H_INCLUDED */ diff --git a/ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp b/ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp index 648471ceef5b..3c4061fff4c9 100644 --- a/ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp +++ b/ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp @@ -34,11 +34,7 @@ /* OGRDGNDataSource() */ /************************************************************************/ -OGRDGNDataSource::OGRDGNDataSource() - : papoLayers(nullptr), nLayers(0), pszName(nullptr), hDGN(nullptr), - papszOptions(nullptr) -{ -} +OGRDGNDataSource::OGRDGNDataSource() = default; /************************************************************************/ /* ~OGRDGNDataSource() */ @@ -62,54 +58,32 @@ OGRDGNDataSource::~OGRDGNDataSource() /* Open() */ /************************************************************************/ -int OGRDGNDataSource::Open(const char *pszNewName, int bTestOpen, int bUpdate) +bool OGRDGNDataSource::Open(GDALOpenInfo *poOpenInfo) { - CPLAssert(nLayers == 0); - - /* -------------------------------------------------------------------- */ - /* For now we require files to have the .dgn or .DGN */ - /* extension. Eventually we will implement a more */ - /* sophisticated test to see if it is a dgn file. */ - /* -------------------------------------------------------------------- */ - if (bTestOpen) - { - - VSILFILE *fp = VSIFOpenL(pszNewName, "rb"); - if (fp == nullptr) - return FALSE; + m_osEncoding = + CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ENCODING", ""); - GByte abyHeader[512]; - const int nHeaderBytes = - static_cast(VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp)); - - VSIFCloseL(fp); - - if (nHeaderBytes < 512) - return FALSE; - - if (!DGNTestOpen(abyHeader, nHeaderBytes)) - return FALSE; - } + CPLAssert(nLayers == 0); /* -------------------------------------------------------------------- */ /* Try to open the file as a DGN file. */ /* -------------------------------------------------------------------- */ - hDGN = DGNOpen(pszNewName, bUpdate); + const bool bUpdate = (poOpenInfo->eAccess == GA_Update); + hDGN = DGNOpen(poOpenInfo->pszFilename, bUpdate); if (hDGN == nullptr) { - if (!bTestOpen) - CPLError(CE_Failure, CPLE_AppDefined, - "Unable to open %s as a Microstation .dgn file.", - pszNewName); - return FALSE; + CPLError(CE_Failure, CPLE_AppDefined, + "Unable to open %s as a Microstation .dgn file.", + poOpenInfo->pszFilename); + return false; } /* -------------------------------------------------------------------- */ /* Create the layer object. */ /* -------------------------------------------------------------------- */ OGRDGNLayer *poLayer = new OGRDGNLayer(this, "elements", hDGN, bUpdate); - pszName = CPLStrdup(pszNewName); + pszName = CPLStrdup(poOpenInfo->pszFilename); /* -------------------------------------------------------------------- */ /* Add layer to data source layer list. */ @@ -118,7 +92,7 @@ int OGRDGNDataSource::Open(const char *pszNewName, int bTestOpen, int bUpdate) CPLRealloc(papoLayers, sizeof(OGRDGNLayer *) * (nLayers + 1))); papoLayers[nLayers++] = poLayer; - return TRUE; + return true; } /************************************************************************/ @@ -163,6 +137,7 @@ bool OGRDGNDataSource::PreCreate(const char *pszFilename, char **papszOptionsIn) papszOptions = CSLDuplicate(papszOptionsIn); pszName = CPLStrdup(pszFilename); + m_osEncoding = CSLFetchNameValueDef(papszOptionsIn, "ENCODING", ""); return true; } diff --git a/ogr/ogrsf_frmts/dgn/ogrdgndriver.cpp b/ogr/ogrsf_frmts/dgn/ogrdgndriver.cpp index 875e07b833f9..718f1b0e6829 100644 --- a/ogr/ogrsf_frmts/dgn/ogrdgndriver.cpp +++ b/ogr/ogrsf_frmts/dgn/ogrdgndriver.cpp @@ -30,7 +30,7 @@ #include "cpl_conv.h" /************************************************************************/ -/* Open() */ +/* OGRDGNDriverIdentify() */ /************************************************************************/ static int OGRDGNDriverIdentify(GDALOpenInfo *poOpenInfo) @@ -76,9 +76,7 @@ static GDALDataset *OGRDGNDriverOpen(GDALOpenInfo *poOpenInfo) OGRDGNDataSource *poDS = new OGRDGNDataSource(); - if (!poDS->Open(poOpenInfo->pszFilename, TRUE, - (poOpenInfo->eAccess == GA_Update)) || - poDS->GetLayerCount() == 0) + if (!poDS->Open(poOpenInfo) || poDS->GetLayerCount() == 0) { delete poDS; return nullptr; @@ -131,6 +129,13 @@ void RegisterOGRDGN() poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES"); poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE"); + poDriver->SetMetadataItem( + GDAL_DMD_OPENOPTIONLIST, + "" + " "); + poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST, "" @@ -167,6 +172,8 @@ void RegisterOGRDGN() " "); poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST, diff --git a/ogr/ogrsf_frmts/dgn/ogrdgnlayer.cpp b/ogr/ogrsf_frmts/dgn/ogrdgnlayer.cpp index 3898ced6d86d..6a8490833954 100644 --- a/ogr/ogrsf_frmts/dgn/ogrdgnlayer.cpp +++ b/ogr/ogrsf_frmts/dgn/ogrdgnlayer.cpp @@ -39,7 +39,7 @@ /* OGRDGNLayer() */ /************************************************************************/ -OGRDGNLayer::OGRDGNLayer(GDALDataset *poDS, const char *pszName, +OGRDGNLayer::OGRDGNLayer(OGRDGNDataSource *poDS, const char *pszName, DGNHandle hDGNIn, int bUpdateIn) : m_poDS(poDS), poFeatureDefn(new OGRFeatureDefn(pszName)), iNextShapeId(0), hDGN(hDGNIn), bUpdate(bUpdateIn) @@ -628,11 +628,23 @@ OGRFeature *OGRDGNLayer::ElementToFeature(DGNElemCore *psElement, int nRecLevel) poFeature->SetGeometryDirectly(poPoint); - const size_t nOgrFSLen = strlen(psText->string) + 150; + const auto &osEncoding = m_poDS->GetEncoding(); + std::string osText; + if (!osEncoding.empty() && osEncoding != CPL_ENC_UTF8) + { + osText = CPLString(psText->string) + .Recode(osEncoding.c_str(), CPL_ENC_UTF8); + } + else + { + osText = psText->string; + } + + const size_t nOgrFSLen = osText.size() + 150; char *pszOgrFS = static_cast(CPLMalloc(nOgrFSLen)); // setup the basic label. - snprintf(pszOgrFS, nOgrFSLen, "LABEL(t:\"%s\"", psText->string); + snprintf(pszOgrFS, nOgrFSLen, "LABEL(t:\"%s\"", osText.c_str()); // set the color if we have it. if (strlen(szFSColor) > 0) @@ -793,7 +805,7 @@ OGRFeature *OGRDGNLayer::ElementToFeature(DGNElemCore *psElement, int nRecLevel) poFeature->SetStyleString(pszOgrFS); CPLFree(pszOgrFS); - poFeature->SetField("Text", psText->string); + poFeature->SetField("Text", osText.c_str()); } break; @@ -928,6 +940,9 @@ int OGRDGNLayer::TestCapability(const char *pszCap) else if (EQUAL(pszCap, OLCZGeometries)) return TRUE; + else if (EQUAL(pszCap, OLCStringsAsUTF8)) + return !m_poDS->GetEncoding().empty(); + return FALSE; } @@ -1183,11 +1198,23 @@ DGNElemCore **OGRDGNLayer::TranslateLabel(OGRFeature *poFeature) } } + std::string osText; + const auto &osEncoding = m_poDS->GetEncoding(); + if (!osEncoding.empty() && osEncoding != CPL_ENC_UTF8) + { + osText = CPLString(pszText).Recode(CPL_ENC_UTF8, osEncoding.c_str()); + } + else + { + osText = pszText; + } + DGNElemCore **papsGroup = static_cast(CPLCalloc(sizeof(void *), 2)); - papsGroup[0] = DGNCreateTextElem( - hDGN, pszText, nFontID, DGNJ_LEFT_BOTTOM, dfCharHeight, dfCharHeight, - dfRotation, nullptr, poPoint->getX(), poPoint->getY(), poPoint->getZ()); + papsGroup[0] = + DGNCreateTextElem(hDGN, osText.c_str(), nFontID, DGNJ_LEFT_BOTTOM, + dfCharHeight, dfCharHeight, dfRotation, nullptr, + poPoint->getX(), poPoint->getY(), poPoint->getZ()); if (poLabel) delete poLabel; @@ -1387,3 +1414,12 @@ OGRErr OGRDGNLayer::CreateFeatureWithGeom(OGRFeature *poFeature, return OGRERR_NONE; } + +/************************************************************************/ +/* GetDataset() */ +/************************************************************************/ + +GDALDataset *OGRDGNLayer::GetDataset() +{ + return m_poDS; +}