Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: XLSX Translator
4 : * Purpose: Implements OGRXLSXDriver.
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 "ogr_xlsx.h"
14 : #include "cpl_conv.h"
15 :
16 : extern "C" void RegisterOGRXLSX();
17 :
18 : using namespace OGRXLSX;
19 :
20 : // g++ -DHAVE_EXPAT -g -Wall -fPIC ogr/ogrsf_frmts/xlsx/*.cpp -shared -o
21 : // ogr_XLSX.so -Iport -Igcore -Iogr -Iogr/ogrsf_frmts -Iogr/ogrsf_frmts/mem
22 : // -Iogr/ogrsf_frmts/xlsx -L. -lgdal
23 :
24 : static const char XLSX_MIMETYPE[] =
25 : "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
26 :
27 : /************************************************************************/
28 : /* Identify() */
29 : /************************************************************************/
30 :
31 61368 : static int OGRXLSXDriverIdentify(GDALOpenInfo *poOpenInfo)
32 : {
33 61368 : if (poOpenInfo->fpL == nullptr &&
34 57555 : STARTS_WITH_CI(poOpenInfo->pszFilename, "XLSX:"))
35 : {
36 2 : return TRUE;
37 : }
38 :
39 61366 : if (STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") ||
40 61332 : STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/"))
41 : {
42 35 : const char *pszExt = poOpenInfo->osExtension.c_str();
43 35 : return EQUAL(pszExt, "XLSX") || EQUAL(pszExt, "XLSM") ||
44 70 : EQUAL(pszExt, "XLSX}") || EQUAL(pszExt, "XLSM}");
45 : }
46 :
47 61331 : if (poOpenInfo->nHeaderBytes > 30 &&
48 3547 : memcmp(poOpenInfo->pabyHeader, "PK\x03\x04", 4) == 0)
49 : {
50 : // Fetch the first filename in the zip
51 114 : const int nFilenameLength =
52 114 : CPL_LSBUINT16PTR(poOpenInfo->pabyHeader + 26);
53 114 : if (30 + nFilenameLength > poOpenInfo->nHeaderBytes)
54 66 : return FALSE;
55 : const std::string osFilename(
56 114 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader) + 30,
57 114 : nFilenameLength);
58 114 : if (STARTS_WITH(osFilename.c_str(), "xl/") ||
59 106 : STARTS_WITH(osFilename.c_str(), "_rels/") ||
60 302 : STARTS_WITH(osFilename.c_str(), "docProps/") ||
61 82 : osFilename == "[Content_Types].xml")
62 : {
63 66 : return TRUE;
64 : }
65 48 : const char *pszExt = poOpenInfo->osExtension.c_str();
66 48 : if (EQUAL(pszExt, "XLSX") || EQUAL(pszExt, "XLSM"))
67 : {
68 0 : CPLDebug(
69 : "XLSX",
70 : "Identify() failed to recognize first filename in zip (%s), "
71 : "but fallback to extension matching",
72 : osFilename.c_str());
73 0 : return TRUE;
74 : }
75 : }
76 61265 : return FALSE;
77 : }
78 :
79 : /************************************************************************/
80 : /* Open() */
81 : /************************************************************************/
82 :
83 34 : static GDALDataset *OGRXLSXDriverOpen(GDALOpenInfo *poOpenInfo)
84 :
85 : {
86 34 : if (!OGRXLSXDriverIdentify(poOpenInfo))
87 0 : return nullptr;
88 :
89 34 : const char *pszFilename = poOpenInfo->pszFilename;
90 34 : if (poOpenInfo->fpL == nullptr && STARTS_WITH_CI(pszFilename, "XLSX:"))
91 : {
92 1 : pszFilename += strlen("XLSX:");
93 : }
94 68 : const bool bIsVsiZipOrTarPrefixed = STARTS_WITH(pszFilename, "/vsizip/") ||
95 34 : STARTS_WITH(pszFilename, "/vsitar/");
96 34 : if (bIsVsiZipOrTarPrefixed)
97 : {
98 0 : if (poOpenInfo->eAccess != GA_ReadOnly)
99 0 : return nullptr;
100 : }
101 :
102 68 : std::string osPrefixedFilename;
103 34 : if (!bIsVsiZipOrTarPrefixed)
104 : {
105 34 : osPrefixedFilename = "/vsizip/{";
106 34 : osPrefixedFilename += pszFilename;
107 34 : osPrefixedFilename += "}";
108 : }
109 : else
110 : {
111 0 : osPrefixedFilename = pszFilename;
112 : }
113 :
114 68 : CPLString osTmpFilename;
115 : osTmpFilename =
116 34 : CPLSPrintf("%s/[Content_Types].xml", osPrefixedFilename.c_str());
117 34 : VSILFILE *fpContent = VSIFOpenL(osTmpFilename, "rb");
118 34 : if (fpContent == nullptr)
119 0 : return nullptr;
120 :
121 68 : std::string osBuffer;
122 : // Content_Types.xml produced by PlanMaker 2026 is 2160 bytes large
123 : // cf https://github.com/OSGeo/gdal/issues/15087
124 34 : osBuffer.resize(10 * 1024);
125 : const size_t nRead =
126 34 : VSIFReadL(osBuffer.data(), 1, osBuffer.size(), fpContent);
127 34 : osBuffer.resize(nRead);
128 :
129 34 : VSIFCloseL(fpContent);
130 :
131 34 : if (osBuffer.find(XLSX_MIMETYPE) == std::string::npos)
132 0 : return nullptr;
133 :
134 : osTmpFilename =
135 34 : CPLSPrintf("%s/xl/workbook.xml", osPrefixedFilename.c_str());
136 34 : VSILFILE *fpWorkbook = VSIFOpenL(osTmpFilename, "rb");
137 34 : if (fpWorkbook == nullptr)
138 0 : return nullptr;
139 :
140 : osTmpFilename =
141 34 : CPLSPrintf("%s/xl/_rels/workbook.xml.rels", osPrefixedFilename.c_str());
142 34 : VSILFILE *fpWorkbookRels = VSIFOpenL(osTmpFilename, "rb");
143 34 : if (fpWorkbookRels == nullptr)
144 : {
145 0 : VSIFCloseL(fpWorkbook);
146 0 : return nullptr;
147 : }
148 :
149 : osTmpFilename =
150 34 : CPLSPrintf("%s/xl/sharedStrings.xml", osPrefixedFilename.c_str());
151 34 : VSILFILE *fpSharedStrings = VSIFOpenL(osTmpFilename, "rb");
152 34 : osTmpFilename = CPLSPrintf("%s/xl/styles.xml", osPrefixedFilename.c_str());
153 34 : VSILFILE *fpStyles = VSIFOpenL(osTmpFilename, "rb");
154 :
155 : OGRXLSXDataSource *poDS =
156 34 : new OGRXLSXDataSource(poOpenInfo->papszOpenOptions);
157 :
158 34 : if (!poDS->Open(pszFilename, osPrefixedFilename.c_str(), fpWorkbook,
159 : fpWorkbookRels, fpSharedStrings, fpStyles,
160 34 : poOpenInfo->eAccess == GA_Update))
161 : {
162 0 : delete poDS;
163 0 : poDS = nullptr;
164 : }
165 : else
166 : {
167 34 : poDS->SetDescription(poOpenInfo->pszFilename);
168 : }
169 :
170 34 : return poDS;
171 : }
172 :
173 : /************************************************************************/
174 : /* OGRXLSXDriverCreate() */
175 : /************************************************************************/
176 :
177 25 : static GDALDataset *OGRXLSXDriverCreate(const char *pszName, int /* nXSize */,
178 : int /* nYSize */, int /* nBands */,
179 : GDALDataType /* eDT */,
180 : CSLConstList papszOptions)
181 :
182 : {
183 25 : if (!EQUAL(CPLGetExtensionSafe(pszName).c_str(), "XLSX"))
184 : {
185 17 : CPLError(CE_Failure, CPLE_AppDefined, "File extension should be XLSX");
186 17 : return nullptr;
187 : }
188 :
189 : /* -------------------------------------------------------------------- */
190 : /* First, ensure there isn't any such file yet. */
191 : /* -------------------------------------------------------------------- */
192 : VSIStatBufL sStatBuf;
193 :
194 8 : if (VSIStatL(pszName, &sStatBuf) == 0)
195 : {
196 0 : CPLError(CE_Failure, CPLE_AppDefined,
197 : "It seems a file system object called '%s' already exists.",
198 : pszName);
199 :
200 0 : return nullptr;
201 : }
202 :
203 : /* -------------------------------------------------------------------- */
204 : /* Try to create datasource. */
205 : /* -------------------------------------------------------------------- */
206 8 : OGRXLSXDataSource *poDS = new OGRXLSXDataSource(nullptr);
207 :
208 8 : if (!poDS->Create(pszName, papszOptions))
209 : {
210 0 : delete poDS;
211 0 : return nullptr;
212 : }
213 : else
214 8 : return poDS;
215 : }
216 :
217 : /************************************************************************/
218 : /* RegisterOGRXLSX() */
219 : /************************************************************************/
220 :
221 2062 : void RegisterOGRXLSX()
222 :
223 : {
224 2062 : if (GDALGetDriverByName("XLSX") != nullptr)
225 263 : return;
226 :
227 1799 : GDALDriver *poDriver = new GDALDriver();
228 :
229 1799 : poDriver->SetDescription("XLSX");
230 1799 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
231 1799 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
232 1799 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
233 1799 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
234 1799 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
235 1799 : poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
236 1799 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS, "Name Type");
237 :
238 1799 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
239 1799 : "MS Office Open XML spreadsheet");
240 1799 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "xlsx xlsm");
241 1799 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/xlsx.html");
242 1799 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
243 1799 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
244 : "Integer Integer64 Real String Date DateTime "
245 1799 : "Time");
246 1799 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean");
247 1799 : poDriver->SetMetadataItem(GDAL_DCAP_NONSPATIAL, "YES");
248 1799 : poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES");
249 1799 : poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
250 1799 : poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
251 1799 : poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
252 1799 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
253 :
254 1799 : poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
255 1799 : poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS, "Features");
256 :
257 1799 : poDriver->SetMetadataItem(
258 : GDAL_DMD_OPENOPTIONLIST,
259 : "<OpenOptionList>"
260 : " <Option name='FIELD_TYPES' type='string-select' "
261 : "description='If set to STRING, all fields will be of type String. "
262 : "Otherwise the driver autodetects the field type from field content.' "
263 : "default='AUTO'>"
264 : " <Value>AUTO</Value>"
265 : " <Value>STRING</Value>"
266 : " </Option>"
267 : " <Option name='HEADERS' type='string-select' "
268 : "description='Defines if the first line should be considered as "
269 : "containing the name of the fields.' "
270 : "default='AUTO'>"
271 : " <Value>AUTO</Value>"
272 : " <Value>FORCE</Value>"
273 : " <Value>DISABLE</Value>"
274 : " </Option>"
275 1799 : "</OpenOptionList>");
276 :
277 1799 : poDriver->pfnIdentify = OGRXLSXDriverIdentify;
278 1799 : poDriver->pfnOpen = OGRXLSXDriverOpen;
279 1799 : poDriver->pfnCreate = OGRXLSXDriverCreate;
280 :
281 1799 : GetGDALDriverManager()->RegisterDriver(poDriver);
282 : }
|