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