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