/****************************************************************/
/* TXT_FIND: generic text file  search utility for SREhttp/2 www server.

I. Abstract:
   Txt_FIND can be used an explicitly invoked addon -- see Txt_Find.SHT
   for a demo.
   Alternatively, it can be used to support "<ISINDEX> searchable indices"
   (which requires "internal redirection" entries in ATTRIBS.CFG)


II. How TXT_FIND works:

TXT_FIND displays "paragraphs" containing "matches".
By default, a paragraph is defined as being all text between blank lines. 
Alternatively, one can define paragraphs as single lines, or as delimited 
by any arbitrary character sequence. 

Matches are determined by comparing the contents of a "paragraph" against
a "search string".

A search string is comprised of "targets". 
There are two kinds of targets: subwords and phrases.

  i) Each space delimited entry in the search string is treated as a 
     seperate "subword".

  ii)Phrases are delimited by ( xx yy zz ); phrases must be matched precisely.

II.a Search algorithims.  

There are two modes: Simple mode and logical expression mode

II.a.1 Simple mode (with highlighting).  

Two "meta commands" and four "target specific" instructions are recognized. 

  Meta commands are signified by *& or *\ at the beginning of the 
  search string.
      *&  means "find paragraphs that match ALL targets in the search string"
      *\  means "find paragraphs that match NONE of the targets in the search 
          string"
  If there are no meta commands, the following "target specific" commands are 
   recognized.
      &  means "paragraphs MUST have this target"
      |  is the default. It  means "accept paragraph if it has this target"
      \  means "paragraph must NOT have this target" 
      %  means "accept paragraph if it does NOT have this target"

    Summarizing: to be a "found" paragraph:
      Test 1a) Any | must be present,    or
           1b)   All of the % are missing
                (if no % is specified, then 1b is ignored)
      Test 2a) If pass test 1, then
           2b) None of the \ can be present, and
               All of the & must be present

     If present, all & and | targets will be highlighted

II.a.2 Logical expression mode without highlighting.

The user enters a logical expression using the following operators:
     & = AND  
     | = OR 
     \ = not 
     @ = xor 
   ( ) to group expressions.

 A sequence of words without any operators is treated as a phrase -- to
 treat each word as a seperate subword, put ( ) around each one.
 Basically, when using this mode, be liberal in your use of ( ).

II.b Syntax 

1) When called as an addon:
       /TXT_FIND?opt1=val1&..&optn=valn
   Note that a FILE and a SEARCH option should always be present.

2) When used to implement an <ISINDEX> searchable index:
     /file_to_search?search_string
   
  Typically, this will be generated when file_to_search contains an 
  <ISINDEX> element in the <HEAD>.

  To implement a searchable index, you also need to set up, in your 
  (possibly host-specific) ATTRIBS.CFG a realm-definition of the form:

    Realm: Search1
    Rule: /file_to_search?*
    Alias: /txt_find?*&FILE=file_to_search&opt1=val1&...
  

II.c. TXT_FIND options



DELIM :  The paragraph delimiter.
         Blank or 0= blank lines   (the DEFAULT)
             $  = Each line is a paragraph
         other  = User specified delimiter
LINE : 
    Maximum number of lines to display in a paragraph. If 0, no lines displayed
    just a summary.   Default is to display all lines.

NUM : Display line/paragraph number

     1 = Display a line, or paragraph number, (the default)
     0 =   Don't display

BAR:  
   1 = Seperate each paragraph/line by a horizontal bar (the default)
   0 = Do not (Default=YES)

EXPERT: Search algorithim mode
   1 = Use "logical expression mode", 
   0 =  Use simple mode (the default)

HIGHLIGHT: highlight the matching "targets". This is ignored if EXPERT<>1
   1 = Highlight mathes, 
   0 = Do not highlight

FILE: Files to search 
   This is REQUIRED.
   FILE is actually a "selector" -- SREhttp/2 will use its standard rules
   (i.e.; look first for a virtual directory, then look under the data
   directory) to map FILE to an actual file.
   You can include * (wildcard) characters -- all files matching this
   wildcarded pattern will be searched.

SEARCH: the searchstring
    If SEARCH is not specified, and if the characters between the
    ? and the first & does NOT contain an = sign, then these characters
    are used as the search string.

    Either a ?xxxx&  syntax, or a SEARCH= option, are REQUIRED.

CASE: Case insensitive search
   0 : Case insensitive (the defaul)
   1 : Case sensitive


*/
/***********************************************************/


