WvStreams
wvglob.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2004 Net Integration Technologies, Inc.
00004  * 
00005  * Implementation of globbing support through WvRegex
00006  */
00007 #include "wvglob.h"
00008 
00009 WvGlob::WvGlob() : WvRegex()
00010 {
00011 }
00012 
00013 WvGlob::WvGlob(WvStringParm glob) : WvRegex()
00014 {
00015     set(glob);
00016 }
00017 
00018 bool WvGlob::set(WvStringParm glob)
00019 {
00020     WvString errstr;
00021     WvString regex = glob_to_regex(glob, &errstr);
00022     if (!!errstr)
00023         WvErrorBase::seterr(errstr);
00024     else if (!!regex)
00025         WvRegex::set(regex);
00026     else WvErrorBase::seterr("Failed to convert glob pattern to regex");
00027     return isok();
00028 }
00029 
00030 const bool WvGlob::normal_quit_chars[256] = {
00031     false, false, false, false, false, false, false, false,
00032     false, false, false, false, false, false, false, false,
00033     false, false, false, false, false, false, false, false,
00034     false, false, false, false, false, false, false, false,
00035     false, false, false, false, false, false, false, false,
00036     false, false, false, false, false, false, false, false,
00037     false, false, false, false, false, false, false, false,
00038     false, false, false, false, false, false, false, false,
00039     false, false, false, false, false, false, false, false,
00040     false, false, false, false, false, false, false, false,
00041     false, false, false, false, false, false, false, false,
00042     false, false, false, false, false, false, false, false,
00043     false, false, false, false, false, false, false, false,
00044     false, false, false, false, false, false, false, false,
00045     false, false, false, false, false, false, false, false,
00046     false, false, false, false, false, false, false, false,
00047     false, false, false, false, false, false, false, false,
00048     false, false, false, false, false, false, false, false,
00049     false, false, false, false, false, false, false, false,
00050     false, false, false, false, false, false, false, false,
00051     false, false, false, false, false, false, false, false,
00052     false, false, false, false, false, false, false, false,
00053     false, false, false, false, false, false, false, false,
00054     false, false, false, false, false, false, false, false,
00055     false, false, false, false, false, false, false, false,
00056     false, false, false, false, false, false, false, false,
00057     false, false, false, false, false, false, false, false,
00058     false, false, false, false, false, false, false, false,
00059     false, false, false, false, false, false, false, false,
00060     false, false, false, false, false, false, false, false,
00061     false, false, false, false, false, false, false, false,
00062     false, false, false, false, false, false, false, false
00063 };
00064 const bool WvGlob::brace_quit_chars[256] = {
00065     false, false, false, false, false, false, false, false,
00066     false, false, false, false, false, false, false, false,
00067     false, false, false, false, false, false, false, false,
00068     false, false, false, false, false, false, false, false,
00069     false, false, false, false, false, false, false, false,
00070     false, false, false, false, true /* , */, false, false, false,
00071     false, false, false, false, false, false, false, false,
00072     false, false, false, false, false, false, false, false,
00073     false, false, false, false, false, false, false, false,
00074     false, false, false, false, false, false, false, false,
00075     false, false, false, false, false, false, false, false,
00076     false, false, false, false, false, false, false, false,
00077     false, false, false, false, false, false, false, false,
00078     false, false, false, false, false, false, false, false,
00079     false, false, false, false, false, false, false, false,
00080     false, false, false, false, false, true /* } */, false, false,
00081     false, false, false, false, false, false, false, false,
00082     false, false, false, false, false, false, false, false,
00083     false, false, false, false, false, false, false, false,
00084     false, false, false, false, false, false, false, false,
00085     false, false, false, false, false, false, false, false,
00086     false, false, false, false, false, false, false, false,
00087     false, false, false, false, false, false, false, false,
00088     false, false, false, false, false, false, false, false,
00089     false, false, false, false, false, false, false, false,
00090     false, false, false, false, false, false, false, false,
00091     false, false, false, false, false, false, false, false,
00092     false, false, false, false, false, false, false, false,
00093     false, false, false, false, false, false, false, false,
00094     false, false, false, false, false, false, false, false,
00095     false, false, false, false, false, false, false, false,
00096     false, false, false, false, false, false, false, false
00097 };
00098 
00099 //
00100 // Known bugs:
00101 //
00102 // - If / is part of a range it will not be excluded in the resulting regex
00103 //   eg. fred[.-0]joe will match fred/joe (this violates glob(7))
00104 //   However, explcit / in bracket expression results in error.
00105 //
00106 WvString WvGlob::glob_to_regex(const char *src, size_t &src_used,
00107         char *dst, size_t &dst_used, const bool quit_chars[256])
00108 {
00109     enum { NORMAL, BACKSLASH, BRACKET, BRACKET_FIRST } state = NORMAL;
00110     src_used = 0;
00111     dst_used = 0;
00112     bool quit_now = false;
00113     while (!quit_now && src[src_used])
00114     {
00115         switch (state)
00116         {
00117             case NORMAL:
00118                 if (quit_chars[src[src_used]])
00119                 {
00120                     quit_now = true;
00121                     break;
00122                 }
00123 
00124                 switch (src[src_used])
00125                 {
00126                     case '\\':
00127                         state = BACKSLASH;
00128                         break;
00129 
00130                     case '[':
00131                         if (src[src_used+1] == '^' && src[src_used+2] == ']')
00132                         {
00133                             // Get rid of degenerate case:
00134                             src_used += 2;
00135                             if (dst) dst[dst_used] = '\\'; ++dst_used;
00136                             if (dst) dst[dst_used] = '^'; ++dst_used;
00137                         }
00138                         else
00139                         {
00140                             if (dst) dst[dst_used] = '('; ++dst_used;
00141                             state = BRACKET_FIRST;
00142                         }
00143                         break;
00144 
00145                     case '*':
00146                         if (dst) dst[dst_used] = '('; ++dst_used;
00147                         if (dst) dst[dst_used] = '['; ++dst_used;
00148                         if (dst) dst[dst_used] = '^'; ++dst_used;
00149                         if (dst) dst[dst_used] = '/'; ++dst_used;
00150                         if (dst) dst[dst_used] = ']'; ++dst_used;
00151                         if (dst) dst[dst_used] = '*'; ++dst_used;
00152                         if (dst) dst[dst_used] = ')'; ++dst_used;
00153                         break;
00154 
00155                     case '?':
00156                         if (dst) dst[dst_used] = '('; ++dst_used;
00157                         if (dst) dst[dst_used] = '['; ++dst_used;
00158                         if (dst) dst[dst_used] = '^'; ++dst_used;
00159                         if (dst) dst[dst_used] = '/'; ++dst_used;
00160                         if (dst) dst[dst_used] = ']'; ++dst_used;
00161                         if (dst) dst[dst_used] = ')'; ++dst_used;
00162                         break;
00163 
00164                     case '{':
00165                         if (dst) dst[dst_used] = '('; ++dst_used;
00166                         ++src_used;
00167                         while (true)
00168                         {
00169                             size_t sub_src_used, sub_dst_used;
00170 
00171                             WvString errstr =
00172                                 glob_to_regex(&src[src_used], sub_src_used,
00173                                     dst? &dst[dst_used]: NULL, sub_dst_used,
00174                                     brace_quit_chars);
00175                             if (errstr) return errstr;
00176 
00177                             src_used += sub_src_used;
00178                             dst_used += sub_dst_used;
00179 
00180                             if (src[src_used] == '}')
00181                                 break;
00182                             else if (src[src_used] != ',')
00183                                 return WvString("Unfinished brace expression (index %s)", src_used);
00184                             if (dst) dst[dst_used] = '|'; ++dst_used;
00185                             ++src_used;
00186                         }
00187                         if (dst) dst[dst_used] = ')'; ++dst_used;
00188                         break;
00189 
00190                     case '^':
00191                     case '.':
00192                     case '$':
00193                     case '(':
00194                     case ')':
00195                     case '|':
00196                     case '+':
00197                         if (dst) dst[dst_used] = '\\'; ++dst_used;
00198                         if (dst) dst[dst_used] = src[src_used]; ++dst_used;
00199                         break;
00200 
00201                     default:
00202                         if (dst) dst[dst_used] = src[src_used]; ++dst_used;
00203                         break;
00204                 }
00205                 break;
00206 
00207             case BACKSLASH:
00208                 switch (src[src_used])
00209                 {
00210                     case '^':
00211                     case '.':
00212                     case '$':
00213                     case '(':
00214                     case ')':
00215                     case '|':
00216                     case '+':
00217                     case '[':
00218                     case '{':
00219                     case '*':
00220                     case '?':
00221                     case '\\':
00222                         if (dst) dst[dst_used] = '\\'; ++dst_used;
00223                         // fall through..
00224                     default:
00225                         if (dst) dst[dst_used] = src[src_used]; ++dst_used;
00226                         break;
00227 
00228                 }
00229                 state = NORMAL;
00230                 break;
00231 
00232             case BRACKET_FIRST:
00233                 switch (src[src_used])
00234                 {
00235                     case '!':
00236                         if (dst) dst[dst_used] = '['; ++dst_used;
00237                         if (dst) dst[dst_used] = '^'; ++dst_used;
00238                         break;
00239 
00240                     case '^':
00241                         if (dst) dst[dst_used] = '\\'; ++dst_used;
00242                         if (dst) dst[dst_used] = '^'; ++dst_used;
00243                         if (dst) dst[dst_used] = '|'; ++dst_used;
00244                         if (dst) dst[dst_used] = '['; ++dst_used;
00245                         break;
00246 
00247                     case '/':
00248                         return WvString("Slash not allowed in bracket expression (index %s)", src_used);
00249 
00250                     default:
00251                         if (dst) dst[dst_used] = '['; ++dst_used;
00252                         if (dst) dst[dst_used] = src[src_used]; ++dst_used;
00253                         break;
00254                 }
00255                 state = BRACKET;
00256                 break;
00257 
00258             case BRACKET:
00259                 switch (src[src_used])
00260                 {
00261                     case ']':
00262                         if (dst) dst[dst_used] = ']'; ++dst_used;
00263                         if (dst) dst[dst_used] = ')'; ++dst_used;
00264                         state = NORMAL;
00265                         break;
00266 
00267                     case '/':
00268                         return WvString("Slash not allowed in bracket expression (index %s)", src_used);
00269 
00270                     default:
00271                         if (dst) dst[dst_used] = src[src_used]; ++dst_used;
00272                         break;
00273                 }
00274                 break;
00275         }
00276 
00277         if (!quit_now) ++src_used;
00278     }
00279 
00280     if (state == BRACKET || state == BRACKET_FIRST)
00281         return WvString("Unfinished bracket expression (index %s)", src_used);
00282     else if (state == BACKSLASH)
00283         return WvString("Unfinished backslash expression (index %s)", src_used);
00284     else return WvString::null;
00285 }
00286 
00287 WvString WvGlob::glob_to_regex(WvStringParm glob, WvString *errstr)
00288 {
00289     if (glob.isnull())
00290     {
00291         if (errstr) *errstr = WvString("Glob is NULL");
00292         return WvString::null;
00293     }
00294 
00295     size_t src_used, dst_used;
00296     WvString local_errstr = glob_to_regex(glob, src_used, NULL, dst_used, normal_quit_chars);
00297     if (!!local_errstr)
00298     {
00299         if (errstr) *errstr = local_errstr;
00300         return WvString::null;
00301     }
00302     
00303     WvString result;
00304     result.setsize(1+dst_used+1+1);
00305     char *dst = result.edit();
00306     *dst++ = '^';
00307     local_errstr = glob_to_regex(glob, src_used, dst, dst_used, normal_quit_chars);
00308     if (!!local_errstr)
00309     {
00310         if (errstr) *errstr = local_errstr;
00311         return WvString::null;
00312     }
00313     dst += dst_used;
00314     *dst++ = '$';
00315     *dst++ = '\0';
00316 
00317     return result;
00318 }