LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/georss - ogrgeorssdatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 188 226 83.2 %
Date: 2024-11-21 22:18:42 Functions: 12 12 100.0 %

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

Generated by: LCOV version 1.14