parse arg list,servername,verb,tempfile,,
          prog_file,reqnum,verbose,client_ip,privset,,
          uri,host_nickname,id_info,aiter,attribs

if verb="" then do
   say " This SREhttp/2 procedure is not meant to be run in stand alone mode"
   exit
end  /* Do */

ddir=sre_datadir()

searchlist=" "
ifiles=0

para_delim=""
maxdisp=1000000
show_number=1
show_bar=1
expert_mode=0
check_case=0
highlight=1

/* possibly add "SEARCH" to list? */
ioo=pos('&',list)
ieq=pos('=',list)
if ieq>ioo then list='SEARCH='||list

/* get option */
do until list=""
  parse var list an1 '&' list
  parse var  an1 aname '=' avalue

  aname0=aname ; aname=translate(aname)
  avalue0=avalue ; 
  avalue0=sre_packur(translate(avalue,' ','+'))

  avalue=translate(avalue0)
  avalue=translate(avalue,' ','"')
  avalue=translate(avalue,' ',"'")

  select
     when pos("DELIM",aname)> 0 then 
         para_delim=avalue0
  
     when pos("LINE",aname)> 0 then
         if datatype(avalue)="NUM" then
              maxdisp=avalue
     when pos("NUM",aname)>0 then
         if left(avalue,1)="N" | avalue=0 then  show_number=0
     when pos("BAR",aname)>0 then
         if left(avalue,1)="N" | avalue=0 then  show_bar=0
     when pos('EXPERT',aname)>0 then
             if left(avalue,1)="Y" | avalue=1 then   expert_mode=1
     when pos('HIGHLIGHT',aname)>0 then do
                if abbrev(avalue,'Y')=1| avalu=1 then highlight=1
                if abbrev(avalue,'N')=1 | avalu=0 then highlight=0
     end
     when abbrev(aname,'FILE')=1 then do
             if avalue<>"" then do
                 ifiles=ifiles+1
                 files.ifiles=avalue
             end
     end

     when pos("CASE",aname)>0 then
         if left(avalue,1)="Y" | avalue=1 then  check_case=1

     when wordpos(aname,'SEARCH SEARCHFOR STRING NEEDLE TARGET')>0 then do
               searchlist=avalue0
      end
     otherwise
  end
end

/* note: default para_delim is "blank lines" */

searchlist=translate(searchlist,' ','+'||'000d0a09'x)
searchlist=sre_packur(searchlist)                   /* do it now, to revitalize &'s */
if left(para_delim,1)='"' & right(para_delim,1)='"' then 
    para_delim=strip(para_delim,,'"')
if para_delim="" then para_delim=" "
if para_delim=0 then para_delim=" "
crlf = '0d0a'x

/* ----------------- Section to do a search   -------------- */

/* If here, we have some  stuff in the request string (after a ? )
   (either supplied explicitly, or  as a response to the searchable index created above */


/* create list of files to search */
files_todo=0
do ido=1 to ifiles
   bpath=files.ido
   iug=lastpos('/',bpath)
   if iug=0 then
        bpath='/'
   else
     bpath=left(bpath,iug)
   afilenam=sreh2_auto_fig_file_name(files.ido,host_nickname,ddir,id_info)

   if afilenam=0 then iterate               /* error */
   eek=sysfiletree(afilenam,'aflist','FO')   /* check for */

   if aflist.0=0 then do
     call lineout tempfile, '<!doctype html public "-//IETF//DTD HTML 2.0//EN">'
     CALL LINEOUT TEMPFILE,'<html> <head> <title> Results of search </title>  </head>'
     call lineout tempfile,'<h3> No search target </h3> '
     if VERBOSE>1 then call sre_pmprintf('TXT_FIND: No such file ' afilenam)
     call lineout tempfile,' <p> <strong> Can not find file: <strong> ' afilenam '</strong>'
     call lineout tempfile,'</body> </html>'
     call lineout tempfile
     return 'FILE  ERASE TYPE text/html NAME ' tempfile
   end
   do ido1=1 to aflist.0             /* any matches */
      bfile=aflist.ido1
      bname=filespec('n',bfile)
      bchk=bpath||bname
      istat=sreh2_auto_check_privs(bchk,privset,host_nickname,id_info) 
      if istat=0 then do 
         if aiter<>'' then 
             return 'AUTH  Access rights required for: '||bchk
         return 'PRIVS  Access rights required for -- '||bchk
      end

      files_todo=files_todo+1
      file_list.files_todo=bfile
      file_list.files_todo.original=files.ido
      file_list.files_todo.!nfiles=aflist.0
      file_list.files_todo.!nthone=ido1
   end
