445 lines
11 KiB
C
445 lines
11 KiB
C
/*************************************************************************\
|
|
* Copyright (c) 2009 Helmholtz-Zentrum Berlin fuer Materialien und Energie.
|
|
* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne
|
|
* National Laboratory.
|
|
* Copyright (c) 2002 The Regents of the University of California, as
|
|
* Operator of Los Alamos National Laboratory.
|
|
* SPDX-License-Identifier: EPICS
|
|
* EPICS BASE is distributed subject to a Software License Agreement found
|
|
* in file LICENSE that is included with this distribution.
|
|
\*************************************************************************/
|
|
|
|
/* Authors: Jun-ichi Odagiri, Marty Kraimer, Eric Norum,
|
|
* Mark Rivers, Andrew Johnson, Ralph Lange
|
|
*
|
|
* Routines in this file should have corresponding test code in
|
|
* libCom/test/epicsStringTest.c
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
|
|
#ifndef vxWorks
|
|
#include <stdint.h>
|
|
#else
|
|
/* VxWorks automaticaly includes stdint.h defining SIZE_MAX in 6.9 but not earlier */
|
|
#ifndef SIZE_MAX
|
|
#define SIZE_MAX (size_t)-1
|
|
#endif
|
|
#endif
|
|
|
|
#include "epicsAssert.h"
|
|
#include "epicsStdio.h"
|
|
#include "cantProceed.h"
|
|
#include "epicsString.h"
|
|
#include "epicsMath.h"
|
|
|
|
/* Deprecated, use epicsStrnRawFromEscaped() instead */
|
|
int dbTranslateEscape(char *dst, const char *src)
|
|
{
|
|
size_t big_enough = strlen(src) + 1;
|
|
|
|
return epicsStrnRawFromEscaped(dst, big_enough, src, big_enough);
|
|
}
|
|
|
|
int epicsStrnRawFromEscaped(char *dst, size_t dstlen, const char *src,
|
|
size_t srclen)
|
|
{
|
|
int rem = dstlen;
|
|
int ndst = 0;
|
|
|
|
while (srclen--) {
|
|
int c = *src++;
|
|
#define OUT(chr) if (--rem > 0) ndst++, *dst++ = chr
|
|
|
|
if (!c) break;
|
|
|
|
input:
|
|
if (c != '\\') {
|
|
OUT(c);
|
|
continue;
|
|
}
|
|
|
|
if (!srclen-- || !(c = *src++)) break;
|
|
|
|
switch (c) {
|
|
case 'a': OUT('\a'); break;
|
|
case 'b': OUT('\b'); break;
|
|
case 'f': OUT('\f'); break;
|
|
case 'n': OUT('\n'); break;
|
|
case 'r': OUT('\r'); break;
|
|
case 't': OUT('\t'); break;
|
|
case 'v': OUT('\v'); break;
|
|
case '\\': OUT('\\'); break;
|
|
case '\'': OUT('\''); break;
|
|
case '\"': OUT('\"'); break;
|
|
case '0': OUT('\0'); break;
|
|
|
|
case 'x' :
|
|
{ /* \xXX */
|
|
unsigned int u = 0;
|
|
|
|
if (!srclen-- || !(c = *src++ & 0xff))
|
|
goto done;
|
|
|
|
if (!isxdigit(c))
|
|
goto input;
|
|
|
|
u = u << 4 | ((c > '9') ? toupper(c) - 'A' + 10 : c - '0');
|
|
|
|
if (!srclen-- || !(c = *src++ & 0xff)) {
|
|
OUT(u);
|
|
goto done;
|
|
}
|
|
|
|
if (!isxdigit(c)) {
|
|
OUT(u);
|
|
goto input;
|
|
}
|
|
|
|
u = u << 4 | ((c > '9') ? toupper(c) - 'A' + 10 : c - '0');
|
|
OUT(u);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
OUT(c);
|
|
}
|
|
#undef OUT
|
|
}
|
|
done:
|
|
if (dstlen)
|
|
*dst = '\0';
|
|
return ndst;
|
|
}
|
|
|
|
int epicsStrnEscapedFromRaw(char *dst, size_t dstlen, const char *src,
|
|
size_t srclen)
|
|
{
|
|
int rem = dstlen;
|
|
int ndst = 0;
|
|
|
|
if (dst == src)
|
|
return -1;
|
|
|
|
while (srclen--) {
|
|
static const char hex[] = "0123456789abcdef";
|
|
int c = *src++;
|
|
#define OUT(chr) ndst++; if (--rem > 0) *dst++ = chr
|
|
|
|
switch (c) {
|
|
case '\a': OUT('\\'); OUT('a'); break;
|
|
case '\b': OUT('\\'); OUT('b'); break;
|
|
case '\f': OUT('\\'); OUT('f'); break;
|
|
case '\n': OUT('\\'); OUT('n'); break;
|
|
case '\r': OUT('\\'); OUT('r'); break;
|
|
case '\t': OUT('\\'); OUT('t'); break;
|
|
case '\v': OUT('\\'); OUT('v'); break;
|
|
case '\\': OUT('\\'); OUT('\\'); break;
|
|
case '\'': OUT('\\'); OUT('\''); break;
|
|
case '\"': OUT('\\'); OUT('\"'); break;
|
|
case '\0': OUT('\\'); OUT('0'); break;
|
|
default:
|
|
if (isprint(c & 0xff)) {
|
|
OUT(c);
|
|
break;
|
|
}
|
|
OUT('\\'); OUT('x');
|
|
OUT(hex[(c >> 4) & 0x0f]);
|
|
OUT(hex[ c & 0x0f]);
|
|
}
|
|
#undef OUT
|
|
}
|
|
if (dstlen)
|
|
*dst = '\0';
|
|
return ndst;
|
|
}
|
|
|
|
size_t epicsStrnEscapedFromRawSize(const char *src, size_t srclen)
|
|
{
|
|
size_t ndst = srclen;
|
|
|
|
while (srclen--) {
|
|
int c = *src++;
|
|
|
|
switch (c) {
|
|
case '\a': case '\b': case '\f': case '\n':
|
|
case '\r': case '\t': case '\v': case '\\':
|
|
case '\'': case '\"': case '\0':
|
|
ndst++;
|
|
break;
|
|
default:
|
|
if (!isprint(c & 0xff))
|
|
ndst += 3;
|
|
}
|
|
}
|
|
return ndst;
|
|
}
|
|
|
|
int epicsStrCaseCmp(const char *s1, const char *s2)
|
|
{
|
|
while (1) {
|
|
int ch1 = toupper((int) *s1);
|
|
int ch2 = toupper((int) *s2);
|
|
|
|
if (ch2 == 0) return (ch1 != 0);
|
|
if (ch1 == 0) return -1;
|
|
if (ch1 < ch2) return -1;
|
|
if (ch1 > ch2) return 1;
|
|
s1++;
|
|
s2++;
|
|
}
|
|
}
|
|
|
|
int epicsStrnCaseCmp(const char *s1, const char *s2, size_t len)
|
|
{
|
|
size_t i = 0;
|
|
|
|
while (i++ < len) {
|
|
int ch1 = toupper((int) *s1);
|
|
int ch2 = toupper((int) *s2);
|
|
|
|
if (ch2 == 0) return (ch1 != 0);
|
|
if (ch1 == 0) return -1;
|
|
if (ch1 < ch2) return -1;
|
|
if (ch1 > ch2) return 1;
|
|
s1++;
|
|
s2++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
char * epicsStrnDup(const char *s, size_t len)
|
|
{
|
|
char *buf = mallocMustSucceed(len + 1, "epicsStrnDup");
|
|
|
|
strncpy(buf, s, len);
|
|
buf[len] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
char * epicsStrDup(const char *s)
|
|
{
|
|
return strcpy(mallocMustSucceed(strlen(s)+1, "epicsStrDup"), s);
|
|
}
|
|
|
|
int epicsStrPrintEscaped(FILE *fp, const char *s, size_t len)
|
|
{
|
|
int nout = 0;
|
|
|
|
while (len--) {
|
|
char c = *s++;
|
|
|
|
switch (c) {
|
|
case '\a': nout += fprintf(fp, "\\a"); break;
|
|
case '\b': nout += fprintf(fp, "\\b"); break;
|
|
case '\f': nout += fprintf(fp, "\\f"); break;
|
|
case '\n': nout += fprintf(fp, "\\n"); break;
|
|
case '\r': nout += fprintf(fp, "\\r"); break;
|
|
case '\t': nout += fprintf(fp, "\\t"); break;
|
|
case '\v': nout += fprintf(fp, "\\v"); break;
|
|
case '\\': nout += fprintf(fp, "\\\\"); break;
|
|
case '\'': nout += fprintf(fp, "\\'"); break;
|
|
case '\"': nout += fprintf(fp, "\\\""); break;
|
|
default:
|
|
if (isprint(0xff & (int)c))
|
|
nout += fprintf(fp, "%c", c);
|
|
else
|
|
nout += fprintf(fp, "\\x%02x", (unsigned char)c);
|
|
break;
|
|
}
|
|
}
|
|
return nout;
|
|
}
|
|
|
|
/* Until Base requires POSIX 2008 we must provide our own implementation */
|
|
size_t epicsStrnLen(const char *s, size_t maxlen)
|
|
{
|
|
size_t i;
|
|
|
|
for (i=0; i<maxlen; i++) {
|
|
if(s[i]=='\0')
|
|
return i;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
int epicsStrnGlobMatch(const char *str, size_t len, const char *pattern)
|
|
{
|
|
const char *mp = NULL;
|
|
size_t cp = 0, i = 0;
|
|
|
|
while ((i < len) && (str[i]) && (*pattern != '*')) {
|
|
if ((*pattern != str[i]) && (*pattern != '?'))
|
|
return 0;
|
|
pattern++;
|
|
i++;
|
|
}
|
|
while ((i < len) && str[i]) {
|
|
if (*pattern == '*') {
|
|
if (!*++pattern)
|
|
return 1;
|
|
mp = pattern;
|
|
cp = i+1;
|
|
}
|
|
else if ((*pattern == str[i]) || (*pattern == '?')) {
|
|
pattern++;
|
|
i++;
|
|
}
|
|
else {
|
|
pattern = mp;
|
|
i = cp++;
|
|
}
|
|
}
|
|
while (*pattern == '*')
|
|
pattern++;
|
|
return !*pattern;
|
|
}
|
|
|
|
int epicsStrGlobMatch(const char *str, const char *pattern) {
|
|
return epicsStrnGlobMatch(str, SIZE_MAX, pattern);
|
|
}
|
|
|
|
char * epicsStrtok_r(char *s, const char *delim, char **lasts)
|
|
{
|
|
const char *spanp;
|
|
int c, sc;
|
|
char *tok;
|
|
|
|
if (s == NULL && (s = *lasts) == NULL)
|
|
return NULL;
|
|
|
|
/*
|
|
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
|
|
*/
|
|
cont:
|
|
c = *s++;
|
|
for (spanp = delim; (sc = *spanp++) != 0;) {
|
|
if (c == sc)
|
|
goto cont;
|
|
}
|
|
|
|
if (c == 0) { /* no non-delimiter characters */
|
|
*lasts = NULL;
|
|
return NULL;
|
|
}
|
|
tok = s - 1;
|
|
|
|
/*
|
|
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
|
|
* Note that delim must have one NUL; we stop if we see that, too.
|
|
*/
|
|
for (;;) {
|
|
c = *s++;
|
|
spanp = delim;
|
|
do {
|
|
if ((sc = *spanp++) == c) {
|
|
if (c == 0)
|
|
s = NULL;
|
|
else
|
|
s[-1] = 0;
|
|
*lasts = s;
|
|
return tok;
|
|
}
|
|
} while (sc != 0);
|
|
}
|
|
}
|
|
|
|
|
|
unsigned int epicsStrHash(const char *str, unsigned int seed)
|
|
{
|
|
unsigned int hash = seed;
|
|
char c;
|
|
|
|
while ((c = *str++)) {
|
|
hash ^= ~((hash << 11) ^ c ^ (hash >> 5));
|
|
if (!(c = *str++)) break;
|
|
hash ^= (hash << 7) ^ c ^ (hash >> 3);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
unsigned int epicsMemHash(const char *str, size_t length, unsigned int seed)
|
|
{
|
|
unsigned int hash = seed;
|
|
|
|
while (length--) {
|
|
hash ^= ~((hash << 11) ^ *str++ ^ (hash >> 5));
|
|
if (!length--) break;
|
|
hash ^= (hash << 7) ^ *str++ ^ (hash >> 3);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
/* Compute normalized Levenshtein distance
|
|
*
|
|
* https://en.wikipedia.org/wiki/Levenshtein_distance
|
|
*
|
|
* We modify this to give half weight to case insensitive substitution.
|
|
* All normal integer weights are multiplied by two, with case
|
|
* insensitive added in as one.
|
|
*/
|
|
double epicsStrSimilarity(const char *A, const char *B)
|
|
{
|
|
double ret = 0;
|
|
size_t lA, lB, a, b;
|
|
size_t norm;
|
|
size_t *dist0, *dist1, *stemp;
|
|
|
|
lA = strlen(A);
|
|
lB = strlen(B);
|
|
|
|
/* max number of edits to change A into B is max(lA, lB) */
|
|
norm = lA > lB ? lA : lB;
|
|
/* take into account our weighting */
|
|
norm *= 2u;
|
|
|
|
dist0 = calloc(1+lB, sizeof(*dist0));
|
|
dist1 = calloc(1+lB, sizeof(*dist1));
|
|
if(!dist0 || !dist1) {
|
|
ret = -1.0;
|
|
goto done;
|
|
}
|
|
|
|
for(b=0; b<1+lB; b++)
|
|
dist0[b] = 2*b;
|
|
|
|
for(a=0; a<lA; a++) {
|
|
dist1[0] = 2*(a+1);
|
|
|
|
for(b=0; b<lB; b++) {
|
|
size_t delcost = dist0[b+1] + 2,
|
|
inscost = dist1[b] + 2,
|
|
subcost = dist0[b],
|
|
mincost = delcost;
|
|
char ca = A[a], cb = B[b];
|
|
|
|
if(ca!=cb)
|
|
subcost++;
|
|
if(toupper((int)ca)!=toupper((int)cb))
|
|
subcost++;
|
|
|
|
if(mincost > inscost)
|
|
mincost = inscost;
|
|
if(mincost > subcost)
|
|
mincost = subcost;
|
|
|
|
dist1[b+1] = mincost;
|
|
}
|
|
|
|
stemp = dist0;
|
|
dist0 = dist1;
|
|
dist1 = stemp;
|
|
}
|
|
|
|
ret = norm ? (norm - dist0[lB]) / (double)norm : 1.0;
|
|
done:
|
|
free(dist0);
|
|
free(dist1);
|
|
return ret;
|
|
}
|