/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 | } |