end
ith_file=0


call lineout tempfile, '<!doctype html public "-//IETF//DTD HTML 2.0//EN">'
CALL LINEOUT TEMPFILE,'<html> <head> <title> Results of search </title>  </head>'
call lineout tempfile,'<body>'

/* now start to process each file ..... */
 call lineout tempfile,'<table align="center" bgcolor="lightgrey"><tr><td><font size=+1> Results of  search </font> </td></table><p> '


NEXTFILE:               /* JUMP HERE TO READ NEXT FILE  *****************    */

ith_file=ith_File+1
filename=strip(file_list.ith_file)
afilenam=filename
aoriginal=file_list.ith_file.original
nstar=file_list.ith_file.!nfiles
nth_nstar=file_list.ith_file.!nthone

goober=filespec('n',filename)

/* read in this target file (filename) into filelines stem variable */
if VERBOSE>1 then sre_pmprintF("TXT_FIND: will examine:" afilenam "(# entries= " getit)
if filename<>"" then do                 /* check for no filename */
   getit=grab_file_lines(afilenam,para_delim)
end

/* problem ... */
if getit <= 0 then   do             /*fatal error */
    call lineout tempfile,'<h2> No search target </h2> '
    if VERBOSE>1 then call sre_pmprintf('TXT_FIND: No such file ' filename)
    call lineout tempfile,' <p> <strong> Can not find file: <strong> ' filename '</strong>'
    call lineout tempfile,'</body> </html>'
    call lineout tempfile
    return 'FILE  ERASE TYPE text/html NAME ' tempfile
end

/* look for meta flags (they over ride individual  flags*/
matchall=0 ; matchnone=0
if word(searchlist,1)='*&' then do
  matchall=1
  searchlist=delword(searchlist,1,1)
end
else if word(searchlist,1)="*\" then do
  matchnone=1
  searchlist=delword(searchlist,1,1)
end

if searchlist="" then do                /*missing searchlist */
  call lineout tempfile,'<h2> No search list </h2> '
   if VERBOSE>1 then call sre_pmprintf('TXT_FIND: No searchlist specified.')
   call lineout tempfile,' <p> <strong> No search list specified  <strong> ' 
   call lineout tempfile,'</body> </html>'
  call lineout tempfile

   return 'FILE  ERASE TYPE text/html NAME ' tempfile
end


/* write some else, basic facts .. */
 call lineout tempfile,' File searched:  <strong> ' goober ' </strong> '
 if nstar>1 then
      call lineout tempfile,' (<em>'||nth_nstar||' of '||nstar||' matching '|| aoriginal||') </em> '
 call lineout tempfile,' <br>Search pattern: <u><tt> ' searchlist ' </tt></u> '
 if matchall=1 then call lineout tempfile, '<em> (must match all) </em> '
 if matchnone=1 then call lineout tempfile, '<em> (must match none) </em> '

 call lineout tempfile,' <hr width= 75%>'

