Merge pull request #11436 from rouault/gml_detect_write_error

GML: detect and report write error
This commit is contained in:
Even Rouault 2024-12-06 11:50:15 +01:00 committed by GitHub
commit b8372bd846
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 260 additions and 159 deletions

View File

@ -236,13 +236,16 @@ if (BUILD_PYTHON_BINDINGS)
endif()
endif()
#
include(${CMAKE_CURRENT_SOURCE_DIR}/gdal.cmake)
option(BUILD_TESTING "Build the testing tree." ON)
# Make sure enable_testing() is defined before including gdal.cmake for
# Java and CSharp tests
if (BUILD_TESTING)
enable_testing()
endif()
#
include(${CMAKE_CURRENT_SOURCE_DIR}/gdal.cmake)
if (BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/autotest")
# unit tests
add_subdirectory(autotest/cpp)

View File

@ -4951,3 +4951,28 @@ def test_ogr_gml_type_override(
assert (
gdal.GetLastErrorMsg().find(expected_warning) != -1
), f"Warning {expected_warning} not found, got {gdal.GetLastErrorMsg()} instead"
###############################################################################
# Test a write error
@gdaltest.enable_exceptions()
def test_ogr_gml_write_error(tmp_vsimem):
filename = str(tmp_vsimem / "test.gml||maxlength=200")
ds = ogr.GetDriverByName("GML").CreateDataSource(
filename, options=["XSISCHEMA=OFF"]
)
with pytest.raises(Exception, match="Could not write line"):
ds.Close()
filename = str(tmp_vsimem / "test.gml||maxlength=600")
ds = ogr.GetDriverByName("GML").CreateDataSource(
filename, options=["XSISCHEMA=OFF"]
)
lyr = ds.CreateLayer("test")
f = ogr.Feature(lyr.GetLayerDefn())
with pytest.raises(Exception, match="Could not write line"):
lyr.CreateFeature(f)
ds.Close()

View File

@ -108,6 +108,7 @@ class OGRGMLDataSource final : public GDALDataset
VSILFILE *fpOutput;
bool bFpOutputIsNonSeekable;
bool bFpOutputSingleFile;
bool m_bWriteError = false;
OGREnvelope3D sBoundingRect{};
bool bBBOX3D;
int nBoundedByLocation;
@ -178,6 +179,7 @@ class OGRGMLDataSource final : public GDALDataset
virtual ~OGRGMLDataSource();
bool Open(GDALOpenInfo *poOpenInfo);
CPLErr Close() override;
bool Create(const char *pszFile, char **papszOptions);
int GetLayerCount() override
@ -226,6 +228,12 @@ class OGRGMLDataSource final : public GDALDataset
return bIsOutputGML32;
}
/** Returns whether a writing error has occured */
inline bool HasWriteError() const
{
return m_bWriteError;
}
OGRGMLSRSNameFormat GetSRSNameFormat() const
{
return eSRSNameFormat;

View File

@ -81,182 +81,212 @@ OGRGMLDataSource::OGRGMLDataSource()
}
/************************************************************************/
/* ~OGRGMLDataSource() */
/* ~OGRGMLDataSource() */
/************************************************************************/
OGRGMLDataSource::~OGRGMLDataSource()
{
if (fpOutput != nullptr)
OGRGMLDataSource::Close();
}
/************************************************************************/
/* Close() */
/************************************************************************/
CPLErr OGRGMLDataSource::Close()
{
CPLErr eErr = CE_None;
if (nOpenFlags != OPEN_FLAGS_CLOSED)
{
if (nLayers == 0)
WriteTopElements();
const char *pszPrefix = GetAppPrefix();
if (GMLFeatureCollection())
PrintLine(fpOutput, "</gml:FeatureCollection>");
else if (RemoveAppPrefix())
PrintLine(fpOutput, "</FeatureCollection>");
else
PrintLine(fpOutput, "</%s:FeatureCollection>", pszPrefix);
if (bFpOutputIsNonSeekable)
if (fpOutput && !m_bWriteError)
{
VSIFCloseL(fpOutput);
fpOutput = nullptr;
}
if (nLayers == 0)
WriteTopElements();
InsertHeader();
const char *pszPrefix = GetAppPrefix();
if (GMLFeatureCollection())
PrintLine(fpOutput, "</gml:FeatureCollection>");
else if (RemoveAppPrefix())
PrintLine(fpOutput, "</FeatureCollection>");
else
PrintLine(fpOutput, "</%s:FeatureCollection>", pszPrefix);
if (!bFpOutputIsNonSeekable && nBoundedByLocation != -1 &&
VSIFSeekL(fpOutput, nBoundedByLocation, SEEK_SET) == 0)
{
if (m_bWriteGlobalSRS && sBoundingRect.IsInit() && IsGML3Output())
if (bFpOutputIsNonSeekable)
{
bool bCoordSwap = false;
char *pszSRSName =
m_poWriteGlobalSRS
? GML_GetSRSName(m_poWriteGlobalSRS.get(),
eSRSNameFormat, &bCoordSwap)
: CPLStrdup("");
char szLowerCorner[75] = {};
char szUpperCorner[75] = {};
VSIFCloseL(fpOutput);
fpOutput = nullptr;
}
OGRWktOptions coordOpts;
InsertHeader();
if (OGRGMLDataSource::GetLayerCount() == 1)
if (!bFpOutputIsNonSeekable && nBoundedByLocation != -1 &&
VSIFSeekL(fpOutput, nBoundedByLocation, SEEK_SET) == 0)
{
if (m_bWriteGlobalSRS && sBoundingRect.IsInit() &&
IsGML3Output())
{
OGRLayer *poLayer = OGRGMLDataSource::GetLayer(0);
if (poLayer->GetLayerDefn()->GetGeomFieldCount() == 1)
bool bCoordSwap = false;
char *pszSRSName =
m_poWriteGlobalSRS
? GML_GetSRSName(m_poWriteGlobalSRS.get(),
eSRSNameFormat, &bCoordSwap)
: CPLStrdup("");
char szLowerCorner[75] = {};
char szUpperCorner[75] = {};
OGRWktOptions coordOpts;
if (OGRGMLDataSource::GetLayerCount() == 1)
{
const auto &oCoordPrec = poLayer->GetLayerDefn()
->GetGeomFieldDefn(0)
->GetCoordinatePrecision();
if (oCoordPrec.dfXYResolution !=
OGRGeomCoordinatePrecision::UNKNOWN)
OGRLayer *poLayer = OGRGMLDataSource::GetLayer(0);
if (poLayer->GetLayerDefn()->GetGeomFieldCount() == 1)
{
coordOpts.format = OGRWktFormat::F;
coordOpts.xyPrecision = OGRGeomCoordinatePrecision::
ResolutionToPrecision(
oCoordPrec.dfXYResolution);
}
if (oCoordPrec.dfZResolution !=
OGRGeomCoordinatePrecision::UNKNOWN)
{
coordOpts.format = OGRWktFormat::F;
coordOpts.zPrecision = OGRGeomCoordinatePrecision::
ResolutionToPrecision(oCoordPrec.dfZResolution);
const auto &oCoordPrec =
poLayer->GetLayerDefn()
->GetGeomFieldDefn(0)
->GetCoordinatePrecision();
if (oCoordPrec.dfXYResolution !=
OGRGeomCoordinatePrecision::UNKNOWN)
{
coordOpts.format = OGRWktFormat::F;
coordOpts.xyPrecision =
OGRGeomCoordinatePrecision::
ResolutionToPrecision(
oCoordPrec.dfXYResolution);
}
if (oCoordPrec.dfZResolution !=
OGRGeomCoordinatePrecision::UNKNOWN)
{
coordOpts.format = OGRWktFormat::F;
coordOpts.zPrecision =
OGRGeomCoordinatePrecision::
ResolutionToPrecision(
oCoordPrec.dfZResolution);
}
}
}
std::string wkt;
if (bCoordSwap)
{
wkt = OGRMakeWktCoordinate(
sBoundingRect.MinY, sBoundingRect.MinX,
sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts);
memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);
wkt = OGRMakeWktCoordinate(
sBoundingRect.MaxY, sBoundingRect.MaxX,
sBoundingRect.MaxZ, bBBOX3D ? 3 : 2, coordOpts);
memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
}
else
{
wkt = OGRMakeWktCoordinate(
sBoundingRect.MinX, sBoundingRect.MinY,
sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts);
memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);
wkt = OGRMakeWktCoordinate(
sBoundingRect.MaxX, sBoundingRect.MaxY,
sBoundingRect.MaxZ, (bBBOX3D) ? 3 : 2, coordOpts);
memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
}
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(
fpOutput,
"<gml:boundedBy><gml:Envelope%s%s><gml:lowerCorner>%s"
"</gml:lowerCorner><gml:upperCorner>%s</"
"gml:upperCorner>"
"</gml:Envelope></gml:boundedBy>",
bBBOX3D ? " srsDimension=\"3\"" : "", pszSRSName,
szLowerCorner, szUpperCorner);
CPLFree(pszSRSName);
}
std::string wkt;
if (bCoordSwap)
else if (m_bWriteGlobalSRS && sBoundingRect.IsInit())
{
wkt = OGRMakeWktCoordinate(
sBoundingRect.MinY, sBoundingRect.MinX,
sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts);
memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);
wkt = OGRMakeWktCoordinate(
sBoundingRect.MaxY, sBoundingRect.MaxX,
sBoundingRect.MaxZ, bBBOX3D ? 3 : 2, coordOpts);
memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(fpOutput, "<gml:boundedBy>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(fpOutput, "<gml:Box>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
VSIFPrintfL(fpOutput,
"<gml:coord><gml:X>%.16g</gml:X>"
"<gml:Y>%.16g</gml:Y>",
sBoundingRect.MinX, sBoundingRect.MinY);
if (bBBOX3D)
VSIFPrintfL(fpOutput, "<gml:Z>%.16g</gml:Z>",
sBoundingRect.MinZ);
PrintLine(fpOutput, "</gml:coord>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
VSIFPrintfL(fpOutput,
"<gml:coord><gml:X>%.16g</gml:X>"
"<gml:Y>%.16g</gml:Y>",
sBoundingRect.MaxX, sBoundingRect.MaxY);
if (bBBOX3D)
VSIFPrintfL(fpOutput, "<gml:Z>%.16g</gml:Z>",
sBoundingRect.MaxZ);
PrintLine(fpOutput, "</gml:coord>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(fpOutput, "</gml:Box>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(fpOutput, "</gml:boundedBy>");
}
else
{
wkt = OGRMakeWktCoordinate(
sBoundingRect.MinX, sBoundingRect.MinY,
sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts);
memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);
wkt = OGRMakeWktCoordinate(
sBoundingRect.MaxX, sBoundingRect.MaxY,
sBoundingRect.MaxZ, (bBBOX3D) ? 3 : 2, coordOpts);
memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
if (IsGML3Output())
PrintLine(
fpOutput,
"<gml:boundedBy><gml:Null /></gml:boundedBy>");
else
PrintLine(fpOutput, "<gml:boundedBy><gml:null>missing"
"</gml:null></gml:boundedBy>");
}
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(
fpOutput,
"<gml:boundedBy><gml:Envelope%s%s><gml:lowerCorner>%s"
"</gml:lowerCorner><gml:upperCorner>%s</gml:upperCorner>"
"</gml:Envelope></gml:boundedBy>",
bBBOX3D ? " srsDimension=\"3\"" : "", pszSRSName,
szLowerCorner, szUpperCorner);
CPLFree(pszSRSName);
}
else if (m_bWriteGlobalSRS && sBoundingRect.IsInit())
{
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(fpOutput, "<gml:boundedBy>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(fpOutput, "<gml:Box>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
VSIFPrintfL(fpOutput,
"<gml:coord><gml:X>%.16g</gml:X>"
"<gml:Y>%.16g</gml:Y>",
sBoundingRect.MinX, sBoundingRect.MinY);
if (bBBOX3D)
VSIFPrintfL(fpOutput, "<gml:Z>%.16g</gml:Z>",
sBoundingRect.MinZ);
PrintLine(fpOutput, "</gml:coord>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
VSIFPrintfL(fpOutput,
"<gml:coord><gml:X>%.16g</gml:X>"
"<gml:Y>%.16g</gml:Y>",
sBoundingRect.MaxX, sBoundingRect.MaxY);
if (bBBOX3D)
VSIFPrintfL(fpOutput, "<gml:Z>%.16g</gml:Z>",
sBoundingRect.MaxZ);
PrintLine(fpOutput, "</gml:coord>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(fpOutput, "</gml:Box>");
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
PrintLine(fpOutput, "</gml:boundedBy>");
}
else
{
if (bWriteSpaceIndentation)
VSIFPrintfL(fpOutput, " ");
if (IsGML3Output())
PrintLine(fpOutput,
"<gml:boundedBy><gml:Null /></gml:boundedBy>");
else
PrintLine(fpOutput, "<gml:boundedBy><gml:null>missing"
"</gml:null></gml:boundedBy>");
}
}
if (fpOutput)
VSIFCloseL(fpOutput);
fpOutput = nullptr;
CSLDestroy(papszCreateOptions);
papszCreateOptions = nullptr;
for (int i = 0; i < nLayers; i++)
delete papoLayers[i];
CPLFree(papoLayers);
papoLayers = nullptr;
nLayers = 0;
if (poReader)
{
if (bOutIsTempFile)
VSIUnlink(poReader->GetSourceFileName());
delete poReader;
poReader = nullptr;
}
delete poStoredGMLFeature;
poStoredGMLFeature = nullptr;
if (m_bUnlinkXSDFilename)
{
VSIUnlink(osXSDFilename);
m_bUnlinkXSDFilename = false;
}
if (m_bWriteError)
eErr = CE_Failure;
}
CSLDestroy(papszCreateOptions);
for (int i = 0; i < nLayers; i++)
delete papoLayers[i];
CPLFree(papoLayers);
if (poReader)
{
if (bOutIsTempFile)
VSIUnlink(poReader->GetSourceFileName());
delete poReader;
}
delete poStoredGMLFeature;
if (m_bUnlinkXSDFilename)
{
VSIUnlink(osXSDFilename);
}
return eErr;
}
/************************************************************************/
@ -3109,6 +3139,7 @@ void OGRGMLDataSource::PrintLine(VSILFILE *fp, const char *fmt, ...)
if (VSIFWriteL(osWork.data(), osWork.size(), 1, fp) != 1 ||
VSIFWriteL(pszEOL, strlen(pszEOL), 1, fp) != 1)
{
m_bWriteError = true;
ReportError(CE_Failure, CPLE_FileIO, "Could not write line %s",
osWork.c_str());
}

View File

@ -669,7 +669,7 @@ OGRErr OGRGMLLayer::ICreateFeature(OGRFeature *poFeature)
const bool bRemoveAppPrefix = poDS->RemoveAppPrefix();
const bool bGMLFeatureCollection = poDS->GMLFeatureCollection();
if (!bWriter)
if (!bWriter || poDS->HasWriteError())
return OGRERR_FAILURE;
poFeature->FillUnsetWithDefault(TRUE, nullptr);
@ -1091,7 +1091,7 @@ OGRErr OGRGMLLayer::ICreateFeature(OGRFeature *poFeature)
poDS->PrintLine(fp, "</gml:featureMember>");
}
return OGRERR_NONE;
return !poDS->HasWriteError() ? OGRERR_NONE : OGRERR_FAILURE;
}
/************************************************************************/

View File

@ -271,6 +271,10 @@ public:
}
}
#ifdef SWIGJAVA
%rename (CloseInternal) Close;
%javamethodmodifiers Close() "private";
#endif
CPLErr Close() {
return GDALClose(self);
}

