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 348 : const OGRFeatureDefn *OGRGeoRSSLayer::GetLayerDefn() const
200 : {
201 348 : if (!bHasReadSchema)
202 63 : const_cast<OGRGeoRSSLayer *>(this)->LoadSchema();
203 :
204 348 : 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 : this->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 0 : OGRExpatGetParseError(oParser));
976 0 : bStopParsing = true;
977 : }
978 13 : } while (!nDone && !bStopParsing && this->nFeatureTabLength == 0);
979 :
980 13 : return this->nFeatureTabLength ? ppoFeatureTab[nFeatureTabIndex++]
981 13 : : nullptr;
982 : #else
983 : return nullptr;
984 : #endif
985 : }
986 :
987 : /************************************************************************/
988 : /* OGRGeoRSSLayerIsStandardFieldInternal() */
989 : /************************************************************************/
990 :
991 187 : static bool OGRGeoRSSLayerIsStandardFieldInternal(const char *pszName,
992 : const char *const *papszNames)
993 : {
994 2223 : for (unsigned int i = 0; papszNames[i] != nullptr; i++)
995 : {
996 2121 : if (strcmp(pszName, papszNames[i]) == 0)
997 : {
998 74 : return true;
999 : }
1000 :
1001 2047 : const char *pszUnderscore = strchr(papszNames[i], '_');
1002 2047 : if (pszUnderscore == nullptr)
1003 : {
1004 1115 : size_t nLen = strlen(papszNames[i]);
1005 1115 : if (strncmp(pszName, papszNames[i], nLen) == 0)
1006 : {
1007 12 : size_t k = nLen;
1008 18 : while (pszName[k] >= '0' && pszName[k] <= '9')
1009 6 : k++;
1010 12 : if (pszName[k] == '\0')
1011 3 : return true;
1012 : }
1013 : }
1014 : else
1015 : {
1016 932 : const size_t nLen =
1017 932 : static_cast<size_t>(pszUnderscore - papszNames[i]);
1018 932 : if (strncmp(pszName, papszNames[i], nLen) == 0)
1019 : {
1020 23 : size_t k = nLen;
1021 37 : while (pszName[k] >= '0' && pszName[k] <= '9')
1022 14 : k++;
1023 23 : if (pszName[k] == '_' &&
1024 23 : strcmp(pszName + k, pszUnderscore) == 0)
1025 8 : return true;
1026 : }
1027 : }
1028 : }
1029 102 : return false;
1030 : }
1031 :
1032 : /************************************************************************/
1033 : /* OGRGeoRSSLayer::IsStandardField() */
1034 : /************************************************************************/
1035 :
1036 187 : bool OGRGeoRSSLayer::IsStandardField(const char *pszName)
1037 : {
1038 187 : if (eFormat == GEORSS_RSS)
1039 : {
1040 165 : return OGRGeoRSSLayerIsStandardFieldInternal(pszName,
1041 165 : apszAllowedRSSFieldNames);
1042 : }
1043 : else
1044 : {
1045 22 : return OGRGeoRSSLayerIsStandardFieldInternal(pszName,
1046 22 : apszAllowedATOMFieldNames);
1047 : }
1048 : }
1049 :
1050 : /************************************************************************/
1051 : /* OGRGeoRSSLayerSplitComposedField() */
1052 : /************************************************************************/
1053 :
1054 103 : static void OGRGeoRSSLayerSplitComposedField(const char *pszName,
1055 : std::string &osElementName,
1056 : std::string &osNumber,
1057 : std::string &osAttributeName)
1058 : {
1059 103 : osElementName = pszName;
1060 :
1061 103 : int i = 0;
1062 751 : while (pszName[i] != '\0' && pszName[i] != '_' &&
1063 663 : !(pszName[i] >= '0' && pszName[i] <= '9'))
1064 : {
1065 648 : i++;
1066 : }
1067 :
1068 103 : osElementName.resize(i);
1069 :
1070 103 : if (pszName[i] >= '0' && pszName[i] <= '9')
1071 : {
1072 15 : osNumber = pszName + i;
1073 15 : const auto nPos = osNumber.find('_');
1074 15 : if (nPos != std::string::npos)
1075 : {
1076 11 : osAttributeName = osNumber.substr(nPos + 1);
1077 11 : osNumber.resize(nPos);
1078 : }
1079 : else
1080 : {
1081 4 : osAttributeName.clear();
1082 15 : }
1083 : }
1084 : else
1085 : {
1086 88 : osNumber.clear();
1087 88 : if (pszName[i] == '_')
1088 : {
1089 29 : osAttributeName = pszName + i + 1;
1090 : }
1091 : else
1092 : {
1093 59 : osAttributeName.clear();
1094 : }
1095 : }
1096 103 : }
1097 :
1098 : /************************************************************************/
1099 : /* OGRGeoRSSLayerWriteSimpleElement() */
1100 : /************************************************************************/
1101 :
1102 8 : static void OGRGeoRSSLayerWriteSimpleElement(VSILFILE *fp,
1103 : const char *pszElementName,
1104 : const char *pszNumber,
1105 : const char *const *papszNames,
1106 : OGRFeatureDefn *poFeatureDefn,
1107 : OGRFeature *poFeature)
1108 : {
1109 8 : VSIFPrintfL(fp, " <%s", pszElementName);
1110 :
1111 152 : for (unsigned int k = 0; papszNames[k] != nullptr; k++)
1112 : {
1113 144 : if (strncmp(papszNames[k], pszElementName, strlen(pszElementName)) ==
1114 20 : 0 &&
1115 20 : papszNames[k][strlen(pszElementName)] == '_')
1116 : {
1117 14 : const char *pszAttributeName =
1118 14 : papszNames[k] + strlen(pszElementName) + 1;
1119 14 : char *pszFieldName = CPLStrdup(CPLSPrintf(
1120 : "%s%s_%s", pszElementName, pszNumber, pszAttributeName));
1121 14 : int iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1122 14 : if (iIndex != -1 && poFeature->IsFieldSetAndNotNull(iIndex))
1123 : {
1124 13 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1125 : poFeature->GetFieldAsString(iIndex));
1126 13 : VSIFPrintfL(fp, " %s=\"%s\"", pszAttributeName, pszValue);
1127 13 : CPLFree(pszValue);
1128 : }
1129 14 : CPLFree(pszFieldName);
1130 : }
1131 : }
1132 :
1133 : char *pszFieldName =
1134 8 : CPLStrdup(CPLSPrintf("%s%s", pszElementName, pszNumber));
1135 8 : const int iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1136 8 : if (iIndex != -1 && poFeature->IsFieldSetAndNotNull(iIndex))
1137 : {
1138 6 : VSIFPrintfL(fp, ">");
1139 :
1140 : char *pszValue =
1141 6 : OGRGetXML_UTF8_EscapedString(poFeature->GetFieldAsString(iIndex));
1142 6 : VSIFPrintfL(fp, "%s", pszValue);
1143 6 : CPLFree(pszValue);
1144 :
1145 6 : VSIFPrintfL(fp, "</%s>\n", pszElementName);
1146 : }
1147 : else
1148 : {
1149 2 : VSIFPrintfL(fp, "/>\n");
1150 : }
1151 8 : CPLFree(pszFieldName);
1152 8 : }
1153 :
1154 : /************************************************************************/
1155 : /* ICreateFeature() */
1156 : /************************************************************************/
1157 :
1158 95 : OGRErr OGRGeoRSSLayer::ICreateFeature(OGRFeature *poFeatureIn)
1159 :
1160 : {
1161 95 : VSILFILE *fp = poDS->GetOutputFP();
1162 95 : if (fp == nullptr)
1163 0 : return OGRERR_FAILURE;
1164 :
1165 95 : nNextFID++;
1166 :
1167 : // Verify that compulsory feeds are set.
1168 : // Otherwise put some default value in them.
1169 95 : if (eFormat == GEORSS_RSS)
1170 : {
1171 94 : const int iFieldTitle = poFeatureDefn->GetFieldIndex("title");
1172 : const int iFieldDescription =
1173 94 : poFeatureDefn->GetFieldIndex("description");
1174 :
1175 94 : VSIFPrintfL(fp, " <item>\n");
1176 :
1177 106 : if ((iFieldTitle == -1 ||
1178 94 : !poFeatureIn->IsFieldSetAndNotNull(iFieldTitle)) &&
1179 0 : (iFieldDescription == -1 ||
1180 0 : !poFeatureIn->IsFieldSetAndNotNull(iFieldDescription)))
1181 : {
1182 82 : VSIFPrintfL(fp, " <title>Feature %d</title>\n", nNextFID);
1183 : }
1184 : }
1185 : else
1186 : {
1187 1 : VSIFPrintfL(fp, " <entry>\n");
1188 :
1189 1 : const int iFieldId = poFeatureDefn->GetFieldIndex("id");
1190 1 : const int iFieldTitle = poFeatureDefn->GetFieldIndex("title");
1191 1 : const int iFieldUpdated = poFeatureDefn->GetFieldIndex("updated");
1192 :
1193 1 : if (iFieldId == -1 || !poFeatureIn->IsFieldSetAndNotNull(iFieldId))
1194 : {
1195 0 : VSIFPrintfL(fp, " <id>Feature %d</id>\n", nNextFID);
1196 : }
1197 :
1198 2 : if (iFieldTitle == -1 ||
1199 1 : !poFeatureIn->IsFieldSetAndNotNull(iFieldTitle))
1200 : {
1201 0 : VSIFPrintfL(fp, " <title>Title for feature %d</title>\n",
1202 : nNextFID);
1203 : }
1204 :
1205 2 : if (iFieldUpdated == -1 ||
1206 1 : !poFeatureIn->IsFieldSetAndNotNull(iFieldUpdated))
1207 : {
1208 0 : VSIFPrintfL(fp, " <updated>2009-01-01T00:00:00Z</updated>\n");
1209 : }
1210 : }
1211 :
1212 95 : const int nFieldCount = poFeatureDefn->GetFieldCount();
1213 95 : int *pbUsed = static_cast<int *>(CPLCalloc(sizeof(int), nFieldCount));
1214 :
1215 226 : for (int i = 0; i < nFieldCount; i++)
1216 : {
1217 131 : OGRFieldDefn *poFieldDefn = poFeatureDefn->GetFieldDefn(i);
1218 131 : const char *pszName = poFieldDefn->GetNameRef();
1219 :
1220 131 : if (!poFeatureIn->IsFieldSetAndNotNull(i))
1221 48 : continue;
1222 :
1223 166 : std::string osElementName;
1224 166 : std::string osNumber;
1225 166 : std::string osAttributeName;
1226 83 : OGRGeoRSSLayerSplitComposedField(pszName, osElementName, osNumber,
1227 : osAttributeName);
1228 :
1229 83 : bool bWillSkip = false;
1230 : // Handle Atom entries with elements with sub-elements like
1231 : // <author><name>...</name><uri>...</uri></author>
1232 83 : if (eFormat == GEORSS_ATOM)
1233 : {
1234 52 : for (unsigned int k = 0;
1235 52 : apszAllowedATOMFieldNamesWithSubElements[k] != nullptr; k++)
1236 : {
1237 74 : if (osElementName ==
1238 42 : apszAllowedATOMFieldNamesWithSubElements[k] &&
1239 5 : !osAttributeName.empty())
1240 : {
1241 5 : bWillSkip = true;
1242 5 : if (pbUsed[i])
1243 2 : break;
1244 :
1245 3 : VSIFPrintfL(fp, " <%s>\n", osElementName.c_str());
1246 :
1247 23 : for (int j = i; j < nFieldCount; j++)
1248 : {
1249 20 : poFieldDefn = poFeatureDefn->GetFieldDefn(j);
1250 20 : if (!poFeatureIn->IsFieldSetAndNotNull(j))
1251 0 : continue;
1252 :
1253 40 : std::string osElementName2;
1254 40 : std::string osNumber2;
1255 40 : std::string osAttributeName2;
1256 20 : OGRGeoRSSLayerSplitComposedField(
1257 : poFieldDefn->GetNameRef(), osElementName2,
1258 : osNumber2, osAttributeName2);
1259 :
1260 26 : if (osElementName2 == osElementName &&
1261 26 : osNumber2 == osNumber && !osAttributeName2.empty())
1262 : {
1263 5 : pbUsed[j] = TRUE;
1264 :
1265 5 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1266 : poFeatureIn->GetFieldAsString(j));
1267 5 : VSIFPrintfL(fp, " <%s>%s</%s>\n",
1268 : osAttributeName2.c_str(), pszValue,
1269 : osAttributeName2.c_str());
1270 5 : CPLFree(pszValue);
1271 : }
1272 : }
1273 :
1274 3 : VSIFPrintfL(fp, " </%s>\n", osElementName.c_str());
1275 :
1276 3 : break;
1277 : }
1278 : }
1279 : }
1280 :
1281 83 : if (bWillSkip)
1282 : {
1283 : // Do nothing
1284 : }
1285 78 : else if (eFormat == GEORSS_RSS && strcmp(pszName, "pubDate") == 0)
1286 : {
1287 12 : const OGRField *psField = poFeatureIn->GetRawFieldRef(i);
1288 12 : char *pszDate = OGRGetRFC822DateTime(psField);
1289 12 : VSIFPrintfL(fp, " <%s>%s</%s>\n", pszName, pszDate, pszName);
1290 12 : CPLFree(pszDate);
1291 : }
1292 66 : else if (eFormat == GEORSS_ATOM && (strcmp(pszName, "updated") == 0 ||
1293 14 : strcmp(pszName, "published") == 0))
1294 : {
1295 2 : const OGRField *psField = poFeatureIn->GetRawFieldRef(i);
1296 2 : char *pszDate = OGRGetXMLDateTime(psField);
1297 2 : VSIFPrintfL(fp, " <%s>%s</%s>\n", pszName, pszDate, pszName);
1298 2 : CPLFree(pszDate);
1299 : }
1300 64 : else if (strcmp(pszName, "dc_date") == 0)
1301 : {
1302 0 : const OGRField *psField = poFeatureIn->GetRawFieldRef(i);
1303 0 : char *pszDate = OGRGetXMLDateTime(psField);
1304 0 : VSIFPrintfL(fp, " <%s>%s</%s>\n", "dc:date", pszDate,
1305 : "dc:date");
1306 0 : CPLFree(pszDate);
1307 : }
1308 : // RSS fields with content and attributes.
1309 154 : else if (eFormat == GEORSS_RSS &&
1310 129 : (osElementName == "category" || osElementName == "guid" ||
1311 39 : osElementName == "source"))
1312 : {
1313 12 : if (osAttributeName.empty())
1314 : {
1315 6 : OGRGeoRSSLayerWriteSimpleElement(
1316 : fp, osElementName.c_str(), osNumber.c_str(),
1317 : apszAllowedRSSFieldNames, poFeatureDefn, poFeatureIn);
1318 : }
1319 : }
1320 : // RSS field with attribute only.
1321 52 : else if (eFormat == GEORSS_RSS && osElementName == "enclosure")
1322 : {
1323 0 : if (osAttributeName == "url")
1324 : {
1325 0 : OGRGeoRSSLayerWriteSimpleElement(
1326 : fp, osElementName.c_str(), osNumber.c_str(),
1327 : apszAllowedRSSFieldNames, poFeatureDefn, poFeatureIn);
1328 : }
1329 : }
1330 : /* ATOM fields with attribute only */
1331 78 : else if (eFormat == GEORSS_ATOM &&
1332 26 : (osElementName == "category" || osElementName == "link"))
1333 : {
1334 21 : if ((osElementName == "category" && osAttributeName == "term") ||
1335 14 : (osElementName == "link" && osAttributeName == "href"))
1336 : {
1337 2 : OGRGeoRSSLayerWriteSimpleElement(
1338 : fp, osElementName.c_str(), osNumber.c_str(),
1339 : apszAllowedATOMFieldNames, poFeatureDefn, poFeatureIn);
1340 : }
1341 : }
1342 45 : else if (eFormat == GEORSS_ATOM && (STARTS_WITH(pszName, "content") ||
1343 2 : STARTS_WITH(pszName, "summary")))
1344 : {
1345 4 : if (strchr(pszName, '_') == nullptr)
1346 : {
1347 1 : VSIFPrintfL(fp, " <%s", pszName);
1348 :
1349 1 : bool bIsXHTML = false;
1350 : char *pszFieldName =
1351 1 : CPLStrdup(CPLSPrintf("%s_%s", pszName, "type"));
1352 1 : int iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1353 1 : if (iIndex != -1 && poFeatureIn->IsFieldSetAndNotNull(iIndex))
1354 : {
1355 1 : bIsXHTML = strcmp(poFeatureIn->GetFieldAsString(iIndex),
1356 : "xhtml") == 0;
1357 1 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1358 : poFeatureIn->GetFieldAsString(iIndex));
1359 1 : VSIFPrintfL(fp, " %s=\"%s\"", "type", pszValue);
1360 1 : CPLFree(pszValue);
1361 : }
1362 1 : CPLFree(pszFieldName);
1363 :
1364 : pszFieldName =
1365 1 : CPLStrdup(CPLSPrintf("%s_%s", pszName, "xml_lang"));
1366 1 : iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1367 1 : if (iIndex != -1 && poFeatureIn->IsFieldSetAndNotNull(iIndex))
1368 : {
1369 1 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1370 : poFeatureIn->GetFieldAsString(iIndex));
1371 1 : VSIFPrintfL(fp, " %s=\"%s\"", "xml:lang", pszValue);
1372 1 : CPLFree(pszValue);
1373 : }
1374 1 : CPLFree(pszFieldName);
1375 :
1376 : pszFieldName =
1377 1 : CPLStrdup(CPLSPrintf("%s_%s", pszName, "xml_base"));
1378 1 : iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1379 1 : if (iIndex != -1 && poFeatureIn->IsFieldSetAndNotNull(iIndex))
1380 : {
1381 1 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1382 : poFeatureIn->GetFieldAsString(iIndex));
1383 1 : VSIFPrintfL(fp, " %s=\"%s\"", "xml:base", pszValue);
1384 1 : CPLFree(pszValue);
1385 : }
1386 1 : CPLFree(pszFieldName);
1387 :
1388 1 : VSIFPrintfL(fp, ">");
1389 1 : if (bIsXHTML)
1390 : {
1391 1 : VSIFPrintfL(fp, "%s", poFeatureIn->GetFieldAsString(i));
1392 : }
1393 : else
1394 : {
1395 0 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1396 : poFeatureIn->GetFieldAsString(i));
1397 0 : VSIFPrintfL(fp, "%s", pszValue);
1398 0 : CPLFree(pszValue);
1399 : }
1400 1 : VSIFPrintfL(fp, " </%s>\n", pszName);
1401 4 : }
1402 : }
1403 41 : else if (STARTS_WITH(pszName, "dc_subject"))
1404 : {
1405 0 : if (strchr(pszName + strlen("dc_subject"), '_') == nullptr)
1406 : {
1407 0 : VSIFPrintfL(fp, " <%s", "dc:subject");
1408 :
1409 : char *pszFieldName =
1410 0 : CPLStrdup(CPLSPrintf("%s_%s", pszName, "xml_lang"));
1411 0 : int iIndex = poFeatureDefn->GetFieldIndex(pszFieldName);
1412 0 : if (iIndex != -1 && poFeatureIn->IsFieldSetAndNotNull(iIndex))
1413 : {
1414 0 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1415 : poFeatureIn->GetFieldAsString(iIndex));
1416 0 : VSIFPrintfL(fp, " %s=\"%s\"", "xml:lang", pszValue);
1417 0 : CPLFree(pszValue);
1418 : }
1419 0 : CPLFree(pszFieldName);
1420 :
1421 0 : char *pszValue = OGRGetXML_UTF8_EscapedString(
1422 : poFeatureIn->GetFieldAsString(i));
1423 0 : VSIFPrintfL(fp, ">%s</%s>\n", pszValue, "dc:subject");
1424 0 : CPLFree(pszValue);
1425 : }
1426 : }
1427 : else
1428 : {
1429 41 : char *pszTagName = CPLStrdup(pszName);
1430 41 : if (IsStandardField(pszName) == FALSE)
1431 : {
1432 3 : int nCountUnderscore = 0;
1433 29 : for (int j = 0; pszTagName[j] != 0; j++)
1434 : {
1435 26 : if (pszTagName[j] == '_')
1436 : {
1437 2 : if (nCountUnderscore == 0)
1438 2 : pszTagName[j] = ':';
1439 2 : nCountUnderscore++;
1440 : }
1441 24 : else if (pszTagName[j] == ' ')
1442 0 : pszTagName[j] = '_';
1443 : }
1444 3 : if (nCountUnderscore == 0)
1445 : {
1446 1 : char *pszTemp = CPLStrdup(CPLSPrintf("ogr:%s", pszTagName));
1447 1 : CPLFree(pszTagName);
1448 1 : pszTagName = pszTemp;
1449 : }
1450 : }
1451 : char *pszValue =
1452 41 : OGRGetXML_UTF8_EscapedString(poFeatureIn->GetFieldAsString(i));
1453 41 : VSIFPrintfL(fp, " <%s>%s</%s>\n", pszTagName, pszValue,
1454 : pszTagName);
1455 41 : CPLFree(pszValue);
1456 41 : CPLFree(pszTagName);
1457 : }
1458 : }
1459 :
1460 95 : CPLFree(pbUsed);
1461 :
1462 95 : OGRGeoRSSGeomDialect eGeomDialect = poDS->GetGeomDialect();
1463 95 : OGRGeometry *poGeom = poFeatureIn->GetGeometryRef();
1464 95 : if (poGeom != nullptr && !poGeom->IsEmpty())
1465 : {
1466 59 : char *pszURN = nullptr;
1467 59 : bool bSwapCoordinates = false;
1468 59 : if (eGeomDialect == GEORSS_GML)
1469 : {
1470 5 : if (poSRS != nullptr)
1471 : {
1472 1 : const char *pszAuthorityName = poSRS->GetAuthorityName();
1473 1 : const char *pszAuthorityCode = poSRS->GetAuthorityCode();
1474 1 : if (pszAuthorityName != nullptr &&
1475 1 : EQUAL(pszAuthorityName, "EPSG") &&
1476 : pszAuthorityCode != nullptr)
1477 : {
1478 1 : if (!EQUAL(pszAuthorityCode, "4326"))
1479 1 : pszURN = CPLStrdup(CPLSPrintf(
1480 : "urn:ogc:def:crs:EPSG::%s", pszAuthorityCode));
1481 :
1482 : /* In case the SRS is a geographic SRS and that we have */
1483 : /* no axis definition, we assume that the order is */
1484 : /* lon/lat. */
1485 : const char *pszAxisName =
1486 1 : poSRS->GetAxis(nullptr, 0, nullptr);
1487 1 : if (poSRS->IsGeographic() &&
1488 0 : (pszAxisName == nullptr ||
1489 0 : STARTS_WITH_CI(pszAxisName, "Lon")))
1490 : {
1491 0 : bSwapCoordinates = true;
1492 1 : }
1493 : }
1494 : else
1495 : {
1496 : static bool bOnce = false;
1497 0 : if (!bOnce)
1498 : {
1499 0 : bOnce = true;
1500 0 : CPLError(CE_Warning, CPLE_AppDefined,
1501 : "Could not translate SRS into GML urn");
1502 : }
1503 : }
1504 : }
1505 : else
1506 : {
1507 4 : bSwapCoordinates = true;
1508 : }
1509 : }
1510 :
1511 59 : char szCoord[75] = {};
1512 59 : switch (wkbFlatten(poGeom->getGeometryType()))
1513 : {
1514 12 : case wkbPoint:
1515 : {
1516 12 : OGRPoint *poPoint = poGeom->toPoint();
1517 12 : const double x = poPoint->getX();
1518 12 : const double y = poPoint->getY();
1519 12 : if (eGeomDialect == GEORSS_GML)
1520 : {
1521 2 : VSIFPrintfL(fp, " <georss:where><gml:Point");
1522 2 : if (pszURN != nullptr)
1523 1 : VSIFPrintfL(fp, " srsName=\"%s\"", pszURN);
1524 2 : if (poGeom->getCoordinateDimension() == 3)
1525 : {
1526 0 : OGRMakeWktCoordinate(szCoord, bSwapCoordinates ? y : x,
1527 : bSwapCoordinates ? x : y,
1528 : poPoint->getZ(), 3);
1529 0 : VSIFPrintfL(fp, " srsDimension=\"3\"><gml:pos>%s",
1530 : szCoord);
1531 : }
1532 : else
1533 : {
1534 2 : OGRMakeWktCoordinate(szCoord, bSwapCoordinates ? y : x,
1535 : bSwapCoordinates ? x : y, 0, 2);
1536 2 : VSIFPrintfL(fp, "><gml:pos>%s", szCoord);
1537 : }
1538 2 : VSIFPrintfL(fp, "</gml:pos></gml:Point></georss:where>\n");
1539 : }
1540 10 : else if (eGeomDialect == GEORSS_SIMPLE)
1541 : {
1542 9 : OGRMakeWktCoordinate(szCoord, y, x, 0, 2);
1543 9 : VSIFPrintfL(fp, " <georss:point>%s</georss:point>\n",
1544 : szCoord);
1545 : }
1546 1 : else if (eGeomDialect == GEORSS_W3C_GEO)
1547 : {
1548 1 : OGRFormatDouble(szCoord, sizeof(szCoord), y, '.');
1549 1 : VSIFPrintfL(fp, " <geo:lat>%s</geo:lat>\n", szCoord);
1550 1 : OGRFormatDouble(szCoord, sizeof(szCoord), x, '.');
1551 1 : VSIFPrintfL(fp, " <geo:long>%s</geo:long>\n", szCoord);
1552 : }
1553 12 : break;
1554 : }
1555 :
1556 11 : case wkbLineString:
1557 : {
1558 11 : OGRLineString *poLineString = poGeom->toLineString();
1559 11 : if (eGeomDialect == GEORSS_GML)
1560 : {
1561 1 : VSIFPrintfL(fp, " <georss:where><gml:LineString");
1562 1 : if (pszURN != nullptr)
1563 0 : VSIFPrintfL(fp, " srsName=\"%s\"", pszURN);
1564 1 : VSIFPrintfL(fp, "><gml:posList>\n");
1565 1 : const int n = poLineString->getNumPoints();
1566 4 : for (int i = 0; i < n; i++)
1567 : {
1568 3 : const double x = poLineString->getX(i);
1569 3 : const double y = poLineString->getY(i);
1570 3 : OGRMakeWktCoordinate(szCoord, bSwapCoordinates ? y : x,
1571 : bSwapCoordinates ? x : y, 0, 2);
1572 3 : VSIFPrintfL(fp, "%s ", szCoord);
1573 : }
1574 1 : VSIFPrintfL(
1575 : fp, "</gml:posList></gml:LineString></georss:where>\n");
1576 : }
1577 10 : else if (eGeomDialect == GEORSS_SIMPLE)
1578 : {
1579 9 : VSIFPrintfL(fp, " <georss:line>\n");
1580 9 : int n = poLineString->getNumPoints();
1581 28 : for (int i = 0; i < n; i++)
1582 : {
1583 19 : double x = poLineString->getX(i);
1584 19 : double y = poLineString->getY(i);
1585 19 : OGRMakeWktCoordinate(szCoord, y, x, 0, 2);
1586 19 : VSIFPrintfL(fp, "%s ", szCoord);
1587 : }
1588 9 : VSIFPrintfL(fp, "</georss:line>\n");
1589 : }
1590 : else
1591 : {
1592 : // Not supported.
1593 : }
1594 11 : break;
1595 : }
1596 :
1597 12 : case wkbPolygon:
1598 : {
1599 12 : OGRPolygon *poPolygon = poGeom->toPolygon();
1600 12 : OGRLineString *poLineString = poPolygon->getExteriorRing();
1601 12 : if (poLineString == nullptr)
1602 0 : break;
1603 :
1604 12 : if (eGeomDialect == GEORSS_GML)
1605 : {
1606 2 : VSIFPrintfL(fp, " <georss:where><gml:Polygon");
1607 2 : if (pszURN != nullptr)
1608 0 : VSIFPrintfL(fp, " srsName=\"%s\"", pszURN);
1609 2 : VSIFPrintfL(
1610 : fp, "><gml:exterior><gml:LinearRing><gml:posList>\n");
1611 2 : const int n = poLineString->getNumPoints();
1612 12 : for (int i = 0; i < n; i++)
1613 : {
1614 10 : const double x = poLineString->getX(i);
1615 10 : const double y = poLineString->getY(i);
1616 10 : OGRMakeWktCoordinate(szCoord, bSwapCoordinates ? y : x,
1617 : bSwapCoordinates ? x : y, 0, 2);
1618 10 : VSIFPrintfL(fp, "%s ", szCoord);
1619 : }
1620 2 : VSIFPrintfL(fp,
1621 : "</gml:posList></gml:LinearRing></gml:exterior>"
1622 : "</gml:Polygon></georss:where>\n");
1623 : }
1624 10 : else if (eGeomDialect == GEORSS_SIMPLE)
1625 : {
1626 8 : VSIFPrintfL(fp, " <georss:polygon>\n");
1627 8 : const int n = poLineString->getNumPoints();
1628 48 : for (int i = 0; i < n; i++)
1629 : {
1630 40 : const double x = poLineString->getX(i);
1631 40 : const double y = poLineString->getY(i);
1632 40 : OGRMakeWktCoordinate(szCoord, y, x, 0, 2);
1633 40 : VSIFPrintfL(fp, "%s ", szCoord);
1634 : }
1635 8 : VSIFPrintfL(fp, "</georss:polygon>\n");
1636 : }
1637 : else
1638 : {
1639 : /* Not supported */
1640 : }
1641 12 : break;
1642 : }
1643 :
1644 24 : default:
1645 : /* Not supported */
1646 24 : break;
1647 : }
1648 59 : CPLFree(pszURN);
1649 : }
1650 :
1651 95 : if (eFormat == GEORSS_RSS)
1652 94 : VSIFPrintfL(fp, " </item>\n");
1653 : else
1654 1 : VSIFPrintfL(fp, " </entry>\n");
1655 :
1656 95 : return OGRERR_NONE;
1657 : }
1658 :
1659 : /************************************************************************/
1660 : /* CreateField() */
1661 : /************************************************************************/
1662 :
1663 146 : OGRErr OGRGeoRSSLayer::CreateField(const OGRFieldDefn *poFieldDefn,
1664 : CPL_UNUSED int bApproxOK)
1665 : {
1666 146 : const char *pszName = poFieldDefn->GetNameRef();
1667 126 : if (((eFormat == GEORSS_RSS && strcmp(pszName, "pubDate") == 0) ||
1668 143 : (eFormat == GEORSS_ATOM && (strcmp(pszName, "updated") == 0 ||
1669 19 : strcmp(pszName, "published") == 0)) ||
1670 292 : strcmp(pszName, "dc:date") == 0) &&
1671 5 : poFieldDefn->GetType() != OFTDateTime)
1672 : {
1673 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong field type for %s",
1674 : pszName);
1675 0 : return OGRERR_FAILURE;
1676 : }
1677 :
1678 447 : for (int iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++)
1679 : {
1680 301 : if (strcmp(poFeatureDefn->GetFieldDefn(iField)->GetNameRef(),
1681 301 : pszName) == 0)
1682 : {
1683 0 : return OGRERR_FAILURE;
1684 : }
1685 : }
1686 :
1687 146 : if (IsStandardField(pszName))
1688 : {
1689 47 : poFeatureDefn->AddFieldDefn(poFieldDefn);
1690 47 : return OGRERR_NONE;
1691 : }
1692 :
1693 99 : if (poDS->GetUseExtensions() == FALSE)
1694 : {
1695 96 : CPLError(
1696 : CE_Failure, CPLE_NotSupported,
1697 : "Field of name '%s' is not supported in %s schema. "
1698 : "Use USE_EXTENSIONS creation option to allow use of extensions.",
1699 96 : pszName, (eFormat == GEORSS_RSS) ? "RSS" : "ATOM");
1700 96 : return OGRERR_FAILURE;
1701 : }
1702 : else
1703 : {
1704 3 : poFeatureDefn->AddFieldDefn(poFieldDefn);
1705 3 : return OGRERR_NONE;
1706 : }
1707 : }
1708 :
1709 : #ifdef HAVE_EXPAT
1710 :
1711 304 : static void XMLCALL startElementLoadSchemaCbk(void *pUserData,
1712 : const char *pszName,
1713 : const char **ppszAttr)
1714 : {
1715 304 : ((OGRGeoRSSLayer *)pUserData)->startElementLoadSchemaCbk(pszName, ppszAttr);
1716 304 : }
1717 :
1718 304 : static void XMLCALL endElementLoadSchemaCbk(void *pUserData,
1719 : const char *pszName)
1720 : {
1721 304 : ((OGRGeoRSSLayer *)pUserData)->endElementLoadSchemaCbk(pszName);
1722 304 : }
1723 :
1724 889 : static void XMLCALL dataHandlerLoadSchemaCbk(void *pUserData, const char *data,
1725 : int nLen)
1726 : {
1727 889 : ((OGRGeoRSSLayer *)pUserData)->dataHandlerLoadSchemaCbk(data, nLen);
1728 889 : }
1729 :
1730 : /************************************************************************/
1731 : /* LoadSchema() */
1732 : /************************************************************************/
1733 :
1734 : /** This function parses the whole file to detect the fields */
1735 67 : void OGRGeoRSSLayer::LoadSchema()
1736 : {
1737 67 : if (bHasReadSchema)
1738 54 : return;
1739 :
1740 67 : bHasReadSchema = true;
1741 :
1742 67 : if (fpGeoRSS == nullptr)
1743 54 : return;
1744 :
1745 13 : oSchemaParser = OGRCreateExpatXMLParser();
1746 13 : XML_SetElementHandler(oSchemaParser, ::startElementLoadSchemaCbk,
1747 : ::endElementLoadSchemaCbk);
1748 13 : XML_SetCharacterDataHandler(oSchemaParser, ::dataHandlerLoadSchemaCbk);
1749 13 : XML_SetUserData(oSchemaParser, this);
1750 :
1751 13 : VSIFSeekL(fpGeoRSS, 0, SEEK_SET);
1752 :
1753 13 : bInFeature = false;
1754 13 : currentDepth = 0;
1755 13 : currentFieldDefn = nullptr;
1756 13 : pszSubElementName = nullptr;
1757 13 : pszSubElementValue = nullptr;
1758 13 : nSubElementValueLen = 0;
1759 13 : this->bSameSRS = true;
1760 13 : CPLFree(this->pszGMLSRSName);
1761 13 : this->pszGMLSRSName = nullptr;
1762 13 : this->eGeomType = wkbUnknown;
1763 13 : this->bFoundGeom = false;
1764 13 : bInTagWithSubTag = false;
1765 13 : pszTagWithSubTag = nullptr;
1766 13 : bStopParsing = false;
1767 13 : nWithoutEventCounter = 0;
1768 13 : nTotalFeatureCount = 0;
1769 13 : setOfFoundFields = nullptr;
1770 :
1771 13 : std::vector<char> aBuf(PARSER_BUF_SIZE);
1772 13 : int nDone = 0;
1773 0 : do
1774 : {
1775 13 : nDataHandlerCounter = 0;
1776 : unsigned int nLen =
1777 13 : (unsigned int)VSIFReadL(aBuf.data(), 1, aBuf.size(), fpGeoRSS);
1778 13 : nDone = nLen < aBuf.size();
1779 13 : if (XML_Parse(oSchemaParser, aBuf.data(), nLen, nDone) ==
1780 : XML_STATUS_ERROR)
1781 : {
1782 0 : CPLError(CE_Failure, CPLE_AppDefined,
1783 : "XML parsing of GeoRSS file failed: %s",
1784 0 : OGRExpatGetParseError(oSchemaParser));
1785 0 : bStopParsing = true;
1786 : }
1787 13 : nWithoutEventCounter++;
1788 13 : } while (!nDone && !bStopParsing && nWithoutEventCounter < 10);
1789 :
1790 13 : XML_ParserFree(oSchemaParser);
1791 :
1792 13 : if (nWithoutEventCounter == 10)
1793 : {
1794 0 : CPLError(CE_Failure, CPLE_AppDefined,
1795 : "Too much data inside one element. File probably corrupted");
1796 0 : bStopParsing = true;
1797 : }
1798 :
1799 13 : CPLAssert(poSRS == nullptr);
1800 13 : if (this->bSameSRS && this->bFoundGeom)
1801 : {
1802 9 : if (this->pszGMLSRSName == nullptr)
1803 : {
1804 8 : poSRS = new OGRSpatialReference();
1805 8 : poSRS->SetWellKnownGeogCS("WGS84");
1806 8 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1807 : }
1808 : else
1809 : {
1810 1 : poSRS = new OGRSpatialReference();
1811 1 : poSRS->importFromURN(pszGMLSRSName);
1812 : }
1813 : }
1814 :
1815 13 : if (this->eGeomType != wkbUnknown)
1816 5 : poFeatureDefn->SetGeomType(eGeomType);
1817 13 : if (poFeatureDefn->GetGeomFieldCount() != 0)
1818 13 : poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
1819 :
1820 13 : if (setOfFoundFields)
1821 13 : CPLHashSetDestroy(setOfFoundFields);
1822 13 : setOfFoundFields = nullptr;
1823 13 : CPLFree(pszGMLSRSName);
1824 13 : pszGMLSRSName = nullptr;
1825 13 : CPLFree(pszTagWithSubTag);
1826 13 : pszTagWithSubTag = nullptr;
1827 :
1828 13 : VSIFSeekL(fpGeoRSS, 0, SEEK_SET);
1829 : }
1830 :
1831 : /************************************************************************/
1832 : /* startElementLoadSchemaCbk() */
1833 : /************************************************************************/
1834 :
1835 304 : void OGRGeoRSSLayer::startElementLoadSchemaCbk(const char *pszName,
1836 : const char **ppszAttr)
1837 : {
1838 304 : if (bStopParsing)
1839 0 : return;
1840 :
1841 304 : nWithoutEventCounter = 0;
1842 304 : const char *pszNoNSName = pszName;
1843 304 : const char *pszColon = strchr(pszNoNSName, ':');
1844 304 : if (pszColon)
1845 77 : pszNoNSName = pszColon + 1;
1846 :
1847 304 : if ((eFormat == GEORSS_ATOM && currentDepth == 1 &&
1848 23 : strcmp(pszNoNSName, "entry") == 0) ||
1849 301 : ((eFormat == GEORSS_RSS || eFormat == GEORSS_RSS_RDF) && !bInFeature &&
1850 68 : (currentDepth == 1 || currentDepth == 2) &&
1851 58 : strcmp(pszNoNSName, "item") == 0))
1852 : {
1853 28 : bInFeature = true;
1854 28 : featureDepth = currentDepth;
1855 :
1856 28 : nTotalFeatureCount++;
1857 :
1858 28 : if (setOfFoundFields)
1859 15 : CPLHashSetDestroy(setOfFoundFields);
1860 28 : setOfFoundFields =
1861 28 : CPLHashSetNew(CPLHashSetHashStr, CPLHashSetEqualStr, CPLFree);
1862 : }
1863 276 : else if (bInTagWithSubTag && currentDepth == 3)
1864 : {
1865 : char *pszFieldName =
1866 15 : CPLStrdup(CPLSPrintf("%s_%s", pszTagWithSubTag, pszNoNSName));
1867 15 : if (poFeatureDefn->GetFieldIndex(pszFieldName) == -1)
1868 : {
1869 30 : OGRFieldDefn newFieldDefn(pszFieldName, OFTString);
1870 15 : poFeatureDefn->AddFieldDefn(&newFieldDefn);
1871 :
1872 15 : if (poFeatureDefn->GetFieldCount() == 100)
1873 : {
1874 0 : CPLError(CE_Failure, CPLE_AppDefined,
1875 : "Too many fields. File probably corrupted");
1876 0 : XML_StopParser(oSchemaParser, XML_FALSE);
1877 0 : bStopParsing = true;
1878 : }
1879 : }
1880 15 : CPLFree(pszFieldName);
1881 : }
1882 291 : else if (bInFeature && eFormat == GEORSS_ATOM && currentDepth == 2 &&
1883 30 : OGRGeoRSSLayerATOMTagHasSubElement(pszNoNSName))
1884 : {
1885 9 : CPLFree(pszTagWithSubTag);
1886 9 : pszTagWithSubTag = CPLStrdup(pszNoNSName);
1887 :
1888 9 : int count = 1;
1889 12 : while (CPLHashSetLookup(setOfFoundFields, pszTagWithSubTag) != nullptr)
1890 : {
1891 3 : count++;
1892 3 : CPLFree(pszTagWithSubTag);
1893 3 : pszTagWithSubTag =
1894 3 : CPLStrdup(CPLSPrintf("%s%d", pszNoNSName, count));
1895 3 : if (pszTagWithSubTag[0] == 0)
1896 : {
1897 0 : XML_StopParser(oSchemaParser, XML_FALSE);
1898 0 : bStopParsing = true;
1899 0 : break;
1900 : }
1901 : }
1902 9 : CPLHashSetInsert(setOfFoundFields, CPLStrdup(pszTagWithSubTag));
1903 :
1904 9 : bInTagWithSubTag = true;
1905 : }
1906 252 : else if (bInFeature && currentDepth == featureDepth + 1 &&
1907 149 : !IS_GEO_ELEMENT(pszName))
1908 : {
1909 127 : if (pszName != pszNoNSName && STARTS_WITH(pszName, "atom:"))
1910 7 : pszName = pszNoNSName;
1911 :
1912 127 : CPLFree(pszSubElementName);
1913 127 : pszSubElementName = CPLStrdup(pszName);
1914 :
1915 127 : int count = 1;
1916 135 : while (CPLHashSetLookup(setOfFoundFields, pszSubElementName) != nullptr)
1917 : {
1918 8 : count++;
1919 8 : CPLFree(pszSubElementName);
1920 8 : pszSubElementName = CPLStrdup(CPLSPrintf("%s%d", pszName, count));
1921 : }
1922 127 : CPLHashSetInsert(setOfFoundFields, CPLStrdup(pszSubElementName));
1923 :
1924 : // Create field definition for element.
1925 : char *pszCompatibleName =
1926 127 : OGRGeoRSS_GetOGRCompatibleTagName(pszSubElementName);
1927 127 : int iField = poFeatureDefn->GetFieldIndex(pszCompatibleName);
1928 127 : if (iField >= 0)
1929 : {
1930 66 : currentFieldDefn = poFeatureDefn->GetFieldDefn(iField);
1931 : }
1932 61 : else if (!((eFormat == GEORSS_RSS || eFormat == GEORSS_RSS_RDF) &&
1933 40 : strcmp(pszNoNSName, "enclosure") == 0) &&
1934 61 : !(eFormat == GEORSS_ATOM &&
1935 21 : strcmp(pszNoNSName, "link") == 0) &&
1936 55 : !(eFormat == GEORSS_ATOM &&
1937 15 : strcmp(pszNoNSName, "category") == 0))
1938 : {
1939 : OGRFieldType eFieldType;
1940 55 : if (((eFormat == GEORSS_RSS || eFormat == GEORSS_RSS_RDF) &&
1941 40 : strcmp(pszNoNSName, "pubDate") == 0) ||
1942 49 : (eFormat == GEORSS_ATOM &&
1943 15 : strcmp(pszNoNSName, "updated") == 0) ||
1944 46 : (eFormat == GEORSS_ATOM &&
1945 12 : strcmp(pszNoNSName, "published") == 0) ||
1946 43 : strcmp(pszName, "dc:date") == 0)
1947 12 : eFieldType = OFTDateTime;
1948 : else
1949 43 : eFieldType = OFTInteger;
1950 :
1951 110 : OGRFieldDefn newFieldDefn(pszCompatibleName, eFieldType);
1952 55 : poFeatureDefn->AddFieldDefn(&newFieldDefn);
1953 55 : currentFieldDefn =
1954 55 : poFeatureDefn->GetFieldDefn(poFeatureDefn->GetFieldCount() - 1);
1955 :
1956 55 : if (poFeatureDefn->GetFieldCount() == 100)
1957 : {
1958 0 : CPLError(CE_Failure, CPLE_AppDefined,
1959 : "Too many fields. File probably corrupted");
1960 0 : XML_StopParser(oSchemaParser, XML_FALSE);
1961 0 : bStopParsing = true;
1962 : }
1963 : }
1964 :
1965 : // Create field definitions for attributes.
1966 168 : for (int i = 0; ppszAttr[i] != nullptr && ppszAttr[i + 1] != nullptr &&
1967 41 : !bStopParsing;
1968 41 : i += 2)
1969 : {
1970 41 : char *pszAttrCompatibleName = OGRGeoRSS_GetOGRCompatibleTagName(
1971 41 : CPLSPrintf("%s_%s", pszSubElementName, ppszAttr[i]));
1972 41 : iField = poFeatureDefn->GetFieldIndex(pszAttrCompatibleName);
1973 41 : OGRFieldDefn *currentAttrFieldDefn = nullptr;
1974 41 : if (iField >= 0)
1975 : {
1976 0 : currentAttrFieldDefn = poFeatureDefn->GetFieldDefn(iField);
1977 : }
1978 : else
1979 : {
1980 82 : OGRFieldDefn newFieldDefn(pszAttrCompatibleName, OFTInteger);
1981 41 : poFeatureDefn->AddFieldDefn(&newFieldDefn);
1982 123 : currentAttrFieldDefn = poFeatureDefn->GetFieldDefn(
1983 41 : poFeatureDefn->GetFieldCount() - 1);
1984 :
1985 41 : if (poFeatureDefn->GetFieldCount() == 100)
1986 : {
1987 0 : CPLError(CE_Failure, CPLE_AppDefined,
1988 : "Too many fields. File probably corrupted");
1989 0 : XML_StopParser(oSchemaParser, XML_FALSE);
1990 0 : bStopParsing = true;
1991 : }
1992 : }
1993 41 : if (currentAttrFieldDefn->GetType() == OFTInteger ||
1994 0 : currentAttrFieldDefn->GetType() == OFTReal)
1995 : {
1996 41 : const CPLValueType eType = CPLGetValueType(ppszAttr[i + 1]);
1997 41 : if (eType == CPL_VALUE_REAL)
1998 : {
1999 0 : currentAttrFieldDefn->SetType(OFTReal);
2000 : }
2001 41 : else if (eType == CPL_VALUE_STRING)
2002 : {
2003 38 : currentAttrFieldDefn->SetType(OFTString);
2004 : }
2005 : }
2006 41 : CPLFree(pszAttrCompatibleName);
2007 : }
2008 :
2009 127 : CPLFree(pszCompatibleName);
2010 : }
2011 125 : else if (strcmp(pszName, "georss:point") == 0 ||
2012 122 : strcmp(pszName, "georss:line") == 0 ||
2013 120 : strcmp(pszName, "geo:line") == 0 || IS_LAT_ELEMENT(pszName) ||
2014 119 : strcmp(pszName, "georss:polygon") == 0 ||
2015 116 : strcmp(pszName, "georss:box") == 0)
2016 : {
2017 11 : if (bSameSRS)
2018 : {
2019 11 : if (pszGMLSRSName != nullptr)
2020 0 : bSameSRS = false;
2021 : }
2022 : }
2023 114 : else if (strcmp(pszName, "gml:Point") == 0 ||
2024 111 : strcmp(pszName, "gml:LineString") == 0 ||
2025 108 : strcmp(pszName, "gml:Polygon") == 0 ||
2026 105 : strcmp(pszName, "gml:MultiPoint") == 0 ||
2027 105 : strcmp(pszName, "gml:MultiLineString") == 0 ||
2028 105 : strcmp(pszName, "gml:MultiPolygon") == 0 ||
2029 105 : strcmp(pszName, "gml:Envelope") == 0)
2030 : {
2031 10 : if (bSameSRS)
2032 : {
2033 10 : bool bFoundSRS = false;
2034 10 : for (int i = 0; ppszAttr[i] != nullptr; i += 2)
2035 : {
2036 1 : if (strcmp(ppszAttr[i], "srsName") == 0)
2037 : {
2038 1 : bFoundSRS = true;
2039 1 : if (pszGMLSRSName != nullptr)
2040 : {
2041 0 : if (strcmp(pszGMLSRSName, ppszAttr[i + 1]) != 0)
2042 0 : bSameSRS = false;
2043 : }
2044 : else
2045 1 : pszGMLSRSName = CPLStrdup(ppszAttr[i + 1]);
2046 1 : break;
2047 : }
2048 : }
2049 10 : if (!bFoundSRS && pszGMLSRSName != nullptr)
2050 0 : bSameSRS = false;
2051 : }
2052 : }
2053 :
2054 304 : if (!bInFeature || currentDepth >= featureDepth + 1)
2055 : {
2056 276 : int nDimension = 2;
2057 367 : for (int i = 0; ppszAttr[i] != nullptr && ppszAttr[i + 1] != nullptr;
2058 91 : i += 2)
2059 : {
2060 91 : if (strcmp(ppszAttr[i], "srsDimension") == 0)
2061 : {
2062 0 : nDimension = atoi(ppszAttr[i + 1]);
2063 0 : break;
2064 : }
2065 : }
2066 :
2067 276 : OGRwkbGeometryType eFoundGeomType = wkbUnknown;
2068 276 : if (strcmp(pszName, "georss:point") == 0 || IS_LAT_ELEMENT(pszName) ||
2069 272 : strcmp(pszName, "gml:Point") == 0)
2070 : {
2071 7 : eFoundGeomType = wkbPoint;
2072 : }
2073 269 : else if (strcmp(pszName, "gml:MultiPoint") == 0)
2074 : {
2075 0 : eFoundGeomType = wkbMultiPoint;
2076 : }
2077 269 : else if (strcmp(pszName, "georss:line") == 0 ||
2078 267 : strcmp(pszName, "geo:line") == 0 ||
2079 267 : strcmp(pszName, "gml:LineString") == 0)
2080 : {
2081 5 : eFoundGeomType = wkbLineString;
2082 : }
2083 264 : else if (strcmp(pszName, "gml:MultiLineString") == 0)
2084 : {
2085 0 : eFoundGeomType = wkbMultiLineString;
2086 : }
2087 264 : else if (strcmp(pszName, "georss:polygon") == 0 ||
2088 261 : strcmp(pszName, "gml:Polygon") == 0 ||
2089 258 : strcmp(pszName, "gml:Envelope") == 0 ||
2090 257 : strcmp(pszName, "georss:box") == 0)
2091 : {
2092 9 : eFoundGeomType = wkbPolygon;
2093 : }
2094 255 : else if (strcmp(pszName, "gml:MultiPolygon") == 0)
2095 : {
2096 0 : eFoundGeomType = wkbMultiPolygon;
2097 : }
2098 :
2099 276 : if (eFoundGeomType != wkbUnknown)
2100 : {
2101 21 : if (!bFoundGeom)
2102 : {
2103 9 : eGeomType = eFoundGeomType;
2104 9 : bFoundGeom = true;
2105 : }
2106 12 : else if (wkbFlatten(eGeomType) != eFoundGeomType)
2107 : {
2108 12 : eGeomType = wkbUnknown;
2109 : }
2110 :
2111 21 : if (nDimension == 3)
2112 0 : eGeomType = wkbSetZ(eGeomType);
2113 : }
2114 : }
2115 :
2116 304 : currentDepth++;
2117 : }
2118 :
2119 : /************************************************************************/
2120 : /* endElementLoadSchemaCbk() */
2121 : /************************************************************************/
2122 :
2123 304 : void OGRGeoRSSLayer::endElementLoadSchemaCbk(const char *pszName)
2124 : {
2125 304 : if (bStopParsing)
2126 0 : return;
2127 :
2128 304 : nWithoutEventCounter = 0;
2129 :
2130 304 : currentDepth--;
2131 :
2132 304 : if (!bInFeature)
2133 67 : return;
2134 :
2135 237 : const char *pszNoNSName = pszName;
2136 237 : const char *pszColon = strchr(pszNoNSName, ':');
2137 237 : if (pszColon)
2138 68 : pszNoNSName = pszColon + 1;
2139 :
2140 237 : if ((eFormat == GEORSS_ATOM && currentDepth == 1 &&
2141 3 : strcmp(pszNoNSName, "entry") == 0) ||
2142 234 : ((eFormat == GEORSS_RSS || eFormat == GEORSS_RSS_RDF) &&
2143 180 : (currentDepth == 1 || currentDepth == 2) &&
2144 25 : strcmp(pszNoNSName, "item") == 0))
2145 : {
2146 28 : bInFeature = false;
2147 : }
2148 239 : else if (eFormat == GEORSS_ATOM && currentDepth == 2 &&
2149 30 : OGRGeoRSSLayerATOMTagHasSubElement(pszNoNSName))
2150 : {
2151 9 : bInTagWithSubTag = false;
2152 : }
2153 200 : else if (currentDepth == featureDepth + 1 && pszSubElementName)
2154 : {
2155 : // Patch field type.
2156 127 : if (pszSubElementValue && nSubElementValueLen && currentFieldDefn)
2157 : {
2158 121 : pszSubElementValue[nSubElementValueLen] = 0;
2159 199 : if (currentFieldDefn->GetType() == OFTInteger ||
2160 78 : currentFieldDefn->GetType() == OFTReal)
2161 : {
2162 43 : const CPLValueType eType = CPLGetValueType(pszSubElementValue);
2163 43 : if (eType == CPL_VALUE_REAL)
2164 : {
2165 0 : currentFieldDefn->SetType(OFTReal);
2166 : }
2167 43 : else if (eType == CPL_VALUE_STRING)
2168 : {
2169 42 : currentFieldDefn->SetType(OFTString);
2170 : }
2171 : }
2172 : }
2173 :
2174 127 : CPLFree(pszSubElementName);
2175 127 : pszSubElementName = nullptr;
2176 127 : CPLFree(pszSubElementValue);
2177 127 : pszSubElementValue = nullptr;
2178 127 : nSubElementValueLen = 0;
2179 127 : currentFieldDefn = nullptr;
2180 : }
2181 : }
2182 :
2183 : /************************************************************************/
2184 : /* dataHandlerLoadSchemaCbk() */
2185 : /************************************************************************/
2186 :
2187 889 : void OGRGeoRSSLayer::dataHandlerLoadSchemaCbk(const char *data, int nLen)
2188 : {
2189 889 : if (bStopParsing)
2190 0 : return;
2191 :
2192 889 : nDataHandlerCounter++;
2193 889 : if (nDataHandlerCounter >= PARSER_BUF_SIZE)
2194 : {
2195 0 : CPLError(CE_Failure, CPLE_AppDefined,
2196 : "File probably corrupted (million laugh pattern)");
2197 0 : XML_StopParser(oSchemaParser, XML_FALSE);
2198 0 : bStopParsing = true;
2199 0 : return;
2200 : }
2201 :
2202 889 : nWithoutEventCounter = 0;
2203 :
2204 889 : if (pszSubElementName)
2205 : {
2206 138 : char *pszNewSubElementValue = static_cast<char *>(VSI_REALLOC_VERBOSE(
2207 : pszSubElementValue, nSubElementValueLen + nLen + 1));
2208 138 : if (pszNewSubElementValue == nullptr)
2209 : {
2210 0 : XML_StopParser(oSchemaParser, XML_FALSE);
2211 0 : bStopParsing = true;
2212 0 : return;
2213 : }
2214 138 : pszSubElementValue = pszNewSubElementValue;
2215 138 : memcpy(pszSubElementValue + nSubElementValueLen, data, nLen);
2216 138 : nSubElementValueLen += nLen;
2217 138 : if (nSubElementValueLen > 100000)
2218 : {
2219 0 : CPLError(CE_Failure, CPLE_AppDefined,
2220 : "Too much data inside one element. "
2221 : "File probably corrupted");
2222 0 : XML_StopParser(oSchemaParser, XML_FALSE);
2223 0 : bStopParsing = true;
2224 : }
2225 : }
2226 : }
2227 : #else
2228 : void OGRGeoRSSLayer::LoadSchema()
2229 : {
2230 : }
2231 : #endif
2232 :
2233 : /************************************************************************/
2234 : /* TestCapability() */
2235 : /************************************************************************/
2236 :
2237 108 : bool OGRGeoRSSLayer::TestCapability(const char *pszCap) const
2238 :
2239 : {
2240 108 : if (EQUAL(pszCap, OLCFastFeatureCount))
2241 0 : return !bWriteMode && bHasReadSchema && m_poFilterGeom == nullptr &&
2242 0 : m_poAttrQuery == nullptr;
2243 :
2244 108 : else if (EQUAL(pszCap, OLCStringsAsUTF8))
2245 0 : return TRUE;
2246 :
2247 108 : else if (EQUAL(pszCap, OLCSequentialWrite))
2248 16 : return bWriteMode;
2249 92 : else if (EQUAL(pszCap, OLCCreateField))
2250 16 : return bWriteMode;
2251 76 : else if (EQUAL(pszCap, OLCZGeometries))
2252 0 : return TRUE;
2253 : else
2254 76 : return FALSE;
2255 : }
2256 :
2257 : /************************************************************************/
2258 : /* GetFeatureCount() */
2259 : /************************************************************************/
2260 :
2261 0 : GIntBig OGRGeoRSSLayer::GetFeatureCount(int bForce)
2262 :
2263 : {
2264 0 : if (bWriteMode)
2265 : {
2266 0 : CPLError(CE_Failure, CPLE_NotSupported,
2267 : "Cannot read features when writing a GeoRSS file");
2268 0 : return 0;
2269 : }
2270 :
2271 0 : if (!bHasReadSchema)
2272 0 : LoadSchema();
2273 :
2274 0 : if (m_poFilterGeom != nullptr || m_poAttrQuery != nullptr)
2275 0 : return OGRLayer::GetFeatureCount(bForce);
2276 :
2277 0 : return nTotalFeatureCount;
2278 : }
2279 :
2280 : /************************************************************************/
2281 : /* GetDataset() */
2282 : /************************************************************************/
2283 :
2284 19 : GDALDataset *OGRGeoRSSLayer::GetDataset()
2285 : {
2286 19 : return poDS;
2287 : }
|