/* before extracting phrases, make sure & / ( and ) are spaced out */
searchlist=sre_replacestrg(searchlist,'&',' & ','ALL') ;
searchlist=sre_replacestrg(searchlist,'\',' \ ','ALL') ; 
searchlist=sre_replacestrg(searchlist,'(',' ( ','ALL') ;
searchlist=sre_replacestrg(searchlist,')',' ) ','ALL') ; 
searchlist=sre_replacestrg(searchlist,'|',' | ','ALL') ;

if expert_mode=0 then do                /* fairly search scheme */
    ith=get_searchfor(searchlist,check_case)   /* searchfor. and cond. are exposed */

    dispdetails=1          /* if a NOT or AND exists, set to 0 (since individual details are inaccurate*/
    /* If there are global conditins, overwrite any spurious local conditions */
    do mm=1 to searchfor.0
      select
        when matchall=1 then
           cond.mm='AND'
        when matchnone=1 then
           cond.mm='ORNOT'
        otherwise
      end
      if  cond.mm<>"OR" then dispdetails=0
    end
end
else  do            /* expert mode: user enters syntatically correct search command */
   searchlist=expert_parse(searchlist,'HAYSTACK',check_case)
end

change_crlf=0
if para_delim<>'$' then change_crlf=1  /* change to <BR> below */

nmatch=0
if expert_mode=0 then do
  do jj=1 to searchfor.0
     allmatch.jj=0
  end
end


do mm=1 to filelines.0          /* search each paragraph for a match */

   apara=filelines.mm

/* see if this paragraph is a hit */
   if expert_mode=0 then do             /* simple mode */
        gotems=match_para(apara,checK_case)  /* searchfor. cond. mlist. are exposed */
   end
   else do              /* expert mode uses natural REXX */
        haystack=apara
        if check_case<>1 then haystack=translate(haystack)
        signal on syntax name oyvey
        interpret 'gotems='||searchlist
        signal off syntax
   end

  if gotems=0 then iterate              /* not a hit */

  if maxdisp=0 then iterate   /* 0 means "don't dipslay acutal finds */

/* if here, got a match. So write out the paragraph */

  nmatch=nmatch+1         /* summary counter */
 
  if para_delim<>'$' then do
     if show_number=1 then
            call lineout tempfile,'<h6 align=center>Paragraph #  '  mm ' </h6><pre> '
     else
             call lineout tempfile,'<p><pre>'    /* make space in output doc */
  end

  aline=some_lines(filelines.mm.!orig,maxdisp)         /* that comprise the paragraph */
  aline=sre_html_encode(aline)
  IF EXPERT_MODE=0 THEN do
     do mm3=1 to searchfor.0    /* we know that pre is weren't present*/
        if mlist.mm3 =0 then iterate
        if HIGHLIGHT=1 then
           aline=make_block(searchfor.mm3,aline,'<u>','</u>',check_case) /* highlight matches */
     end         /* Note: expert mode does NOT have highlighting */
  end
  if change_crlf=1 then                 /* convert crlf in custom delimited blocks */
        aline= sre_replacestrg(aline,crlf,'<BR>','ALL')


  if para_delim='$' then do
     if show_number=1 then
        call lineout tempfile,'<p><em>'||mm||': </em> <tt>'|| aline ||'</tt>'
     else
        call lineout tempfile,'<p><tt>'|| aline ||'</tt>'
  end
  else do
        call lineout tempfile,aline ||'</pre>'
  end
  if para_delim<>'$' & show_bar=1 then
         call lineout tempfile,' <hr width=10% > '

     /* jump here if matchall=1 and not all matches */
end                    /* do next paragraph */


asummary:
  call lineout tempfile,' <hr width= 75%>'
  call lineout tempfile,' <p> <h3> Summary of results: ' goober ' </h3> '
  call lineout tempfile,'  # "paragraphs" = ' filelines.0
  if para_delim=" "  para_delim="$" then do
    call lineout tempfile,' <menu> '
    call lineout tempfile,' <li> # of lines = ' filelines.0
    call lineout tempfile,'</menu> '
  end
  else do
    call lineout tempfile,'<br> '
  end

  call lineout tempfile,'  # paragraphs with matches= ' nmatch
  if dispdetails=1 then do
    call lineout tempfile,'<menu> '
    do hmm=1 to searchfor.0
       call lineout tempfile,'<li> ' searchfor.hmm ' = ' allmatch.hmm
    end
    call lineout tempfile,'</menu> '
  end

 if ith_file<files_todo then do                     /* get next file */
      call lineout tempfile,'  <hr size=7> '

/*      call lineout tempfile,'  <Hr width=5> '
      call lineout tempfile,' <hr> ' */

      signal nextfile
end

  call lineout tempfile,'</body>'
  call lineout tempfile,'</html>'
  call lineout tempfile

 return 'FILE  ERASE TYPE text/html NAME ' tempfile

oyvey:                  /* jump here if bad expert mode */
  call lineout tempfile,'<h3> Bad logical search expression </h2> '
  if VERBOSE>1 then  call sre_pmprintf('TXT_FIND: Bad  searchlist specified.')
   call lineout tempfile,' <p> <strong> A bad logical expression was specified  <strong> '
   call lineout tempfile,'</body> </html>'
  call lineout tempfile

   return 'FILE  ERASE TYPE text/html NAME ' tempfile


/*********************/
/* get first mlines of string */
some_lines:procedure
parse arg astring,mlines
anew=''
do jj=1 to mlines
   if astring='' then leave
   parse var astring aline '0d0a'x astring
   if anew='' then
        anew=aline
   else
        anew=anew||'0d0a'x||aline
end
return anew

/* ----------------------------------------------------------------------- */
/* GET_SEARCHFOR: Create the "search for" list (of things to search for ) */
/* ----------------------------------------------------------------------- */

get_searchfor: procedure expose searchfor.  cond. verbose
parse arg searchlist, check_case

ith=0
if check_case<>1 then searchlist=translate(searchlist)

acondstate='OR'                 /* default state */
zmm=0
do until zmm=words(searchlist)
   zmm=zmm+1
   aword=word(searchlist,zmm)
   a1a=verify('\&(|%',aword,'m')
   select 
     when a1a=0  then do   
        ith=ith+1
        searchfor.ith=aword
        cond.ith=acondstate
        acondstate='OR'            /* reset to OR */
     end
     when a1a=1 then     /*  Not is an AND NOT */
        acondstate='NOT'
     when a1a=2 then
        acondstate='AND'
     when a1a=4 then            /* included for completeness */
        acondstate='OR'
     when a1a=5 then
        acondstate='ORNOT'
     when a1a=3 then do  /* begin a phrase -- find the first ) to end it */
         ajj=wordpos(')',searchlist,zmm)
         if ajj=0 then ajj=words(searchlist)+1
         if ajj>zmm+1 then do
            ith=ith+1
            searchfor.ith=' '||subword(searchlist,zmm+1,ajj-(zmm+1))||' '
            cond.ith=acondstate
         end
         zmm=ajj
      end
      otherwise
   end
end 
searchfor.0=ith
return ith


/* -------------------------------------------------------- */
/* BUILD_PARAS: Build paragraphs from lines (blank line delimits a paragraph */
/* -------------------------------------------------------- */

build_paras: procedure expose filelines. paras. verbose
  
     apara=0
     nthpara=0
     do kmm=1 to filelines.0
       if filelines.kmm="" then do
          if apara=1 then do    /* second or more of a series of blank lines */
              paras.nthpara.last=kmm-1
             apara=0
          end
        end
        else do
          if apara=0  then do
             nthpara=nthpara+1
             paras.nthpara.first=kmm
             apara=1
          end
        end
     end  /* Do filelines.0 */

     if apara=1 then paras.nthpara.last=filelines.0
     return nthpara

/* ------------------------------------------------------------------- */
/* MATCH_PARA: Does this paragraph match the search string(s)  */
/* ------------------------------------------------------------------- */

match_para: procedure expose searchfor. cond. mlist. allmatch. verbose
  parse arg apara , check_case

  apara2=apara
  if check_case<>1 then apara2=translate(apara2)

/* scan for matches in the paragraph */
    gotems=0 ; numors=0

    do nn= 1 to searchfor.0                     /*see how many or conditions there are */
        if cond.nn="OR" then numors=numors+1
        mlist.nn=0
    end
 
    do is=1 to searchfor.0   /*search for targets in this paragraph*/
       joe=pos(searchfor.is,apara2)

       if joe=0 & cond.is="AND" then do    /* failure of an "all matches" */
           gotems=0
           leave
       end
       if joe>0 & cond.is="NOT" then do   /* failure of a "not any of these*/
           gotems=0
           leave
       end
       if joe>0 & cond.is="ORNOT"  then do  /* or not condition failed */
           gotems=0
           leave
       end
 
/* if here, no fatal flaw */
      if (joe>0 ) & (pos('NOT',cond.is)=0) then mlist.is=1

      if  (joe>0 & cond.is="OR") | (joe=0 & cond.is="ORNOT")  then do
              allmatch.is=allmatch.is+1
              gotems=gotems+1
       end
       if (joe>0 & cond.is="AND" & numors=0) then do  /* if no or conditions.. */
              gotems=gotems+1
              allmatch.is=allmatch.is+1
       end
       
     end
     return gotems



/* -------------------------------------------------------------- */
/*  GRAB_FILE_LINES:  Get a file, parse into a "lines" stem variable
   Actually, "lines" is "paragraphs", since each "line" may contain
   multiple lines of the text file.

.  Usage:
.   nlines=grab_file_lines(afile,30,optional_eol_delimiter)
.   (filelines.1 to filelines.(filelines.0) contain afile)

delim:
         Blank or 0= blank lines   (the DEFAULT)
             $  = Each line is a paragraph
         other  = User specified delimiter

*/
/* ------------------------------------------------------------- */

grab_file_lines: procedure expose filelines. verbose

parse arg afile, aneol    /* file to get, seconds to wait, eol delimiter */

crlf = '0d0a'x
filelines.=''
filelines.0=0

if aneol="" then aneol=0
if aneol='$'  then aneol=crlf
aneol=strip(aneol)
ause=sre_read_file(afile,3,5,1)      /* 5 means "read even if locked */
if ause='' then return 0                  /* couldn't get it */

/* got a file, let's parse it */
i=0
if aneol<>0 then do     /* trivial "delimited" paragraphs (i.e.; crlf ) */
  do until ause=""
     i=i+1
     parse var ause filelines.i (aneol) ause
     filelines.i.!orig=filelines.i
  end
  filelines.0=i
  return filelines.0
end

/* "blank line" delimited paragraphs */
apara=''; apara2=''
do until ause=''
   parse var ause a1 '0d0a'x ause
   if a1=''   then do     /* blank line signals end of paragraph */
      if apara='' then iterate  /* but only if  some non-blank lines were preceding */
      i=i+1
      filelines.i=apara
      filelines.i.!orig=apara2
      apara='' ; apara2=''
      iterate
   end
   if ause='' then do   /* not a blank link, but is the last line */
      if apara='' then do
        apara=a1
        apara2=a1
      end
      else do
        apara=apara||' '||a1
        apara2=apara2||'0d0a'x||a1
      end
      i=i+1
      filelines.i=apara
      filelines.i.!orig=apara2
      leave
   end 

   if apara='' then do
        apara=a1
        apara2=a1
   end
   else do
        apara=apara||' '||a1
        apara2=apara2||'0d0a'x||a1
   end

end 

filelines.0=i
return filelines.0 

/********************************************************************************/
/***********************************/
/* Tim Osborne's fancy logical parser */

expert_parse: procedure  expose verbose
 parse  arg mystring , haystack , check_case
  if check_case<>1 then mystring=translate(mystring)

 if haystack="" then haystack="HAYSTACK"

/* ------
  User enters logical search string

   User can include any level of nested parentheses to
   override normal order of logical statement.  Parentheses
   are not required however.

   say 'What are you looking for?'
  pull mystring
-------- */

mystring='('||mystring||')'
mystring=space(mystring)
if pos('(',mystring)=0 & pos('|',mystring)=0 & pos('\',mystring)=0 & pos('&',mystring)=0 & pos('@',mystring)=0 then do
   mystring='('||mystring||')'
   end
else do
   blanks= pos(' (',mystring)>0 | pos('( ',mystring)>0 | pos(' &',mystring) >0 | pos('& ',mystring)>0 |,
           pos(' |',mystring)>0 | pos('| ',mystring)>0 | pos(' @',mystring) >0 | pos('@ ',mystring)>0 |,
           pos(' \',mystring)>0 | pos('\ ',mystring)>0 | pos(' )',mystring) >0 | pos(') ',mystring)>0
   do while blanks > 0
      if pos(' (',mystring) \=0 then mystring=substr(mystring,1,pos(' (',mystring)-1)||'('||substr(mystring,pos(' (',mystring)+2)
      if pos(' )',mystring) \=0 then mystring=substr(mystring,1,pos(' )',mystring)-1)||')'||substr(mystring,pos(' )',mystring)+2)
      if pos(' |',mystring) \=0 then mystring=substr(mystring,1,pos(' |',mystring)-1)||'|'||substr(mystring,pos(' |',mystring)+2)
      if pos(' &',mystring) \=0 then mystring=substr(mystring,1,pos(' &',mystring)-1)||'&'||substr(mystring,pos(' &',mystring)+2)
      if pos(' @',mystring) \=0 then mystring=substr(mystring,1,pos(' @',mystring)-1)||'@'||substr(mystring,pos(' @',mystring)+2)
      if pos(' \',mystring) \=0 then mystring=substr(mystring,1,pos(' \',mystring)-1)||'\'||substr(mystring,pos(' \',mystring)+2)
      if pos('( ',mystring) \=0 then mystring=substr(mystring,1,pos('( ',mystring)-1)||'('||substr(mystring,pos('( ',mystring)+2)
      if pos(') ',mystring) \=0 then mystring=substr(mystring,1,pos(') ',mystring)-1)||')'||substr(mystring,pos(') ',mystring)+2)
      if pos('| ',mystring) \=0 then mystring=substr(mystring,1,pos('| ',mystring)-1)||'|'||substr(mystring,pos('| ',mystring)+2)
      if pos('& ',mystring) \=0 then mystring=substr(mystring,1,pos('& ',mystring)-1)||'&'||substr(mystring,pos('& ',mystring)+2)
      if pos('@ ',mystring) \=0 then mystring=substr(mystring,1,pos('@ ',mystring)-1)||'@'||substr(mystring,pos('@ ',mystring)+2)
      if pos('\ ',mystring) \=0 then mystring=substr(mystring,1,pos('\ ',mystring)-1)||'\'||substr(mystring,pos('\ ',mystring)+2)

           blanks= pos(' (',mystring)>0 | pos('( ',mystring)>0 | pos(' &',mystring) >0 | pos('& ',mystring)>0 |,
              pos(' |',mystring)>0 | pos('| ',mystring)>0 | pos(' @',mystring) >0 | pos('@ ',mystring)>0 |,
              pos(' \',mystring)>0 | pos('\ ',mystring)>0 | pos(' )',mystring) >0 | pos(') ',mystring)>0
    end
end
if lastpos(')',mystring) \= length(mystring) then mystring='('||mystring||')'
mystring=mystring||'*'
pointer=1
notin=1
mychar=substr(mystring,pointer,1)
do until mychar='*'
           if pos(substr(mystring,pointer,1),'()|&@\')=0 & notin then do
              mystring=substr(mystring,1,pointer-1)||'pos('''||substr(mystring,pointer)
              notin=0
              pointer=pointer+4
           end
           else do
              if pos(substr(mystring,pointer,1),'()|&@\')>0 & notin=0 then do
                 mystring=substr(mystring,1,pointer-1)||''',haystack)>0'||substr(mystring,pointer)
                 notin=1
                 pointer=pointer+12
              end
           end
           pointer=pointer+1
           mychar=substr(mystring,pointer,1)
end
mystring=substr(mystring,1,length(mystring)-1)
do while pos('@',mystring)>0
   mystring=insert('&&',mystring,pos('@',mystring))
   mystring=delstr(mystring,pos('@',mystring),1)
end
/*say " mystring: " mystring*/
return mystring




/* ----------------------------------------------------------------------- */
/* MAKE BLOCK: Replace all occurences of NEEDLE in HAYSTACK
.        with delim1 needle delim2.
.        If delim1 and delim2 not give, then { AND } are used.
.   Example: make_block(boys,' there are wild boys out there','<b>',' </b>')
.      returns 'there are wild <b>boys </b> out  there'
.      (note that spaces are all retained)
*/
/* ----------------------------------------------------------------------- */

make_block:

parse arg needle, haystack, delim1 , delim2, check_case
if delim1="" then delim1='{'
if delim2="" then delim1='}'

build=""
do forever
  if check_case<>1 then
     rmm=pos(translate(needle),translate(haystack))
  else
     rmm=pos(needle,haystack)

  if rmm=0 then do
    build=build||haystack
    return build
  end

  t1=substr(haystack,1,rmm-1)
  t2=substr(haystack,rmm,length(needle))
  haystack=substr(haystack,rmm+length(needle))
  build=build||t1||delim1||t2||delim2

end


