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  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #include "ogr_xlsx.h"
30 #include "cpl_conv.h"
31 
32 CPL_CVSID("$Id: ogrxlsxdriver.cpp 1761acd90777d5bcc49eddbc13c193098f0ed40b 2020-10-01 12:12:00 +0200 Even Rouault $")
33 
34 extern "C" void RegisterOGRXLSX();
35 
36 using namespace OGRXLSX;
37 
38 // g++ -DHAVE_EXPAT -g -Wall -fPIC ogr/ogrsf_frmts/xlsx/*.cpp -shared -o ogr_XLSX.so -Iport -Igcore -Iogr -Iogr/ogrsf_frmts -Iogr/ogrsf_frmts/mem -Iogr/ogrsf_frmts/xlsx -L. -lgdal
39 
40 static const char XLSX_MIMETYPE[] =
41     "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
42 
43 /************************************************************************/
44 /*                              Identify()                              */
45 /************************************************************************/
46 
OGRXLSXDriverIdentify(GDALOpenInfo * poOpenInfo)47 static int OGRXLSXDriverIdentify( GDALOpenInfo* poOpenInfo )
48 {
49     const char* pszExt = CPLGetExtension(poOpenInfo->pszFilename);
50     if (!EQUAL(pszExt, "XLSX") && !EQUAL(pszExt, "XLSM") &&
51         !EQUAL(pszExt, "XLSX}") && !EQUAL(pszExt, "XLSM}"))
52         return FALSE;
53 
54     if( STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") ||
55         STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") )
56         return poOpenInfo->eAccess == GA_ReadOnly;
57 
58     return poOpenInfo->nHeaderBytes > 2 &&
59            memcmp(poOpenInfo->pabyHeader, "PK", 2) == 0;
60 }
61 
62 /************************************************************************/
63 /*                                Open()                                */
64 /************************************************************************/
65 
OGRXLSXDriverOpen(GDALOpenInfo * poOpenInfo)66 static GDALDataset* OGRXLSXDriverOpen( GDALOpenInfo* poOpenInfo )
67 
68 {
69     if (!OGRXLSXDriverIdentify(poOpenInfo) )
70         return nullptr;
71 
72     CPLString osPrefixedFilename("/vsizip/");
73     osPrefixedFilename += poOpenInfo->pszFilename;
74     if( STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") ||
75         STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") )
76     {
77         osPrefixedFilename = poOpenInfo->pszFilename;
78     }
79 
80     CPLString osTmpFilename;
81     osTmpFilename = CPLSPrintf("%s/[Content_Types].xml", osPrefixedFilename.c_str());
82     VSILFILE* fpContent = VSIFOpenL(osTmpFilename, "rb");
83     if (fpContent == nullptr)
84         return nullptr;
85 
86     char szBuffer[2048];
87     int nRead = (int)VSIFReadL(szBuffer, 1, sizeof(szBuffer) - 1, fpContent);
88     szBuffer[nRead] = 0;
89 
90     VSIFCloseL(fpContent);
91 
92     if (strstr(szBuffer, XLSX_MIMETYPE) == nullptr)
93         return nullptr;
94 
95     osTmpFilename = CPLSPrintf("%s/xl/workbook.xml", osPrefixedFilename.c_str());
96     VSILFILE* fpWorkbook = VSIFOpenL(osTmpFilename, "rb");
97     if (fpWorkbook == nullptr)
98         return nullptr;
99 
100     osTmpFilename = CPLSPrintf("%s/xl/_rels/workbook.xml.rels", osPrefixedFilename.c_str());
101     VSILFILE* fpWorkbookRels = VSIFOpenL(osTmpFilename, "rb");
102     if (fpWorkbookRels == nullptr)
103     {
104         VSIFCloseL(fpWorkbook);
105         return nullptr;
106     }
107 
108     osTmpFilename = CPLSPrintf("%s/xl/sharedStrings.xml", osPrefixedFilename.c_str());
109     VSILFILE* fpSharedStrings = VSIFOpenL(osTmpFilename, "rb");
110     osTmpFilename = CPLSPrintf("%s/xl/styles.xml", osPrefixedFilename.c_str());
111     VSILFILE* fpStyles = VSIFOpenL(osTmpFilename, "rb");
112 
113     OGRXLSXDataSource   *poDS = new OGRXLSXDataSource();
114 
115     if( !poDS->Open( poOpenInfo->pszFilename, osPrefixedFilename,
116                      fpWorkbook, fpWorkbookRels, fpSharedStrings, fpStyles,
117                      poOpenInfo->eAccess == GA_Update ) )
118     {
119         delete poDS;
120         poDS = nullptr;
121     }
122 
123     return poDS;
124 }
125 
126 /************************************************************************/
127 /*                       OGRXLSXDriverCreate()                          */
128 /************************************************************************/
129 
130 static
OGRXLSXDriverCreate(const char * pszName,int,int,int,GDALDataType,char ** papszOptions)131 GDALDataset *OGRXLSXDriverCreate( const char *pszName,
132                                  int /* nXSize */,
133                                  int /* nYSize */,
134                                  int /* nBands */,
135                                  GDALDataType /* eDT */,
136                                  char **papszOptions )
137 
138 {
139     if (!EQUAL(CPLGetExtension(pszName), "XLSX"))
140     {
141         CPLError( CE_Failure, CPLE_AppDefined, "File extension should be XLSX" );
142         return nullptr;
143     }
144 
145 /* -------------------------------------------------------------------- */
146 /*      First, ensure there isn't any such file yet.                    */
147 /* -------------------------------------------------------------------- */
148     VSIStatBufL sStatBuf;
149 
150     if( VSIStatL( pszName, &sStatBuf ) == 0 )
151     {
152         CPLError( CE_Failure, CPLE_AppDefined,
153                   "It seems a file system object called '%s' already exists.",
154                   pszName );
155 
156         return nullptr;
157     }
158 
159 /* -------------------------------------------------------------------- */
160 /*      Try to create datasource.                                       */
161 /* -------------------------------------------------------------------- */
162     OGRXLSXDataSource *poDS = new OGRXLSXDataSource();
163 
164     if( !poDS->Create( pszName, papszOptions ) )
165     {
166         delete poDS;
167         return nullptr;
168     }
169     else
170         return poDS;
171 }
172 
173 /************************************************************************/
174 /*                           RegisterOGRXLSX()                           */
175 /************************************************************************/
176 
RegisterOGRXLSX()177 void RegisterOGRXLSX()
178 
179 {
180     if( GDALGetDriverByName( "XLSX" ) != nullptr )
181         return;
182 
183     GDALDriver *poDriver = new GDALDriver();
184 
185     poDriver->SetDescription( "XLSX" );
186     poDriver->SetMetadataItem( GDAL_DCAP_VECTOR, "YES" );
187     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
188                                "MS Office Open XML spreadsheet" );
189     poDriver->SetMetadataItem( GDAL_DMD_EXTENSIONS, "xlsx xlsm" );
190     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/vector/xlsx.html" );
191     poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
192     poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATATYPES,
193                                "Integer Integer64 Real String Date DateTime "
194                                "Time" );
195     poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean" );
196     poDriver->SetMetadataItem( GDAL_DCAP_NONSPATIAL, "YES" );
197 
198     poDriver->pfnIdentify = OGRXLSXDriverIdentify;
199     poDriver->pfnOpen = OGRXLSXDriverOpen;
200     poDriver->pfnCreate = OGRXLSXDriverCreate;
201 
202     GetGDALDriverManager()->RegisterDriver( poDriver );
203 }
204