LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s57 - s57classregistrar.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 161 223 72.2 %
Date: 2024-11-25 13:07:18 Functions: 17 18 94.4 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  S-57 Translator
       4             :  * Purpose:  Implements S57ClassRegistrar class for keeping track of
       5             :  *           information on S57 object classes.
       6             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 1999, Frank Warmerdam
      10             :  * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "cpl_conv.h"
      16             : #include "cpl_string.h"
      17             : #include "s57.h"
      18             : 
      19             : #ifdef EMBED_RESOURCE_FILES
      20             : #include "embedded_resources.h"
      21             : #endif
      22             : 
      23             : /************************************************************************/
      24             : /*                         S57ClassRegistrar()                          */
      25             : /************************************************************************/
      26             : 
      27           3 : S57ClassRegistrar::S57ClassRegistrar()
      28           3 :     : nClasses(0), nAttrCount(0), papszNextLine(nullptr)
      29             : {
      30           3 : }
      31             : 
      32             : /************************************************************************/
      33             : /*                         ~S57ClassRegistrar()                         */
      34             : /************************************************************************/
      35             : 
      36           2 : S57ClassRegistrar::~S57ClassRegistrar()
      37             : 
      38             : {
      39           2 :     nClasses = 0;
      40       80004 :     for (size_t i = 0; i < aoAttrInfos.size(); i++)
      41       80002 :         delete aoAttrInfos[i];
      42           2 :     aoAttrInfos.resize(0);
      43           2 :     nAttrCount = 0;
      44           2 : }
      45             : 
      46             : /************************************************************************/
      47             : /*                        S57ClassContentExplorer()                     */
      48             : /************************************************************************/
      49             : 
      50          55 : S57ClassContentExplorer::S57ClassContentExplorer(
      51          55 :     S57ClassRegistrar *poRegistrarIn)
      52             :     : poRegistrar(poRegistrarIn), papapszClassesFields(nullptr),
      53          55 :       iCurrentClass(-1), papszCurrentFields(nullptr), papszTempResult(nullptr)
      54             : {
      55          55 : }
      56             : 
      57             : /************************************************************************/
      58             : /*                        ~S57ClassContentExplorer()                    */
      59             : /************************************************************************/
      60             : 
      61         110 : S57ClassContentExplorer::~S57ClassContentExplorer()
      62             : {
      63          55 :     CSLDestroy(papszTempResult);
      64             : 
      65          55 :     if (papapszClassesFields != nullptr)
      66             :     {
      67       15675 :         for (int i = 0; i < poRegistrar->nClasses; i++)
      68       15620 :             CSLDestroy(papapszClassesFields[i]);
      69          55 :         CPLFree(papapszClassesFields);
      70             :     }
      71          55 : }
      72             : 
      73             : /************************************************************************/
      74             : /*                              FindFile()                              */
      75             : /************************************************************************/
      76             : 
      77           6 : bool S57ClassRegistrar::FindFile(const char *pszTarget,
      78             :                                  const char *pszDirectory, bool bReportErr,
      79             :                                  VSILFILE **pfp)
      80             : 
      81             : {
      82           6 :     const char *pszFilename = nullptr;
      83             : 
      84           6 :     *pfp = nullptr;
      85             : 
      86           6 :     if (pszDirectory == nullptr)
      87             :     {
      88             : #if defined(USE_ONLY_EMBEDDED_RESOURCE_FILES)
      89             :         pszFilename = pszTarget;
      90             : #else
      91           6 :         pszFilename = CPLFindFile("s57", pszTarget);
      92           6 :         if (pszFilename == nullptr)
      93           0 :             pszFilename = pszTarget;
      94             : #endif
      95           6 :         if (EQUAL(pszFilename, pszTarget))
      96             :         {
      97             : #ifdef EMBED_RESOURCE_FILES
      98             :             const char *pszContent = S57GetEmbeddedCSV(pszTarget);
      99             :             if (pszContent)
     100             :             {
     101             :                 CPLDebug("S57", "Using embedded %s", pszTarget);
     102             :                 *pfp = VSIFileFromMemBuffer(
     103             :                     nullptr,
     104             :                     const_cast<GByte *>(
     105             :                         reinterpret_cast<const GByte *>(pszContent)),
     106             :                     static_cast<int>(strlen(pszContent)),
     107             :                     /* bTakeOwnership = */ false);
     108             :             }
     109             : #endif
     110             :         }
     111             :     }
     112             :     else
     113             :     {
     114           0 :         pszFilename = CPLFormFilename(pszDirectory, pszTarget, nullptr);
     115             :     }
     116             : 
     117             : #ifdef EMBED_RESOURCE_FILES
     118             :     if (!(*pfp))
     119             : #endif
     120             :     {
     121           6 :         *pfp = VSIFOpenL(pszFilename, "rb");
     122             :     }
     123             : 
     124           6 :     if (*pfp == nullptr)
     125             :     {
     126           0 :         if (bReportErr)
     127           0 :             CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %s.\n",
     128             :                      pszFilename);
     129           0 :         return FALSE;
     130             :     }
     131             : 
     132           6 :     return TRUE;
     133             : }
     134             : 
     135             : /************************************************************************/
     136             : /*                              ReadLine()                              */
     137             : /*                                                                      */
     138             : /*      Read a line from the provided file, or from the "built-in"      */
     139             : /*      configuration file line list if the file is NULL.               */
     140             : /************************************************************************/
     141             : 
     142        2319 : const char *S57ClassRegistrar::ReadLine(VSILFILE *fp)
     143             : 
     144             : {
     145        2319 :     if (fp != nullptr)
     146        2319 :         return CPLReadLineL(fp);
     147             : 
     148           0 :     if (papszNextLine == nullptr)
     149           0 :         return nullptr;
     150             : 
     151           0 :     if (*papszNextLine == nullptr)
     152             :     {
     153           0 :         papszNextLine = nullptr;
     154           0 :         return nullptr;
     155             :     }
     156             : 
     157           0 :     return *(papszNextLine++);
     158             : }
     159             : 
     160             : /************************************************************************/
     161             : /*                              LoadInfo()                              */
     162             : /************************************************************************/
     163             : 
     164           3 : bool S57ClassRegistrar::LoadInfo(const char *pszDirectory,
     165             :                                  const char *pszProfile, bool bReportErr)
     166             : 
     167             : {
     168           3 :     if (pszDirectory == nullptr)
     169           3 :         pszDirectory = CPLGetConfigOption("S57_CSV", nullptr);
     170             : 
     171             :     /* ==================================================================== */
     172             :     /*      Read the s57objectclasses file.                                 */
     173             :     /* ==================================================================== */
     174           3 :     if (pszProfile == nullptr)
     175           3 :         pszProfile = CPLGetConfigOption("S57_PROFILE", "");
     176             : 
     177             :     char szTargetFile[1024];  // TODO: Get this off of the stack.
     178           3 :     if (EQUAL(pszProfile, "Additional_Military_Layers"))
     179             :     {
     180             :         // Has been suppressed in GDAL data/
     181           0 :         snprintf(szTargetFile, sizeof(szTargetFile), "s57objectclasses_%s.csv",
     182             :                  "aml");
     183             :     }
     184           3 :     else if (EQUAL(pszProfile, "Inland_Waterways"))
     185             :     {
     186             :         // Has been suppressed in GDAL data/
     187           0 :         snprintf(szTargetFile, sizeof(szTargetFile), "s57objectclasses_%s.csv",
     188             :                  "iw");
     189             :     }
     190           3 :     else if (strlen(pszProfile) > 0)
     191             :     {
     192           0 :         snprintf(szTargetFile, sizeof(szTargetFile), "s57objectclasses_%s.csv",
     193             :                  pszProfile);
     194             :     }
     195             :     else
     196             :     {
     197           3 :         strcpy(szTargetFile, "s57objectclasses.csv");
     198             :     }
     199             : 
     200           3 :     VSILFILE *fp = nullptr;
     201           3 :     if (!FindFile(szTargetFile, pszDirectory, bReportErr, &fp))
     202             :     {
     203           0 :         if (EQUAL(pszProfile, "Additional_Military_Layers") ||
     204           0 :             EQUAL(pszProfile, "Inland_Waterways"))
     205             :         {
     206           0 :             strcpy(szTargetFile, "s57objectclasses.csv");
     207           0 :             if (!FindFile(szTargetFile, pszDirectory, bReportErr, &fp))
     208           0 :                 return false;
     209             :         }
     210           0 :         return false;
     211             :     }
     212             : 
     213             :     /* -------------------------------------------------------------------- */
     214             :     /*      Skip the line defining the column titles.                       */
     215             :     /* -------------------------------------------------------------------- */
     216           3 :     const char *pszLine = ReadLine(fp);
     217             : 
     218           3 :     if (!EQUAL(pszLine,
     219             :                "\"Code\",\"ObjectClass\",\"Acronym\",\"Attribute_A\","
     220             :                "\"Attribute_B\",\"Attribute_C\",\"Class\",\"Primitives\""))
     221             :     {
     222           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     223             :                  "s57objectclasses columns don't match expected format!\n");
     224           0 :         if (fp != nullptr)
     225           0 :             VSIFCloseL(fp);
     226           0 :         return false;
     227             :     }
     228             : 
     229             :     /* -------------------------------------------------------------------- */
     230             :     /*      Read and form string list.                                      */
     231             :     /* -------------------------------------------------------------------- */
     232           3 :     apszClassesInfo.Clear();
     233         861 :     while ((pszLine = ReadLine(fp)) != nullptr)
     234             :     {
     235         858 :         if (strstr(pszLine, "###") != nullptr)
     236           6 :             continue;
     237         852 :         apszClassesInfo.AddString(pszLine);
     238             :     }
     239             : 
     240             :     /* -------------------------------------------------------------------- */
     241             :     /*      Cleanup, and establish state.                                   */
     242             :     /* -------------------------------------------------------------------- */
     243           3 :     if (fp != nullptr)
     244           3 :         VSIFCloseL(fp);
     245             : 
     246           3 :     nClasses = apszClassesInfo.size();
     247           3 :     if (nClasses == 0)
     248           0 :         return false;
     249             : 
     250             :     /* ==================================================================== */
     251             :     /*      Read the attributes list.                                       */
     252             :     /* ==================================================================== */
     253             : 
     254           3 :     if (EQUAL(pszProfile, "Additional_Military_Layers"))
     255             :     {
     256             :         // Has been suppressed in GDAL data/
     257           0 :         snprintf(szTargetFile, sizeof(szTargetFile), "s57attributes_%s.csv",
     258             :                  "aml");
     259             :     }
     260           3 :     else if (EQUAL(pszProfile, "Inland_Waterways"))
     261             :     {
     262             :         // Has been suppressed in GDAL data/
     263           0 :         snprintf(szTargetFile, sizeof(szTargetFile), "s57attributes_%s.csv",
     264             :                  "iw");
     265             :     }
     266           3 :     else if (strlen(pszProfile) > 0)
     267             :     {
     268           0 :         snprintf(szTargetFile, sizeof(szTargetFile), "s57attributes_%s.csv",
     269             :                  pszProfile);
     270             :     }
     271             :     else
     272             :     {
     273           3 :         strcpy(szTargetFile, "s57attributes.csv");
     274             :     }
     275             : 
     276           3 :     if (!FindFile(szTargetFile, pszDirectory, bReportErr, &fp))
     277             :     {
     278           0 :         if (EQUAL(pszProfile, "Additional_Military_Layers") ||
     279           0 :             EQUAL(pszProfile, "Inland_Waterways"))
     280             :         {
     281           0 :             strcpy(szTargetFile, "s57attributes.csv");
     282           0 :             if (!FindFile(szTargetFile, pszDirectory, bReportErr, &fp))
     283           0 :                 return false;
     284             :         }
     285           0 :         return false;
     286             :     }
     287             : 
     288             :     /* -------------------------------------------------------------------- */
     289             :     /*      Skip the line defining the column titles.                       */
     290             :     /* -------------------------------------------------------------------- */
     291           3 :     pszLine = ReadLine(fp);
     292             : 
     293           3 :     if (!EQUAL(
     294             :             pszLine,
     295             :             "\"Code\",\"Attribute\",\"Acronym\",\"Attributetype\",\"Class\""))
     296             :     {
     297           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     298             :                  "s57attributes columns don't match expected format!\n");
     299           0 :         if (fp != nullptr)
     300           0 :             VSIFCloseL(fp);
     301           0 :         return false;
     302             :     }
     303             : 
     304             :     /* -------------------------------------------------------------------- */
     305             :     /*      Read and form string list.                                      */
     306             :     /* -------------------------------------------------------------------- */
     307        1452 :     while ((pszLine = ReadLine(fp)) != nullptr)
     308             :     {
     309        1449 :         if (strstr(pszLine, "###") != nullptr)
     310           9 :             continue;
     311             : 
     312        1440 :         char **papszTokens = CSLTokenizeStringComplex(pszLine, ",", TRUE, TRUE);
     313             : 
     314        1440 :         if (CSLCount(papszTokens) < 5)
     315             :         {
     316           0 :             CSLDestroy(papszTokens);
     317           0 :             continue;
     318             :         }
     319             : 
     320        1440 :         int iAttr = atoi(papszTokens[0]);
     321        1440 :         if (iAttr >= (int)aoAttrInfos.size())
     322        1440 :             aoAttrInfos.resize(iAttr + 1);
     323             : 
     324        1440 :         if (iAttr < 0 || aoAttrInfos[iAttr] != nullptr)
     325             :         {
     326           0 :             CPLDebug("S57", "Duplicate/corrupt definition for attribute %d:%s",
     327           0 :                      iAttr, papszTokens[2]);
     328           0 :             CSLDestroy(papszTokens);
     329           0 :             continue;
     330             :         }
     331             : 
     332        1440 :         aoAttrInfos[iAttr] = new S57AttrInfo();
     333        1440 :         aoAttrInfos[iAttr]->osName = papszTokens[1];
     334        1440 :         aoAttrInfos[iAttr]->osAcronym = papszTokens[2];
     335        1440 :         aoAttrInfos[iAttr]->chType = papszTokens[3][0];
     336        1440 :         aoAttrInfos[iAttr]->chClass = papszTokens[4][0];
     337        1440 :         anAttrIndex.push_back(iAttr);
     338        1440 :         CSLDestroy(papszTokens);
     339             :     }
     340             : 
     341           3 :     if (fp != nullptr)
     342           3 :         VSIFCloseL(fp);
     343             : 
     344           3 :     nAttrCount = static_cast<int>(anAttrIndex.size());
     345             : 
     346             :     /* -------------------------------------------------------------------- */
     347             :     /*      Sort index by acronym.                                          */
     348             :     /* -------------------------------------------------------------------- */
     349           3 :     bool bModified = false;
     350        1050 :     do
     351             :     {
     352        1050 :         bModified = false;
     353      504000 :         for (int iAttr = 0; iAttr < nAttrCount - 1; iAttr++)
     354             :         {
     355      502950 :             if (strcmp(aoAttrInfos[anAttrIndex[iAttr]]->osAcronym,
     356     1005900 :                        aoAttrInfos[anAttrIndex[iAttr + 1]]->osAcronym) > 0)
     357             :             {
     358       51513 :                 int nTemp = anAttrIndex[iAttr];
     359       51513 :                 anAttrIndex[iAttr] = anAttrIndex[iAttr + 1];
     360       51513 :                 anAttrIndex[iAttr + 1] = nTemp;
     361       51513 :                 bModified = true;
     362             :             }
     363             :         }
     364             :     } while (bModified);
     365             : 
     366           3 :     return true;
     367             : }
     368             : 
     369             : /************************************************************************/
     370             : /*                         SelectClassByIndex()                         */
     371             : /************************************************************************/
     372             : 
     373       46851 : bool S57ClassContentExplorer::SelectClassByIndex(int nNewIndex)
     374             : 
     375             : {
     376       46851 :     if (nNewIndex < 0 || nNewIndex >= poRegistrar->nClasses)
     377          18 :         return false;
     378             : 
     379             :     /* -------------------------------------------------------------------- */
     380             :     /*      Do we have our cache of class information field lists?          */
     381             :     /* -------------------------------------------------------------------- */
     382       46833 :     if (papapszClassesFields == nullptr)
     383             :     {
     384          55 :         papapszClassesFields =
     385          55 :             (char ***)CPLCalloc(sizeof(void *), poRegistrar->nClasses);
     386             :     }
     387             : 
     388             :     /* -------------------------------------------------------------------- */
     389             :     /*      Has this info been parsed yet?                                  */
     390             :     /* -------------------------------------------------------------------- */
     391       46833 :     if (papapszClassesFields[nNewIndex] == nullptr)
     392       15620 :         papapszClassesFields[nNewIndex] = CSLTokenizeStringComplex(
     393       15620 :             poRegistrar->apszClassesInfo[nNewIndex], ",", TRUE, TRUE);
     394             : 
     395       46833 :     papszCurrentFields = papapszClassesFields[nNewIndex];
     396             : 
     397       46833 :     iCurrentClass = nNewIndex;
     398             : 
     399       46833 :     return true;
     400             : }
     401             : 
     402             : /************************************************************************/
     403             : /*                             SelectClass()                            */
     404             : /************************************************************************/
     405             : 
     406        5281 : bool S57ClassContentExplorer::SelectClass(int nOBJL)
     407             : 
     408             : {
     409      748695 :     for (int i = 0; i < poRegistrar->nClasses; i++)
     410             :     {
     411      748695 :         if (atoi(poRegistrar->apszClassesInfo[i]) == nOBJL)
     412        5281 :             return SelectClassByIndex(i);
     413             :     }
     414             : 
     415           0 :     return FALSE;
     416             : }
     417             : 
     418             : /************************************************************************/
     419             : /*                            SelectClass()                             */
     420             : /************************************************************************/
     421             : 
     422         260 : bool S57ClassContentExplorer::SelectClass(const char *pszAcronym)
     423             : 
     424             : {
     425       36489 :     for (int i = 0; i < poRegistrar->nClasses; i++)
     426             :     {
     427       36440 :         if (!SelectClassByIndex(i))
     428           0 :             continue;
     429             : 
     430       36440 :         const char *pszClassAcronym = GetAcronym();
     431       36440 :         if (pszClassAcronym != nullptr &&
     432       36440 :             strcmp(pszClassAcronym, pszAcronym) == 0)
     433         211 :             return true;
     434             :     }
     435             : 
     436          49 :     return false;
     437             : }
     438             : 
     439             : /************************************************************************/
     440             : /*                              GetOBJL()                               */
     441             : /************************************************************************/
     442             : 
     443        5281 : int S57ClassContentExplorer::GetOBJL()
     444             : 
     445             : {
     446        5281 :     if (iCurrentClass >= 0)
     447        5281 :         return atoi(poRegistrar->apszClassesInfo[iCurrentClass]);
     448             : 
     449           0 :     return -1;
     450             : }
     451             : 
     452             : /************************************************************************/
     453             : /*                           GetDescription()                           */
     454             : /************************************************************************/
     455             : 
     456          54 : const char *S57ClassContentExplorer::GetDescription() const
     457             : 
     458             : {
     459          54 :     if (iCurrentClass >= 0 && papszCurrentFields[0] != nullptr)
     460          54 :         return papszCurrentFields[1];
     461             : 
     462           0 :     return nullptr;
     463             : }
     464             : 
     465             : /************************************************************************/
     466             : /*                             GetAcronym()                             */
     467             : /************************************************************************/
     468             : 
     469       48074 : const char *S57ClassContentExplorer::GetAcronym() const
     470             : 
     471             : {
     472       48074 :     if (iCurrentClass >= 0 && papszCurrentFields[0] != nullptr &&
     473       48074 :         papszCurrentFields[1] != nullptr)
     474       48074 :         return papszCurrentFields[2];
     475             : 
     476           0 :     return nullptr;
     477             : }
     478             : 
     479             : /************************************************************************/
     480             : /*                          GetAttributeList()                          */
     481             : /*                                                                      */
     482             : /*      The passed string can be "a", "b", "c" or NULL for all.  The    */
     483             : /*      returned list remained owned by this object, not the caller.    */
     484             : /************************************************************************/
     485             : 
     486        5305 : char **S57ClassContentExplorer::GetAttributeList(const char *pszType)
     487             : 
     488             : {
     489        5305 :     if (iCurrentClass < 0)
     490           0 :         return nullptr;
     491             : 
     492        5305 :     CSLDestroy(papszTempResult);
     493        5305 :     papszTempResult = nullptr;
     494             : 
     495       21220 :     for (int iColumn = 3; iColumn < 6; iColumn++)
     496             :     {
     497       15915 :         if (pszType != nullptr && iColumn == 3 && !EQUAL(pszType, "a"))
     498           0 :             continue;
     499             : 
     500       15915 :         if (pszType != nullptr && iColumn == 4 && !EQUAL(pszType, "b"))
     501           0 :             continue;
     502             : 
     503       15915 :         if (pszType != nullptr && iColumn == 5 && !EQUAL(pszType, "c"))
     504           0 :             continue;
     505             : 
     506       31830 :         char **papszTokens = CSLTokenizeStringComplex(
     507       15915 :             papszCurrentFields[iColumn], ";", TRUE, FALSE);
     508             : 
     509       15915 :         papszTempResult = CSLInsertStrings(papszTempResult, -1, papszTokens);
     510             : 
     511       15915 :         CSLDestroy(papszTokens);
     512             :     }
     513             : 
     514        5305 :     return papszTempResult;
     515             : }
     516             : 
     517             : /************************************************************************/
     518             : /*                            GetClassCode()                            */
     519             : /************************************************************************/
     520             : 
     521           0 : char S57ClassContentExplorer::GetClassCode() const
     522             : 
     523             : {
     524           0 :     if (iCurrentClass >= 0 && papszCurrentFields[0] != nullptr &&
     525           0 :         papszCurrentFields[1] != nullptr && papszCurrentFields[2] != nullptr &&
     526           0 :         papszCurrentFields[3] != nullptr && papszCurrentFields[4] != nullptr &&
     527           0 :         papszCurrentFields[5] != nullptr && papszCurrentFields[6] != nullptr)
     528           0 :         return papszCurrentFields[6][0];
     529             : 
     530           0 :     return '\0';
     531             : }
     532             : 
     533             : /************************************************************************/
     534             : /*                           GetPrimitives()                            */
     535             : /************************************************************************/
     536             : 
     537        5281 : char **S57ClassContentExplorer::GetPrimitives()
     538             : 
     539             : {
     540        5281 :     if (iCurrentClass >= 0 && CSLCount(papszCurrentFields) > 7)
     541             :     {
     542        5281 :         CSLDestroy(papszTempResult);
     543        5281 :         papszTempResult =
     544        5281 :             CSLTokenizeStringComplex(papszCurrentFields[7], ";", TRUE, FALSE);
     545        5281 :         return papszTempResult;
     546             :     }
     547             : 
     548           0 :     return nullptr;
     549             : }
     550             : 
     551             : /************************************************************************/
     552             : /*                            GetAttrInfo()                             */
     553             : /************************************************************************/
     554             : 
     555      109511 : const S57AttrInfo *S57ClassRegistrar::GetAttrInfo(int iAttr)
     556             : {
     557      109511 :     if (iAttr < 0 || iAttr >= (int)aoAttrInfos.size())
     558           0 :         return nullptr;
     559             : 
     560      109511 :     return aoAttrInfos[iAttr];
     561             : }
     562             : 
     563             : /************************************************************************/
     564             : /*                         FindAttrByAcronym()                          */
     565             : /************************************************************************/
     566             : 
     567      108045 : int S57ClassRegistrar::FindAttrByAcronym(const char *pszName)
     568             : 
     569             : {
     570      108045 :     int iStart = 0;
     571      108045 :     int iEnd = nAttrCount - 1;
     572             : 
     573      842530 :     while (iStart <= iEnd)
     574             :     {
     575      842476 :         const int iCandidate = (iStart + iEnd) / 2;
     576             :         int nCompareValue =
     577      842476 :             strcmp(pszName, aoAttrInfos[anAttrIndex[iCandidate]]->osAcronym);
     578             : 
     579      842476 :         if (nCompareValue < 0)
     580             :         {
     581      393427 :             iEnd = iCandidate - 1;
     582             :         }
     583      449049 :         else if (nCompareValue > 0)
     584             :         {
     585      341058 :             iStart = iCandidate + 1;
     586             :         }
     587             :         else
     588      107991 :             return anAttrIndex[iCandidate];
     589             :     }
     590             : 
     591          54 :     return -1;
     592             : }

Generated by: LCOV version 1.14