LCOV - code coverage report
Current view: top level - contrib/fuzzystrmatch - daitch_mokotoff.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 210 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 12 0
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*
       2              :  * Daitch-Mokotoff Soundex
       3              :  *
       4              :  * Copyright (c) 2023-2026, PostgreSQL Global Development Group
       5              :  *
       6              :  * This module was originally sponsored by Finance Norway /
       7              :  * Trafikkforsikringsforeningen, and implemented by Dag Lem <dag@nimrod.no>
       8              :  *
       9              :  * The implementation of the Daitch-Mokotoff Soundex System aims at correctness
      10              :  * and high performance, and can be summarized as follows:
      11              :  *
      12              :  * - The processing of each phoneme is initiated by an O(1) table lookup.
      13              :  * - For phonemes containing more than one character, a coding tree is traversed
      14              :  *   to process the complete phoneme.
      15              :  * - The (alternate) soundex codes are produced digit by digit in-place in
      16              :  *   another tree structure.
      17              :  *
      18              :  * References:
      19              :  *
      20              :  * https://www.avotaynu.com/soundex.htm
      21              :  * https://www.jewishgen.org/InfoFiles/Soundex.html
      22              :  * https://familypedia.fandom.com/wiki/Daitch-Mokotoff_Soundex
      23              :  * https://stevemorse.org/census/soundex.html (dmlat.php, dmsoundex.php)
      24              :  * https://github.com/apache/commons-codec/ (dmrules.txt, DaitchMokotoffSoundex.java)
      25              :  * https://metacpan.org/pod/Text::Phonetic (DaitchMokotoff.pm)
      26              :  *
      27              :  * A few notes on other implementations:
      28              :  *
      29              :  * - All other known implementations have the same unofficial rules for "UE",
      30              :  *   these are also adapted by this implementation (0, 1, NC).
      31              :  * - The only other known implementation which is capable of generating all
      32              :  *   correct soundex codes in all cases is the JOS Soundex Calculator at
      33              :  *   https://www.jewishgen.org/jos/jossound.htm
      34              :  * - "J" is considered (only) a vowel in dmlat.php
      35              :  * - The official rules for "RS" are commented out in dmlat.php
      36              :  * - Identical code digits for adjacent letters are not collapsed correctly in
      37              :  *   dmsoundex.php when double digit codes are involved. E.g. "BESST" yields
      38              :  *   744300 instead of 743000 as for "BEST".
      39              :  * - "J" is considered (only) a consonant in DaitchMokotoffSoundex.java
      40              :  * - "Y" is not considered a vowel in DaitchMokotoffSoundex.java
      41              : */
      42              : 
      43              : #include "postgres.h"
      44              : 
      45              : #include "catalog/pg_type.h"
      46              : #include "mb/pg_wchar.h"
      47              : #include "utils/array.h"
      48              : #include "utils/builtins.h"
      49              : #include "utils/memutils.h"
      50              : 
      51              : 
      52              : /*
      53              :  * The soundex coding chart table is adapted from
      54              :  * https://www.jewishgen.org/InfoFiles/Soundex.html
      55              :  * See daitch_mokotoff_header.pl for details.
      56              : */
      57              : 
      58              : /* Generated coding chart table */
      59              : #include "daitch_mokotoff.h"
      60              : 
      61              : #define DM_CODE_DIGITS 6
      62              : 
      63              : /* Node in soundex code tree */
      64              : typedef struct dm_node
      65              : {
      66              :         int                     soundex_length; /* Length of generated soundex code */
      67              :         char            soundex[DM_CODE_DIGITS];        /* Soundex code */
      68              :         int                     is_leaf;                /* Candidate for complete soundex code */
      69              :         int                     last_update;    /* Letter number for last update of node */
      70              :         char            code_digit;             /* Last code digit, 0 - 9 */
      71              : 
      72              :         /*
      73              :          * One or two alternate code digits leading to this node. If there are two
      74              :          * digits, one of them is always an 'X'. Repeated code digits and 'X' lead
      75              :          * back to the same node.
      76              :          */
      77              :         char            prev_code_digits[2];
      78              :         /* One or two alternate code digits moving forward. */
      79              :         char            next_code_digits[2];
      80              :         /* ORed together code index(es) used to reach current node. */
      81              :         int                     prev_code_index;
      82              :         int                     next_code_index;
      83              :         /* Possible nodes branching out from this node - digits 0-9. */
      84              :         struct dm_node *children[10];
      85              :         /* Next node in linked list. Alternating index for each iteration. */
      86              :         struct dm_node *next[2];
      87              : } dm_node;
      88              : 
      89              : /* Template for new node in soundex code tree. */
      90              : static const dm_node start_node = {
      91              :         .soundex_length = 0,
      92              :         .soundex = "000000",          /* Six digits */
      93              :         .is_leaf = 0,
      94              :         .last_update = 0,
      95              :         .code_digit = '\0',
      96              :         .prev_code_digits = {'\0', '\0'},
      97              :         .next_code_digits = {'\0', '\0'},
      98              :         .prev_code_index = 0,
      99              :         .next_code_index = 0,
     100              :         .children = {NULL},
     101              :         .next = {NULL}
     102              : };
     103              : 
     104              : /* Dummy soundex codes at end of input. */
     105              : static const dm_codes end_codes[2] =
     106              : {
     107              :         {
     108              :                 "X", "X", "X"
     109              :         }
     110              : };
     111              : 
     112              : /* Mapping from ISO8859-1 to upper-case ASCII, covering the range 0x60..0xFF. */
     113              : static const char iso8859_1_to_ascii_upper[] =
     114              : "`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~                                  !                             ?AAAAAAECEEEEIIIIDNOOOOO*OUUUUYDSAAAAAAECEEEEIIIIDNOOOOO/OUUUUYDY";
     115              : 
     116              : /* Internal C implementation */
     117              : static bool daitch_mokotoff_coding(const char *word, ArrayBuildState *soundex);
     118              : 
     119              : 
     120            0 : PG_FUNCTION_INFO_V1(daitch_mokotoff);
     121              : 
     122              : Datum
     123            0 : daitch_mokotoff(PG_FUNCTION_ARGS)
     124              : {
     125            0 :         text       *arg = PG_GETARG_TEXT_PP(0);
     126            0 :         Datum           retval;
     127            0 :         char       *string;
     128            0 :         ArrayBuildState *soundex;
     129            0 :         MemoryContext old_ctx,
     130              :                                 tmp_ctx;
     131              : 
     132              :         /* Work in a temporary context to simplify cleanup. */
     133            0 :         tmp_ctx = AllocSetContextCreate(CurrentMemoryContext,
     134              :                                                                         "daitch_mokotoff temporary context",
     135              :                                                                         ALLOCSET_DEFAULT_SIZES);
     136            0 :         old_ctx = MemoryContextSwitchTo(tmp_ctx);
     137              : 
     138              :         /* We must convert the string to UTF-8 if it isn't already. */
     139            0 :         string = pg_server_to_any(text_to_cstring(arg), VARSIZE_ANY_EXHDR(arg),
     140              :                                                           PG_UTF8);
     141              : 
     142              :         /* The result is built in this ArrayBuildState. */
     143            0 :         soundex = initArrayResult(TEXTOID, tmp_ctx, false);
     144              : 
     145            0 :         if (!daitch_mokotoff_coding(string, soundex))
     146              :         {
     147              :                 /* No encodable characters in input */
     148            0 :                 MemoryContextSwitchTo(old_ctx);
     149            0 :                 MemoryContextDelete(tmp_ctx);
     150            0 :                 PG_RETURN_NULL();
     151            0 :         }
     152              : 
     153            0 :         retval = makeArrayResult(soundex, old_ctx);
     154              : 
     155            0 :         MemoryContextSwitchTo(old_ctx);
     156            0 :         MemoryContextDelete(tmp_ctx);
     157              : 
     158            0 :         PG_RETURN_DATUM(retval);
     159            0 : }
     160              : 
     161              : 
     162              : /* Initialize soundex code tree node for next code digit. */
     163              : static void
     164            0 : initialize_node(dm_node *node, int last_update)
     165              : {
     166            0 :         if (node->last_update < last_update)
     167              :         {
     168            0 :                 node->prev_code_digits[0] = node->next_code_digits[0];
     169            0 :                 node->prev_code_digits[1] = node->next_code_digits[1];
     170            0 :                 node->next_code_digits[0] = '\0';
     171            0 :                 node->next_code_digits[1] = '\0';
     172            0 :                 node->prev_code_index = node->next_code_index;
     173            0 :                 node->next_code_index = 0;
     174            0 :                 node->is_leaf = 0;
     175            0 :                 node->last_update = last_update;
     176            0 :         }
     177            0 : }
     178              : 
     179              : 
     180              : /* Update soundex code tree node with next code digit. */
     181              : static void
     182            0 : add_next_code_digit(dm_node *node, int code_index, char code_digit)
     183              : {
     184              :         /* OR in index 1 or 2. */
     185            0 :         node->next_code_index |= code_index;
     186              : 
     187            0 :         if (!node->next_code_digits[0])
     188            0 :                 node->next_code_digits[0] = code_digit;
     189            0 :         else if (node->next_code_digits[0] != code_digit)
     190            0 :                 node->next_code_digits[1] = code_digit;
     191            0 : }
     192              : 
     193              : 
     194              : /* Mark soundex code tree node as leaf. */
     195              : static void
     196            0 : set_leaf(dm_node *first_node[2], dm_node *last_node[2],
     197              :                  dm_node *node, int ix_node)
     198              : {
     199            0 :         if (!node->is_leaf)
     200              :         {
     201            0 :                 node->is_leaf = 1;
     202              : 
     203            0 :                 if (first_node[ix_node] == NULL)
     204            0 :                         first_node[ix_node] = node;
     205              :                 else
     206            0 :                         last_node[ix_node]->next[ix_node] = node;
     207              : 
     208            0 :                 last_node[ix_node] = node;
     209            0 :                 node->next[ix_node] = NULL;
     210            0 :         }
     211            0 : }
     212              : 
     213              : 
     214              : /* Find next node corresponding to code digit, or create a new node. */
     215              : static dm_node *
     216            0 : find_or_create_child_node(dm_node *parent, char code_digit,
     217              :                                                   ArrayBuildState *soundex)
     218              : {
     219            0 :         int                     i = code_digit - '0';
     220            0 :         dm_node   **nodes = parent->children;
     221            0 :         dm_node    *node = nodes[i];
     222              : 
     223            0 :         if (node)
     224              :         {
     225              :                 /* Found existing child node. Skip completed nodes. */
     226            0 :                 return node->soundex_length < DM_CODE_DIGITS ? node : NULL;
     227              :         }
     228              : 
     229              :         /* Create new child node. */
     230            0 :         node = palloc_object(dm_node);
     231            0 :         nodes[i] = node;
     232              : 
     233            0 :         *node = start_node;
     234            0 :         memcpy(node->soundex, parent->soundex, sizeof(parent->soundex));
     235            0 :         node->soundex_length = parent->soundex_length;
     236            0 :         node->soundex[node->soundex_length++] = code_digit;
     237            0 :         node->code_digit = code_digit;
     238            0 :         node->next_code_index = node->prev_code_index;
     239              : 
     240            0 :         if (node->soundex_length < DM_CODE_DIGITS)
     241              :         {
     242            0 :                 return node;
     243              :         }
     244              :         else
     245              :         {
     246              :                 /* Append completed soundex code to output array. */
     247            0 :                 text       *out = cstring_to_text_with_len(node->soundex,
     248              :                                                                                                    DM_CODE_DIGITS);
     249              : 
     250            0 :                 accumArrayResult(soundex,
     251            0 :                                                  PointerGetDatum(out),
     252              :                                                  false,
     253              :                                                  TEXTOID,
     254            0 :                                                  CurrentMemoryContext);
     255            0 :                 return NULL;
     256            0 :         }
     257            0 : }
     258              : 
     259              : 
     260              : /* Update node for next code digit(s). */
     261              : static void
     262            0 : update_node(dm_node *first_node[2], dm_node *last_node[2],
     263              :                         dm_node *node, int ix_node,
     264              :                         int letter_no, int prev_code_index, int next_code_index,
     265              :                         const char *next_code_digits, int digit_no,
     266              :                         ArrayBuildState *soundex)
     267              : {
     268            0 :         int                     i;
     269            0 :         char            next_code_digit = next_code_digits[digit_no];
     270            0 :         int                     num_dirty_nodes = 0;
     271            0 :         dm_node    *dirty_nodes[2];
     272              : 
     273            0 :         initialize_node(node, letter_no);
     274              : 
     275            0 :         if (node->prev_code_index && !(node->prev_code_index & prev_code_index))
     276              :         {
     277              :                 /*
     278              :                  * If the sound (vowel / consonant) of this letter encoding doesn't
     279              :                  * correspond to the coding index of the previous letter, we skip this
     280              :                  * letter encoding. Note that currently, only "J" can be either a
     281              :                  * vowel or a consonant.
     282              :                  */
     283            0 :                 return;
     284              :         }
     285              : 
     286            0 :         if (next_code_digit == 'X' ||
     287            0 :                 (digit_no == 0 &&
     288            0 :                  (node->prev_code_digits[0] == next_code_digit ||
     289            0 :                   node->prev_code_digits[1] == next_code_digit)))
     290              :         {
     291              :                 /* The code digit is the same as one of the previous (i.e. not added). */
     292            0 :                 dirty_nodes[num_dirty_nodes++] = node;
     293            0 :         }
     294              : 
     295            0 :         if (next_code_digit != 'X' &&
     296            0 :                 (digit_no > 0 ||
     297            0 :                  node->prev_code_digits[0] != next_code_digit ||
     298            0 :                  node->prev_code_digits[1]))
     299              :         {
     300              :                 /* The code digit is different from one of the previous (i.e. added). */
     301            0 :                 node = find_or_create_child_node(node, next_code_digit, soundex);
     302            0 :                 if (node)
     303              :                 {
     304            0 :                         initialize_node(node, letter_no);
     305            0 :                         dirty_nodes[num_dirty_nodes++] = node;
     306            0 :                 }
     307            0 :         }
     308              : 
     309            0 :         for (i = 0; i < num_dirty_nodes; i++)
     310              :         {
     311              :                 /* Add code digit leading to the current node. */
     312            0 :                 add_next_code_digit(dirty_nodes[i], next_code_index, next_code_digit);
     313              : 
     314            0 :                 if (next_code_digits[++digit_no])
     315              :                 {
     316            0 :                         update_node(first_node, last_node, dirty_nodes[i], ix_node,
     317            0 :                                                 letter_no, prev_code_index, next_code_index,
     318            0 :                                                 next_code_digits, digit_no,
     319            0 :                                                 soundex);
     320            0 :                 }
     321              :                 else
     322              :                 {
     323              :                         /* Add incomplete leaf node to linked list. */
     324            0 :                         set_leaf(first_node, last_node, dirty_nodes[i], ix_node);
     325              :                 }
     326            0 :         }
     327            0 : }
     328              : 
     329              : 
     330              : /* Update soundex tree leaf nodes. */
     331              : static void
     332            0 : update_leaves(dm_node *first_node[2], int *ix_node, int letter_no,
     333              :                           const dm_codes *codes, const dm_codes *next_codes,
     334              :                           ArrayBuildState *soundex)
     335              : {
     336            0 :         int                     i,
     337              :                                 j,
     338              :                                 code_index;
     339            0 :         dm_node    *node,
     340              :                            *last_node[2];
     341            0 :         const dm_code *code,
     342              :                            *next_code;
     343            0 :         int                     ix_node_next = (*ix_node + 1) & 1;  /* Alternating index: 0, 1 */
     344              : 
     345              :         /* Initialize for new linked list of leaves. */
     346            0 :         first_node[ix_node_next] = NULL;
     347            0 :         last_node[ix_node_next] = NULL;
     348              : 
     349              :         /* Process all nodes. */
     350            0 :         for (node = first_node[*ix_node]; node; node = node->next[*ix_node])
     351              :         {
     352              :                 /* One or two alternate code sequences. */
     353            0 :                 for (i = 0; i < 2 && (code = codes[i]) && code[0][0]; i++)
     354              :                 {
     355              :                         /* Coding for previous letter - before vowel: 1, all other: 2 */
     356            0 :                         int                     prev_code_index = (code[0][0] > '1') + 1;
     357              : 
     358              :                         /* One or two alternate next code sequences. */
     359            0 :                         for (j = 0; j < 2 && (next_code = next_codes[j]) && next_code[0][0]; j++)
     360              :                         {
     361              :                                 /* Determine which code to use. */
     362            0 :                                 if (letter_no == 0)
     363              :                                 {
     364              :                                         /* This is the first letter. */
     365            0 :                                         code_index = 0;
     366            0 :                                 }
     367            0 :                                 else if (next_code[0][0] <= '1')
     368              :                                 {
     369              :                                         /* The next letter is a vowel. */
     370            0 :                                         code_index = 1;
     371            0 :                                 }
     372              :                                 else
     373              :                                 {
     374              :                                         /* All other cases. */
     375            0 :                                         code_index = 2;
     376              :                                 }
     377              : 
     378              :                                 /* One or two sequential code digits. */
     379            0 :                                 update_node(first_node, last_node, node, ix_node_next,
     380            0 :                                                         letter_no, prev_code_index, code_index,
     381            0 :                                                         code[code_index], 0,
     382            0 :                                                         soundex);
     383            0 :                         }
     384            0 :                 }
     385            0 :         }
     386              : 
     387            0 :         *ix_node = ix_node_next;
     388            0 : }
     389              : 
     390              : 
     391              : /*
     392              :  * Return next character, converted from UTF-8 to uppercase ASCII.
     393              :  * *ix is the current string index and is incremented by the character length.
     394              :  */
     395              : static char
     396            0 : read_char(const unsigned char *str, int *ix)
     397              : {
     398              :         /* Substitute character for skipped code points. */
     399            0 :         const char      na = '\x1a';
     400            0 :         pg_wchar        c;
     401              : 
     402              :         /* Decode UTF-8 character to ISO 10646 code point. */
     403            0 :         str += *ix;
     404            0 :         c = utf8_to_unicode(str);
     405              : 
     406              :         /* Advance *ix, but (for safety) not if we've reached end of string. */
     407            0 :         if (c)
     408            0 :                 *ix += pg_utf_mblen(str);
     409              : 
     410              :         /* Convert. */
     411            0 :         if (c >= (unsigned char) '[' && c <= (unsigned char) ']')
     412              :         {
     413              :                 /* ASCII characters [, \, and ] are reserved for conversions below. */
     414            0 :                 return na;
     415              :         }
     416            0 :         else if (c < 0x60)
     417              :         {
     418              :                 /* Other non-lowercase ASCII characters can be used as-is. */
     419            0 :                 return (char) c;
     420              :         }
     421            0 :         else if (c < 0x100)
     422              :         {
     423              :                 /* ISO-8859-1 code point; convert to upper-case ASCII via table. */
     424            0 :                 return iso8859_1_to_ascii_upper[c - 0x60];
     425              :         }
     426              :         else
     427              :         {
     428              :                 /* Conversion of non-ASCII characters in the coding chart. */
     429            0 :                 switch (c)
     430              :                 {
     431              :                         case 0x0104:            /* LATIN CAPITAL LETTER A WITH OGONEK */
     432              :                         case 0x0105:            /* LATIN SMALL LETTER A WITH OGONEK */
     433            0 :                                 return '[';
     434              :                         case 0x0118:            /* LATIN CAPITAL LETTER E WITH OGONEK */
     435              :                         case 0x0119:            /* LATIN SMALL LETTER E WITH OGONEK */
     436            0 :                                 return '\\';
     437              :                         case 0x0162:            /* LATIN CAPITAL LETTER T WITH CEDILLA */
     438              :                         case 0x0163:            /* LATIN SMALL LETTER T WITH CEDILLA */
     439              :                         case 0x021A:            /* LATIN CAPITAL LETTER T WITH COMMA BELOW */
     440              :                         case 0x021B:            /* LATIN SMALL LETTER T WITH COMMA BELOW */
     441            0 :                                 return ']';
     442              :                         default:
     443            0 :                                 return na;
     444              :                 }
     445              :         }
     446            0 : }
     447              : 
     448              : 
     449              : /* Read next ASCII character, skipping any characters not in [A-\]]. */
     450              : static char
     451            0 : read_valid_char(const char *str, int *ix)
     452              : {
     453            0 :         char            c;
     454              : 
     455            0 :         while ((c = read_char((const unsigned char *) str, ix)) != '\0')
     456              :         {
     457            0 :                 if (c >= 'A' && c <= ']')
     458            0 :                         break;
     459              :         }
     460              : 
     461            0 :         return c;
     462            0 : }
     463              : 
     464              : 
     465              : /* Return sound coding for "letter" (letter sequence) */
     466              : static const dm_codes *
     467            0 : read_letter(const char *str, int *ix)
     468              : {
     469            0 :         char            c,
     470              :                                 cmp;
     471            0 :         int                     i,
     472              :                                 j;
     473            0 :         const dm_letter *letters;
     474            0 :         const dm_codes *codes;
     475              : 
     476              :         /* First letter in sequence. */
     477            0 :         if ((c = read_valid_char(str, ix)) == '\0')
     478            0 :                 return NULL;
     479              : 
     480            0 :         letters = &letter_[c - 'A'];
     481            0 :         codes = letters->codes;
     482            0 :         i = *ix;
     483              : 
     484              :         /* Any subsequent letters in sequence. */
     485            0 :         while ((letters = letters->letters) && (c = read_valid_char(str, &i)))
     486              :         {
     487            0 :                 for (j = 0; (cmp = letters[j].letter); j++)
     488              :                 {
     489            0 :                         if (cmp == c)
     490              :                         {
     491              :                                 /* Letter found. */
     492            0 :                                 letters = &letters[j];
     493            0 :                                 if (letters->codes)
     494              :                                 {
     495              :                                         /* Coding for letter sequence found. */
     496            0 :                                         codes = letters->codes;
     497            0 :                                         *ix = i;
     498            0 :                                 }
     499            0 :                                 break;
     500              :                         }
     501            0 :                 }
     502            0 :                 if (!cmp)
     503              :                 {
     504              :                         /* The sequence of letters has no coding. */
     505            0 :                         break;
     506              :                 }
     507              :         }
     508              : 
     509            0 :         return codes;
     510            0 : }
     511              : 
     512              : 
     513              : /*
     514              :  * Generate all Daitch-Mokotoff soundex codes for word,
     515              :  * adding them to the "soundex" ArrayBuildState.
     516              :  * Returns false if string has no encodable characters, else true.
     517              :  */
     518              : static bool
     519            0 : daitch_mokotoff_coding(const char *word, ArrayBuildState *soundex)
     520              : {
     521            0 :         int                     i = 0;
     522            0 :         int                     letter_no = 0;
     523            0 :         int                     ix_node = 0;
     524            0 :         const dm_codes *codes,
     525              :                            *next_codes;
     526            0 :         dm_node    *first_node[2],
     527              :                            *node;
     528              : 
     529              :         /* First letter. */
     530            0 :         if (!(codes = read_letter(word, &i)))
     531              :         {
     532              :                 /* No encodable character in input. */
     533            0 :                 return false;
     534              :         }
     535              : 
     536              :         /* Starting point. */
     537            0 :         first_node[ix_node] = palloc_object(dm_node);
     538            0 :         *first_node[ix_node] = start_node;
     539              : 
     540              :         /*
     541              :          * Loop until either the word input is exhausted, or all generated soundex
     542              :          * codes are completed to six digits.
     543              :          */
     544            0 :         while (codes && first_node[ix_node])
     545              :         {
     546            0 :                 next_codes = read_letter(word, &i);
     547              : 
     548              :                 /* Update leaf nodes. */
     549            0 :                 update_leaves(first_node, &ix_node, letter_no,
     550            0 :                                           codes, next_codes ? next_codes : end_codes,
     551            0 :                                           soundex);
     552              : 
     553            0 :                 codes = next_codes;
     554            0 :                 letter_no++;
     555              :         }
     556              : 
     557              :         /* Append all remaining (incomplete) soundex codes to output array. */
     558            0 :         for (node = first_node[ix_node]; node; node = node->next[ix_node])
     559              :         {
     560            0 :                 text       *out = cstring_to_text_with_len(node->soundex,
     561              :                                                                                                    DM_CODE_DIGITS);
     562              : 
     563            0 :                 accumArrayResult(soundex,
     564            0 :                                                  PointerGetDatum(out),
     565              :                                                  false,
     566              :                                                  TEXTOID,
     567            0 :                                                  CurrentMemoryContext);
     568            0 :         }
     569              : 
     570            0 :         return true;
     571            0 : }
        

Generated by: LCOV version 2.3.2-1