Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GeoRSS Translator
4 : * Purpose: Implements OGRGeoRSSDataSource class
5 : * Author: Even Rouault, even dot rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "ogr_georss.h"
15 :
16 : #include <cstdio>
17 : #include <cstring>
18 :
19 : #include "cpl_conv.h"
20 : #include "cpl_csv.h"
21 : #include "cpl_error.h"
22 : #include "cpl_string.h"
23 : #include "cpl_vsi.h"
24 : #ifdef HAVE_EXPAT
25 : #include "expat.h"
26 : #endif
27 : #include "ogr_core.h"
28 : #include "ogr_expat.h"
29 : #include "ogr_spatialref.h"
30 : #include "ogrsf_frmts.h"
31 :
32 : /************************************************************************/
33 : /* OGRGeoRSSDataSource() */
34 : /************************************************************************/
35 :
36 55 : OGRGeoRSSDataSource::OGRGeoRSSDataSource()
37 : : papoLayers(nullptr), nLayers(0), fpOutput(nullptr),
38 : #ifdef HAVE_EXPAT
39 : validity(GEORSS_VALIDITY_UNKNOWN),
40 : #endif
41 : eFormat(GEORSS_RSS), eGeomDialect(GEORSS_SIMPLE), bUseExtensions(false),
42 : bWriteHeaderAndFooter(true)
43 : #ifdef HAVE_EXPAT
44 : ,
45 55 : oCurrentParser(nullptr), nDataHandlerCounter(0)
46 : #endif
47 : {
48 55 : }
49 :
50 : /************************************************************************/
51 : /* ~OGRGeoRSSDataSource() */
52 : /************************************************************************/
53 :
54 110 : OGRGeoRSSDataSource::~OGRGeoRSSDataSource()
55 :
56 : {
57 55 : if (fpOutput != nullptr)
58 : {
59 39 : if (bWriteHeaderAndFooter)
60 : {
61 39 : if (eFormat == GEORSS_RSS)
62 : {
63 38 : VSIFPrintfL(fpOutput, " </channel>\n");
64 38 : VSIFPrintfL(fpOutput, "</rss>\n");
65 : }
66 : else
67 : {
68 1 : VSIFPrintfL(fpOutput, "</feed>\n");
69 : }
70 : }
71 39 : VSIFCloseL(fpOutput);
72 : }
73 :
74 123 : for (int i = 0; i < nLayers; i++)
75 68 : delete papoLayers[i];
76 55 : CPLFree(papoLayers);
77 110 : }
78 :
79 : /************************************************************************/
80 : /* TestCapability() */
81 : /************************************************************************/
82 :
83 48 : int OGRGeoRSSDataSource::TestCapability(const char *pszCap) const
84 :
85 : {
86 48 : if (EQUAL(pszCap, ODsCCreateLayer))
87 32 : return TRUE;
88 : // else if( EQUAL(pszCap,ODsCDeleteLayer) )
89 : // return FALSE;
90 16 : else if (EQUAL(pszCap, ODsCZGeometries))
91 0 : return TRUE;
92 :
93 16 : return FALSE;
94 : }
95 :
96 : /************************************************************************/
97 : /* GetLayer() */
98 : /************************************************************************/
99 :
100 13 : const OGRLayer *OGRGeoRSSDataSource::GetLayer(int iLayer) const
101 :
102 : {
103 13 : if (iLayer < 0 || iLayer >= nLayers)
104 0 : return nullptr;
105 :
106 13 : return papoLayers[iLayer];
107 : }
108 :
109 : /************************************************************************/
110 : /* ICreateLayer() */
111 : /************************************************************************/
112 :
113 : OGRLayer *
114 55 : OGRGeoRSSDataSource::ICreateLayer(const char *pszLayerName,
115 : const OGRGeomFieldDefn *poGeomFieldDefn,
116 : CSLConstList /*papszOptions*/)
117 : {
118 55 : if (fpOutput == nullptr)
119 0 : return nullptr;
120 :
121 : const auto poSRS =
122 55 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
123 55 : if (poSRS != nullptr && eGeomDialect != GEORSS_GML)
124 : {
125 1 : OGRSpatialReference oSRS;
126 1 : oSRS.SetWellKnownGeogCS("WGS84");
127 1 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
128 1 : const char *const apszOptions[] = {
129 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
130 1 : if (!poSRS->IsSame(&oSRS, apszOptions))
131 : {
132 1 : CPLError(CE_Failure, CPLE_NotSupported,
133 : "For a non GML dialect, only WGS84 SRS is supported");
134 1 : return nullptr;
135 : }
136 : }
137 :
138 54 : nLayers++;
139 54 : papoLayers = static_cast<OGRGeoRSSLayer **>(
140 54 : CPLRealloc(papoLayers, nLayers * sizeof(OGRGeoRSSLayer *)));
141 54 : OGRSpatialReference *poSRSClone = nullptr;
142 54 : if (poSRS)
143 : {
144 1 : poSRSClone = poSRS->Clone();
145 1 : poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
146 : }
147 54 : papoLayers[nLayers - 1] = new OGRGeoRSSLayer(GetDescription(), pszLayerName,
148 54 : this, poSRSClone, TRUE);
149 54 : if (poSRSClone)
150 1 : poSRSClone->Release();
151 :
152 54 : return papoLayers[nLayers - 1];
153 : }
154 :
155 : #ifdef HAVE_EXPAT
156 : /************************************************************************/
157 : /* startElementValidateCbk() */
158 : /************************************************************************/
159 :
160 335 : void OGRGeoRSSDataSource::startElementValidateCbk(const char *pszNameIn,
161 : const char **ppszAttr)
162 : {
163 335 : if (validity == GEORSS_VALIDITY_UNKNOWN)
164 : {
165 15 : if (strcmp(pszNameIn, "rss") == 0)
166 : {
167 11 : validity = GEORSS_VALIDITY_VALID;
168 11 : eFormat = GEORSS_RSS;
169 : }
170 4 : else if (strcmp(pszNameIn, "feed") == 0 ||
171 1 : strcmp(pszNameIn, "atom:feed") == 0)
172 : {
173 4 : validity = GEORSS_VALIDITY_VALID;
174 4 : eFormat = GEORSS_ATOM;
175 : }
176 0 : else if (strcmp(pszNameIn, "rdf:RDF") == 0)
177 : {
178 0 : const char **ppszIter = ppszAttr;
179 0 : while (*ppszIter)
180 : {
181 0 : if (strcmp(*ppszIter, "xmlns:georss") == 0)
182 : {
183 0 : validity = GEORSS_VALIDITY_VALID;
184 0 : eFormat = GEORSS_RSS_RDF;
185 : }
186 0 : ppszIter += 2;
187 : }
188 : }
189 : else
190 : {
191 0 : validity = GEORSS_VALIDITY_INVALID;
192 : }
193 : }
194 335 : }
195 :
196 : /************************************************************************/
197 : /* dataHandlerValidateCbk() */
198 : /************************************************************************/
199 :
200 986 : void OGRGeoRSSDataSource::dataHandlerValidateCbk(const char * /* data */,
201 : int /* nLen */)
202 : {
203 986 : nDataHandlerCounter++;
204 986 : if (nDataHandlerCounter >= PARSER_BUF_SIZE)
205 : {
206 0 : CPLError(CE_Failure, CPLE_AppDefined,
207 : "File probably corrupted (million laugh pattern)");
208 0 : XML_StopParser(oCurrentParser, XML_FALSE);
209 : }
210 986 : }
211 :
212 335 : static void XMLCALL startElementValidateCbk(void *pUserData,
213 : const char *pszName,
214 : const char **ppszAttr)
215 : {
216 335 : OGRGeoRSSDataSource *poDS = static_cast<OGRGeoRSSDataSource *>(pUserData);
217 335 : poDS->startElementValidateCbk(pszName, ppszAttr);
218 335 : }
219 :
220 986 : static void XMLCALL dataHandlerValidateCbk(void *pUserData, const char *data,
221 : int nLen)
222 : {
223 986 : OGRGeoRSSDataSource *poDS = static_cast<OGRGeoRSSDataSource *>(pUserData);
224 986 : poDS->dataHandlerValidateCbk(data, nLen);
225 986 : }
226 : #endif
227 :
228 : /************************************************************************/
229 : /* Open() */
230 : /************************************************************************/
231 :
232 15 : int OGRGeoRSSDataSource::Open(const char *pszFilename, int bUpdateIn)
233 :
234 : {
235 15 : if (bUpdateIn)
236 : {
237 0 : CPLError(CE_Failure, CPLE_NotSupported,
238 : "OGR/GeoRSS driver does not support opening a file "
239 : "in update mode");
240 0 : return FALSE;
241 : }
242 : #ifdef HAVE_EXPAT
243 :
244 : // Try to open the file.
245 15 : VSILFILE *fp = VSIFOpenL(pszFilename, "r");
246 15 : if (fp == nullptr)
247 0 : return FALSE;
248 :
249 15 : validity = GEORSS_VALIDITY_UNKNOWN;
250 :
251 15 : XML_Parser oParser = OGRCreateExpatXMLParser();
252 15 : XML_SetUserData(oParser, this);
253 15 : XML_SetElementHandler(oParser, ::startElementValidateCbk, nullptr);
254 15 : XML_SetCharacterDataHandler(oParser, ::dataHandlerValidateCbk);
255 15 : oCurrentParser = oParser;
256 :
257 15 : std::vector<char> aBuf(PARSER_BUF_SIZE);
258 15 : int nDone = 0;
259 15 : unsigned int nLen = 0;
260 15 : int nCount = 0;
261 :
262 : // Begin to parse the file and look for the <rss> or <feed> element.
263 : // It *MUST* be the first element of an XML file.
264 : // Once we have read the first element, we know if we can
265 : // handle the file or not with that driver.
266 0 : do
267 : {
268 15 : nDataHandlerCounter = 0;
269 15 : nLen = static_cast<unsigned int>(
270 15 : VSIFReadL(aBuf.data(), 1, aBuf.size(), fp));
271 15 : nDone = nLen < aBuf.size();
272 15 : if (XML_Parse(oParser, aBuf.data(), nLen, nDone) == XML_STATUS_ERROR)
273 : {
274 1 : if (nLen <= PARSER_BUF_SIZE - 1)
275 1 : aBuf[nLen] = 0;
276 : else
277 0 : aBuf[PARSER_BUF_SIZE - 1] = 0;
278 :
279 2 : if (strstr(aBuf.data(), "<?xml") &&
280 1 : (strstr(aBuf.data(), "<rss") || strstr(aBuf.data(), "<feed") ||
281 0 : strstr(aBuf.data(), "<atom:feed")))
282 : {
283 1 : CPLError(CE_Failure, CPLE_AppDefined,
284 : "XML parsing of GeoRSS file failed: "
285 : "%s at line %d, column %d",
286 : XML_ErrorString(XML_GetErrorCode(oParser)),
287 1 : static_cast<int>(XML_GetCurrentLineNumber(oParser)),
288 1 : static_cast<int>(XML_GetCurrentColumnNumber(oParser)));
289 : }
290 1 : validity = GEORSS_VALIDITY_INVALID;
291 1 : break;
292 : }
293 14 : if (validity == GEORSS_VALIDITY_INVALID)
294 : {
295 0 : break;
296 : }
297 14 : else if (validity == GEORSS_VALIDITY_VALID)
298 : {
299 14 : break;
300 : }
301 : else
302 : {
303 : // After reading 50 * PARSER_BUF_SIZE bytes, and not finding whether the file
304 : // is GeoRSS or not, we give up and fail silently.
305 0 : nCount++;
306 0 : if (nCount == 50)
307 0 : break;
308 : }
309 0 : } while (!nDone && nLen > 0);
310 :
311 15 : XML_ParserFree(oParser);
312 :
313 15 : VSIFCloseL(fp);
314 :
315 15 : if (validity == GEORSS_VALIDITY_VALID)
316 : {
317 14 : CPLDebug("GeoRSS", "%s seems to be a GeoRSS file.", pszFilename);
318 :
319 14 : nLayers = 1;
320 14 : papoLayers = static_cast<OGRGeoRSSLayer **>(
321 14 : CPLRealloc(papoLayers, nLayers * sizeof(OGRGeoRSSLayer *)));
322 14 : papoLayers[0] =
323 14 : new OGRGeoRSSLayer(pszFilename, "georss", this, nullptr, FALSE);
324 : }
325 :
326 15 : return validity == GEORSS_VALIDITY_VALID;
327 : #else
328 : VSILFILE *fp = VSIFOpenL(pszFilename, "r");
329 : if (fp)
330 : {
331 : char aBuf[256];
332 : const unsigned int nLen =
333 : static_cast<unsigned int>(VSIFReadL(aBuf, 1, 255, fp));
334 : aBuf[nLen] = '\0';
335 : if (strstr(aBuf, "<?xml") &&
336 : (strstr(aBuf, "<rss") || strstr(aBuf, "<atom:feed") ||
337 : strstr(aBuf, "<feed")))
338 : {
339 : CPLError(CE_Failure, CPLE_NotSupported,
340 : "OGR/GeoRSS driver has not been built with read support. "
341 : "Expat library required");
342 : }
343 : VSIFCloseL(fp);
344 : }
345 : return FALSE;
346 : #endif
347 : }
348 :
349 : /************************************************************************/
350 : /* Create() */
351 : /************************************************************************/
352 :
353 40 : int OGRGeoRSSDataSource::Create(const char *pszFilename,
354 : CSLConstList papszOptions)
355 : {
356 40 : if (fpOutput != nullptr)
357 : {
358 0 : CPLAssert(false);
359 : return FALSE;
360 : }
361 :
362 40 : if (strcmp(pszFilename, "/dev/stdout") == 0)
363 0 : pszFilename = "/vsistdout/";
364 :
365 : /* -------------------------------------------------------------------- */
366 : /* Do not override exiting file. */
367 : /* -------------------------------------------------------------------- */
368 : VSIStatBufL sStatBuf;
369 :
370 40 : if (VSIStatL(pszFilename, &sStatBuf) == 0)
371 : {
372 0 : CPLError(CE_Failure, CPLE_NotSupported,
373 : "You have to delete %s before being able to create it "
374 : "with the GeoRSS driver",
375 : pszFilename);
376 0 : return FALSE;
377 : }
378 :
379 : /* -------------------------------------------------------------------- */
380 : /* Create the output file. */
381 : /* -------------------------------------------------------------------- */
382 40 : fpOutput = VSIFOpenL(pszFilename, "w");
383 40 : if (fpOutput == nullptr)
384 : {
385 1 : CPLError(CE_Failure, CPLE_OpenFailed,
386 : "Failed to create GeoRSS file %s.", pszFilename);
387 1 : return FALSE;
388 : }
389 :
390 39 : const char *pszFormat = CSLFetchNameValue(papszOptions, "FORMAT");
391 39 : if (pszFormat)
392 : {
393 1 : if (EQUAL(pszFormat, "RSS"))
394 0 : eFormat = GEORSS_RSS;
395 1 : else if (EQUAL(pszFormat, "ATOM"))
396 1 : eFormat = GEORSS_ATOM;
397 : else
398 0 : CPLError(CE_Warning, CPLE_NotSupported,
399 : "Unsupported value for %s : %s", "FORMAT", pszFormat);
400 : }
401 :
402 : const char *pszGeomDialect =
403 39 : CSLFetchNameValue(papszOptions, "GEOM_DIALECT");
404 39 : if (pszGeomDialect)
405 : {
406 3 : if (EQUAL(pszGeomDialect, "GML"))
407 2 : eGeomDialect = GEORSS_GML;
408 1 : else if (EQUAL(pszGeomDialect, "SIMPLE"))
409 0 : eGeomDialect = GEORSS_SIMPLE;
410 1 : else if (EQUAL(pszGeomDialect, "W3C_GEO"))
411 1 : eGeomDialect = GEORSS_W3C_GEO;
412 : else
413 0 : CPLError(CE_Warning, CPLE_NotSupported,
414 : "Unsupported value for %s : %s", "GEOM_DIALECT",
415 : pszGeomDialect);
416 : }
417 :
418 : const char *pszWriteHeaderAndFooter =
419 39 : CSLFetchNameValue(papszOptions, "WRITE_HEADER_AND_FOOTER");
420 39 : if (pszWriteHeaderAndFooter && !CPLTestBool(pszWriteHeaderAndFooter))
421 : {
422 0 : bWriteHeaderAndFooter = false;
423 0 : return TRUE;
424 : }
425 :
426 39 : const char *pszTitle = nullptr;
427 39 : const char *pszDescription = nullptr;
428 39 : const char *pszLink = nullptr;
429 39 : const char *pszUpdated = nullptr;
430 39 : const char *pszAuthorName = nullptr;
431 39 : const char *pszId = nullptr;
432 :
433 39 : const char *pszHeader = CSLFetchNameValue(papszOptions, "HEADER");
434 :
435 39 : if (eFormat == GEORSS_RSS && pszHeader == nullptr)
436 : {
437 38 : pszTitle = CSLFetchNameValue(papszOptions, "TITLE");
438 38 : if (pszTitle == nullptr)
439 38 : pszTitle = "title";
440 :
441 38 : pszDescription = CSLFetchNameValue(papszOptions, "DESCRIPTION");
442 38 : if (pszDescription == nullptr)
443 38 : pszDescription = "channel_description";
444 :
445 38 : pszLink = CSLFetchNameValue(papszOptions, "LINK");
446 38 : if (pszLink == nullptr)
447 38 : pszLink = "channel_link";
448 : }
449 1 : else if (eFormat == GEORSS_ATOM && pszHeader == nullptr)
450 : {
451 1 : pszTitle = CSLFetchNameValue(papszOptions, "TITLE");
452 1 : if (pszTitle == nullptr)
453 1 : pszTitle = "title";
454 :
455 1 : pszUpdated = CSLFetchNameValue(papszOptions, "UPDATED");
456 1 : if (pszUpdated == nullptr)
457 1 : pszUpdated = "2009-01-01T00:00:00Z";
458 :
459 1 : pszAuthorName = CSLFetchNameValue(papszOptions, "AUTHOR_NAME");
460 1 : if (pszAuthorName == nullptr)
461 1 : pszAuthorName = "author";
462 :
463 1 : pszId = CSLFetchNameValue(papszOptions, "ID");
464 1 : if (pszId == nullptr)
465 1 : pszId = "id";
466 : }
467 :
468 : const char *pszUseExtensions =
469 39 : CSLFetchNameValue(papszOptions, "USE_EXTENSIONS");
470 39 : bUseExtensions = pszUseExtensions && CPLTestBool(pszUseExtensions);
471 :
472 : /* -------------------------------------------------------------------- */
473 : /* Output header of GeoRSS file. */
474 : /* -------------------------------------------------------------------- */
475 39 : VSIFPrintfL(fpOutput, "<?xml version=\"1.0\"?>\n");
476 39 : if (eFormat == GEORSS_RSS)
477 : {
478 38 : VSIFPrintfL(fpOutput, "<rss version=\"2.0\" ");
479 38 : if (eGeomDialect == GEORSS_GML)
480 2 : VSIFPrintfL(fpOutput,
481 : "xmlns:georss=\"http://www.georss.org/georss\" "
482 : "xmlns:gml=\"http://www.opengis.net/gml\"");
483 36 : else if (eGeomDialect == GEORSS_SIMPLE)
484 35 : VSIFPrintfL(fpOutput,
485 : "xmlns:georss=\"http://www.georss.org/georss\"");
486 : else
487 1 : VSIFPrintfL(
488 : fpOutput,
489 : "xmlns:geo=\"http://www.w3.org/2003/01/geo/wgs84_pos#\"");
490 38 : VSIFPrintfL(fpOutput, ">\n");
491 38 : VSIFPrintfL(fpOutput, " <channel>\n");
492 38 : if (pszHeader)
493 : {
494 0 : VSIFPrintfL(fpOutput, "%s", pszHeader);
495 : }
496 : else
497 : {
498 38 : VSIFPrintfL(fpOutput, " <title>%s</title>\n", pszTitle);
499 38 : VSIFPrintfL(fpOutput, " <description>%s</description>\n",
500 : pszDescription);
501 38 : VSIFPrintfL(fpOutput, " <link>%s</link>\n", pszLink);
502 : }
503 : }
504 : else
505 : {
506 1 : VSIFPrintfL(fpOutput, "<feed xmlns=\"http://www.w3.org/2005/Atom\" ");
507 1 : if (eGeomDialect == GEORSS_GML)
508 0 : VSIFPrintfL(fpOutput, "xmlns:gml=\"http://www.opengis.net/gml\"");
509 1 : else if (eGeomDialect == GEORSS_SIMPLE)
510 1 : VSIFPrintfL(fpOutput,
511 : "xmlns:georss=\"http://www.georss.org/georss\"");
512 : else
513 0 : VSIFPrintfL(
514 : fpOutput,
515 : "xmlns:geo=\"http://www.w3.org/2003/01/geo/wgs84_pos#\"");
516 1 : VSIFPrintfL(fpOutput, ">\n");
517 1 : if (pszHeader)
518 : {
519 0 : VSIFPrintfL(fpOutput, "%s", pszHeader);
520 : }
521 : else
522 : {
523 1 : VSIFPrintfL(fpOutput, " <title>%s</title>\n", pszTitle);
524 1 : VSIFPrintfL(fpOutput, " <updated>%s</updated>\n", pszUpdated);
525 1 : VSIFPrintfL(fpOutput, " <author><name>%s</name></author>\n",
526 : pszAuthorName);
527 1 : VSIFPrintfL(fpOutput, " <id>%s</id>\n", pszId);
528 : }
529 : }
530 :
531 39 : return TRUE;
532 : }
|