Coverage Report

Created: 2025-11-15 08:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/ods/ogrodsdriver.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  ODS Translator
4
 * Purpose:  Implements OGRODSDriver.
5
 * Author:   Even Rouault, even dot rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2012, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_conv.h"
14
#include "ogr_ods.h"
15
#include "ogrsf_frmts.h"
16
17
using namespace OGRODS;
18
19
// g++ -DHAVE_EXPAT -g -Wall -fPIC ogr/ogrsf_frmts/ods/*.cpp -shared
20
// -o ogr_ODS.so -Iport -Igcore -Iogr -Iogr/ogrsf_frmts
21
// -Iogr/ogrsf_frmts/mem -Iogr/ogrsf_frmts/ods -L. -lgdal
22
23
/************************************************************************/
24
/*                              Identify()                              */
25
/************************************************************************/
26
27
static int OGRODSDriverIdentify(GDALOpenInfo *poOpenInfo)
28
118k
{
29
118k
    if (poOpenInfo->fpL == nullptr &&
30
45.4k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "ODS:"))
31
6
    {
32
6
        return TRUE;
33
6
    }
34
35
118k
    if (EQUAL(CPLGetFilename(poOpenInfo->pszFilename), "content.xml"))
36
10
    {
37
10
        return poOpenInfo->nHeaderBytes != 0 &&
38
10
               strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
39
10
                      "<office:document-content") != nullptr;
40
10
    }
41
42
118k
    const char *pszExt = poOpenInfo->osExtension.c_str();
43
118k
    if (!EQUAL(pszExt, "ODS") && !EQUAL(pszExt, "ODS}"))
44
96.8k
        return FALSE;
45
46
21.7k
    if (STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") ||
47
21.7k
        STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/"))
48
21.2k
        return TRUE;
49
50
493
    return poOpenInfo->nHeaderBytes > 4 &&
51
322
           memcmp(poOpenInfo->pabyHeader, "PK\x03\x04", 4) == 0;
52
21.7k
}
53
54
/************************************************************************/
55
/*                                Open()                                */
56
/************************************************************************/
57
58
static GDALDataset *OGRODSDriverOpen(GDALOpenInfo *poOpenInfo)
59
60
10.7k
{
61
10.7k
    if (!OGRODSDriverIdentify(poOpenInfo))
62
0
        return nullptr;
63
64
10.7k
    const char *pszFilename = poOpenInfo->pszFilename;
65
10.7k
    const bool bIsODSPrefixed =
66
10.7k
        poOpenInfo->fpL == nullptr && STARTS_WITH_CI(pszFilename, "ODS:");
67
10.7k
    const bool bIsVsiZipOrTarPrefixed = STARTS_WITH(pszFilename, "/vsizip/") ||
68
10.7k
                                        STARTS_WITH(pszFilename, "/vsitar/");
69
10.7k
    if (bIsVsiZipOrTarPrefixed)
70
10.6k
    {
71
10.6k
        if (poOpenInfo->eAccess != GA_ReadOnly)
72
0
            return nullptr;
73
10.6k
    }
74
75
10.7k
    bool bIsZIP = false;
76
10.7k
    if (bIsODSPrefixed)
77
3
    {
78
3
        pszFilename += strlen("ODS:");
79
3
        if (!bIsVsiZipOrTarPrefixed)
80
3
        {
81
3
            VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
82
3
            if (fp == nullptr)
83
3
                return nullptr;
84
0
            GByte abyHeader[4] = {0};
85
0
            VSIFReadL(abyHeader, 1, 4, fp);
86
0
            VSIFCloseL(fp);
87
0
            bIsZIP = memcmp(abyHeader, "PK\x03\x04", 4) == 0;
88
0
        }
89
3
    }
90
10.7k
    else
91
10.7k
    {
92
10.7k
        bIsZIP = true;
93
10.7k
    }
94
95
10.7k
    std::string osPrefixedFilename;
96
10.7k
    if (bIsZIP)
97
10.7k
    {
98
10.7k
        if (!bIsVsiZipOrTarPrefixed)
99
154
        {
100
154
            osPrefixedFilename = "/vsizip/{";
101
154
            osPrefixedFilename += pszFilename;
102
154
            osPrefixedFilename += "}";
103
154
        }
104
10.6k
        else
105
10.6k
        {
106
10.6k
            osPrefixedFilename = pszFilename;
107
10.6k
        }
108
10.7k
    }
109
110
10.7k
    CPLString osContentFilename(pszFilename);
111
10.7k
    if (bIsZIP)
112
10.7k
    {
113
10.7k
        osContentFilename.Printf("%s/content.xml", osPrefixedFilename.c_str());
114
10.7k
    }
115
0
    else if (poOpenInfo->eAccess ==
116
0
             GA_Update) /* We cannot update the xml file, only the .ods */
117
0
    {
118
0
        return nullptr;
119
0
    }
120
121
10.7k
    VSILFILE *fpContent = VSIFOpenL(osContentFilename, "rb");
122
10.7k
    if (fpContent == nullptr)
123
1.30k
        return nullptr;
124
125
9.46k
    char szBuffer[1024];
126
9.46k
    int nRead = (int)VSIFReadL(szBuffer, 1, sizeof(szBuffer) - 1, fpContent);
127
9.46k
    szBuffer[nRead] = 0;
128
129
9.46k
    if (strstr(szBuffer, "<office:document-content") == nullptr)
130
103
    {
131
103
        VSIFCloseL(fpContent);
132
103
        return nullptr;
133
103
    }
134
135
    /* We could also check that there's a <office:spreadsheet>, but it might be
136
     * further */
137
    /* in the XML due to styles, etc... */
138
139
9.36k
    VSILFILE *fpSettings = nullptr;
140
9.36k
    if (bIsZIP)
141
9.36k
    {
142
9.36k
        CPLString osTmpFilename(
143
9.36k
            CPLSPrintf("%s/settings.xml", osPrefixedFilename.c_str()));
144
9.36k
        fpSettings = VSIFOpenL(osTmpFilename, "rb");
145
9.36k
    }
146
147
9.36k
    OGRODSDataSource *poDS = new OGRODSDataSource(poOpenInfo->papszOpenOptions);
148
149
9.36k
    if (!poDS->Open(pszFilename, fpContent, fpSettings,
150
9.36k
                    poOpenInfo->eAccess == GA_Update))
151
0
    {
152
0
        delete poDS;
153
0
        poDS = nullptr;
154
0
    }
155
9.36k
    else
156
9.36k
    {
157
9.36k
        poDS->SetDescription(poOpenInfo->pszFilename);
158
9.36k
    }
159
160
9.36k
    return poDS;
161
9.46k
}
162
163
/************************************************************************/
164
/*                         OGRODSDriverCreate()                         */
165
/************************************************************************/
166
167
static GDALDataset *OGRODSDriverCreate(const char *pszName, int /* nXSize */,
168
                                       int /* nYSize */, int /* nBands */,
169
                                       GDALDataType /* eDT */,
170
                                       char **papszOptions)
