LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/openfilegdb - filegdbtable_priv.h (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 180 189 95.2 %
Date: 2025-01-18 12:42:00 Functions: 28 28 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implements reading of FileGDB tables
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #ifndef FILEGDBTABLE_PRIV_H_INCLUDED
      14             : #define FILEGDBTABLE_PRIV_H_INCLUDED
      15             : 
      16             : #include "filegdbtable.h"
      17             : 
      18             : #include "cpl_conv.h"
      19             : #include "cpl_error.h"
      20             : #include "cpl_time.h"
      21             : 
      22             : #include <algorithm>
      23             : #include <cwchar>
      24             : #include <vector>
      25             : #include <limits>
      26             : 
      27             : #define DIV_ROUND_UP(a, b) (((a) % (b)) == 0 ? ((a) / (b)) : (((a) / (b)) + 1))
      28             : 
      29             : #define TEST_BIT(ar, bit) (ar[(bit) / 8] & (1 << ((bit) % 8)))
      30             : #define BIT_ARRAY_SIZE_IN_BYTES(bitsize) (((bitsize) + 7) / 8)
      31             : 
      32             : namespace OpenFileGDB
      33             : {
      34             : 
      35             : /************************************************************************/
      36             : /*                              GetInt16()                              */
      37             : /************************************************************************/
      38             : 
      39        7680 : inline GInt16 GetInt16(const GByte *pBaseAddr, int iOffset)
      40             : {
      41             :     GInt16 nVal;
      42        7680 :     memcpy(&nVal, pBaseAddr + sizeof(nVal) * iOffset, sizeof(nVal));
      43        7680 :     CPL_LSBPTR16(&nVal);
      44        7680 :     return nVal;
      45             : }
      46             : 
      47             : /************************************************************************/
      48             : /*                              GetUInt16()                             */
      49             : /************************************************************************/
      50             : 
      51        8294 : inline GUInt16 GetUInt16(const GByte *pBaseAddr, int iOffset)
      52             : {
      53             :     GUInt16 nVal;
      54        8294 :     memcpy(&nVal, pBaseAddr + sizeof(nVal) * iOffset, sizeof(nVal));
      55        8294 :     CPL_LSBPTR16(&nVal);
      56        8294 :     return nVal;
      57             : }
      58             : 
      59             : /************************************************************************/
      60             : /*                              GetInt32()                              */
      61             : /************************************************************************/
      62             : 
      63       51623 : inline GInt32 GetInt32(const GByte *pBaseAddr, int iOffset)
      64             : {
      65             :     GInt32 nVal;
      66       51623 :     memcpy(&nVal, pBaseAddr + sizeof(nVal) * iOffset, sizeof(nVal));
      67       51623 :     CPL_LSBPTR32(&nVal);
      68       51623 :     return nVal;
      69             : }
      70             : 
      71             : /************************************************************************/
      72             : /*                              GetUInt32()                             */
      73             : /************************************************************************/
      74             : 
      75      543651 : inline GUInt32 GetUInt32(const GByte *pBaseAddr, int iOffset)
      76             : {
      77             :     GUInt32 nVal;
      78      543651 :     memcpy(&nVal, pBaseAddr + sizeof(nVal) * iOffset, sizeof(nVal));
      79      543651 :     CPL_LSBPTR32(&nVal);
      80      543651 :     return nVal;
      81             : }
      82             : 
      83             : /************************************************************************/
      84             : /*                              GetInt64()                              */
      85             : /************************************************************************/
      86             : 
      87       83111 : inline int64_t GetInt64(const GByte *pBaseAddr, int iOffset)
      88             : {
      89             :     int64_t nVal;
      90       83111 :     memcpy(&nVal, pBaseAddr + sizeof(nVal) * iOffset, sizeof(nVal));
      91       83111 :     CPL_LSBPTR64(&nVal);
      92       83111 :     return nVal;
      93             : }
      94             : 
      95             : /************************************************************************/
      96             : /*                              GetUInt64()                             */
      97             : /************************************************************************/
      98             : 
      99        4763 : inline uint64_t GetUInt64(const GByte *pBaseAddr, int iOffset)
     100             : {
     101             :     uint64_t nVal;
     102        4763 :     memcpy(&nVal, pBaseAddr + sizeof(nVal) * iOffset, sizeof(nVal));
     103        4763 :     CPL_LSBPTR64(&nVal);
     104        4763 :     return nVal;
     105             : }
     106             : 
     107             : /************************************************************************/
     108             : /*                             GetFloat32()                             */
     109             : /************************************************************************/
     110             : 
     111        7696 : inline float GetFloat32(const GByte *pBaseAddr, int iOffset)
     112             : {
     113             :     float fVal;
     114        7696 :     memcpy(&fVal, pBaseAddr + sizeof(fVal) * iOffset, sizeof(fVal));
     115        7696 :     CPL_LSBPTR32(&fVal);
     116        7696 :     return fVal;
     117             : }
     118             : 
     119             : /************************************************************************/
     120             : /*                             GetFloat64()                             */
     121             : /************************************************************************/
     122             : 
     123       75566 : inline double GetFloat64(const GByte *pBaseAddr, int iOffset)
     124             : {
     125             :     double dfVal;
     126       75566 :     memcpy(&dfVal, pBaseAddr + sizeof(dfVal) * iOffset, sizeof(dfVal));
     127       75566 :     CPL_LSBPTR64(&dfVal);
     128       75566 :     return dfVal;
     129             : }
     130             : 
     131             : /************************************************************************/
     132             : /*                          ReadUInt32()                                */
     133             : /************************************************************************/
     134             : 
     135        7999 : inline bool ReadUInt32(VSILFILE *fp, uint32_t &nVal)
     136             : {
     137        7999 :     const bool bRet = VSIFReadL(&nVal, 1, sizeof(nVal), fp) == sizeof(nVal);
     138        7999 :     CPL_LSBPTR32(&nVal);
     139        7999 :     return bRet;
     140             : }
     141             : 
     142             : /************************************************************************/
     143             : /*                          WriteUInt32()                               */
     144             : /************************************************************************/
     145             : 
     146       77762 : inline bool WriteUInt32(VSILFILE *fp, uint32_t nVal)
     147             : {
     148       77762 :     CPL_LSBPTR32(&nVal);
     149       77762 :     return VSIFWriteL(&nVal, 1, sizeof(nVal), fp) == sizeof(nVal);
     150             : }
     151             : 
     152             : /************************************************************************/
     153             : /*                          WriteUInt64()                               */
     154             : /************************************************************************/
     155             : 
     156       10714 : inline bool WriteUInt64(VSILFILE *fp, uint64_t nVal)
     157             : {
     158       10714 :     CPL_LSBPTR64(&nVal);
     159       10714 :     return VSIFWriteL(&nVal, 1, sizeof(nVal), fp) == sizeof(nVal);
     160             : }
     161             : 
     162             : /************************************************************************/
     163             : /*                          WriteFloat64()                               */
     164             : /************************************************************************/
     165             : 
     166         650 : inline bool WriteFloat64(VSILFILE *fp, double dfVal)
     167             : {
     168         650 :     CPL_LSBPTR64(&dfVal);
     169         650 :     return VSIFWriteL(&dfVal, 1, sizeof(dfVal), fp) == sizeof(dfVal);
     170             : }
     171             : 
     172             : /************************************************************************/
     173             : /*                          WriteUInt32()                               */
     174             : /************************************************************************/
     175             : 
     176       21914 : inline void WriteUInt32(std::vector<GByte> &abyBuffer, uint32_t nVal)
     177             : {
     178       21914 :     CPL_LSBPTR32(&nVal);
     179       21914 :     const GByte *pabyInput = reinterpret_cast<const GByte *>(&nVal);
     180       21914 :     abyBuffer.insert(abyBuffer.end(), pabyInput, pabyInput + sizeof(nVal));
     181       21914 : }
     182             : 
     183             : /************************************************************************/
     184             : /*                          WriteUInt32()                               */
     185             : /************************************************************************/
     186             : 
     187       10210 : inline void WriteUInt32(std::vector<GByte> &abyBuffer, uint32_t nVal,
     188             :                         size_t nPos)
     189             : {
     190       10210 :     CPL_LSBPTR32(&nVal);
     191       10210 :     const GByte *pabyInput = reinterpret_cast<const GByte *>(&nVal);
     192       10210 :     memcpy(&abyBuffer[nPos], pabyInput, sizeof(nVal));
     193       10210 : }
     194             : 
     195             : /************************************************************************/
     196             : /*                          WriteFloat32()                               */
     197             : /************************************************************************/
     198             : 
     199          54 : inline void WriteFloat32(std::vector<GByte> &abyBuffer, float fVal)
     200             : {
     201          54 :     CPL_LSBPTR32(&fVal);
     202          54 :     const GByte *pabyInput = reinterpret_cast<const GByte *>(&fVal);
     203          54 :     abyBuffer.insert(abyBuffer.end(), pabyInput, pabyInput + sizeof(fVal));
     204          54 : }
     205             : 
     206             : /************************************************************************/
     207             : /*                          WriteFloat64()                               */
     208             : /************************************************************************/
     209             : 
     210       13063 : inline void WriteFloat64(std::vector<GByte> &abyBuffer, double dfVal)
     211             : {
     212       13063 :     CPL_LSBPTR64(&dfVal);
     213       13063 :     const GByte *pabyInput = reinterpret_cast<const GByte *>(&dfVal);
     214       13063 :     abyBuffer.insert(abyBuffer.end(), pabyInput, pabyInput + sizeof(dfVal));
     215       13063 : }
     216             : 
     217             : /************************************************************************/
     218             : /*                          WriteInt32()                                */
     219             : /************************************************************************/
     220             : 
     221        3952 : inline void WriteInt32(std::vector<GByte> &abyBuffer, int32_t nVal)
     222             : {
     223        3952 :     CPL_LSBPTR32(&nVal);
     224        3952 :     const GByte *pabyInput = reinterpret_cast<const GByte *>(&nVal);
     225        3952 :     abyBuffer.insert(abyBuffer.end(), pabyInput, pabyInput + sizeof(nVal));
     226        3952 : }
     227             : 
     228             : /************************************************************************/
     229             : /*                          WriteInt64()                                */
     230             : /************************************************************************/
     231             : 
     232           3 : inline void WriteInt64(std::vector<GByte> &abyBuffer, int64_t nVal)
     233             : {
     234           3 :     CPL_LSBPTR64(&nVal);
     235           3 :     const GByte *pabyInput = reinterpret_cast<const GByte *>(&nVal);
     236           3 :     abyBuffer.insert(abyBuffer.end(), pabyInput, pabyInput + sizeof(nVal));
     237           3 : }
     238             : 
     239             : /************************************************************************/
     240             : /*                          WriteUInt16()                               */
     241             : /************************************************************************/
     242             : 
     243        4571 : inline void WriteUInt16(std::vector<GByte> &abyBuffer, uint16_t nVal)
     244             : {
     245        4571 :     CPL_LSBPTR16(&nVal);
     246        4571 :     const GByte *pabyInput = reinterpret_cast<const GByte *>(&nVal);
     247        4571 :     abyBuffer.insert(abyBuffer.end(), pabyInput, pabyInput + sizeof(nVal));
     248        4571 : }
     249             : 
     250             : /************************************************************************/
     251             : /*                          WriteInt16()                                */
     252             : /************************************************************************/
     253             : 
     254        3756 : inline void WriteInt16(std::vector<GByte> &abyBuffer, int16_t nVal)
     255             : {
     256        3756 :     CPL_LSBPTR16(&nVal);
     257        3756 :     const GByte *pabyInput = reinterpret_cast<const GByte *>(&nVal);
     258        3756 :     abyBuffer.insert(abyBuffer.end(), pabyInput, pabyInput + sizeof(nVal));
     259        3756 : }
     260             : 
     261             : /************************************************************************/
     262             : /*                          WriteUInt8()                                */
     263             : /************************************************************************/
     264             : 
     265      203754 : inline void WriteUInt8(std::vector<GByte> &abyBuffer, uint8_t nVal)
     266             : {
     267      203754 :     abyBuffer.push_back(nVal);
     268      203754 : }
     269             : 
     270             : /************************************************************************/
     271             : /*                          WriteUInt64()                               */
     272             : /************************************************************************/
     273             : 
     274        4061 : inline void WriteUInt64(std::vector<GByte> &abyBuffer, uint64_t nVal)
     275             : {
     276        4061 :     CPL_LSBPTR64(&nVal);
     277        4061 :     const GByte *pabyInput = reinterpret_cast<const GByte *>(&nVal);
     278        4061 :     abyBuffer.insert(abyBuffer.end(), pabyInput, pabyInput + sizeof(nVal));
     279        4061 : }
     280             : 
     281             : /************************************************************************/
     282             : /*                             WriteVarUInt()                           */
     283             : /************************************************************************/
     284             : 
     285      121848 : inline void WriteVarUInt(std::vector<GByte> &abyBuffer, uint64_t nVal)
     286             : {
     287             :     while (true)
     288             :     {
     289      121848 :         if (nVal >= 0x80)
     290             :         {
     291       54261 :             WriteUInt8(abyBuffer, static_cast<uint8_t>(0x80 | (nVal & 0x7F)));
     292       54261 :             nVal >>= 7;
     293             :         }
     294             :         else
     295             :         {
     296       67587 :             WriteUInt8(abyBuffer, static_cast<uint8_t>(nVal));
     297       67587 :             break;
     298             :         }
     299             :     }
     300       67587 : }
     301             : 
     302             : /************************************************************************/
     303             : /*                             WriteVarInt()                            */
     304             : /************************************************************************/
     305             : 
     306        3389 : inline void WriteVarInt(std::vector<GByte> &abyBuffer, int64_t nVal)
     307             : {
     308             :     uint64_t nUVal;
     309        3389 :     if (nVal < 0)
     310             :     {
     311         427 :         if (nVal == std::numeric_limits<int64_t>::min())
     312           0 :             nUVal = static_cast<uint64_t>(1) << 63;
     313             :         else
     314         427 :             nUVal = -nVal;
     315         427 :         if (nUVal >= 0x40)
     316             :         {
     317         427 :             WriteUInt8(abyBuffer,
     318         427 :                        static_cast<uint8_t>(0x80 | 0x40 | (nUVal & 0x3F)));
     319         427 :             nUVal >>= 6;
     320             :         }
     321             :         else
     322             :         {
     323           0 :             WriteUInt8(abyBuffer, static_cast<uint8_t>(0x40 | (nUVal & 0x3F)));
     324           0 :             return;
     325             :         }
     326             :     }
     327             :     else
     328             :     {
     329        2962 :         nUVal = nVal;
     330        2962 :         if (nUVal >= 0x40)
     331             :         {
     332         705 :             WriteUInt8(abyBuffer, static_cast<uint8_t>(0x80 | (nUVal & 0x3F)));
     333         705 :             nUVal >>= 6;
     334             :         }
     335             :         else
     336             :         {
     337        2257 :             WriteUInt8(abyBuffer, static_cast<uint8_t>((nUVal & 0x3F)));
     338        2257 :             return;
     339             :         }
     340             :     }
     341             : 
     342        1132 :     WriteVarUInt(abyBuffer, nUVal);
     343             : }
     344             : 
     345             : /************************************************************************/
     346             : /*                            ReadUTF16String()                         */
     347             : /************************************************************************/
     348             : 
     349      100103 : inline std::string ReadUTF16String(const GByte *pabyIter, int nCarCount)
     350             : {
     351      200206 :     std::wstring osWideStr;
     352      795020 :     for (int j = 0; j < nCarCount; j++)
     353      694917 :         osWideStr += pabyIter[2 * j] | (pabyIter[2 * j + 1] << 8);
     354             :     char *pszStr =
     355      100103 :         CPLRecodeFromWChar(osWideStr.c_str(), CPL_ENC_UCS2, CPL_ENC_UTF8);
     356      100103 :     std::string osRet(pszStr);
     357      100103 :     CPLFree(pszStr);
     358      200206 :     return osRet;
     359             : }
     360             : 
     361             : /************************************************************************/
     362             : /*                           WriteUTF16String()                         */
     363             : /************************************************************************/
     364             : 
     365             : enum UTF16StringFormat
     366             : {
     367             :     NUMBER_OF_BYTES_ON_UINT16,
     368             :     NUMBER_OF_BYTES_ON_VARUINT,
     369             :     NUMBER_OF_CHARS_ON_UINT8,
     370             :     NUMBER_OF_CHARS_ON_UINT32,
     371             : };
     372             : 
     373       29282 : inline void WriteUTF16String(std::vector<GByte> &abyBuffer, const char *pszStr,
     374             :                              UTF16StringFormat eFormat)
     375             : {
     376       29282 :     wchar_t *pszWStr = CPLRecodeToWChar(pszStr, CPL_ENC_UTF8, CPL_ENC_UCS2);
     377       29282 :     size_t nWLen = wcslen(pszWStr);
     378       29282 :     switch (eFormat)
     379             :     {
     380         546 :         case NUMBER_OF_BYTES_ON_UINT16:
     381             :         {
     382             :             // Write length as bytes
     383             :             const auto nLenToWrite =
     384         546 :                 std::min(static_cast<size_t>(65534), sizeof(uint16_t) * nWLen);
     385         546 :             if (nLenToWrite < sizeof(uint16_t) * nWLen)
     386             :             {
     387           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     388             :                          "String %s truncated to %u bytes", pszStr,
     389             :                          static_cast<uint32_t>(nLenToWrite));
     390           0 :                 nWLen = nLenToWrite / sizeof(uint16_t);
     391             :             }
     392         546 :             WriteUInt16(abyBuffer, static_cast<uint16_t>(nLenToWrite));
     393         546 :             break;
     394             :         }
     395             : 
     396           2 :         case NUMBER_OF_BYTES_ON_VARUINT:
     397             :         {
     398             :             // Write length as bytes
     399           2 :             WriteVarUInt(abyBuffer, sizeof(uint16_t) * nWLen);
     400           2 :             break;
     401             :         }
     402             : 
     403       27722 :         case NUMBER_OF_CHARS_ON_UINT8:
     404             :         {
     405             :             // Write length as number of UTF16 characters
     406       27722 :             const auto nLenToWrite = std::min(static_cast<size_t>(255), nWLen);
     407       27722 :             if (nLenToWrite < nWLen)
     408             :             {
     409           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     410             :                          "String %s truncated to %u UTF16 characters", pszStr,
     411             :                          static_cast<uint32_t>(nLenToWrite));
     412           0 :                 nWLen = nLenToWrite;
     413             :             }
     414       27722 :             WriteUInt8(abyBuffer, static_cast<uint8_t>(nLenToWrite));
     415       27722 :             break;
     416             :         }
     417             : 
     418        1012 :         case NUMBER_OF_CHARS_ON_UINT32:
     419             :         {
     420             :             // Write length as number of UTF16 characters
     421        1012 :             WriteUInt32(abyBuffer, static_cast<uint32_t>(nWLen));
     422        1012 :             break;
     423             :         }
     424             :     }
     425             : 
     426       29282 :     if (nWLen)
     427             :     {
     428       15425 :         std::vector<uint16_t> anChars(nWLen);
     429      182272 :         for (size_t i = 0; i < nWLen; ++i)
     430             :         {
     431      166847 :             anChars[i] = static_cast<uint16_t>(pszWStr[i]);
     432      166847 :             CPL_LSBPTR16(&anChars[i]);
     433             :         }
     434             :         const GByte *pabyInput =
     435       15425 :             reinterpret_cast<const GByte *>(anChars.data());
     436           0 :         abyBuffer.insert(abyBuffer.end(), pabyInput,
     437       15425 :                          pabyInput + nWLen * sizeof(uint16_t));
     438             :     }
     439       29282 :     CPLFree(pszWStr);
     440       29282 : }
     441             : 
     442             : /************************************************************************/
     443             : /*                      FileGDBOGRDateToDoubleDate()                    */
     444             : /************************************************************************/
     445             : 
     446         134 : inline double FileGDBOGRDateToDoubleDate(const OGRField *psField,
     447             :                                          bool bConvertToGMT,
     448             :                                          bool bHighPrecision)
     449             : {
     450             :     struct tm brokendowntime;
     451         134 :     brokendowntime.tm_year = psField->Date.Year - 1900;
     452         134 :     brokendowntime.tm_mon = psField->Date.Month - 1;
     453         134 :     brokendowntime.tm_mday = psField->Date.Day;
     454         134 :     brokendowntime.tm_hour = psField->Date.Hour;
     455         134 :     brokendowntime.tm_min = psField->Date.Minute;
     456         134 :     brokendowntime.tm_sec = bHighPrecision
     457         134 :                                 ? static_cast<int>(psField->Date.Second)
     458         118 :                                 : static_cast<int>(psField->Date.Second + 0.5);
     459         134 :     GIntBig nUnixTime = CPLYMDHMSToUnixTime(&brokendowntime);
     460         134 :     if (bConvertToGMT && psField->Date.TZFlag > 1 &&
     461          39 :         psField->Date.TZFlag != 100)
     462             :     {
     463             :         // Convert to GMT
     464          38 :         const int TZOffset = std::abs(psField->Date.TZFlag - 100) * 15;
     465          38 :         const int TZHour = TZOffset / 60;
     466          38 :         const int TZMinute = TZOffset - TZHour * 60;
     467          38 :         const int nOffset = TZHour * 3600 + TZMinute * 60;
     468          38 :         if (psField->Date.TZFlag >= 100)
     469          38 :             nUnixTime -= nOffset;
     470             :         else
     471           0 :             nUnixTime += nOffset;
     472             :     }
     473             :     // 25569: Number of days between 1899/12/30 00:00:00 and 1970/01/01 00:00:00
     474             :     return static_cast<double>(
     475         268 :                nUnixTime +
     476             :                (bHighPrecision
     477         134 :                     ? fmod(static_cast<double>(psField->Date.Second), 1.0)
     478         134 :                     : 0)) /
     479         134 :                3600.0 / 24.0 +
     480         134 :            25569.0;
     481             : }
     482             : 
     483             : /************************************************************************/
     484             : /*                      FileGDBOGRTimeToDoubleTime()                    */
     485             : /************************************************************************/
     486             : 
     487          12 : inline double FileGDBOGRTimeToDoubleTime(const OGRField *psField)
     488             : {
     489          12 :     return static_cast<double>(psField->Date.Hour * 3600 +
     490          12 :                                psField->Date.Minute * 60 +
     491          12 :                                psField->Date.Second) /
     492          12 :            3600.0 / 24.0;
     493             : }
     494             : 
     495             : void FileGDBTablePrintError(const char *pszFile, int nLineNumber);
     496             : 
     497             : #define PrintError() FileGDBTablePrintError(__FILE__, __LINE__)
     498             : 
     499             : /************************************************************************/
     500             : /*                          returnError()                               */
     501             : /************************************************************************/
     502             : 
     503             : #define returnError()                                                          \
     504             :     do                                                                         \
     505             :     {                                                                          \
     506             :         PrintError();                                                          \
     507             :         return (errorRetValue);                                                \
     508             :     } while (0)
     509             : 
     510             : /************************************************************************/
     511             : /*                         returnErrorIf()                              */
     512             : /************************************************************************/
     513             : 
     514             : #define returnErrorIf(expr)                                                    \
     515             :     do                                                                         \
     516             :     {                                                                          \
     517             :         if ((expr))                                                            \
     518             :             returnError();                                                     \
     519             :     } while (0)
     520             : 
     521             : /************************************************************************/
     522             : /*                       returnErrorAndCleanupIf()                      */
     523             : /************************************************************************/
     524             : 
     525             : #define returnErrorAndCleanupIf(expr, cleanup)                                 \
     526             :     do                                                                         \
     527             :     {                                                                          \
     528             :         if ((expr))                                                            \
     529             :         {                                                                      \
     530             :             cleanup;                                                           \
     531             :             returnError();                                                     \
     532             :         }                                                                      \
     533             :     } while (0)
     534             : 
     535             : } /* namespace OpenFileGDB */
     536             : 
     537             : #endif /* FILEGDBTABLE_PRIV_H_INCLUDED */

Generated by: LCOV version 1.14