Line data Source code
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 45288 : static int OGRODSDriverIdentify(GDALOpenInfo *poOpenInfo)
28 : {
29 45288 : if (poOpenInfo->fpL == nullptr &&
30 42681 : STARTS_WITH_CI(poOpenInfo->pszFilename, "ODS:"))
31 : {
32 4 : return TRUE;
33 : }
34 :
35 45284 : if (EQUAL(CPLGetFilename(poOpenInfo->pszFilename), "content.xml"))
36 : {
37 0 : return poOpenInfo->nHeaderBytes != 0 &&
38 0 : strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
39 0 : "<office:document-content") != nullptr;
40 : }
41 :
42 45284 : const char *pszExt = CPLGetExtension(poOpenInfo->pszFilename);
43 45284 : if (!EQUAL(pszExt, "ODS") && !EQUAL(pszExt, "ODS}"))
44 45068 : return FALSE;
45 :
46 216 : if (STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") ||
47 216 : STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/"))
48 0 : return TRUE;
49 :
50 356 : return poOpenInfo->nHeaderBytes > 4 &&
51 356 : memcmp(poOpenInfo->pabyHeader, "PK\x03\x04", 4) == 0;
52 : }
53 :
54 : /************************************************************************/
55 : /* Open() */
56 : /************************************************************************/
57 :
58 72 : static GDALDataset *OGRODSDriverOpen(GDALOpenInfo *poOpenInfo)
59 :
60 : {
61 72 : if (!OGRODSDriverIdentify(poOpenInfo))
62 0 : return nullptr;
63 :
64 72 : const char *pszFilename = poOpenInfo->pszFilename;
65 72 : const bool bIsODSPrefixed =
66 72 : poOpenInfo->fpL == nullptr && STARTS_WITH_CI(pszFilename, "ODS:");
67 144 : const bool bIsVsiZipOrTarPrefixed = STARTS_WITH(pszFilename, "/vsizip/") ||
68 72 : STARTS_WITH(pszFilename, "/vsitar/");
69 72 : if (bIsVsiZipOrTarPrefixed)
70 : {
71 0 : if (poOpenInfo->eAccess != GA_ReadOnly)
72 0 : return nullptr;
73 : }
74 :
75 72 : bool bIsZIP = false;
76 72 : if (bIsODSPrefixed)
77 : {
78 2 : pszFilename += strlen("ODS:");
79 2 : if (!bIsVsiZipOrTarPrefixed)
80 : {
81 2 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
82 2 : if (fp == nullptr)
83 0 : return nullptr;
84 2 : GByte abyHeader[4] = {0};
85 2 : VSIFReadL(abyHeader, 1, 4, fp);
86 2 : VSIFCloseL(fp);
87 2 : bIsZIP = memcmp(abyHeader, "PK\x03\x04", 4) == 0;
88 : }
89 : }
90 : else
91 : {
92 70 : bIsZIP = true;
93 : }
94 :
95 144 : std::string osPrefixedFilename;
96 72 : if (bIsZIP)
97 : {
98 71 : if (!bIsVsiZipOrTarPrefixed)
99 : {
100 71 : osPrefixedFilename = "/vsizip/{";
101 71 : osPrefixedFilename += pszFilename;
102 71 : osPrefixedFilename += "}";
103 : }
104 : else
105 : {
106 0 : osPrefixedFilename = pszFilename;
107 : }
108 : }
109 :
110 144 : CPLString osContentFilename(pszFilename);
111 72 : if (bIsZIP)
112 : {
113 71 : osContentFilename.Printf("%s/content.xml", osPrefixedFilename.c_str());
114 : }
115 1 : else if (poOpenInfo->eAccess ==
116 : GA_Update) /* We cannot update the xml file, only the .ods */
117 : {
118 0 : return nullptr;
119 : }
120 :
121 72 : VSILFILE *fpContent = VSIFOpenL(osContentFilename, "rb");
122 72 : if (fpContent == nullptr)
123 0 : return nullptr;
124 :
125 : char szBuffer[1024];
126 72 : int nRead = (int)VSIFReadL(szBuffer, 1, sizeof(szBuffer) - 1, fpContent);
127 72 : szBuffer[nRead] = 0;
128 :
129 72 : if (strstr(szBuffer, "<office:document-content") == nullptr)
130 : {
131 0 : VSIFCloseL(fpContent);
132 0 : return nullptr;
133 : }
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 72 : VSILFILE *fpSettings = nullptr;
140 72 : if (bIsZIP)
141 : {
142 : CPLString osTmpFilename(
143 71 : CPLSPrintf("%s/settings.xml", osPrefixedFilename.c_str()));
144 71 : fpSettings = VSIFOpenL(osTmpFilename, "rb");
145 : }
146 :
147 72 : OGRODSDataSource *poDS = new OGRODSDataSource(poOpenInfo->papszOpenOptions);
148 :
149 72 : if (!poDS->Open(pszFilename, fpContent, fpSettings,
150 72 : poOpenInfo->eAccess == GA_Update))
151 : {
152 0 : delete poDS;
153 0 : poDS = nullptr;
154 : }
155 : else
156 : {
157 72 : poDS->SetDescription(poOpenInfo->pszFilename);
158 : }
159 :
160 72 : return poDS;
161 : }
162 :
163 : /************************************************************************/
164 : /* OGRODSDriverCreate() */
165 : /************************************************************************/
166 :
167 38 : static GDALDataset *OGRODSDriverCreate(const char *pszName, int /* nXSize */,
168 : int /* nYSize */, int /* nBands */,
169 : GDALDataType /* eDT */,
170 : char **papszOptions)
171 :
172 : {
173 38 : if (!EQUAL(CPLGetExtension(pszName), "ODS"))
174 : {
175 0 : CPLError(CE_Failure, CPLE_AppDefined, "File extension should be ODS");
176 0 : return nullptr;
177 : }
178 :
179 : /* -------------------------------------------------------------------- */
180 : /* First, ensure there isn't any such file yet. */
181 : /* -------------------------------------------------------------------- */
182 : VSIStatBufL sStatBuf;
183 :
184 38 : if (VSIStatL(pszName, &sStatBuf) == 0)
185 : {
186 0 : CPLError(CE_Failure, CPLE_AppDefined,
187 : "It seems a file system object called '%s' already exists.",
188 : pszName);
189 :
190 0 : return nullptr;
191 : }
192 :
193 : /* -------------------------------------------------------------------- */
194 : /* Try to create datasource. */
195 : /* -------------------------------------------------------------------- */
196 38 : OGRODSDataSource *poDS = new OGRODSDataSource(nullptr);
197 :
198 38 : if (!poDS->Create(pszName, papszOptions))
199 : {
200 0 : delete poDS;
201 0 : return nullptr;
202 : }
203 :
204 38 : return poDS;
205 : }
206 :
207 : /************************************************************************/
208 : /* RegisterOGRODS() */
209 : /************************************************************************/
210 :
211 1595 : void RegisterOGRODS()
212 :
213 : {
214 1595 : if (GDALGetDriverByName("ODS") != nullptr)
215 302 : return;
216 :
217 1293 : GDALDriver *poDriver = new GDALDriver();
218 :
219 1293 : poDriver->SetDescription("ODS");
220 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
221 1293 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
222 1293 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
223 1293 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Open Document/ LibreOffice / "
224 1293 : "OpenOffice Spreadsheet");
225 1293 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ods");
226 1293 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/ods.html");
227 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
228 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
229 : "Integer Integer64 Real String Date DateTime "
230 1293 : "Time Binary");
231 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean");
232 1293 : poDriver->SetMetadataItem(GDAL_DCAP_NONSPATIAL, "YES");
233 1293 : poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES");
234 1293 : poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
235 1293 : poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
236 1293 : poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
237 1293 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
238 1293 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
239 1293 : poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
240 1293 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS, "Name Type");
241 1293 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
242 :
243 1293 : poDriver->SetMetadataItem(
244 : GDAL_DMD_OPENOPTIONLIST,
245 : "<OpenOptionList>"
246 : " <Option name='FIELD_TYPES' type='string-select' "
247 : "description='If set to STRING, all fields will be of type String. "
248 : "Otherwise the driver autodetects the field type from field content.' "
249 : "default='AUTO'>"
250 : " <Value>AUTO</Value>"
251 : " <Value>STRING</Value>"
252 : " </Option>"
253 : " <Option name='HEADERS' type='string-select' "
254 : "description='Defines if the first line should be considered as "
255 : "containing the name of the fields.' "
256 : "default='AUTO'>"
257 : " <Value>AUTO</Value>"
258 : " <Value>FORCE</Value>"
259 : " <Value>DISABLE</Value>"
260 : " </Option>"
261 1293 : "</OpenOptionList>");
262 :
263 1293 : poDriver->pfnIdentify = OGRODSDriverIdentify;
264 1293 : poDriver->pfnOpen = OGRODSDriverOpen;
265 1293 : poDriver->pfnCreate = OGRODSDriverCreate;
266 :
267 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
268 : }
|