171
172
85
{
173
85
    if (!EQUAL(CPLGetExtensionSafe(pszName).c_str(), "ODS"))
174
0
    {
175
0
        CPLError(CE_Failure, CPLE_AppDefined, "File extension should be ODS");
176
0
        return nullptr;
177
0
    }
178
179
    /* -------------------------------------------------------------------- */
180
    /*      First, ensure there isn't any such file yet.                    */
181
    /* -------------------------------------------------------------------- */
182
85
    VSIStatBufL sStatBuf;
183
184
85
    if (VSIStatL(pszName, &sStatBuf) == 0)
185
0
    {
186
0
        CPLError(CE_Failure, CPLE_AppDefined,
187
0
                 "It seems a file system object called '%s' already exists.",
188
0
                 pszName);
189
190
0
        return nullptr;
191
0
    }
192
193
    /* -------------------------------------------------------------------- */
194
    /*      Try to create datasource.                                       */
195
    /* -------------------------------------------------------------------- */
196
85
    OGRODSDataSource *poDS = new OGRODSDataSource(nullptr);
197
198
85
    if (!poDS->Create(pszName, papszOptions))
199
0
    {
200
0
        delete poDS;
201
0
        return nullptr;
202
0
    }
203
204
85
    return poDS;
205
85
}
206
207
/************************************************************************/
208
/*                           RegisterOGRODS()                           */
209
/************************************************************************/
210
211
void RegisterOGRODS()
212
213
24
{
214
24
    if (GDALGetDriverByName("ODS") != nullptr)
215
0
        return;
216
217
24
    GDALDriver *poDriver = new GDALDriver();
218
219
24
    poDriver->SetDescription("ODS");
220
24
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
221
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
222
24
    poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
223
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Open Document/ LibreOffice / "
224
24
                                                 "OpenOffice Spreadsheet");
225
24
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ods");
226
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/ods.html");
227
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
228
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
229
24
                              "Integer Integer64 Real String Date DateTime "
230
24
                              "Time Binary");
231
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean");
232
24
    poDriver->SetMetadataItem(GDAL_DCAP_NONSPATIAL, "YES");
233
24
    poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES");
234
24
    poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
235
24
    poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
236
24
    poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
237
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
238
24
    poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
239
24
    poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
240
24
    poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS, "Name Type");
241
24
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
242
243
24
    poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
244
24
    poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS, "Features");
245
246
24
    poDriver->SetMetadataItem(
247
24
        GDAL_DMD_OPENOPTIONLIST,
248
24
        "<OpenOptionList>"
249
24
        "  <Option name='FIELD_TYPES' type='string-select' "
250
24
        "description='If set to STRING, all fields will be of type String. "
251
24
        "Otherwise the driver autodetects the field type from field content.' "
252
24
        "default='AUTO'>"
253
24
        "    <Value>AUTO</Value>"
254
24
        "    <Value>STRING</Value>"
255
24
        "  </Option>"
256
24
        "  <Option name='HEADERS' type='string-select' "
257
24
        "description='Defines if the first line should be considered as "
258
24
        "containing the name of the fields.' "
259
24
        "default='AUTO'>"
260
24
        "    <Value>AUTO</Value>"
261
24
        "    <Value>FORCE</Value>"
262
24
        "    <Value>DISABLE</Value>"
263
24
        "  </Option>"
264
24
        "</OpenOptionList>");
265
266
24
    poDriver->pfnIdentify = OGRODSDriverIdentify;
267
24
    poDriver->pfnOpen = OGRODSDriverOpen;
268
24
    poDriver->pfnCreate = OGRODSDriverCreate;
269
270
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
271
24
}