Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GeoRSS Translator
4 : * Purpose: Implements OGRGeoRSSLayer class.
5 : * Author: Even Rouault, even dot rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2008-2014, 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 <cstddef>
17 : #include <cstdio>
18 : #include <cstdlib>
19 : #include <cstring>
20 :
21 : #include "cpl_conv.h"
22 : #include "cpl_error.h"
23 : #include "cpl_hash_set.h"
24 : #include "cpl_minixml.h"
25 : #include "cpl_string.h"
26 : #include "cpl_vsi.h"
27 : #ifdef HAVE_EXPAT
28 : #include "expat.h"
29 : #endif
30 : #include "ogr_api.h"
31 : #include "ogr_core.h"
32 : #include "ogr_expat.h"
33 : #include "ogr_feature.h"
34 : #include "ogr_geometry.h"
35 : #include "ogr_p.h"
36 : #include "ogr_spatialref.h"
37 : #include "ogrsf_frmts.h"
38 :
39 : static const char *const apszAllowedATOMFieldNamesWithSubElements[] = {
40 : "author", "contributor", nullptr};
41 :
42 : static const char *const apszAllowedRSSFieldNames[] = {
43 : "title", "link", "description",
44 : "author", "category", "category_domain",
45 : "comments", "enclosure_url", "enclosure_length",
46 : "enclosure_type", "guid", "guid_isPermaLink",
47 : "pubDate", "source", "source_url",
48 : nullptr};
49 :
50 : static const char *const apszAllowedATOMFieldNames[] = {"category_term",
51 : "category_scheme",
52 : "category_label",
53 : "content",
54 : "content_type",
55 : "content_xml_lang",
56 : "content_xml_base",
57 : "summary",
58 : "summary_type",
59 : "summary_xml_lang",
60 : "summary_xml_base",
61 : "author_name",
62 : "author_uri",
63 : "author_email",
64 : "contributor_name",
65 : "contributor_uri",
66 : "contributor_email",
67 : "link_href",
68 : "link_rel",
69 : "link_type",
70 : "link_length",
71 : "id",
72 : "published",
73 : "rights",
74 : "source",
75 : "title",
76 : "updated",
77 : nullptr};
78 :
79 : #define IS_LAT_ELEMENT(pszName) \
80 : (STARTS_WITH(pszName, "geo:lat") || STARTS_WITH(pszName, "icbm:lat") || \
81 : STARTS_WITH(pszName, "geourl:lat"))
82 :
83 : #define IS_LON_ELEMENT(pszName) \
84 : (STARTS_WITH(pszName, "geo:lon") || STARTS_WITH(pszName, "icbm:lon") || \
85 : STARTS_WITH(pszName, "geourl:lon"))
86 :
87 : #define IS_GEO_ELEMENT(pszName) \
88 : (strcmp(pszName, "georss:point") == 0 || \
89 : strcmp(pszName, "georss:line") == 0 || \
90 : strcmp(pszName, "georss:box") == 0 || \
91 : strcmp(pszName, "georss:polygon") == 0 || \
92 : strcmp(pszName, "georss:where") == 0 || STARTS_WITH(pszName, "gml:") || \
93 : STARTS_WITH(pszName, "geo:") || STARTS_WITH(pszName, "icbm:") || \
94 : STARTS_WITH(pszName, "geourl:"))
95 :
96 : /************************************************************************/
97 : /* OGRGeoRSSLayer() */
98 : /************************************************************************/
99 :
100 68 : OGRGeoRSSLayer::OGRGeoRSSLayer(const char *pszFilename,
101 : const char *pszLayerName,
102 : OGRGeoRSSDataSource *poDS_,
103 68 : OGRSpatialReference *poSRSIn, bool bWriteMode_)
104 68 : : poFeatureDefn(new OGRFeatureDefn(pszLayerName)), poSRS(poSRSIn),
105 68 : poDS(poDS_), eFormat(poDS_->GetFormat()), bWriteMode(bWriteMode_),
106 : nTotalFeatureCount(0), eof(false), nNextFID(0), fpGeoRSS(nullptr),
107 : bHasReadSchema(false),
108 : #ifdef HAVE_EXPAT
109 : oParser(nullptr), oSchemaParser(nullptr),
110 : #endif
111 : poGlobalGeom(nullptr), bStopParsing(false), bInFeature(false),
112 : hasFoundLat(false), hasFoundLon(false),
113 : #ifdef HAVE_EXPAT
114 : latVal(0.0), lonVal(0.0),
115 : #endif
116 : pszSubElementName(nullptr), pszSubElementValue(nullptr),
117 : nSubElementValueLen(0),
118 : #ifdef HAVE_EXPAT
119 : iCurrentField(0),
120 : #endif
121 : bInSimpleGeometry(false), bInGMLGeometry(false), bInGeoLat(false),
122 : bInGeoLong(false),
123 : #ifdef HAVE_EXPAT
124 : bFoundGeom(false), bSameSRS(false),
125 : #endif
126 : eGeomType(wkbUnknown), pszGMLSRSName(nullptr), bInTagWithSubTag(false),
127 : pszTagWithSubTag(nullptr), currentDepth(0), featureDepth(0),
128 : geometryDepth(0),
129 : #ifdef HAVE_EXPAT
130 : currentFieldDefn(nullptr), nWithoutEventCounter(0),
131 : nDataHandlerCounter(0),
132 : #endif
133 : setOfFoundFields(nullptr), poFeature(nullptr), ppoFeatureTab(nullptr),
134 136 : nFeatureTabLength(0), nFeatureTabIndex(0)
135 : {
136 68 : SetDescription(poFeatureDefn->GetName());
137 68 : poFeatureDefn->Reference();
138 :
139 68 : if (poSRS)
140 : {
141 1 : poSRS->Reference();
142 1 : poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
143 : }
144 :
145 68 : if (!bWriteMode)
146 : {
147 14 : fpGeoRSS = VSIFOpenL(pszFilename, "r");
148 14 : if (fpGeoRSS == nullptr)
149 : {
150 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
151 : pszFilename);
152 0 : return;
153 : }
154 : }
155 :
156 68 : OGRGeoRSSLayer::ResetReading();
157 : }
158 :
159 : /************************************************************************/
160 : /* ~OGRGeoRSSLayer() */
161 : /************************************************************************/
162 :
163 136 : OGRGeoRSSLayer::~OGRGeoRSSLayer()
164 :
165 : {
166 : #ifdef HAVE_EXPAT
167 68 : if (oParser)
168 14 : XML_ParserFree(oParser);
169 : #endif
170 68 : poFeatureDefn->Release();
171 :
172 68 : if (poSRS)
173 10 : poSRS->Release();
174 :
175 68 : CPLFree(pszSubElementName);
176 68 : CPLFree(pszSubElementValue);
177 68 : CPLFree(pszGMLSRSName);
178 68 : CPLFree(pszTagWithSubTag);
179 68 : if (setOfFoundFields)
180 13 : CPLHashSetDestroy(setOfFoundFields);
181 68 : if (poGlobalGeom)
182 0 : delete poGlobalGeom;
183 :
184 68 : for (int i = nFeatureTabIndex; i < nFeatureTabLength; i++)
185 0 : delete ppoFeatureTab[i];
186 68 : CPLFree(ppoFeatureTab);
187 :
188 68 : if (poFeature)
189 0 : delete poFeature;
190 :
191 68 : if (fpGeoRSS)
192 14 : VSIFCloseL(fpGeoRSS);
193 136 : }
194 :
195 : /************************************************************************/
196 : /* GetLayerDefn() */
197 : /************************************************************************/
198 :
199 369 : OGRFeatureDefn *OGRGeoRSSLayer::GetLayerDefn()
200 : {
201 369 : if (!bHasReadSchema)
202 63 : LoadSchema();
203 :
204 369 : return poFeatureDefn;
205 : }
206 :
207 : #ifdef HAVE_EXPAT
208 :
209 304 : static void XMLCALL startElementCbk(void *pUserData, const char *pszName,
210 : const char **ppszAttr)
211 : {
212 304 : ((OGRGeoRSSLayer *)pUserData)->startElementCbk(pszName, ppszAttr);
213 304 : }
214 :
215 304 : static void XMLCALL endElementCbk(void *pUserData, const char *pszName)
216 : {
217 304 : ((OGRGeoRSSLayer *)pUserData)->endElementCbk(pszName);
218 304 : }
219 :
220 889 : static void XMLCALL dataHandlerCbk(void *pUserData, const char *data, int nLen)
221 : {
222 889 : ((OGRGeoRSSLayer *)pUserData)->dataHandlerCbk(data, nLen);
223 889 : }
224 :
225 : #endif
226 :
227 : /************************************************************************/
228 : /* ResetReading() */
229 : /************************************************************************/
230 :
231 84 : void OGRGeoRSSLayer::ResetReading()
232 :
233 : {
234 84 : if (bWriteMode)
235 70 : return;
236 :
237 14 : eof = false;
238 14 : nNextFID = 0;
239 14 : if (fpGeoRSS)
240 : {
241 14 : VSIFSeekL(fpGeoRSS, 0, SEEK_SET);
242 14 : VSIFClearErrL(fpGeoRSS);
243 : #ifdef HAVE_EXPAT
244 14 : if (oParser)
245 0 : XML_ParserFree(oParser);
246 :
247 14 : oParser = OGRCreateExpatXMLParser();
248 14 : XML_SetElementHandler(oParser, ::startElementCbk, ::endElementCbk);
249 14 : XML_SetCharacterDataHandler(oParser, ::dataHandlerCbk);
250 14 : XML_SetUserData(oParser, this);
251 : #endif
252 : }
253 14 : bInFeature = false;
254 14 : hasFoundLat = false;
255 14 : hasFoundLon = false;
256 14 : bInSimpleGeometry = false;
257 14 : bInGMLGeometry = false;
258 14 : bInGeoLat = false;
259 14 : bInGeoLong = false;
260 14 : eGeomType = wkbUnknown;
261 14 : CPLFree(pszSubElementName);
262 14 : pszSubElementName = nullptr;
263 14 : CPLFree(pszSubElementValue);
264 14 : pszSubElementValue = nullptr;
265 14 : nSubElementValueLen = 0;
266 14 : CPLFree(pszGMLSRSName);
267 14 : pszGMLSRSName = nullptr;
268 :
269 14 : if (setOfFoundFields)
270 0 : CPLHashSetDestroy(setOfFoundFields);
271 14 : setOfFoundFields = nullptr;
272 :
273 14 : for (int i = nFeatureTabIndex; i < nFeatureTabLength; i++)
274 0 : delete ppoFeatureTab[i];
275 14 : CPLFree(ppoFeatureTab);
276 14 : nFeatureTabIndex = 0;
277 14 : nFeatureTabLength = 0;
278 14 : ppoFeatureTab = nullptr;
279 14 : if (poFeature)
280 0 : delete poFeature;
281 14 : poFeature = nullptr;
282 :
283 14 : currentDepth = 0;
284 14 : featureDepth = 0;
285 14 : geometryDepth = 0;
286 14 : bInTagWithSubTag = false;
287 14 : CPLFree(pszTagWithSubTag);
288 14 : pszTagWithSubTag = nullptr;
289 : }
290 :
291 : #ifdef HAVE_EXPAT
292 :
293 : /************************************************************************/
294 : /* AddStrToSubElementValue() */
295 : /************************************************************************/
296 :
297 211 : void OGRGeoRSSLayer::AddStrToSubElementValue(const char *pszStr)
298 : {
299 211 : int len = static_cast<int>(strlen(pszStr));
300 : char *pszNewSubElementValue = static_cast<char *>(
301 211 : VSI_REALLOC_VERBOSE(pszSubElementValue, nSubElementValueLen + len + 1));
302 211 : if (pszNewSubElementValue == nullptr)
303 : {
304 0 : XML_StopParser(oParser, XML_FALSE);
305 0 : bStopParsing = true;
306 0 : return;
307 : }
308 211 : pszSubElementValue = pszNewSubElementValue;
309 :
310 211 : memcpy(pszSubElementValue + nSubElementValueLen, pszStr, len);
311 211 : nSubElementValueLen += len;
312 : }
313 :
314 : /************************************************************************/
315 : /* OGRGeoRSS_GetOGRCompatibleTagName() */
316 : /************************************************************************/
317 :
318 : /** Replace ':' from XML NS element name by '_' more OGR friendly */
319 346 : static char *OGRGeoRSS_GetOGRCompatibleTagName(const char *pszName)
320 : {
321 346 : char *pszModName = CPLStrdup(pszName);
322 3046 : for (int i = 0; pszModName[i] != 0; i++)
323 : {
324 2700 : if (pszModName[i] == ':')
325 28 : pszModName[i] = '_';
326 : }
327 346 : return pszModName;
328 : }
329 :
330 : /************************************************************************/
331 : /* OGRGeoRSSLayerATOMTagHasSubElement() */
332 : /************************************************************************/
333 :
334 120 : static bool OGRGeoRSSLayerATOMTagHasSubElement(const char *pszName)
335 : {
336 312 : for (unsigned int i = 0;
337 312 : apszAllowedATOMFieldNamesWithSubElements[i] != nullptr; i++)
338 : {
339 228 : if (strcmp(pszName, apszAllowedATOMFieldNamesWithSubElements[i]) == 0)
340 36 : return true;
341 : }
342 84 : return false;
343 : }
344 :
345 : /************************************************************************/
346 : /* startElementCbk() */
347 : /************************************************************************/
348 :
349 304 : void OGRGeoRSSLayer::startElementCbk(const char *pszName, const char **ppszAttr)
350 : {
351 304 : bool bSerializeTag = false;
352 304 : const char *pszNoNSName = pszName;
353 304 : const char *pszColon = strchr(pszNoNSName, ':');
354 304 : if (pszColon)
355 77 : pszNoNSName = pszColon + 1;
356 :
357 304 : if (bStopParsing)
358 0 : return;
359 :
360 304 : if ((eFormat == GEORSS_ATOM && currentDepth == 1 &&
361 23 : strcmp(pszNoNSName, "entry") == 0) ||
362 301 : ((eFormat == GEORSS_RSS || eFormat == GEORSS_RSS_RDF) && !bInFeature &&
363 68 : (currentDepth == 1 || currentDepth == 2) &&
364 58 : strcmp(pszNoNSName, "item") == 0))
365 : {
366 28 : featureDepth = currentDepth;
367 :
368 28 : if (poFeature)
369 0 : delete poFeature;
370 :
371 28 : poFeature = new OGRFeature(poFeatureDefn);
372 28 : poFeature->SetFID(nNextFID++);
373 :
374 28 : bInFeature = true;
375 28 : hasFoundLat = false;
376 28 : hasFoundLon = false;
377 28 : bInSimpleGeometry = false;
378 28 : bInGMLGeometry = false;
379 28 : bInGeoLat = false;
380 28 : bInGeoLong = false;
381 28 : eGeomType = wkbUnknown;
382 28 : geometryDepth = 0;
383 28 : bInTagWithSubTag = false;
384 :
385 28 : if (setOfFoundFields)
386 15 : CPLHashSetDestroy(setOfFoundFields);
387 28 : setOfFoundFields =
388 28 : CPLHashSetNew(CPLHashSetHashStr, CPLHashSetEqualStr, CPLFree);
389 : }
390 276 : else if (bInFeature && bInTagWithSubTag && currentDepth == 3)
391 : {
392 : char *pszFieldName =
393 15 : CPLStrdup(CPLSPrintf("%s_%s", pszTagWithSubTag, pszNoNSName));
394 :
395 15 : CPLFree(pszSubElementName);
396 15 : pszSubElementName = nullptr;
397 15 : CPLFree(pszSubElementValue);
398 15 : pszSubElementValue = nullptr;
399 15 : nSubElementValueLen = 0;
400 :
401 15 : iCurrentField = poFeatureDefn->GetFieldIndex(pszFieldName);
402 15 : if (iCurrentField >= 0)
403 15 : pszSubElementName = CPLStrdup(pszFieldName);
404 :
405 15 : CPLFree(pszFieldName);
406 : }
407 291 : else if (bInFeature && eFormat == GEORSS_ATOM && currentDepth == 2 &&
408 30 : OGRGeoRSSLayerATOMTagHasSubElement(pszNoNSName))
409 : {
410 9 : CPLFree(pszTagWithSubTag);
411 9 : pszTagWithSubTag = CPLStrdup(pszNoNSName);
412 :
413 9 : int count = 1;
414 12 : while (CPLHashSetLookup(setOfFoundFields, pszTagWithSubTag) != nullptr)
415 : {
416 3 : count++;
417 3 : CPLFree(pszTagWithSubTag);
418 3 : pszTagWithSubTag =
419 3 : CPLStrdup(CPLSPrintf("%s%d", pszNoNSName, count));
420 : }
421 9 : CPLHashSetInsert(setOfFoundFields, CPLStrdup(pszTagWithSubTag));
422 :
423 9 : bInTagWithSubTag = true;
424 : }
425 252 : else if (bInGMLGeometry)
426 : {
427 17 : bSerializeTag = true;
428 : }
429 235 : else if (bInSimpleGeometry || bInGeoLat || bInGeoLong)
430 : {
431 : /* Should not happen for a valid document. */
432 : }
433 235 : else if (IS_LAT_ELEMENT(pszName))
434 : {
435 1 : CPLFree(pszSubElementValue);
436 1 : pszSubElementValue = nullptr;
437 1 : nSubElementValueLen = 0;
438 1 : bInGeoLat = true;
439 : }
440 234 : else if (IS_LON_ELEMENT(pszName))
441 : {
442 1 : CPLFree(pszSubElementValue);
443 1 : pszSubElementValue = nullptr;
444 1 : nSubElementValueLen = 0;
445 1 : bInGeoLong = true;
446 : }
447 233 : else if (strcmp(pszName, "georss:point") == 0 ||
448 230 : strcmp(pszName, "georss:line") == 0 ||
449 228 : strcmp(pszName, "geo:line") == 0 ||
450 228 : strcmp(pszName, "georss:polygon") == 0 ||
451 225 : strcmp(pszName, "georss:box") == 0)
452 : {
453 10 : CPLFree(pszSubElementValue);
454 10 : pszSubElementValue = nullptr;
455 10 : nSubElementValueLen = 0;
456 17 : eGeomType = strcmp(pszName, "georss:point") == 0 ? wkbPoint
457 12 : : (strcmp(pszName, "georss:line") == 0 ||
458 5 : strcmp(pszName, "geo:line") == 0)
459 12 : ? wkbLineString
460 7 : : (strcmp(pszName, "georss:polygon") == 0 ||
461 2 : strcmp(pszName, "georss:box") == 0)
462 7 : ? wkbPolygon
463 : : wkbUnknown;
464 10 : bInSimpleGeometry = true;
465 10 : geometryDepth = currentDepth;
466 : }
467 223 : else if (strcmp(pszName, "gml:Point") == 0 ||
468 220 : strcmp(pszName, "gml:LineString") == 0 ||
469 217 : strcmp(pszName, "gml:Polygon") == 0 ||
470 214 : strcmp(pszName, "gml:MultiPoint") == 0 ||
471 214 : strcmp(pszName, "gml:MultiLineString") == 0 ||
472 214 : strcmp(pszName, "gml:MultiPolygon") == 0 ||
473 214 : strcmp(pszName, "gml:Envelope") == 0)
474 : {
475 10 : CPLFree(pszSubElementValue);
476 10 : pszSubElementValue = nullptr;
477 10 : nSubElementValueLen = 0;
478 10 : AddStrToSubElementValue(CPLSPrintf("<%s>", pszName));
479 10 : bInGMLGeometry = true;
480 10 : geometryDepth = currentDepth;
481 10 : CPLFree(pszGMLSRSName);
482 10 : pszGMLSRSName = nullptr;
483 11 : for (int i = 0; ppszAttr[i]; i += 2)
484 : {
485 1 : if (strcmp(ppszAttr[i], "srsName") == 0)
486 : {
487 1 : if (pszGMLSRSName == nullptr)
488 1 : pszGMLSRSName = CPLStrdup(ppszAttr[i + 1]);
489 : }
490 10 : }
491 : }
492 213 : else if (bInFeature && currentDepth == featureDepth + 1)
493 : {
494 137 : CPLFree(pszSubElementName);
495 137 : pszSubElementName = nullptr;
496 137 : CPLFree(pszSubElementValue);
497 137 : pszSubElementValue = nullptr;
498 137 : nSubElementValueLen = 0;
499 137 : iCurrentField = -1;
500 :
501 137 : if (pszName != pszNoNSName && STARTS_WITH(pszName, "atom:"))
502 7 : pszName = pszNoNSName;
503 :
504 137 : pszSubElementName = CPLStrdup(pszName);
505 137 : int count = 1;
506 145 : while (CPLHashSetLookup(setOfFoundFields, pszSubElementName) != nullptr)
507 : {
508 8 : count++;
509 8 : if (count == 100)
510 : {
511 0 : CPLError(CE_Failure, CPLE_AppDefined,
512 : "Too many repeated fields");
513 0 : CPLFree(pszSubElementName);
514 0 : pszSubElementName = nullptr;
515 0 : break;
516 : }
517 8 : CPLFree(pszSubElementName);
518 8 : pszSubElementName = CPLStrdup(CPLSPrintf("%s%d", pszName, count));
519 : }
520 137 : if (pszSubElementName)
521 : {
522 137 : CPLHashSetInsert(setOfFoundFields, CPLStrdup(pszSubElementName));
523 :
524 : char *pszCompatibleName =
525 137 : OGRGeoRSS_GetOGRCompatibleTagName(pszSubElementName);
526 137 : iCurrentField = poFeatureDefn->GetFieldIndex(pszCompatibleName);
527 137 : CPLFree(pszSubElementName);
528 :
529 137 : for (int i = 0;
530 178 : ppszAttr[i] != nullptr && ppszAttr[i + 1] != nullptr; i += 2)
531 : {
532 41 : char *pszAttrCompatibleName = OGRGeoRSS_GetOGRCompatibleTagName(
533 41 : CPLSPrintf("%s_%s", pszCompatibleName, ppszAttr[i]));
534 : const int iAttrField =
535 41 : poFeatureDefn->GetFieldIndex(pszAttrCompatibleName);
536 41 : if (iAttrField >= 0)
537 : {
538 41 : if (poFeatureDefn->GetFieldDefn(iAttrField)->GetType() ==
539 : OFTReal)
540 0 : poFeature->SetField(iAttrField,
541 0 : CPLAtof(ppszAttr[i + 1]));
542 : else
543 41 : poFeature->SetField(iAttrField, ppszAttr[i + 1]);
544 : }
545 41 : CPLFree(pszAttrCompatibleName);
546 : }
547 :
548 137 : if (iCurrentField < 0)
549 : {
550 16 : pszSubElementName = nullptr;
551 : }
552 : else
553 : {
554 121 : pszSubElementName = CPLStrdup(pszCompatibleName);
555 : }
556 137 : CPLFree(pszCompatibleName);
557 137 : }
558 : }
559 76 : else if (bInFeature && currentDepth > featureDepth + 1 &&
560 9 : pszSubElementName != nullptr)
561 : {
562 9 : bSerializeTag = true;
563 : }
564 :
565 304 : if (bSerializeTag)
566 : {
567 26 : AddStrToSubElementValue("<");
568 26 : AddStrToSubElementValue(pszName);
569 29 : for (int i = 0; ppszAttr[i] != nullptr && ppszAttr[i + 1] != nullptr;
570 3 : i += 2)
571 : {
572 3 : AddStrToSubElementValue(" ");
573 3 : AddStrToSubElementValue(ppszAttr[i]);
574 3 : AddStrToSubElementValue("=\"");
575 3 : AddStrToSubElementValue(ppszAttr[i + 1]);
576 3 : AddStrToSubElementValue("\"");
577 : }
578 26 : AddStrToSubElementValue(">");
579 : }
580 :
581 304 : currentDepth++;
582 : }
583 :
584 : /************************************************************************/
585 : /* OGRGeoRSSLayerTrimLeadingAndTrailingSpaces() */
586 : /************************************************************************/
587 :
588 10 : static void OGRGeoRSSLayerTrimLeadingAndTrailingSpaces(char *pszStr)
589 : {
590 : // Trim leading spaces, tabs and newlines.
591 10 : int i = 0;
592 13 : while (pszStr[i] != '\0' &&
593 13 : (pszStr[i] == ' ' || pszStr[i] == '\t' || pszStr[i] == '\n'))
594 3 : i++;
595 10 : memmove(pszStr, pszStr + i, strlen(pszStr + i) + 1);
596 :
597 : // Trim trailing spaces, tabs and newlines.
598 10 : i = static_cast<int>(strlen(pszStr)) - 1;
599 13 : while (i >= 0 &&
600 13 : (pszStr[i] == ' ' || pszStr[i] == '\t' || pszStr[i] == '\n'))
601 : {
602 3 : pszStr[i] = '\0';
603 3 : i--;
604 : }
605 10 : }
606 :
607 : /************************************************************************/
608 : /* endElementCbk() */
609 : /************************************************************************/
610 :
611 304 : void OGRGeoRSSLayer::endElementCbk(const char *pszName)
612 : {
613 304 : OGRGeometry *poGeom = nullptr;
614 :
615 304 : if (bStopParsing)
616 0 : return;
617 :
618 304 : currentDepth--;
619 304 : const char *pszNoNSName = pszName;
620 304 : const char *pszColon = strchr(pszNoNSName, ':');
621 304 : if (pszColon)
622 77 : pszNoNSName = pszColon + 1;
623 :
624 304 : if (bInFeature && currentDepth == featureDepth)
625 : {
626 28 : bInFeature = false;
627 28 : bInTagWithSubTag = false;
628 :
629 28 : if (hasFoundLat && hasFoundLon)
630 1 : poFeature->SetGeometryDirectly(new OGRPoint(lonVal, latVal));
631 36 : else if (poFeature->GetGeometryRef() == nullptr &&
632 9 : poGlobalGeom != nullptr)
633 0 : poFeature->SetGeometry(poGlobalGeom);
634 :
635 28 : hasFoundLat = false;
636 28 : hasFoundLon = false;
637 :
638 28 : if (poSRS != nullptr && poFeature->GetGeometryRef() != nullptr)
639 19 : poFeature->GetGeometryRef()->assignSpatialReference(poSRS);
640 :
641 56 : if ((m_poFilterGeom == nullptr ||
642 56 : FilterGeometry(poFeature->GetGeometryRef())) &&
643 28 : (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
644 : {
645 56 : ppoFeatureTab = static_cast<OGRFeature **>(CPLRealloc(
646 28 : ppoFeatureTab, sizeof(OGRFeature *) * (nFeatureTabLength + 1)));
647 28 : ppoFeatureTab[nFeatureTabLength] = poFeature;
648 28 : nFeatureTabLength++;
649 : }
650 : else
651 : {
652 0 : delete poFeature;
653 : }
654 28 : poFeature = nullptr;
655 28 : return;
656 : }
657 :
658 276 : if (bInTagWithSubTag && currentDepth == 3)
659 : {
660 : char *pszFieldName =
661 15 : CPLStrdup(CPLSPrintf("%s_%s", pszTagWithSubTag, pszNoNSName));
662 :
663 15 : if (iCurrentField != -1 && pszSubElementName &&
664 15 : strcmp(pszFieldName, pszSubElementName) == 0 && poFeature &&
665 15 : pszSubElementValue && nSubElementValueLen)
666 : {
667 15 : pszSubElementValue[nSubElementValueLen] = 0;
668 15 : if (poFeatureDefn->GetFieldDefn(iCurrentField)->GetType() ==
669 : OFTReal)
670 0 : poFeature->SetField(iCurrentField, CPLAtof(pszSubElementValue));
671 : else
672 15 : poFeature->SetField(iCurrentField, pszSubElementValue);
673 : }
674 :
675 15 : CPLFree(pszSubElementName);
676 15 : pszSubElementName = nullptr;
677 15 : CPLFree(pszSubElementValue);
678 15 : pszSubElementValue = nullptr;
679 15 : nSubElementValueLen = 0;
680 :
681 15 : CPLFree(pszFieldName);
682 : }
683 291 : else if (bInFeature && eFormat == GEORSS_ATOM && currentDepth == 2 &&
684 30 : OGRGeoRSSLayerATOMTagHasSubElement(pszNoNSName))
685 : {
686 9 : bInTagWithSubTag = false;
687 : }
688 252 : else if (bInGMLGeometry)
689 : {
690 27 : AddStrToSubElementValue("</");
691 27 : AddStrToSubElementValue(pszName);
692 27 : AddStrToSubElementValue(">");
693 27 : if (currentDepth > geometryDepth)
694 : {
695 : }
696 : else
697 : {
698 10 : pszSubElementValue[nSubElementValueLen] = 0;
699 10 : CPLAssert(STARTS_WITH(pszName, "gml:"));
700 10 : poGeom = (OGRGeometry *)OGR_G_CreateFromGML(pszSubElementValue);
701 :
702 10 : if (poGeom != nullptr && !poGeom->IsEmpty())
703 : {
704 9 : bool bSwapCoordinates = false;
705 9 : if (pszGMLSRSName)
706 : {
707 : OGRSpatialReference *poSRSFeature =
708 1 : new OGRSpatialReference();
709 1 : poSRSFeature->importFromURN(pszGMLSRSName);
710 1 : poGeom->assignSpatialReference(poSRSFeature);
711 1 : poSRSFeature->Release();
712 : }
713 : else
714 : {
715 8 : bSwapCoordinates = true; /* lat, lon WGS 84 */
716 : }
717 :
718 9 : if (bSwapCoordinates)
719 : {
720 8 : poGeom->swapXY();
721 : }
722 : }
723 10 : bInGMLGeometry = false;
724 : }
725 : }
726 225 : else if (bInSimpleGeometry)
727 : {
728 10 : if (currentDepth > geometryDepth)
729 : {
730 : // Should not happen for a valid document.
731 : }
732 : else
733 : {
734 10 : if (pszSubElementValue)
735 : {
736 10 : pszSubElementValue[nSubElementValueLen] = 0;
737 :
738 : // Trim any leading and trailing spaces, tabs, newlines, etc.
739 10 : OGRGeoRSSLayerTrimLeadingAndTrailingSpaces(pszSubElementValue);
740 :
741 : // Caution: Order is latitude, longitude.
742 20 : char **papszTokens = CSLTokenizeStringComplex(
743 10 : pszSubElementValue, " ,", TRUE, FALSE);
744 :
745 10 : const int nTokens = CSLCount(papszTokens);
746 10 : if ((nTokens % 2) != 0 ||
747 9 : (eGeomType == wkbPoint && nTokens != 2) ||
748 9 : (eGeomType == wkbLineString && nTokens < 4) ||
749 9 : (strcmp(pszName, "georss:polygon") == 0 && nTokens < 6) ||
750 9 : (strcmp(pszName, "georss:box") == 0 && nTokens != 4))
751 : {
752 1 : CPLError(CE_Failure, CPLE_AppDefined,
753 : "Wrong number of coordinates in %s",
754 : pszSubElementValue);
755 : }
756 9 : else if (eGeomType == wkbPoint)
757 : {
758 6 : poGeom = new OGRPoint(CPLAtof(papszTokens[1]),
759 3 : CPLAtof(papszTokens[0]));
760 : }
761 6 : else if (eGeomType == wkbLineString)
762 : {
763 2 : OGRLineString *poLineString = new OGRLineString();
764 2 : poGeom = poLineString;
765 8 : for (int i = 0; i < nTokens; i += 2)
766 : {
767 6 : poLineString->addPoint(CPLAtof(papszTokens[i + 1]),
768 6 : CPLAtof(papszTokens[i]));
769 : }
770 : }
771 4 : else if (eGeomType == wkbPolygon)
772 : {
773 4 : OGRPolygon *poPolygon = new OGRPolygon();
774 4 : OGRLinearRing *poLinearRing = new OGRLinearRing();
775 4 : poGeom = poPolygon;
776 4 : poPolygon->addRingDirectly(poLinearRing);
777 4 : if (strcmp(pszName, "georss:polygon") == 0)
778 : {
779 18 : for (int i = 0; i < nTokens; i += 2)
780 : {
781 15 : poLinearRing->addPoint(CPLAtof(papszTokens[i + 1]),
782 15 : CPLAtof(papszTokens[i]));
783 : }
784 : }
785 : else
786 : {
787 1 : const double lat1 = CPLAtof(papszTokens[0]);
788 1 : const double lon1 = CPLAtof(papszTokens[1]);
789 1 : const double lat2 = CPLAtof(papszTokens[2]);
790 1 : const double lon2 = CPLAtof(papszTokens[3]);
791 1 : poLinearRing->addPoint(lon1, lat1);
792 1 : poLinearRing->addPoint(lon1, lat2);
793 1 : poLinearRing->addPoint(lon2, lat2);
794 1 : poLinearRing->addPoint(lon2, lat1);
795 1 : poLinearRing->addPoint(lon1, lat1);
796 : }
797 : }
798 :
799 10 : CSLDestroy(papszTokens);
800 : }
801 10 : bInSimpleGeometry = false;
802 : }
803 : }
804 215 : else if (IS_LAT_ELEMENT(pszName))
805 : {
806 1 : if (pszSubElementValue)
807 : {
808 1 : hasFoundLat = true;
809 1 : pszSubElementValue[nSubElementValueLen] = 0;
810 1 : latVal = CPLAtof(pszSubElementValue);
811 : }
812 1 : bInGeoLat = false;
813 : }
814 214 : else if (IS_LON_ELEMENT(pszName))
815 : {
816 1 : if (pszSubElementValue)
817 : {
818 1 : hasFoundLon = true;
819 1 : pszSubElementValue[nSubElementValueLen] = 0;
820 1 : lonVal = CPLAtof(pszSubElementValue);
821 : }
822 1 : bInGeoLong = false;
823 : }
824 213 : else if (bInFeature && currentDepth == featureDepth + 1)
825 : {
826 137 : if (iCurrentField != -1 && pszSubElementName && poFeature &&
827 121 : pszSubElementValue && nSubElementValueLen)
828 : {
829 121 : pszSubElementValue[nSubElementValueLen] = 0;
830 121 : if (poFeatureDefn->GetFieldDefn(iCurrentField)->GetType() ==
831 : OFTDateTime)
832 : {
833 : OGRField sField;
834 27 : if (OGRParseRFC822DateTime(pszSubElementValue, &sField))
835 : {
836 21 : poFeature->SetField(iCurrentField, &sField);
837 : }
838 6 : else if (OGRParseXMLDateTime(pszSubElementValue, &sField))
839 : {
840 6 : poFeature->SetField(iCurrentField, &sField);
841 : }
842 : else
843 : {
844 0 : CPLError(CE_Warning, CPLE_AppDefined,
845 : "Could not parse %s as a valid dateTime",
846 : pszSubElementValue);
847 : }
848 : }
849 : else
850 : {
851 94 : if (poFeatureDefn->GetFieldDefn(iCurrentField)->GetType() ==
852 : OFTReal)
853 0 : poFeature->SetField(iCurrentField,
854 0 : CPLAtof(pszSubElementValue));
855 : else
856 94 : poFeature->SetField(iCurrentField, pszSubElementValue);
857 : }
858 : }
859 :
860 137 : CPLFree(pszSubElementName);
861 137 : pszSubElementName = nullptr;
862 137 : CPLFree(pszSubElementValue);
863 137 : pszSubElementValue = nullptr;
864 137 : nSubElementValueLen = 0;
865 : }
866 76 : else if (bInFeature && currentDepth > featureDepth + 1 &&
867 9 : pszSubElementName != nullptr)
868 : {
869 9 : AddStrToSubElementValue("</");
870 9 : AddStrToSubElementValue(pszName);
871 9 : AddStrToSubElementValue(">");
872 : }
873 :
874 276 : if (poGeom != nullptr)
875 : {
876 18 : if (poFeature != nullptr)
877 : {
878 18 : poFeature->SetGeometryDirectly(poGeom);
879 : }
880 0 : else if (!bInFeature)
881 : {
882 0 : if (poGlobalGeom != nullptr)
883 0 : delete poGlobalGeom;
884 0 : poGlobalGeom = poGeom;
885 : }
886 : else
887 : {
888 0 : delete poGeom;
889 : }
890 : }
891 258 : else if (!bInFeature && hasFoundLat && hasFoundLon)
892 : {
893 0 : if (poGlobalGeom != nullptr)
894 0 : delete poGlobalGeom;
895 0 : poGlobalGeom = new OGRPoint(lonVal, latVal);
896 0 : hasFoundLat = false;
897 0 : hasFoundLon = false;
898 : }
899 : }
900 :
901 : /************************************************************************/
902 : /* dataHandlerCbk() */
903 : /************************************************************************/
904 :
905 889 : void OGRGeoRSSLayer::dataHandlerCbk(const char *data, int nLen)
906 : {
907 889 : if (bStopParsing)
908 0 : return;
909 :
910 889 : if (bInGMLGeometry || bInSimpleGeometry || bInGeoLat || bInGeoLong ||
911 834 : pszSubElementName != nullptr)
912 : {
913 208 : char *pszNewSubElementValue = static_cast<char *>(VSI_REALLOC_VERBOSE(
914 : pszSubElementValue, nSubElementValueLen + nLen + 1));
915 208 : if (pszNewSubElementValue == nullptr)
916 : {
917 0 : XML_StopParser(oSchemaParser, XML_FALSE);
918 0 : bStopParsing = true;
919 0 : return;
920 : }
921 208 : pszSubElementValue = pszNewSubElementValue;
922 208 : memcpy(pszSubElementValue + nSubElementValueLen, data, nLen);
923 208 : nSubElementValueLen += nLen;
924 : }
925 : }
926 : #endif
927 :
928 : /************************************************************************/
929 : /* GetNextFeature() */
930 : /************************************************************************/
931 :
932 44 : OGRFeature *OGRGeoRSSLayer::GetNextFeature()
933 : {
934 44 : if (bWriteMode)
935 : {
936 16 : CPLError(CE_Failure, CPLE_NotSupported,
937 : "Cannot read features when writing a GeoRSS file");
938 16 : return nullptr;
939 : }
940 :
941 28 : if (fpGeoRSS == nullptr)
942 0 : return nullptr;
943 :
944 28 : if (!bHasReadSchema)
945 4 : LoadSchema();
946 :
947 28 : if (bStopParsing)
948 0 : return nullptr;
949 :
950 : #ifdef HAVE_EXPAT
951 28 : if (nFeatureTabIndex < nFeatureTabLength)
952 : {
953 15 : return ppoFeatureTab[nFeatureTabIndex++];
954 : }
955 :
956 13 : if (VSIFEofL(fpGeoRSS) || VSIFErrorL(fpGeoRSS))
957 0 : return nullptr;
958 :
959 13 : CPLFree(ppoFeatureTab);
960 13 : ppoFeatureTab = nullptr;
961 13 : nFeatureTabLength = 0;
962 13 : nFeatureTabIndex = 0;
963 :
964 13 : int nDone = 0;
965 26 : std::vector<char> aBuf(PARSER_BUF_SIZE);
966 0 : do
967 : {
968 : unsigned int nLen = static_cast<unsigned int>(
969 13 : VSIFReadL(aBuf.data(), 1, aBuf.size(), fpGeoRSS));
970 13 : nDone = nLen < aBuf.size();
971 13 : if (XML_Parse(oParser, aBuf.data(), nLen, nDone) == XML_STATUS_ERROR)
972 : {
973 0 : CPLError(CE_Failure, CPLE_AppDefined,
974 : "XML parsing of GeoRSS file failed : %s "
975 : "at line %d, column %d",
976 : XML_ErrorString(XML_GetErrorCode(oParser)),
977 0 : static_cast<int>(XML_GetCurrentLineNumber(oParser)),
978 0 : static_cast<int>(XML_GetCurrentColumnNumber(oParser)));
979 0 : bStopParsing = true;
980 : }
981 13 : } while (!nDone && !bStopParsing && nFeatureTabLength == 0);
982 :
983 13 : return nFeatureTabLength ? ppoFeatureTab[nFeatureTabIndex++] : nullptr;
984 : #else
985 : return nullptr;
986 : #endif
987 : }
988 :
989 : /************************************************************************/
990 : /* OGRGeoRSSLayerIsStandardFieldInternal() */
991 : /************************************************************************/
992 :
993 187 : static bool OGRGeoRSSLayerIsStandardFieldInternal(const char *pszName,
994 : const char *const *papszNames)
995 : {
996 2223 : for (unsigned int i = 0; papszNames[i] != nullptr; i++)
997 : {
998 2121 : if (strcmp(pszName, papszNames[i]) == 0)
999 : {
1000 74 : return true;
1001 : }
1002 :
1003 2047 : const char *pszUnderscore = strchr(papszNames[i], '_');
1004 2047 : if (pszUnderscore == nullptr)
1005 : {
1006 1115 : size_t nLen = strlen(papszNames[i]);
1007 1115 : if (strncmp(pszName, papszNames[i], nLen) == 0)
1008 : {
1009 12 : size_t k = nLen;
1010 18 : while (pszName[k] >= '0' && pszName[k] <= '9')
1011 6 : k++;
1012 12 : if (pszName[k] == '\0')
1013 3 : return true;
1014 : }
1015 : }
1016 : else
1017 : {
1018 932 : const size_t nLen =
1019 932 : static_cast<size_t>(pszUnderscore - papszNames[i]);
1020 932 : if (strncmp(pszName, papszNames[i], nLen) == 0)
1021 : {
1022 23 : size_t k = nLen;
1023 37 : while (pszName[k] >= '0' && pszName[k] <= '9')
1024 14 : k++;
1025 23 : if (pszName[k] == '_' &&
1026 23 : strcmp(pszName + k, pszUnderscore) == 0)
1027 8 : return true;
1028 : }
1029 : }
1030 : }
1031 102 : return false;
1032 : }
1033 :
1034 : /************************************************************************/
1035 : /* OGRGeoRSSLayer::IsStandardField() */
1036 : /************************************************************************/
1037 :
1038 187 : bool OGRGeoRSSLayer::IsStandardField(const char *pszName)
1039 : {
1040 187 : if (eFormat == GEORSS_RSS)
1041 : {
1042 165 : return OGRGeoRSSLayerIsStandardFieldInternal(pszName,
1043 165 : apszAllowedRSSFieldNames);
1044 : }
1045 : else
1046 : {
1047 22 : return OGRGeoRSSLayerIsStandardFieldInternal(pszName,
1048 22 : apszAllowedATOMFieldNames);
1049 : }
1050 : }
1051 :
1052 : /************************************************************************/
1053 : /* OGRGeoRSSLayerSplitComposedField() */
1054 : /************************************************************************/
1055 :
1056 103 : static void OGRGeoRSSLayerSplitComposedField(const char *pszName,
1057 : std::string &osElementName,
1058 : std::string &osNumber,
1059 : std::string &osAttributeName)
1060 : {
1061 103 : osElementName = pszName;
1062 :
1063 103 : int i = 0;
1064 751 : while (pszName[i] != '\0' && pszName[i] != '_' &&
1065 663 : !(pszName[i] >= '0' && pszName[i] <= '9'))
1066 : {
1067 648 : i++;
1068 : }
1069 :
1070 103 : osElementName.resize(i);
1071 :
1072 103 : if (pszName[i] >= '0' && pszName[i] <= '9')
1073 : {
1074 15 : osNumber = pszName + i;
1075 15 : const auto nPos = osNumber.find('_');
1076 15 : if (nPos != std::string::npos)
1077 : {
1078 11 : osAttributeName = osNumber.substr(nPos + 1);
1079 11 : osNumber.resize(nPos);
1080 : }
1081 : else
1082 : {
1083 4 : osAttributeName.clear();
1084 15 : }
1085 : }
1086 : else
1087 : {
1088 88 : osNumber.clear();
1089 88 : if (pszName[i] == '_')
1090 : {
1091 29 : osAttributeName = pszName + i + 1;
1092 : }
1093 : else
1094 : {
1095 59 : osAttributeName.clear();
1096 : }
1097 : }
1098 103 : }
1099 :
1100 : /************************************************************************/
1101 : /* OGRGeoRSSLayerWriteSimpleElement() */
1102 : /************************************************************************/
1103 :
1104 8 : static void OGRGeoRSSLayerWriteSimpleElement(VSILFILE *fp,
1105 : const char *pszElementName,
1106 : const char *pszNumber,
1107 : const char *const *papszNames,
1108 : OGRFeatureDefn *poFeatureDefn,
1109 : OGRFeature *poFeature)
1110 : {
1111 8 : VSIFPrintfL(fp, " <%s", pszElementName);
1112 :
1113 152 : for (unsigned int k = 0; papszNames[k] != nullptr; k++)
1114 : {
1115 144 : if (strncmp(papszNames[k], pszElementName, strlen(pszElementName)) ==
1116 20 : 0 &&
1117 20 : papszNames[k][strlen(pszElementName)] == '_')
1118 : {
1119 14 : const char *pszAttributeName =
1120 14 : papszNames[k] + strlen(pszElementName) + 1;
1121 14 : char *pszFieldName = CPLStrdup(CPLSPrintf(
1122 : "%s%s_%s", pszElementName, pszNumber, pszAttributeName));
1123 14 : int iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1124 14 : if (iIndex != -1 && poFeature->IsFieldSetAndNotNull(iIndex))
1125 : {
1126 13 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1127 : poFeature->GetFieldAsString(iIndex));
1128 13 : VSIFPrintfL(fp, " %s=\"%s\"", pszAttributeName, pszValue);
1129 13 : CPLFree(pszValue);
1130 : }
1131 14 : CPLFree(pszFieldName);
1132 : }
1133 : }
1134 :
1135 : char *pszFieldName =
1136 8 : CPLStrdup(CPLSPrintf("%s%s", pszElementName, pszNumber));
1137 8 : const int iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1138 8 : if (iIndex != -1 && poFeature->IsFieldSetAndNotNull(iIndex))
1139 : {
1140 6 : VSIFPrintfL(fp, ">");
1141 :
1142 : char *pszValue =
1143 6 : OGRGetXML_UTF8_EscapedString(poFeature->GetFieldAsString(iIndex));
1144 6 : VSIFPrintfL(fp, "%s", pszValue);
1145 6 : CPLFree(pszValue);
1146 :
1147 6 : VSIFPrintfL(fp, "</%s>\n", pszElementName);
1148 : }
1149 : else
1150 : {
1151 2 : VSIFPrintfL(fp, "/>\n");
1152 : }
1153 8 : CPLFree(pszFieldName);
1154 8 : }
1155 :
1156 : /************************************************************************/
1157 : /* ICreateFeature() */
1158 : /************************************************************************/
1159 :
1160 95 : OGRErr OGRGeoRSSLayer::ICreateFeature(OGRFeature *poFeatureIn)
1161 :
1162 : {
1163 95 : VSILFILE *fp = poDS->GetOutputFP();
1164 95 : if (fp == nullptr)
1165 0 : return OGRERR_FAILURE;
1166 :
1167 95 : nNextFID++;
1168 :
1169 : // Verify that compulsory feeds are set.
1170 : // Otherwise put some default value in them.
1171 95 : if (eFormat == GEORSS_RSS)
1172 : {
1173 94 : const int iFieldTitle = poFeatureDefn->GetFieldIndex("title");
1174 : const int iFieldDescription =
1175 94 : poFeatureDefn->GetFieldIndex("description");
1176 :
1177 94 : VSIFPrintfL(fp, " <item>\n");
1178 :
1179 106 : if ((iFieldTitle == -1 ||
1180 94 : !poFeatureIn->IsFieldSetAndNotNull(iFieldTitle)) &&
1181 0 : (iFieldDescription == -1 ||
1182 0 : !poFeatureIn->IsFieldSetAndNotNull(iFieldDescription)))
1183 : {
1184 82 : VSIFPrintfL(fp, " <title>Feature %d</title>\n", nNextFID);
1185 : }
1186 : }
1187 : else
1188 : {
1189 1 : VSIFPrintfL(fp, " <entry>\n");
1190 :
1191 1 : const int iFieldId = poFeatureDefn->GetFieldIndex("id");
1192 1 : const int iFieldTitle = poFeatureDefn->GetFieldIndex("title");
1193 1 : const int iFieldUpdated = poFeatureDefn->GetFieldIndex("updated");
1194 :
1195 1 : if (iFieldId == -1 || !poFeatureIn->IsFieldSetAndNotNull(iFieldId))
1196 : {
1197 0 : VSIFPrintfL(fp, " <id>Feature %d</id>\n", nNextFID);
1198 : }
1199 :
1200 2 : if (iFieldTitle == -1 ||
1201 1 : !poFeatureIn->IsFieldSetAndNotNull(iFieldTitle))
1202 : {
1203 0 : VSIFPrintfL(fp, " <title>Title for feature %d</title>\n",
1204 : nNextFID);
1205 : }
1206 :
1207 2 : if (iFieldUpdated == -1 ||
1208 1 : !poFeatureIn->IsFieldSetAndNotNull(iFieldUpdated))
1209 : {
1210 0 : VSIFPrintfL(fp, " <updated>2009-01-01T00:00:00Z</updated>\n");
1211 : }
1212 : }
1213 :
1214 95 : const int nFieldCount = poFeatureDefn->GetFieldCount();
1215 95 : int *pbUsed = static_cast<int *>(CPLCalloc(sizeof(int), nFieldCount));
1216 :
1217 226 : for (int i = 0; i < nFieldCount; i++)
1218 : {
1219 131 : OGRFieldDefn *poFieldDefn = poFeatureDefn->GetFieldDefn(i);
1220 131 : const char *pszName = poFieldDefn->GetNameRef();
1221 :
1222 131 : if (!poFeatureIn->IsFieldSetAndNotNull(i))
1223 48 : continue;
1224 :
1225 166 : std::string osElementName;
1226 166 : std::string osNumber;
1227 166 : std::string osAttributeName;
1228 83 : OGRGeoRSSLayerSplitComposedField(pszName, osElementName, osNumber,
1229 : osAttributeName);
1230 :
1231 83 : bool bWillSkip = false;
1232 : // Handle Atom entries with elements with sub-elements like
1233 : // <author><name>...</name><uri>...</uri></author>
1234 83 : if (eFormat == GEORSS_ATOM)
1235 : {
1236 52 : for (unsigned int k = 0;
1237 52 : apszAllowedATOMFieldNamesWithSubElements[k] != nullptr; k++)
1238 : {
1239 74 : if (osElementName ==
1240 42 : apszAllowedATOMFieldNamesWithSubElements[k] &&
1241 5 : !osAttributeName.empty())
1242 : {
1243 5 : bWillSkip = true;
1244 5 : if (pbUsed[i])
1245 2 : break;
1246 :
1247 3 : VSIFPrintfL(fp, " <%s>\n", osElementName.c_str());
1248 :
1249 23 : for (int j = i; j < nFieldCount; j++)
1250 : {
1251 20 : poFieldDefn = poFeatureDefn->GetFieldDefn(j);
1252 20 : if (!poFeatureIn->IsFieldSetAndNotNull(j))
1253 0 : continue;
1254 :
1255 40 : std::string osElementName2;
1256 40 : std::string osNumber2;
1257 40 : std::string osAttributeName2;
1258 20 : OGRGeoRSSLayerSplitComposedField(
1259 : poFieldDefn->GetNameRef(), osElementName2,
1260 : osNumber2, osAttributeName2);
1261 :
1262 26 : if (osElementName2 == osElementName &&
1263 26 : osNumber2 == osNumber && !osAttributeName2.empty())
1264 : {
1265 5 : pbUsed[j] = TRUE;
1266 :
1267 5 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1268 : poFeatureIn->GetFieldAsString(j));
1269 5 : VSIFPrintfL(fp, " <%s>%s</%s>\n",
1270 : osAttributeName2.c_str(), pszValue,
1271 : osAttributeName2.c_str());
1272 5 : CPLFree(pszValue);
1273 : }
1274 : }
1275 :
1276 3 : VSIFPrintfL(fp, " </%s>\n", osElementName.c_str());
1277 :
1278 3 : break;
1279 : }
1280 : }
1281 : }
1282 :
1283 83 : if (bWillSkip)
1284 : {
1285 : // Do nothing
1286 : }
1287 78 : else if (eFormat == GEORSS_RSS && strcmp(pszName, "pubDate") == 0)
1288 : {
1289 12 : const OGRField *psField = poFeatureIn->GetRawFieldRef(i);
1290 12 : char *pszDate = OGRGetRFC822DateTime(psField);
1291 12 : VSIFPrintfL(fp, " <%s>%s</%s>\n", pszName, pszDate, pszName);
1292 12 : CPLFree(pszDate);
1293 : }
1294 66 : else if (eFormat == GEORSS_ATOM && (strcmp(pszName, "updated") == 0 ||
1295 14 : strcmp(pszName, "published") == 0))
1296 : {
1297 2 : const OGRField *psField = poFeatureIn->GetRawFieldRef(i);
1298 2 : char *pszDate = OGRGetXMLDateTime(psField);
1299 2 : VSIFPrintfL(fp, " <%s>%s</%s>\n", pszName, pszDate, pszName);
1300 2 : CPLFree(pszDate);
1301 : }
1302 64 : else if (strcmp(pszName, "dc_date") == 0)
1303 : {
1304 0 : const OGRField *psField = poFeatureIn->GetRawFieldRef(i);
1305 0 : char *pszDate = OGRGetXMLDateTime(psField);
1306 0 : VSIFPrintfL(fp, " <%s>%s</%s>\n", "dc:date", pszDate,
1307 : "dc:date");
1308 0 : CPLFree(pszDate);
1309 : }
1310 : // RSS fields with content and attributes.
1311 154 : else if (eFormat == GEORSS_RSS &&
1312 129 : (osElementName == "category" || osElementName == "guid" ||
1313 39 : osElementName == "source"))
1314 : {
1315 12 : if (osAttributeName.empty())
1316 : {
1317 6 : OGRGeoRSSLayerWriteSimpleElement(
1318 : fp, osElementName.c_str(), osNumber.c_str(),
1319 : apszAllowedRSSFieldNames, poFeatureDefn, poFeatureIn);
1320 : }
1321 : }
1322 : // RSS field with attribute only.
1323 52 : else if (eFormat == GEORSS_RSS && osElementName == "enclosure")
1324 : {
1325 0 : if (osAttributeName == "url")
1326 : {
1327 0 : OGRGeoRSSLayerWriteSimpleElement(
1328 : fp, osElementName.c_str(), osNumber.c_str(),
1329 : apszAllowedRSSFieldNames, poFeatureDefn, poFeatureIn);
1330 : }
1331 : }
1332 : /* ATOM fields with attribute only */
1333 78 : else if (eFormat == GEORSS_ATOM &&
1334 26 : (osElementName == "category" || osElementName == "link"))
1335 : {
1336 21 : if ((osElementName == "category" && osAttributeName == "term") ||
1337 14 : (osElementName == "link" && osAttributeName == "href"))
1338 : {
1339 2 : OGRGeoRSSLayerWriteSimpleElement(
1340 : fp, osElementName.c_str(), osNumber.c_str(),
1341 : apszAllowedATOMFieldNames, poFeatureDefn, poFeatureIn);
1342 : }
1343 : }
1344 45 : else if (eFormat == GEORSS_ATOM && (STARTS_WITH(pszName, "content") ||
1345 2 : STARTS_WITH(pszName, "summary")))
1346 : {
1347 4 : if (strchr(pszName, '_') == nullptr)
1348 : {
1349 1 : VSIFPrintfL(fp, " <%s", pszName);
1350 :
1351 1 : bool bIsXHTML = false;
1352 : char *pszFieldName =
1353 1 : CPLStrdup(CPLSPrintf("%s_%s", pszName, "type"));
1354 1 : int iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1355 1 : if (iIndex != -1 && poFeatureIn->IsFieldSetAndNotNull(iIndex))
1356 : {
1357 1 : bIsXHTML = strcmp(poFeatureIn->GetFieldAsString(iIndex),
1358 : "xhtml") == 0;
1359 1 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1360 : poFeatureIn->GetFieldAsString(iIndex));
1361 1 : VSIFPrintfL(fp, " %s=\"%s\"", "type", pszValue);
1362 1 : CPLFree(pszValue);
1363 : }
1364 1 : CPLFree(pszFieldName);
1365 :
1366 : pszFieldName =
1367 1 : CPLStrdup(CPLSPrintf("%s_%s", pszName, "xml_lang"));
1368 1 : iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1369 1 : if (iIndex != -1 && poFeatureIn->IsFieldSetAndNotNull(iIndex))
1370 : {
1371 1 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1372 : poFeatureIn->GetFieldAsString(iIndex));
1373 1 : VSIFPrintfL(fp, " %s=\"%s\"", "xml:lang", pszValue);
1374 1 : CPLFree(pszValue);
1375 : }
1376 1 : CPLFree(pszFieldName);
1377 :
1378 : pszFieldName =
1379 1 : CPLStrdup(CPLSPrintf("%s_%s", pszName, "xml_base"));
1380 1 : iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1381 1 : if (iIndex != -1 && poFeatureIn->IsFieldSetAndNotNull(iIndex))
1382 : {
1383 1 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1384 : poFeatureIn->GetFieldAsString(iIndex));
1385 1 : VSIFPrintfL(fp, " %s=\"%s\"", "xml:base", pszValue);
1386 1 : CPLFree(pszValue);
1387 : }
1388 1 : CPLFree(pszFieldName);
1389 :
1390 1 : VSIFPrintfL(fp, ">");
1391 1 : if (bIsXHTML)
1392 : {
1393 1 : VSIFPrintfL(fp, "%s", poFeatureIn->GetFieldAsString(i));
1394 : }
1395 : else
1396 : {
1397 0 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1398 : poFeatureIn->GetFieldAsString(i));
1399 0 : VSIFPrintfL(fp, "%s", pszValue);
1400 0 : CPLFree(pszValue);
1401 : }
1402 1 : VSIFPrintfL(fp, " </%s>\n", pszName);
1403 4 : }
1404 : }
1405 41 : else if (STARTS_WITH(pszName, "dc_subject"))
1406 : {
1407 0 : if (strchr(pszName + strlen("dc_subject"), '_') == nullptr)
1408 : {
1409 0 : VSIFPrintfL(fp, " <%s", "dc:subject");
1410 :
1411 : char *pszFieldName =
1412 0 : CPLStrdup(CPLSPrintf("%s_%s", pszName, "xml_lang"));
1413 0 : int iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1414 0 : if (iIndex != -1 && poFeatureIn->IsFieldSetAndNotNull(iIndex))
1415 : {
1416 0 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1417 : poFeatureIn->GetFieldAsString(iIndex));
1418 0 : VSIFPrintfL(fp, " %s=\"%s\"", "xml:lang", pszValue);
1419 0 : CPLFree(pszValue);
1420 : }
1421 0 : CPLFree(pszFieldName);
1422 :
1423 0 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1424 : poFeatureIn->GetFieldAsString(i));
1425 0 : VSIFPrintfL(fp, ">%s</%s>\n", pszValue, "dc:subject");
1426 0 : CPLFree(pszValue);
1427 : }
1428 : }
1429 : else
1430 : {
1431 41 : char *pszTagName = CPLStrdup(pszName);
1432 41 : if (IsStandardField(pszName) == FALSE)
1433 : {
1434 3 : int nCountUnderscore = 0;
1435 29 : for (int j = 0; pszTagName[j] != 0; j++)
1436 : {
1437 26 : if (pszTagName[j] == '_')
1438 : {
1439 2 : if (nCountUnderscore == 0)
1440 2 : pszTagName[j] = ':';
1441 2 : nCountUnderscore++;
1442 : }
1443 24 : else if (pszTagName[j] == ' ')
1444 0 : pszTagName[j] = '_';
1445 : }
1446 3 : if (nCountUnderscore == 0)
1447 : {
1448 1 : char *pszTemp = CPLStrdup(CPLSPrintf("ogr:%s", pszTagName));
1449 1 : CPLFree(pszTagName);
1450 1 : pszTagName = pszTemp;
1451 : }
1452 : }
1453 : char *pszValue =
1454 41 : OGRGetXML_UTF8_EscapedString(poFeatureIn->GetFieldAsString(i));
1455 41 : VSIFPrintfL(fp, " <%s>%s</%s>\n", pszTagName, pszValue,
1456 : pszTagName);
1457 41 : CPLFree(pszValue);
1458 41 : CPLFree(pszTagName);
1459 : }
1460 : }
1461 :
1462 95 : CPLFree(pbUsed);
1463 :
1464 95 : OGRGeoRSSGeomDialect eGeomDialect = poDS->GetGeomDialect();
1465 95 : OGRGeometry *poGeom = poFeatureIn->GetGeometryRef();
1466 95 : if (poGeom != nullptr && !poGeom->IsEmpty())
1467 : {
1468 59 : char *pszURN = nullptr;
1469 59 : bool bSwapCoordinates = false;
1470 59 : if (eGeomDialect == GEORSS_GML)
1471 : {
1472 5 : if (poSRS != nullptr)
1473 : {
1474 1 : const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
1475 1 : const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
1476 1 : if (pszAuthorityName != nullptr &&
1477 1 : EQUAL(pszAuthorityName, "EPSG") &&
1478 : pszAuthorityCode != nullptr)
1479 : {
1480 1 : if (!EQUAL(pszAuthorityCode, "4326"))
1481 1 : pszURN = CPLStrdup(CPLSPrintf(
1482 : "urn:ogc:def:crs:EPSG::%s", pszAuthorityCode));
1483 :
1484 : /* In case the SRS is a geographic SRS and that we have */
1485 : /* no axis definition, we assume that the order is */
1486 : /* lon/lat. */
1487 : const char *pszAxisName =
1488 1 : poSRS->GetAxis(nullptr, 0, nullptr);
1489 1 : if (poSRS->IsGeographic() &&
1490 0 : (pszAxisName == nullptr ||
1491 0 : STARTS_WITH_CI(pszAxisName, "Lon")))
1492 : {
1493 0 : bSwapCoordinates = true;
1494 1 : }
1495 : }
1496 : else
1497 : {
1498 : static bool bOnce = false;
1499 0 : if (!bOnce)
1500 : {
1501 0 : bOnce = true;
1502 0 : CPLError(CE_Warning, CPLE_AppDefined,
1503 : "Could not translate SRS into GML urn");
1504 : }
1505 : }
1506 : }
1507 : else
1508 : {
1509 4 : bSwapCoordinates = true;
1510 : }
1511 : }
1512 :
1513 59 : char szCoord[75] = {};
1514 59 : switch (wkbFlatten(poGeom->getGeometryType()))
1515 : {
1516 12 : case wkbPoint:
1517 : {
1518 12 : OGRPoint *poPoint = poGeom->toPoint();
1519 12 : const double x = poPoint->getX();
1520 12 : const double y = poPoint->getY();
1521 12 : if (eGeomDialect == GEORSS_GML)
1522 : {
1523 2 : VSIFPrintfL(fp, " <georss:where><gml:Point");
1524 2 : if (pszURN != nullptr)
1525 1 : VSIFPrintfL(fp, " srsName=\"%s\"", pszURN);
1526 2 : if (poGeom->getCoordinateDimension() == 3)
1527 : {
1528 0 : OGRMakeWktCoordinate(szCoord, bSwapCoordinates ? y : x,
1529 : bSwapCoordinates ? x : y,
1530 : poPoint->getZ(), 3);
1531 0 : VSIFPrintfL(fp, " srsDimension=\"3\"><gml:pos>%s",
1532 : szCoord);
1533 : }
1534 : else
1535 : {
1536 2 : OGRMakeWktCoordinate(szCoord, bSwapCoordinates ? y : x,
1537 : bSwapCoordinates ? x : y, 0, 2);
1538 2 : VSIFPrintfL(fp, "><gml:pos>%s", szCoord);
1539 : }
1540 2 : VSIFPrintfL(fp, "</gml:pos></gml:Point></georss:where>\n");
1541 : }
1542 10 : else if (eGeomDialect == GEORSS_SIMPLE)
1543 : {
1544 9 : OGRMakeWktCoordinate(szCoord, y, x, 0, 2);
1545 9 : VSIFPrintfL(fp, " <georss:point>%s</georss:point>\n",
1546 : szCoord);
1547 : }
1548 1 : else if (eGeomDialect == GEORSS_W3C_GEO)
1549 : {
1550 1 : OGRFormatDouble(szCoord, sizeof(szCoord), y, '.');
1551 1 : VSIFPrintfL(fp, " <geo:lat>%s</geo:lat>\n", szCoord);
1552 1 : OGRFormatDouble(szCoord, sizeof(szCoord), x, '.');
1553 1 : VSIFPrintfL(fp, " <geo:long>%s</geo:long>\n", szCoord);
1554 : }
1555 12 : break;
1556 : }
1557 :
1558 11 : case wkbLineString:
1559 : {
1560 11 : OGRLineString *poLineString = poGeom->toLineString();
1561 11 : if (eGeomDialect == GEORSS_GML)
1562 : {
1563 1 : VSIFPrintfL(fp, " <georss:where><gml:LineString");
1564 1 : if (pszURN != nullptr)
1565 0 : VSIFPrintfL(fp, " srsName=\"%s\"", pszURN);
1566 1 : VSIFPrintfL(fp, "><gml:posList>\n");
1567 1 : const int n = poLineString->getNumPoints();
1568 4 : for (int i = 0; i < n; i++)
1569 : {
1570 3 : const double x = poLineString->getX(i);
1571 3 : const double y = poLineString->getY(i);
1572 3 : OGRMakeWktCoordinate(szCoord, bSwapCoordinates ? y : x,
1573 : bSwapCoordinates ? x : y, 0, 2);
1574 3 : VSIFPrintfL(fp, "%s ", szCoord);
1575 : }
1576 1 : VSIFPrintfL(
1577 : fp, "</gml:posList></gml:LineString></georss:where>\n");
1578 : }
1579 10 : else if (eGeomDialect == GEORSS_SIMPLE)
1580 : {
1581 9 : VSIFPrintfL(fp, " <georss:line>\n");
1582 9 : int n = poLineString->getNumPoints();
1583 28 : for (int i = 0; i < n; i++)
1584 : {
1585 19 : double x = poLineString->getX(i);
1586 19 : double y = poLineString->getY(i);
1587 19 : OGRMakeWktCoordinate(szCoord, y, x, 0, 2);
1588 19 : VSIFPrintfL(fp, "%s ", szCoord);
1589 : }
1590 9 : VSIFPrintfL(fp, "</georss:line>\n");
1591 : }
1592 : else
1593 : {
1594 : // Not supported.
1595 : }
1596 11 : break;
1597 : }
1598 :
1599 12 : case wkbPolygon:
1600 : {
1601 12 : OGRPolygon *poPolygon = poGeom->toPolygon();
1602 12 : OGRLineString *poLineString = poPolygon->getExteriorRing();
1603 12 : if (poLineString == nullptr)
1604 0 : break;
1605 :
1606 12 : if (eGeomDialect == GEORSS_GML)
1607 : {
1608 2 : VSIFPrintfL(fp, " <georss:where><gml:Polygon");
1609 2 : if (pszURN != nullptr)
1610 0 : VSIFPrintfL(fp, " srsName=\"%s\"", pszURN);
1611 2 : VSIFPrintfL(
1612 : fp, "><gml:exterior><gml:LinearRing><gml:posList>\n");
1613 2 : const int n = poLineString->getNumPoints();
1614 12 : for (int i = 0; i < n; i++)
1615 : {
1616 10 : const double x = poLineString->getX(i);
1617 10 : const double y = poLineString->getY(i);
1618 10 : OGRMakeWktCoordinate(szCoord, bSwapCoordinates ? y : x,
1619 : bSwapCoordinates ? x : y, 0, 2);
1620 10 : VSIFPrintfL(fp, "%s ", szCoord);
1621 : }
1622 2 : VSIFPrintfL(fp,
1623 : "</gml:posList></gml:LinearRing></gml:exterior>"
1624 : "</gml:Polygon></georss:where>\n");
1625 : }
1626 10 : else if (eGeomDialect == GEORSS_SIMPLE)
1627 : {
1628 8 : VSIFPrintfL(fp, " <georss:polygon>\n");
1629 8 : const int n = poLineString->getNumPoints();
1630 48 : for (int i = 0; i < n; i++)
1631 : {
1632 40 : const double x = poLineString->getX(i);
1633 40 : const double y = poLineString->getY(i);
1634 40 : OGRMakeWktCoordinate(szCoord, y, x, 0, 2);
1635 40 : VSIFPrintfL(fp, "%s ", szCoord);
1636 : }
1637 8 : VSIFPrintfL(fp, "</georss:polygon>\n");
1638 : }
1639 : else
1640 : {
1641 : /* Not supported */
1642 : }
1643 12 : break;
1644 : }
1645 :
1646 24 : default:
1647 : /* Not supported */
1648 24 : break;
1649 : }
1650 59 : CPLFree(pszURN);
1651 : }
1652 :
1653 95 : if (eFormat == GEORSS_RSS)
1654 94 : VSIFPrintfL(fp, " </item>\n");
1655 : else
1656 1 : VSIFPrintfL(fp, " </entry>\n");
1657 :
1658 95 : return OGRERR_NONE;
1659 : }
1660 :
1661 : /************************************************************************/
1662 : /* CreateField() */
1663 : /************************************************************************/
1664 :
1665 146 : OGRErr OGRGeoRSSLayer::CreateField(const OGRFieldDefn *poFieldDefn,
1666 : CPL_UNUSED int bApproxOK)
1667 : {
1668 146 : const char *pszName = poFieldDefn->GetNameRef();
1669 126 : if (((eFormat == GEORSS_RSS && strcmp(pszName, "pubDate") == 0) ||
1670 143 : (eFormat == GEORSS_ATOM && (strcmp(pszName, "updated") == 0 ||
1671 19 : strcmp(pszName, "published") == 0)) ||
1672 292 : strcmp(pszName, "dc:date") == 0) &&
1673 5 : poFieldDefn->GetType() != OFTDateTime)
1674 : {
1675 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong field type for %s",
1676 : pszName);
1677 0 : return OGRERR_FAILURE;
1678 : }
1679 :
1680 447 : for (int iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++)
1681 : {
1682 301 : if (strcmp(poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
1683 301 : pszName) == 0)
1684 : {
1685 0 : return OGRERR_FAILURE;
1686 : }
1687 : }
1688 :
1689 146 : if (IsStandardField(pszName))
1690 : {
1691 47 : poFeatureDefn->AddFieldDefn(poFieldDefn);
1692 47 : return OGRERR_NONE;
1693 : }
1694 :
1695 99 : if (poDS->GetUseExtensions() == FALSE)
1696 : {
1697 96 : CPLError(
1698 : CE_Failure, CPLE_NotSupported,
1699 : "Field of name '%s' is not supported in %s schema. "
1700 : "Use USE_EXTENSIONS creation option to allow use of extensions.",
1701 96 : pszName, (eFormat == GEORSS_RSS) ? "RSS" : "ATOM");
1702 96 : return OGRERR_FAILURE;
1703 : }
1704 : else
1705 : {
1706 3 : poFeatureDefn->AddFieldDefn(poFieldDefn);
1707 3 : return OGRERR_NONE;
1708 : }
1709 : }
1710 :
1711 : #ifdef HAVE_EXPAT
1712 :
1713 304 : static void XMLCALL startElementLoadSchemaCbk(void *pUserData,
1714 : const char *pszName,
1715 : const char **ppszAttr)
1716 : {
1717 304 : ((OGRGeoRSSLayer *)pUserData)->startElementLoadSchemaCbk(pszName, ppszAttr);
1718 304 : }
1719 :
1720 304 : static void XMLCALL endElementLoadSchemaCbk(void *pUserData,
1721 : const char *pszName)
1722 : {
1723 304 : ((OGRGeoRSSLayer *)pUserData)->endElementLoadSchemaCbk(pszName);
1724 304 : }
1725 :
1726 889 : static void XMLCALL dataHandlerLoadSchemaCbk(void *pUserData, const char *data,
1727 : int nLen)
1728 : {
1729 889 : ((OGRGeoRSSLayer *)pUserData)->dataHandlerLoadSchemaCbk(data, nLen);
1730 889 : }
1731 :
1732 : /************************************************************************/
1733 : /* LoadSchema() */
1734 : /************************************************************************/
1735 :
1736 : /** This function parses the whole file to detect the fields */
1737 67 : void OGRGeoRSSLayer::LoadSchema()
1738 : {
1739 67 : if (bHasReadSchema)
1740 54 : return;
1741 :
1742 67 : bHasReadSchema = true;
1743 :
1744 67 : if (fpGeoRSS == nullptr)
1745 54 : return;
1746 :
1747 13 : oSchemaParser = OGRCreateExpatXMLParser();
1748 13 : XML_SetElementHandler(oSchemaParser, ::startElementLoadSchemaCbk,
1749 : ::endElementLoadSchemaCbk);
1750 13 : XML_SetCharacterDataHandler(oSchemaParser, ::dataHandlerLoadSchemaCbk);
1751 13 : XML_SetUserData(oSchemaParser, this);
1752 :
1753 13 : VSIFSeekL(fpGeoRSS, 0, SEEK_SET);
1754 :
1755 13 : bInFeature = false;
1756 13 : currentDepth = 0;
1757 13 : currentFieldDefn = nullptr;
1758 13 : pszSubElementName = nullptr;
1759 13 : pszSubElementValue = nullptr;
1760 13 : nSubElementValueLen = 0;
1761 13 : bSameSRS = true;
1762 13 : CPLFree(pszGMLSRSName);
1763 13 : pszGMLSRSName = nullptr;
1764 13 : eGeomType = wkbUnknown;
1765 13 : bFoundGeom = false;
1766 13 : bInTagWithSubTag = false;
1767 13 : pszTagWithSubTag = nullptr;
1768 13 : bStopParsing = false;
1769 13 : nWithoutEventCounter = 0;
1770 13 : nTotalFeatureCount = 0;
1771 13 : setOfFoundFields = nullptr;
1772 :
1773 26 : std::vector<char> aBuf(PARSER_BUF_SIZE);
1774 13 : int nDone = 0;
1775 0 : do
1776 : {
1777 13 : nDataHandlerCounter = 0;
1778 : unsigned int nLen =
1779 13 : (unsigned int)VSIFReadL(aBuf.data(), 1, aBuf.size(), fpGeoRSS);
1780 13 : nDone = nLen < aBuf.size();
1781 13 : if (XML_Parse(oSchemaParser, aBuf.data(), nLen, nDone) ==
1782 : XML_STATUS_ERROR)
1783 : {
1784 0 : CPLError(
1785 : CE_Failure, CPLE_AppDefined,
1786 : "XML parsing of GeoRSS file failed : %s at line %d, column %d",
1787 : XML_ErrorString(XML_GetErrorCode(oSchemaParser)),
1788 0 : static_cast<int>(XML_GetCurrentLineNumber(oSchemaParser)),
1789 0 : static_cast<int>(XML_GetCurrentColumnNumber(oSchemaParser)));
1790 0 : bStopParsing = true;
1791 : }
1792 13 : nWithoutEventCounter++;
1793 13 : } while (!nDone && !bStopParsing && nWithoutEventCounter < 10);
1794 :
1795 13 : XML_ParserFree(oSchemaParser);
1796 :
1797 13 : if (nWithoutEventCounter == 10)
1798 : {
1799 0 : CPLError(CE_Failure, CPLE_AppDefined,
1800 : "Too much data inside one element. File probably corrupted");
1801 0 : bStopParsing = true;
1802 : }
1803 :
1804 13 : CPLAssert(poSRS == nullptr);
1805 13 : if (bSameSRS && bFoundGeom)
1806 : {
1807 9 : if (pszGMLSRSName == nullptr)
1808 : {
1809 8 : poSRS = new OGRSpatialReference();
1810 8 : poSRS->SetWellKnownGeogCS("WGS84");
1811 8 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1812 : }
1813 : else
1814 : {
1815 1 : poSRS = new OGRSpatialReference();
1816 1 : poSRS->importFromURN(pszGMLSRSName);
1817 : }
1818 : }
1819 :
1820 13 : if (eGeomType != wkbUnknown)
1821 5 : poFeatureDefn->SetGeomType(eGeomType);
1822 13 : if (poFeatureDefn->GetGeomFieldCount() != 0)
1823 13 : poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
1824 :
1825 13 : if (setOfFoundFields)
1826 13 : CPLHashSetDestroy(setOfFoundFields);
1827 13 : setOfFoundFields = nullptr;
1828 13 : CPLFree(pszGMLSRSName);
1829 13 : pszGMLSRSName = nullptr;
1830 13 : CPLFree(pszTagWithSubTag);
1831 13 : pszTagWithSubTag = nullptr;
1832 :
1833 13 : VSIFSeekL(fpGeoRSS, 0, SEEK_SET);
1834 : }
1835 :
1836 : /************************************************************************/
1837 : /* startElementLoadSchemaCbk() */
1838 : /************************************************************************/
1839 :
1840 304 : void OGRGeoRSSLayer::startElementLoadSchemaCbk(const char *pszName,
1841 : const char **ppszAttr)
1842 : {
1843 304 : if (bStopParsing)
1844 0 : return;
1845 :
1846 304 : nWithoutEventCounter = 0;
1847 304 : const char *pszNoNSName = pszName;
1848 304 : const char *pszColon = strchr(pszNoNSName, ':');
1849 304 : if (pszColon)
1850 77 : pszNoNSName = pszColon + 1;
1851 :
1852 304 : if ((eFormat == GEORSS_ATOM && currentDepth == 1 &&
1853 23 : strcmp(pszNoNSName, "entry") == 0) ||
1854 301 : ((eFormat == GEORSS_RSS || eFormat == GEORSS_RSS_RDF) && !bInFeature &&
1855 68 : (currentDepth == 1 || currentDepth == 2) &&
1856 58 : strcmp(pszNoNSName, "item") == 0))
1857 : {
1858 28 : bInFeature = true;
1859 28 : featureDepth = currentDepth;
1860 :
1861 28 : nTotalFeatureCount++;
1862 :
1863 28 : if (setOfFoundFields)
1864 15 : CPLHashSetDestroy(setOfFoundFields);
1865 28 : setOfFoundFields =
1866 28 : CPLHashSetNew(CPLHashSetHashStr, CPLHashSetEqualStr, CPLFree);
1867 : }
1868 276 : else if (bInTagWithSubTag && currentDepth == 3)
1869 : {
1870 : char *pszFieldName =
1871 15 : CPLStrdup(CPLSPrintf("%s_%s", pszTagWithSubTag, pszNoNSName));
1872 15 : if (poFeatureDefn->GetFieldIndex(pszFieldName) == -1)
1873 : {
1874 30 : OGRFieldDefn newFieldDefn(pszFieldName, OFTString);
1875 15 : poFeatureDefn->AddFieldDefn(&newFieldDefn);
1876 :
1877 15 : if (poFeatureDefn->GetFieldCount() == 100)
1878 : {
1879 0 : CPLError(CE_Failure, CPLE_AppDefined,
1880 : "Too many fields. File probably corrupted");
1881 0 : XML_StopParser(oSchemaParser, XML_FALSE);
1882 0 : bStopParsing = true;
1883 : }
1884 : }
1885 15 : CPLFree(pszFieldName);
1886 : }
1887 291 : else if (bInFeature && eFormat == GEORSS_ATOM && currentDepth == 2 &&
1888 30 : OGRGeoRSSLayerATOMTagHasSubElement(pszNoNSName))
1889 : {
1890 9 : CPLFree(pszTagWithSubTag);
1891 9 : pszTagWithSubTag = CPLStrdup(pszNoNSName);
1892 :
1893 9 : int count = 1;
1894 12 : while (CPLHashSetLookup(setOfFoundFields, pszTagWithSubTag) != nullptr)
1895 : {
1896 3 : count++;
1897 3 : CPLFree(pszTagWithSubTag);
1898 3 : pszTagWithSubTag =
1899 3 : CPLStrdup(CPLSPrintf("%s%d", pszNoNSName, count));
1900 3 : if (pszTagWithSubTag[0] == 0)
1901 : {
1902 0 : XML_StopParser(oSchemaParser, XML_FALSE);
1903 0 : bStopParsing = true;
1904 0 : break;
1905 : }
1906 : }
1907 9 : CPLHashSetInsert(setOfFoundFields, CPLStrdup(pszTagWithSubTag));
1908 :
1909 9 : bInTagWithSubTag = true;
1910 : }
1911 252 : else if (bInFeature && currentDepth == featureDepth + 1 &&
1912 149 : !IS_GEO_ELEMENT(pszName))
1913 : {
1914 127 : if (pszName != pszNoNSName && STARTS_WITH(pszName, "atom:"))
1915 7 : pszName = pszNoNSName;
1916 :
1917 127 : CPLFree(pszSubElementName);
1918 127 : pszSubElementName = CPLStrdup(pszName);
1919 :
1920 127 : int count = 1;
1921 135 : while (CPLHashSetLookup(setOfFoundFields, pszSubElementName) != nullptr)
1922 : {
1923 8 : count++;
1924 8 : CPLFree(pszSubElementName);
1925 8 : pszSubElementName = CPLStrdup(CPLSPrintf("%s%d", pszName, count));
1926 : }
1927 127 : CPLHashSetInsert(setOfFoundFields, CPLStrdup(pszSubElementName));
1928 :
1929 : // Create field definition for element.
1930 : char *pszCompatibleName =
1931 127 : OGRGeoRSS_GetOGRCompatibleTagName(pszSubElementName);
1932 127 : int iField = poFeatureDefn->GetFieldIndex(pszCompatibleName);
1933 127 : if (iField >= 0)
1934 : {
1935 66 : currentFieldDefn = poFeatureDefn->GetFieldDefn(iField);
1936 : }
1937 61 : else if (!((eFormat == GEORSS_RSS || eFormat == GEORSS_RSS_RDF) &&
1938 40 : strcmp(pszNoNSName, "enclosure") == 0) &&
1939 61 : !(eFormat == GEORSS_ATOM &&
1940 21 : strcmp(pszNoNSName, "link") == 0) &&
1941 55 : !(eFormat == GEORSS_ATOM &&
1942 15 : strcmp(pszNoNSName, "category") == 0))
1943 : {
1944 : OGRFieldType eFieldType;
1945 55 : if (((eFormat == GEORSS_RSS || eFormat == GEORSS_RSS_RDF) &&
1946 40 : strcmp(pszNoNSName, "pubDate") == 0) ||
1947 49 : (eFormat == GEORSS_ATOM &&
1948 15 : strcmp(pszNoNSName, "updated") == 0) ||
1949 46 : (eFormat == GEORSS_ATOM &&
1950 12 : strcmp(pszNoNSName, "published") == 0) ||
1951 43 : strcmp(pszName, "dc:date") == 0)
1952 12 : eFieldType = OFTDateTime;
1953 : else
1954 43 : eFieldType = OFTInteger;
1955 :
1956 110 : OGRFieldDefn newFieldDefn(pszCompatibleName, eFieldType);
1957 55 : poFeatureDefn->AddFieldDefn(&newFieldDefn);
1958 55 : currentFieldDefn =
1959 55 : poFeatureDefn->GetFieldDefn(poFeatureDefn->GetFieldCount() - 1);
1960 :
1961 55 : if (poFeatureDefn->GetFieldCount() == 100)
1962 : {
1963 0 : CPLError(CE_Failure, CPLE_AppDefined,
1964 : "Too many fields. File probably corrupted");
1965 0 : XML_StopParser(oSchemaParser, XML_FALSE);
1966 0 : bStopParsing = true;
1967 : }
1968 : }
1969 :
1970 : // Create field definitions for attributes.
1971 168 : for (int i = 0; ppszAttr[i] != nullptr && ppszAttr[i + 1] != nullptr &&
1972 41 : !bStopParsing;
1973 41 : i += 2)
1974 : {
1975 41 : char *pszAttrCompatibleName = OGRGeoRSS_GetOGRCompatibleTagName(
1976 41 : CPLSPrintf("%s_%s", pszSubElementName, ppszAttr[i]));
1977 41 : iField = poFeatureDefn->GetFieldIndex(pszAttrCompatibleName);
1978 41 : OGRFieldDefn *currentAttrFieldDefn = nullptr;
1979 41 : if (iField >= 0)
1980 : {
1981 0 : currentAttrFieldDefn = poFeatureDefn->GetFieldDefn(iField);
1982 : }
1983 : else
1984 : {
1985 82 : OGRFieldDefn newFieldDefn(pszAttrCompatibleName, OFTInteger);
1986 41 : poFeatureDefn->AddFieldDefn(&newFieldDefn);
1987 123 : currentAttrFieldDefn = poFeatureDefn->GetFieldDefn(
1988 41 : poFeatureDefn->GetFieldCount() - 1);
1989 :
1990 41 : if (poFeatureDefn->GetFieldCount() == 100)
1991 : {
1992 0 : CPLError(CE_Failure, CPLE_AppDefined,
1993 : "Too many fields. File probably corrupted");
1994 0 : XML_StopParser(oSchemaParser, XML_FALSE);
1995 0 : bStopParsing = true;
1996 : }
1997 : }
1998 41 : if (currentAttrFieldDefn->GetType() == OFTInteger ||
1999 0 : currentAttrFieldDefn->GetType() == OFTReal)
2000 : {
2001 41 : const CPLValueType eType = CPLGetValueType(ppszAttr[i + 1]);
2002 41 : if (eType == CPL_VALUE_REAL)
2003 : {
2004 0 : currentAttrFieldDefn->SetType(OFTReal);
2005 : }
2006 41 : else if (eType == CPL_VALUE_STRING)
2007 : {
2008 38 : currentAttrFieldDefn->SetType(OFTString);
2009 : }
2010 : }
2011 41 : CPLFree(pszAttrCompatibleName);
2012 : }
2013 :
2014 127 : CPLFree(pszCompatibleName);
2015 : }
2016 125 : else if (strcmp(pszName, "georss:point") == 0 ||
2017 122 : strcmp(pszName, "georss:line") == 0 ||
2018 120 : strcmp(pszName, "geo:line") == 0 || IS_LAT_ELEMENT(pszName) ||
2019 119 : strcmp(pszName, "georss:polygon") == 0 ||
2020 116 : strcmp(pszName, "georss:box") == 0)
2021 : {
2022 11 : if (bSameSRS)
2023 : {
2024 11 : if (pszGMLSRSName != nullptr)
2025 0 : bSameSRS = false;
2026 : }
2027 : }
2028 114 : else if (strcmp(pszName, "gml:Point") == 0 ||
2029 111 : strcmp(pszName, "gml:LineString") == 0 ||
2030 108 : strcmp(pszName, "gml:Polygon") == 0 ||
2031 105 : strcmp(pszName, "gml:MultiPoint") == 0 ||
2032 105 : strcmp(pszName, "gml:MultiLineString") == 0 ||
2033 105 : strcmp(pszName, "gml:MultiPolygon") == 0 ||
2034 105 : strcmp(pszName, "gml:Envelope") == 0)
2035 : {
2036 10 : if (bSameSRS)
2037 : {
2038 10 : bool bFoundSRS = false;
2039 10 : for (int i = 0; ppszAttr[i] != nullptr; i += 2)
2040 : {
2041 1 : if (strcmp(ppszAttr[i], "srsName") == 0)
2042 : {
2043 1 : bFoundSRS = true;
2044 1 : if (pszGMLSRSName != nullptr)
2045 : {
2046 0 : if (strcmp(pszGMLSRSName, ppszAttr[i + 1]) != 0)
2047 0 : bSameSRS = false;
2048 : }
2049 : else
2050 1 : pszGMLSRSName = CPLStrdup(ppszAttr[i + 1]);
2051 1 : break;
2052 : }
2053 : }
2054 10 : if (!bFoundSRS && pszGMLSRSName != nullptr)
2055 0 : bSameSRS = false;
2056 : }
2057 : }
2058 :
2059 304 : if (!bInFeature || currentDepth >= featureDepth + 1)
2060 : {
2061 276 : int nDimension = 2;
2062 367 : for (int i = 0; ppszAttr[i] != nullptr && ppszAttr[i + 1] != nullptr;
2063 91 : i += 2)
2064 : {
2065 91 : if (strcmp(ppszAttr[i], "srsDimension") == 0)
2066 : {
2067 0 : nDimension = atoi(ppszAttr[i + 1]);
2068 0 : break;
2069 : }
2070 : }
2071 :
2072 276 : OGRwkbGeometryType eFoundGeomType = wkbUnknown;
2073 276 : if (strcmp(pszName, "georss:point") == 0 || IS_LAT_ELEMENT(pszName) ||
2074 272 : strcmp(pszName, "gml:Point") == 0)
2075 : {
2076 7 : eFoundGeomType = wkbPoint;
2077 : }
2078 269 : else if (strcmp(pszName, "gml:MultiPoint") == 0)
2079 : {
2080 0 : eFoundGeomType = wkbMultiPoint;
2081 : }
2082 269 : else if (strcmp(pszName, "georss:line") == 0 ||
2083 267 : strcmp(pszName, "geo:line") == 0 ||
2084 267 : strcmp(pszName, "gml:LineString") == 0)
2085 : {
2086 5 : eFoundGeomType = wkbLineString;
2087 : }
2088 264 : else if (strcmp(pszName, "gml:MultiLineString") == 0)
2089 : {
2090 0 : eFoundGeomType = wkbMultiLineString;
2091 : }
2092 264 : else if (strcmp(pszName, "georss:polygon") == 0 ||
2093 261 : strcmp(pszName, "gml:Polygon") == 0 ||
2094 258 : strcmp(pszName, "gml:Envelope") == 0 ||
2095 257 : strcmp(pszName, "georss:box") == 0)
2096 : {
2097 9 : eFoundGeomType = wkbPolygon;
2098 : }
2099 255 : else if (strcmp(pszName, "gml:MultiPolygon") == 0)
2100 : {
2101 0 : eFoundGeomType = wkbMultiPolygon;
2102 : }
2103 :
2104 276 : if (eFoundGeomType != wkbUnknown)
2105 : {
2106 21 : if (!bFoundGeom)
2107 : {
2108 9 : eGeomType = eFoundGeomType;
2109 9 : bFoundGeom = true;
2110 : }
2111 12 : else if (wkbFlatten(eGeomType) != eFoundGeomType)
2112 : {
2113 12 : eGeomType = wkbUnknown;
2114 : }
2115 :
2116 21 : if (nDimension == 3)
2117 0 : eGeomType = wkbSetZ(eGeomType);
2118 : }
2119 : }
2120 :
2121 304 : currentDepth++;
2122 : }
2123 :
2124 : /************************************************************************/
2125 : /* endElementLoadSchemaCbk() */
2126 : /************************************************************************/
2127 :
2128 304 : void OGRGeoRSSLayer::endElementLoadSchemaCbk(const char *pszName)
2129 : {
2130 304 : if (bStopParsing)
2131 0 : return;
2132 :
2133 304 : nWithoutEventCounter = 0;
2134 :
2135 304 : currentDepth--;
2136 :
2137 304 : if (!bInFeature)
2138 67 : return;
2139 :
2140 237 : const char *pszNoNSName = pszName;
2141 237 : const char *pszColon = strchr(pszNoNSName, ':');
2142 237 : if (pszColon)
2143 68 : pszNoNSName = pszColon + 1;
2144 :
2145 237 : if ((eFormat == GEORSS_ATOM && currentDepth == 1 &&
2146 3 : strcmp(pszNoNSName, "entry") == 0) ||
2147 234 : ((eFormat == GEORSS_RSS || eFormat == GEORSS_RSS_RDF) &&
2148 180 : (currentDepth == 1 || currentDepth == 2) &&
2149 25 : strcmp(pszNoNSName, "item") == 0))
2150 : {
2151 28 : bInFeature = false;
2152 : }
2153 239 : else if (eFormat == GEORSS_ATOM && currentDepth == 2 &&
2154 30 : OGRGeoRSSLayerATOMTagHasSubElement(pszNoNSName))
2155 : {
2156 9 : bInTagWithSubTag = false;
2157 : }
2158 200 : else if (currentDepth == featureDepth + 1 && pszSubElementName)
2159 : {
2160 : // Patch field type.
2161 127 : if (pszSubElementValue && nSubElementValueLen && currentFieldDefn)
2162 : {
2163 121 : pszSubElementValue[nSubElementValueLen] = 0;
2164 199 : if (currentFieldDefn->GetType() == OFTInteger ||
2165 78 : currentFieldDefn->GetType() == OFTReal)
2166 : {
2167 43 : const CPLValueType eType = CPLGetValueType(pszSubElementValue);
2168 43 : if (eType == CPL_VALUE_REAL)
2169 : {
2170 0 : currentFieldDefn->SetType(OFTReal);
2171 : }
2172 43 : else if (eType == CPL_VALUE_STRING)
2173 : {
2174 42 : currentFieldDefn->SetType(OFTString);
2175 : }
2176 : }
2177 : }
2178 :
2179 127 : CPLFree(pszSubElementName);
2180 127 : pszSubElementName = nullptr;
2181 127 : CPLFree(pszSubElementValue);
2182 127 : pszSubElementValue = nullptr;
2183 127 : nSubElementValueLen = 0;
2184 127 : currentFieldDefn = nullptr;
2185 : }
2186 : }
2187 :
2188 : /************************************************************************/
2189 : /* dataHandlerLoadSchemaCbk() */
2190 : /************************************************************************/
2191 :
2192 889 : void OGRGeoRSSLayer::dataHandlerLoadSchemaCbk(const char *data, int nLen)
2193 : {
2194 889 : if (bStopParsing)
2195 0 : return;
2196 :
2197 889 : nDataHandlerCounter++;
2198 889 : if (nDataHandlerCounter >= PARSER_BUF_SIZE)
2199 : {
2200 0 : CPLError(CE_Failure, CPLE_AppDefined,
2201 : "File probably corrupted (million laugh pattern)");
2202 0 : XML_StopParser(oSchemaParser, XML_FALSE);
2203 0 : bStopParsing = true;
2204 0 : return;
2205 : }
2206 :
2207 889 : nWithoutEventCounter = 0;
2208 :
2209 889 : if (pszSubElementName)
2210 : {
2211 138 : char *pszNewSubElementValue = static_cast<char *>(VSI_REALLOC_VERBOSE(
2212 : pszSubElementValue, nSubElementValueLen + nLen + 1));
2213 138 : if (pszNewSubElementValue == nullptr)
2214 : {
2215 0 : XML_StopParser(oSchemaParser, XML_FALSE);
2216 0 : bStopParsing = true;
2217 0 : return;
2218 : }
2219 138 : pszSubElementValue = pszNewSubElementValue;
2220 138 : memcpy(pszSubElementValue + nSubElementValueLen, data, nLen);
2221 138 : nSubElementValueLen += nLen;
2222 138 : if (nSubElementValueLen > 100000)
2223 : {
2224 0 : CPLError(CE_Failure, CPLE_AppDefined,
2225 : "Too much data inside one element. "
2226 : "File probably corrupted");
2227 0 : XML_StopParser(oSchemaParser, XML_FALSE);
2228 0 : bStopParsing = true;
2229 : }
2230 : }
2231 : }
2232 : #else
2233 : void OGRGeoRSSLayer::LoadSchema()
2234 : {
2235 : }
2236 : #endif
2237 :
2238 : /************************************************************************/
2239 : /* TestCapability() */
2240 : /************************************************************************/
2241 :
2242 108 : int OGRGeoRSSLayer::TestCapability(const char *pszCap)
2243 :
2244 : {
2245 108 : if (EQUAL(pszCap, OLCFastFeatureCount))
2246 0 : return !bWriteMode && bHasReadSchema && m_poFilterGeom == nullptr &&
2247 0 : m_poAttrQuery == nullptr;
2248 :
2249 108 : else if (EQUAL(pszCap, OLCStringsAsUTF8))
2250 0 : return TRUE;
2251 :
2252 108 : else if (EQUAL(pszCap, OLCSequentialWrite))
2253 16 : return bWriteMode;
2254 92 : else if (EQUAL(pszCap, OLCCreateField))
2255 16 : return bWriteMode;
2256 76 : else if (EQUAL(pszCap, OLCZGeometries))
2257 0 : return TRUE;
2258 : else
2259 76 : return FALSE;
2260 : }
2261 :
2262 : /************************************************************************/
2263 : /* GetFeatureCount() */
2264 : /************************************************************************/
2265 :
2266 0 : GIntBig OGRGeoRSSLayer::GetFeatureCount(int bForce)
2267 :
2268 : {
2269 0 : if (bWriteMode)
2270 : {
2271 0 : CPLError(CE_Failure, CPLE_NotSupported,
2272 : "Cannot read features when writing a GeoRSS file");
2273 0 : return 0;
2274 : }
2275 :
2276 0 : if (!bHasReadSchema)
2277 0 : LoadSchema();
2278 :
2279 0 : if (m_poFilterGeom != nullptr || m_poAttrQuery != nullptr)
2280 0 : return OGRLayer::GetFeatureCount(bForce);
2281 :
2282 0 : return nTotalFeatureCount;
2283 : }
2284 :
2285 : /************************************************************************/
2286 : /* GetDataset() */
2287 : /************************************************************************/
2288 :
2289 19 : GDALDataset *OGRGeoRSSLayer::GetDataset()
2290 : {
2291 19 : return poDS;
2292 : }
|