View File

@ -563,6 +563,17 @@ import org.gdal.gdalconst.gdalconstConstants;
}
%}
%extend GDALDatasetShadow {
%proxycode %{
public int Close() {
int ret = gdalJNI.Dataset_CloseInternal(swigCPtr, this);
swigCPtr = 0;
swigCMemOwn = false;
return ret;
}
%}
}
%typemap(javacode) GDALDatasetShadow %{
// Preferred name to match C++ API

View File

@ -1623,13 +1623,29 @@ CPLErr ReadRaster1( double xoff, double yoff, double xsize, double ysize,
self._child_references.add(val)
%}
%feature("pythonprepend") Close %{
self._invalidate_children()
%}
%feature("shadow") Close %{
def Close(self, *args):
r"""
Close(Dataset self) -> CPLErr
%feature("pythonappend") Close %{
self.thisown = 0
self.this = None
Closes opened dataset and releases allocated resources.
This method can be used to force the dataset to close
when one more references to the dataset are still
reachable. If :py:meth:`Close` is never called, the dataset will
be closed automatically during garbage collection.
In most cases, it is preferable to open or create a dataset
using a context manager instead of calling :py:meth:`Close`
directly.
"""
self._invalidate_children()
try:
return _gdal.Dataset_Close(self, *args)
finally:
self.thisown = 0
self.this = None
%}
%feature("shadow") ExecuteSQL %{

View File

@ -215,6 +215,9 @@ public class GDALTestIO implements Runnable
if (data1[i] != data2[i])
throw new RuntimeException("int64 write and read values are not the same "+data1[i]+" "+data2[i]);
}
dataset.Close();
dataset.Close();
}
private static void testGetMemFileBuffer()