home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 18 REXX / 18-REXX.zip / rxmulch2.zip / rxMulch.cmd < prev    next >
OS/2 REXX Batch file  |  1996-11-25  |  49KB  |  1,194 lines

  1. /*
  2. program: rxMulch.cmd
  3. type:    REXXSAA-OS/2, version 2.x, or REXXSAA 6.0 (Object REXX)
  4. purpose: find/change strings in a file, allow for hex-characters
  5.          (program to find/replace characters (strings) in file; allows
  6.          definition of hexadecimal or decimal value of single characters)
  7.  
  8. version: 1.0.1
  9. date:    1994-01-08
  10.  
  11. changed: 1995-04-05, rgf, changed a bug related to renaming an already
  12.                           deleted tmp-file (because no changes took place)
  13.          1995-09-22, rgf, adjusted for checking if running under Object REXX; if so
  14.                           using COUNTSTR() and CHANGESTR() instead, which speeds up
  15.                           the program *quite* a bit
  16.  
  17. author:  Rony G. Flatscher,
  18.          Wirtschaftsuniversität/Vienna
  19.          Rony.Flatscher@wu-wien.ac.at
  20.  
  21. needs:   DATERGF.CMD, all RexxUtil-function loaded (automatically loaded, if needed)
  22.  
  23. usage:
  24.     rxMulch [infile] [outfile] {[-]controlfile | /[-]switch}
  25.       infile:      if missing from STDIN:
  26.  
  27.       outfile:     if missing, RxMulch will replace infile;
  28.                    if no infile than output to STDOUT:
  29.  
  30.       controlfile OR switch MUST be present:
  31.  
  32.       controlfile: change for every search-string/replace-string line the
  33.                    'search-string' into 'replace-string'; if more than one
  34.                    search/replace-line is given, subsequent search/replaces
  35.                    start from the beginning.
  36.                    If the controlfile is preceded with a minus (-) the
  37.                    meaning of the 'search-string' and 'replace-string' is
  38.                    swapped.
  39.                    If a line is empty or if there is a semi-colon (;) at the very
  40.                    first column, the line is treated as a comment.
  41.  
  42.  
  43.       switch:      If the switch is preceded with a minus (-) the meaning of
  44.                    the 'search-string' and 'replace-string' is swapped.
  45.  
  46.                    'F'search-string/replace-string
  47.                    ... count the number of occurrences of 'search-string'
  48.  
  49.  
  50.                    'C'search-string/replace-string
  51.                    ... Change all occurrences of 'search-string' to
  52.                        'replace-string'.
  53.  
  54.                    '[L[1|2|3|4|5]][H[1|2]]'
  55.                    ...  change low-/high-characters to any of the following
  56.                         representations:
  57.  
  58.                         L: change all low-char values c2d(0-32)
  59.                             L .... defaults to L1
  60.                             L1 ... char(0-32) to decimal
  61.                             L2 ... char(0-32) to hexadecimal
  62.                             L3 ... char(0-32) to control-sequence
  63.                             L4 ... char(0-32) to abbreviated comm-characters
  64.                             L5 ... char(0-32) to all representations above
  65.  
  66.                         H: change all high-char values c2d(128-255)
  67.                             H  ... defaults to H1
  68.                             H1 ... char(128-255) to decimal
  69.                             H2 ... char(128-255) to hexadecimal
  70.  
  71.                         The appropriate search-string/replace-string pairs are
  72.                         generated automatically.
  73.  
  74.       search-string/replace-string:
  75.                   (delimiter)search-values(delimiter)replace-values(delimiter)
  76.  
  77.             delimiter:
  78.                    very first character in search-string/replace-string IMMEDIATELY
  79.                    following switch-character
  80.  
  81.             search-values
  82.             replace-values:
  83.                    any ASCII-string intermixed with the following escape-codes
  84.  
  85.                    escape-codes:
  86.                        @C    ... CR
  87.                        @L    ... LF
  88.                        @T    ... TAB
  89.                        @E    ... ESC
  90.                        @Z    ... CTL-Z
  91.                        @@    ... @ (escape for @)
  92.                        @Xnn
  93.                        @Hnn  ... char with the hexadecimal of value 'nn'
  94.                        @Dnnn ... char with the decimal value 'nnn'
  95.  
  96.    RxMulch can be called as a function from another REXX-program, e.g.
  97.  
  98.       some_variable = RxMulch(REXXstring, "[/][-]switch")
  99.  
  100.  
  101.    examples:
  102.  
  103.        rxMulch infile outfile controlfile
  104.            ... change 'infile' according to 'controlfile', place results into
  105.                'outfile'
  106.  
  107.        rxMulch infile controlfile
  108.            ... change 'infile' according to 'controlfile', place results into
  109.                'infile' (i.e. replace 'infile' itself)
  110.  
  111.        rxMulch < some_in_file > some_out_file controlfile
  112.            ... change 'some_in_file' according to 'controlfile', place results
  113.                into 'some_out_file'; 'some_in_file' and 'some_out_file' are
  114.                redirected ('<' and '>'). rxMulch therefore can be used in pipes
  115.                too.
  116.  
  117.        rxMulch infile outfile1 /C.Microsoft Excel.Lotus 1-2-3.
  118.            ... change 'infile' according to commandline switch (replace all
  119.                occurrences of 'Microsoft Excel' with 'Lotus 1-2-3'), place
  120.                results into 'outfile1'
  121.  
  122.        rxMulch outfile1 outfile2 /-C.Microsoft Excel.Lotus 1-2-3.
  123.            ... change 'outfile1' according to commandline switch (replace all
  124.                occurrences of 'Lotus 1-2-3' with 'Microsoft Excel', note the
  125.                minus (-) right before the switch-character), place results into
  126.                'outfile2'; could be also expressed as:
  127.                      rxMulch outfile1 outfile2 /C.Lotus 1-2-3.Microsoft Excel.
  128.  
  129.        rxMulch infile /C.;.@c@l.
  130.            ... change 'infile' according to commandline switch (replace
  131.                semicolons (;) with a carriage-return/linefeed), replace
  132.                'infile'; could be also expressed as:
  133.                      rxMulch infile /C.;.@xd@xa.
  134.                      rxMulch infile /C.;.@x0d@x0a.
  135.                      rxMulch infile /C.;.@d13@d10.
  136.  
  137.        rxMulch infile /C.@c@l@c@l.@c@l.
  138.            ... change 'infile' according to commandline switch (replace
  139.                consecutive carriage-return/linefeeds with one
  140.                carriage-return/linefeed, i.e. remove one empty line), replace
  141.                'infile'; could be also expressed as:
  142.                      rxMulch infile /C.@xd@xa@xd@xa.@xd@xa.
  143.                      rxMulch infile /C!@d13@d10@d13@d10!@d13@d10!
  144.                      rxMulch infile /C/@d13@d10@d13@d10/@c@l/
  145.  
  146.        rxMulch infile /-C.@c@l@c@l.@c@l.
  147.            ... change 'infile' according to commandline switch (replace a
  148.                carriage-return/linefeed with two consecutive
  149.                carriage-return/linefeeds, i.e. insert an empty line after each
  150.                line), replace 'infile'; could be also expressed as:
  151.                      rxMulch infile /C,@c@l,@c@l@c@l,
  152.                      rxMulch infile /C=@x0d@x0a=@x0d@x0a@x0dx@0a=
  153.                      rxMulch infile /C=@d13@d10=@d13@d10@x0dx@0a=
  154.  
  155.        rxMulch infile /C=@x00@x00@x00@x00=@x01@x01@x01@x01=
  156.            ... change 'infile' according to commandline switch (replace all
  157.                hexadecimal strings of 0x00000000 with 0x01010101), replace
  158.                'infile'; could be also expressed as:
  159.                      rxMulch infile /C=@x0@x0@x0@x0=@x1@x1@x1@x1=
  160.                      rxMulch infile /C/@d0@d0@d0@d0/@d1@d1@d1@d1/
  161.  
  162.        rxMulch infile /F.OS/2.
  163.            ... count occurrences of string 'OS/2' in 'infile'
  164.  
  165.        rxMulch infile /F.@c@l@c@l.
  166.            ... count number of lines in 'infile', which are immediately
  167.                followed by a blank line
  168.  
  169.    examples for calling RxMulch from a REXX-procedure:
  170.        string1 = 'this is nice'
  171.        string2 = RxMulch(string1, '/c.this.that.')  /* change 'this' to 'that' */
  172.            ... string2 = 'that is nice'
  173.        string2 = RxMulch(string2, '/-c.this.that.') /* change 'that' to 'this' */
  174.            ... string2 = 'this is nice'
  175.        occurrences = RxMulch(string2, 'f.this.')    /* count 'this' in string2 */
  176.            ... occurrences = 1
  177.  
  178.  
  179. All rights reserved, copyrighted 1994, no guarantee that it works without
  180. errors, etc. etc.
  181.  
  182. donated to the public domain granted that you are not charging anything (money
  183. etc.) for it and derivates based upon it, as you did not write it,
  184. etc. if that holds you may bundle it with commercial programs too
  185.  
  186. you may freely distribute this program, granted that no changes are made
  187. to it and that DATERGF.CMD is being distributed with it.
  188.  
  189. Please, if you find an error, post me a message describing it, I will
  190. try to fix and rerelease it to the net.
  191.  
  192. */
  193.  
  194.  
  195. SIGNAL ON ERROR
  196. SIGNAL ON HALT
  197.  
  198. global.         = ""            /* default for empty array-elements */
  199. global.eTotalOfNeedles = 0      /* number of search/replace-needles */
  200. global.eTotalOfChanges = 0      /* sum of all changes */
  201. global.eDirection = 1           /* regular, i.e. first needle to be replaced with second */
  202. delimiter = ""
  203. mode = ""                       /* "F" find, "C" change, i.e. replacement */
  204. writeFile = 1                   /* write results to file */
  205. readFile  = 1                   /* read from file */
  206.  
  207. PARSE SOURCE op_sys global.eCall_type proc_name .
  208. PARSE VERSION . global.eVersion .       /* get REXX-version */
  209.  
  210. /* check whether RxFuncs are loaded, if not, load them */
  211. IF RxFuncQuery('SysLoadFuncs') THEN
  212. DO
  213.     /* load the load-function */
  214.     CALL RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
  215.  
  216.     /* load all Sys* utilities in RexxUtil */
  217.     CALL SysLoadFuncs
  218. END
  219.  
  220.  
  221. IF global.eCall_type = "FUNCTION" THEN
  222. DO
  223.    tmpVar1 = ARG(1)             /* data to be worked on */
  224.    IF LEFT(ARG(2), 1) <> "/" THEN       /* in case "/" is not supplied */
  225.       arg2 = ARG(2)
  226.    ELSE
  227.       arg2    = SUBSTR(ARG(2), 2)       /* command-string without leadin "/" */
  228.  
  229.    writeFile = 0                /* no file to be produced */
  230.    readFile  = 0                /* do not read from file */
  231. END
  232. ELSE
  233.    PARSE ARG arg1 "/"arg2          /* control-string on command-line ?     */
  234.  
  235.  
  236. IF arg2 <> "" THEN              /* parse arguments with control-string on command-line */
  237. DO
  238.    full_control_string = "/" || arg2
  239.    IF LEFT(arg2, 1) = "-" THEN
  240.    DO
  241.       global.eDirection = 0      /* inverse, i.e. swap meaning of from-needle and to-needle */
  242.       arg2 = SUBSTR(arg2, 2)
  243.    END
  244.  
  245.    tmp1 = TRANSLATE(LEFT(arg2, 1))
  246.  
  247.    IF tmp1 = "C" THEN        /* needle & replacement is given on command-line */
  248.    DO
  249.      active_switch = "C"
  250.      delimiter = SUBSTR(arg2, 2, 1)
  251.      /* without "C" */
  252.      CALL parse_replacement_string SUBSTR(arg2, 2), "command line"
  253.      mode = "C"
  254.    END
  255.    ELSE IF tmp1 = "F" THEN      /* count occurrences of given needle */
  256.    DO
  257.      delimiter = SUBSTR(arg2, 2, 1)                             /* get delimiter */
  258.      tmp_string = SUBSTR(arg2, 2) || delimiter || delimiter     /* make sure, that enough delimiters are supplied */
  259.      CALL parse_replacement_string tmp_string, "command"
  260.      mode = "F"                 /* mode is "find" all occurrences */
  261.      writeFile = 0              /* don't write to a file */
  262.    END
  263.    ELSE                         /* needles & replacements to built automatically */
  264.    DO
  265.       tmp2 = TRANSLATE(SUBSTR(arg2, 2, 1))
  266.       tmp3 = TRANSLATE(SUBSTR(arg2, 3, 1))
  267.       tmp4 = TRANSLATE(SUBSTR(arg2, 4, 1))
  268.  
  269.       tmpL = (tmp1 = 'L') | (tmp2 = 'L') | (tmp3 = 'L')
  270.       tmpH = (tmp1 = 'H') | (tmp2 = 'H') | (tmp3 = 'H')
  271.  
  272.       IF tmpL THEN              /* replace low-chars: d2c(0-32) */
  273.       DO
  274.          arg2 = TRANSLATE(arg2)
  275.          /* is "L" followed by a number ? If so, extract it */
  276.          tmpLnr = SUBSTR(arg2, POS("L", SUBSTR(arg2, 1, 3)) + 1, 1)
  277.  
  278.          IF \DATATYPE(tmpLnr, "NUMERIC") THEN tmpLnr = ""
  279.  
  280.          CALL build_low tmpLnr          /* build the translation strings */
  281.       END
  282.  
  283.       IF tmpH THEN              /* replace high-chars: d2c(128-255) */
  284.       DO
  285.          arg2 = TRANSLATE(arg2)
  286.          /* is "H" followed by a number ? If so, extract it */
  287.          tmpHnr = SUBSTR(arg2, POS("H", SUBSTR(arg2, 1, 3)) + 1, 1)
  288.  
  289.          IF \DATATYPE(tmpHnr, "NUMERIC") THEN tmpHnr = ""
  290.          CALL build_high tmpHnr         /* build the translation strings */
  291.       END
  292.  
  293.       IF \(tmpL | tmpH) THEN            /* wrong switch ! */
  294.          SIGNAL usage
  295.  
  296.      mode = "CLH"
  297.    END
  298.  
  299.    global.eControl = ""                 /* no control-file */
  300.    PARSE VAR arg1 global.eFilein global.eFileout .
  301. END
  302. ELSE                            /* parse a control file ************************/
  303. DO
  304.    PARSE ARG global.eFilein global.eFileout global.eControl
  305.  
  306.    IF global.eControl = "" THEN         /* no control-file given ? */
  307.    DO
  308.       IF global.eFileout = "" THEN      /* no output-file given */
  309.       DO
  310.          IF global.eFilein = "" THEN    /* no input-file given, now we are in trouble */
  311.          DO
  312.             SIGNAL usage
  313.          END
  314.          ELSE                   /* assign files */
  315.          DO
  316.             global.eControl = global.eFilein    /* last file is control-file */
  317.             global.eFilein  = ""        /* user wants to read/write stdin/stdout */
  318.          END
  319.       END
  320.       ELSE
  321.       DO
  322.          global.eControl = global.eFileout                 /* last file is control-file */
  323.          global.eFileout = ""                      /* user wants to overwrite global.eFilein */
  324.       END
  325.    END
  326.  
  327.    IF LEFT(global.eControl, 1) = "-" THEN
  328.    DO
  329.       global.eDirection = 0                /* inverse */
  330.       global.eControl = SUBSTR(global.eControl, 2)         /* get rid of "-" */
  331.    END
  332.  
  333.    IF global.eFileout = global.eFilein THEN global.eFileout = ""   /* replace input file */
  334. END
  335.  
  336. /* remove leading & trailing spaces */
  337. global.eFilein  = STRIP(global.eFilein)
  338. global.eFileout = STRIP(global.eFileout)
  339. global.eControl = STRIP(global.eControl)
  340.  
  341. IF readFile & (global.eFilein <> "") THEN       /* check, whether input file exists */
  342. DO
  343.    IF (STREAM(global.eFilein, "C", "QUERY EXISTS") = "") THEN
  344.       CALL say_error global.eFilein || ": input-file does not exist !", -1
  345. END
  346.  
  347. /* erase output-file, if it exists */
  348. IF (global.eFileout <> "") & writeFile THEN
  349. DO
  350.    IF (STREAM(global.eFileout, "C", "QUERY EXISTS") <> "") THEN
  351.    DO
  352.       CALL BEEP 550, 250
  353.       CALL say2stderr "Output file [" || global.eFileout || "] exists already !"
  354.       CALL say2stderr "Overwrite it ? (Y/N)"
  355.       IF TRANSLATE(SysGetKey()) <> "Y" THEN
  356.          EXIT
  357.  
  358.       ADDRESS CMD "@del" global.eFileout
  359.       CALL say2stderr
  360.       CALL say2stderr "Output File [" || global.eFileout || "] deleted."
  361.       CALL say2stderr
  362.    END
  363. END
  364.  
  365. IF global.eControl <> "" THEN           /* check whether control-file exists */
  366. DO
  367.    IF STREAM(global.eControl, "C", "QUERY EXISTS") = "" THEN
  368.    DO
  369.       /* control-file not found:
  370.            if control file was given *WITHOUT* path and drive, lookup the drive and
  371.            path of RxMulch.CMD and try to get the control-file from there
  372.       */
  373.       IF FILESPEC("Name", global.eControl) = global.eControl THEN
  374.       DO
  375.          PARSE SOURCE . . this_proc
  376.          global.eControl = FILESPEC("Drive", this_proc) || FILESPEC("PATH", this_proc) || global.eControl
  377.       END
  378.  
  379.       IF STREAM(global.eControl, "C", "QUERY EXISTS") = "" THEN
  380.          CALL say_error global.eFilein || ": control-file does not exist !", -2
  381.    END
  382.    CALL parse_control_file global.eControl      /* build search/replace-values from control-file */
  383. END
  384. /*
  385. ELSE
  386.    CALL parse_control_file global.eControl      /* build search/replace-values from control-file */
  387. */
  388.  
  389.  
  390. IF readFile & global.eFilein <> "" THEN
  391. DO
  392.    CALL STREAM global.eFilein, "C", "OPEN READ"    /* open input-file */
  393.  
  394.    IF global.eFileout = "" & writeFile THEN
  395.    DO
  396.       global.eTmp = SysTempFileName("tmp???.rxm")
  397.       global.eFileout     = global.eTmp
  398.    END
  399. END
  400.  
  401. IF global.eFileout <> "" & writeFile THEN
  402.    CALL STREAM global.eFileout, "C", "OPEN WRITE"  /* open output-file */
  403.  
  404. start_read = DATE("S") TIME()
  405.  
  406. IF readFile & global.eFilein = "" THEN     /* read from stdin: */
  407. DO
  408.    tmpVar1 = ""
  409.    DO WHILE STREAM('STDIN:','S') == 'READY'     /* read from stdin: */
  410.         tmpVar1 = tmpVar1 || CHARIN("STDIN:", , 131072)     /* read 4096 * 32 = 128KB to speed things up */
  411.    END
  412.  
  413.    IF tmpVar1 = "" THEN
  414.       CALL say_error "no data from stdin: received", -100
  415.  
  416.   global.eFilein  = "STDIN:"
  417.   global.eFileout = "STDOUT:"
  418. END
  419. ELSE IF readFile THEN  /* read entire file into variable */
  420.     tmpVar1  = CHARIN(global.eFilein, 1, STREAM(global.eFilein, "C", "QUERY SIZE"))
  421.  
  422. end_read = DATE("S") TIME()
  423.  
  424. IF global.eCall_type <> "FUNCTION" THEN
  425. DO
  426.    IF readFile THEN
  427.       CALL say2stderr RIGHT("Input-File:", 30)   "[" || global.eFilein  || "]"
  428.    IF writeFile THEN
  429.       CALL say2stderr RIGHT("Output-File:", 30)  "[" || global.eFileout || "]"
  430.  
  431.    IF global.eControl <> "" THEN
  432.    DO
  433.       CALL say2stderr RIGHT("global.eControl-File:", 30) "[" || global.eControl || "]"
  434.       CALL say2stderr
  435.       CALL say2stderr RIGHT("", 30) global.eTotalOfNeedles "needle(s) to be replaced."
  436.    END
  437.    ELSE
  438.    DO
  439.       CALL say2stderr RIGHT("global.eControl-Command Line:", 30) "[" || full_control_string || "]"
  440.       IF \(delimiter == "") THEN
  441.          CALL say2stderr RIGHT("", 30) "needle-delimiter: [" || delimiter || "]"
  442.       CALL say2stderr
  443.  
  444.       IF mode = "C" THEN                /* change mode */
  445.       DO
  446.          CALL say2stderr RIGHT("replacing:", 41) "[" || global.1.eDebug_In || "]"
  447.          CALL say2stderr RIGHT("with:", 41) "[" || global.1.eDebug_Out || "]"
  448.          CALL say2stderr
  449.          CALL say2stderr RIGHT("", 30) global.eTotalOfNeedles "needle(s) to be replaced."
  450.       END
  451.       ELSE IF mode = "CLH" THEN         /* automatic change mode, low/high values */
  452.       DO
  453.          CALL say2stderr RIGHT("replacing:", 41) "[CHAR(0-32) or/and CHAR(128-255)]"
  454.          CALL say2stderr
  455.          CALL say2stderr RIGHT("", 30) global.eTotalOfNeedles "needle(s) to be replaced."
  456.       END
  457.       ELSE IF mode = "F" THEN
  458.       DO
  459.          CALL say2stderr RIGHT("looking for:", 43) "[" || global.1.eDebug_In || "]"
  460.       END
  461.    END
  462. END
  463.  
  464.  
  465. start_replace = DATE("S") TIME()
  466.  
  467. /*
  468.    save space on large files, hence minimize usage of variable assignments
  469. */
  470. bDirty = 0                               /* bDirty, if replacements happened */
  471. IF mode = "F" THEN
  472. DO
  473.    DO i = 1 TO global.eTotalOfNeedles   /* find all search/replace-strings */
  474.       CALL find_needles global.i.eNeedle_In, global.i.eNeedle_Out
  475.    END
  476. END
  477. ELSE
  478. DO
  479.    tmpVar2 = ""
  480.    bWorkOnTmpVar1 = 1                   /* work on variable "tmpVar1" or "tmpVar2" */
  481.    DO i = 1 TO global.eTotalOfNeedles   /* process all search/replace-strings */
  482.       IF bWorkOnTmpVar1 THEN
  483.          CALL work_on_tmpVar1 global.i.eNeedle_In, global.i.eNeedle_Out
  484.       ELSE
  485.          CALL work_on_tmpVar2 global.i.eNeedle_In, global.i.eNeedle_Out
  486.  
  487.       IF bDirty THEN
  488.          bWorkOnTmpVar1 = \bWorkOnTmpVar1
  489.    END
  490. END
  491.  
  492. end_replace = DATE("S") TIME()
  493.  
  494. IF readFile THEN
  495.    CALL STREAM global.eFilein, "C", "CLOSE"     /* close input file */
  496.  
  497. start_write = DATE("S") TIME()
  498.  
  499.  
  500. IF global.eCall_type = "FUNCTION" THEN          /* was called as a function */
  501. DO
  502.    IF mode <> "F" THEN
  503.    DO
  504.      IF bWorkOnTmpVar1 THEN             /* result in variable "tmpVar1" */
  505.         RETURN tmpVar1
  506.      ELSE                               /* result in variable "tmpVar2" */
  507.         RETURN tmpVar2
  508.    END
  509.  
  510.    RETURN global.eTotalOfChanges        /* return number of occurrences found */
  511. END
  512.  
  513. IF writeFile THEN                       /* write output file */
  514. DO
  515.    IF mode <> "F" THEN
  516.    DO
  517.      IF global.eTotalOfChanges <> 0 THEN
  518.      DO
  519.         IF bWorkOnTmpVar1 THEN             /* result in variable "tmpVar1" */
  520.            CALL CHAROUT global.eFileout, tmpVar1
  521.         ELSE                               /* result in variable "tmpVar2" */
  522.            CALL CHAROUT global.eFileout, tmpVar2
  523.      END
  524.      ELSE       /* no replacements took place, erase output-file */
  525.      DO
  526.         IF STREAM(global.eFileout) = "READY" THEN
  527.            CALL STREAM global.eFileout, "C", "CLOSE"    /* close output file */
  528.  
  529.         ADDRESS CMD "@del" global.eFileout
  530.      END
  531.    END
  532.  
  533.    IF STREAM(global.eFileout) = "READY" THEN
  534.       CALL STREAM global.eFileout, "C", "CLOSE"  /* close output file */
  535. END
  536.  
  537. end_write = DATE("S") TIME()
  538.  
  539. IF global.eTmp <> "" & writeFile THEN   /* output file was temporary, replacement of input file sought */
  540. DO
  541.    IF global.eTotalOfChanges > 0 THEN           /* changes took place */
  542.    DO
  543.       CALL SysFileDelete(global.eFilein)           /* delete input file */
  544.       ADDRESS CMD "@ren" global.eFileout global.eFilein    /* rename output to input name */
  545.    END
  546. END
  547.  
  548. total_time   = calc_time(start_read, end_write)
  549. read_time    = calc_time(start_read, end_read)
  550.  
  551. IF writeFile THEN
  552.    write_time   = calc_time(start_write, end_write)
  553.  
  554. replace_time = calc_time(start_replace, end_replace)
  555.  
  556. IF mode <> "F" THEN
  557.    CALL say2stderr RIGHT("", 30) global.eTotalOfChanges "occurrence(s) replaced."
  558. ELSE
  559.    CALL say2stderr RIGHT("", 30) global.eTotalOfChanges "occurrence(s) found."
  560.  
  561. CALL say2stderr
  562. CALL say2stderr RIGHT("time for reading file:", 30)  read_time
  563.  
  564. IF mode <> "F" THEN
  565.    CALL say2stderr RIGHT("time for replacing:", 30)     replace_time
  566.  
  567. IF writeFile THEN
  568.    CALL say2stderr RIGHT("time for writing file:", 30)  write_time
  569.  
  570. CALL say2stderr
  571. CALL say2stderr RIGHT("total time:", 30)             total_time
  572.  
  573. EXIT
  574.  
  575.  
  576.  
  577.  
  578. /*********************************************************************************
  579.    search string and replace it, if found, return tmpVar2 string
  580.  
  581.    EXPOSE both strings, so no copies of them need to be produced in this procedure
  582.    (could be deadly on multi-MB-files in terms of speed)
  583. */
  584. WORK_ON_TMPVAR1: PROCEDURE EXPOSE global. tmpVar1 tmpVar2 bDirty
  585.     from_needle  = ARG(1)
  586.     to_needle    = ARG(2)
  587.  
  588.     bDirty = 0           /* does a change occur ? */
  589.  
  590.     IF global.eVersion >= 6 THEN        /* running under Object REXX */
  591.     DO
  592.        tmpCount = COUNTSTR(from_needle, tmpVar1)
  593.        bDirty = tmpCount > 0
  594.        IF bDirty THEN
  595.        DO
  596.           global.eTotalOfChanges = global.eTotalOfChanges + tmpCount
  597.           tmpVar2 = CHANGESTR(from_needle, tmpVar1, to_needle)
  598.        END
  599.     END
  600.     ELSE                                /* running under traditional REXX */
  601.     DO
  602.        from_needle_length = LENGTH(from_needle)      /* get length of search-string */
  603.        start = 1           /* start position in tmpVar1 */
  604.        tmpVar2   = ""
  605.    
  606.        DO FOREVER
  607.           pos = POS(from_needle, tmpVar1, start)         /* get position of search-string */
  608.           IF pos = 0 THEN                          /* search-string not found */
  609.           DO
  610.              IF bDirty THEN
  611.                 tmpVar2 = tmpVar2 || SUBSTR(tmpVar1, start)
  612.              LEAVE            /* done, no from_needle found */
  613.           END
  614.    
  615.           tmpVar2 = tmpVar2 || SUBSTR(tmpVar1, start, pos-start) || to_needle
  616.           global.eTotalOfChanges = global.eTotalOfChanges + 1
  617.           start = pos + from_needle_length
  618.           bDirty = 1                /* change occurred ! */
  619.        END
  620.     END
  621.  
  622.  
  623.     RETURN
  624.  
  625.  
  626. WORK_ON_TMPVAR2: PROCEDURE EXPOSE global. tmpVar2 tmpVar1 bDirty
  627.     from_needle = ARG(1)
  628.     to_needle   = ARG(2)
  629.  
  630.     bDirty = 0           /* does a change occur ? */
  631.  
  632.     IF global.eVersion >= 6 THEN        /* running under Object REXX */
  633.     DO
  634.        tmpCount = COUNTSTR(from_needle, tmpVar2)
  635.        bDirty = tmpCount > 0
  636.        IF bDirty THEN
  637.        DO
  638.           global.eTotalOfChanges = global.eTotalOfChanges + tmpCount
  639.           tmpVar1 = CHANGESTR(from_needle, tmpVar2, to_needle)
  640.        END
  641.     END
  642.     ELSE                                /* running under traditional REXX */
  643.     DO
  644.        from_needle_length = LENGTH(from_needle)      /* get length of search-string */
  645.        start = 1           /* start position in tmpVar2 */
  646.        tmpVar1   = ""
  647.    
  648.        DO FOREVER
  649.           pos = POS(from_needle, tmpVar2, start)         /* get position of search-string */
  650.    
  651.           IF pos = 0 THEN                          /* search-string not found */
  652.           DO
  653.              IF bDirty THEN
  654.                 tmpVar1 = tmpVar1 || SUBSTR(tmpVar2, start)
  655.              LEAVE            /* done, no from_needle found */
  656.           END
  657.    
  658.           tmpVar1 = tmpVar1 || SUBSTR(tmpVar2, start, pos-start) || to_needle
  659.           global.eTotalOfChanges = global.eTotalOfChanges + 1
  660.           start = pos + from_needle_length
  661.           bDirty = 1                /* change occurred ! */
  662.        END
  663.     END
  664.  
  665.     RETURN
  666.  
  667.  
  668.  
  669. /*********************************************************************************
  670.    find string for number of occurences
  671. */
  672. FIND_NEEDLES: PROCEDURE EXPOSE global. tmpVar1 bDirty
  673.     from_needle  = ARG(1)
  674.  
  675.     from_needle_length = LENGTH(from_needle)      /* get length of search-string */
  676.     start = 1           /* start position in tmpVar1 */
  677.     bDirty = 0           /* does a change occur ? */
  678.  
  679.     IF global.eVersion >= 6 THEN        /* running under Object REXX */
  680.     DO
  681.        tmpCount = COUNTSTR(from_needle, tmpVar2)
  682.        bDirty = tmpCount > 0
  683.        global.eTotalOfChanges = global.eTotalOfChanges + tmpCount
  684.     END
  685.     ELSE                                /* running under traditional REXX */
  686.     DO
  687.        DO FOREVER
  688.           pos = POS(from_needle, tmpVar1, start)         /* get position of search-string */
  689.           IF pos = 0 THEN                          /* search-string not found */
  690.           DO
  691.              LEAVE            /* done, no from_needle found anymore */
  692.           END
  693.    
  694.           global.eTotalOfChanges = global.eTotalOfChanges + 1
  695.           start = pos + from_needle_length
  696.           bDirty = 1                /* changes occurred ! */
  697.        END
  698.     END
  699.  
  700.  
  701.     RETURN
  702.  
  703.  
  704.  
  705.  
  706. /*********************************************************************************
  707.     parse control file, setup needle_in-/needle_out-array
  708.     ARG(1) ... control-file-name
  709. */
  710. PARSE_CONTROL_FILE: PROCEDURE EXPOSE global.
  711.     linecount = 0
  712.     DO WHILE LINES(ARG(1)) > 0
  713. /*                                      allow a blank as a delimiter !
  714.        line = STRIP(LINEIN(ARG(1)))
  715. */
  716.        line = LINEIN(ARG(1))
  717.  
  718.        linecount = linecount + 1        /* count processed lines */
  719.        IF line = "" THEN ITERATE
  720.        delimiter = LEFT(line, 1)        /* get delimiter character */
  721.        IF delimiter = ";" THEN iterate  /* comment encountered */
  722.        CALL parse_replacement_string line, linecount
  723.     END
  724.  
  725.     CALL STREAM ARG(1), "C", "CLOSE"    /* close the control file */
  726.  
  727.     RETURN
  728.  
  729.  
  730. /*
  731.    parse the replacement string in hand, extract replacement-strings
  732. */
  733. PARSE_REPLACEMENT_STRING: PROCEDURE EXPOSE global.
  734.     line      = ARG(1)
  735.     linecount = ARG(2)
  736.     delimiter = LEFT(line, 1)        /* get delimiter character */
  737.  
  738.     /* get last delimiter and check whether it truly is a delimiter */
  739.     PARSE VAR line (delimiter) needle_in (delimiter) needle_out (delimiter) +0 checkDelimiter +1 .
  740.  
  741.     IF checkDelimiter <> delimiter THEN
  742.     DO
  743.        IF global.eCall_type <> "FUNCTION" THEN
  744.        DO
  745.           CALL say2stderr "Delimiter error in control-string:"
  746.           CALL say2stderr "   line # ["linecount"]"
  747.           CALL say2stderr "   line   ["line"]"
  748.        END
  749.        CALL say_error  "   last delimiter (" || delimiter || ") is missing !", -5
  750.     END
  751.  
  752.     i = global.eTotalOfNeedles + 1
  753.  
  754.  
  755.     IF global.eDirection THEN        /* regular */
  756.     DO
  757.        global.i.eNeedle_In  = parse_needle(needle_in, linecount, "search-string")
  758.        global.i.eNeedle_Out = parse_needle(needle_out, linecount, "replace-string")
  759.        global.i.eDebug_in  = needle_in
  760.        global.i.eDebug_out = needle_out
  761.     END
  762.     ELSE                             /* inverse (switch strings) */
  763.     DO
  764.        global.i.eNeedle_In  = parse_needle(needle_out, linecount, "search-string")
  765.        global.i.eNeedle_Out = parse_needle(needle_in, linecount, "replace-string")
  766.        global.i.eDebug_in  = needle_out
  767.        global.i.eDebug_out = needle_in
  768.     END
  769.  
  770.     global.eTotalOfNeedles = i
  771.  
  772.     RETURN
  773.  
  774.  
  775. /*********************************************************************************
  776.     parse needles and replaces the special character-sequences ... with:
  777.        @C    ... CR
  778.        @L    ... LF
  779.        @T    ... TAB
  780.        @E    ... ESC
  781.        @Z    ... CTL-Z
  782.        @@    ... @ (escape for @)
  783.        @Xnn  ... with the hexadecimal "nn" value
  784.        @Hnn  ... with the hexadecimal "nn" value
  785.        @Dnnn ... with the decimal "nnn" value
  786.  
  787.     ARG(1)   ... contains string to be parsed
  788.     ARG(2)   ... contains linenumber in control-file
  789.     ARG(3)   ... contains hint which part is in error
  790. */
  791. PARSE_NEEDLE: PROCEDURE
  792.      needle = ARG(1)
  793.      error_msg_string = ARG(3) "[" || ARG(1) || "] in line [" || ARG(2) || "] in error !"
  794.  
  795.      new_needle = ""
  796.      DO FOREVER
  797.         PARSE VAR needle left_side "@" needle
  798.         new_needle = new_needle || left_side
  799.  
  800.         IF needle <> "" THEN
  801.         DO
  802.            a = TRANSLATE(LEFT(needle, 1))       /* translate into uppercase */
  803.            value = ""
  804.            needle = SUBSTR(needle, 2)           /* remove lead-in */
  805.            SELECT
  806.               WHEN a = "C" THEN value = "0D"X   /* CR */
  807.               WHEN a = "L" THEN value = "0A"X   /* LF */
  808.               WHEN a = "T" THEN value = "09"X   /* TAB */
  809.               WHEN a = "E" THEN value = "1B"X   /* ESCape */
  810.               WHEN a = "Z" THEN value = "1A"X   /* CTL-Z == EOF */
  811.               WHEN a = "@" THEN value = "@"     /* character "@" itself */
  812.               WHEN a = "H" | a = "X" THEN       /* get character from hex-value */
  813.                    DO
  814.                       /* check whether a valid hex-string, two hex-digits mandatory ! */
  815.                       i = 2
  816.                       tmp = TRANSLATE(SUBSTR(needle, 1, i))
  817.  
  818.                       IF VERIFY(tmp, "0123456789ABCDEF") <> 0 THEN      /* hexadecimal value ? */
  819.                          CALL say_error "looking for 2-hex-digits, not a hex-value:" error_msg_string, -10
  820.  
  821.                       value = X2C(tmp)
  822.                       needle = SUBSTR(needle, i + 1)
  823.                    END
  824.  
  825.               WHEN a = "D" THEN                 /* get character from decimal-value */
  826.                    DO
  827.                       /* check for numeric datatype, 3-digits a must ! */
  828.                       i = 3
  829.                       tmp = TRANSLATE(SUBSTR(needle, 1, i))
  830.  
  831.                       IF \DATATYPE(SUBSTR(needle, 1, i), "N") THEN      /* numeric value ? */
  832.                          CALL say_error "looking for 3-digits, not a decimal-value:" error_msg_string, -10
  833.  
  834.                       value = D2C(tmp)
  835.                       needle = SUBSTR(needle, i + 1)
  836.                    END
  837.               OTHERWISE
  838.                       CALL say_error "unknown control-value:" error_msg_string, -10
  839.  
  840.            END
  841.               new_needle = new_needle || value
  842.         END
  843.         ELSE
  844.            LEAVE
  845.  
  846.      END
  847.  
  848.      RETURN new_needle
  849.  
  850.  
  851. /*********************************************************************************
  852.   build low-control needles (00x --> 20x)
  853. */
  854. BUILD_LOW: PROCEDURE EXPOSE global.
  855.    type = ARG(1)        /* should it be decimal (1), hexadecimal (2),
  856.                                         control-sequence (3),
  857.                                         abbreviation of comm-characters (4) or
  858.                                         all representations (5)
  859.                          */
  860.  
  861.    /* default to 1 */
  862.    IF type <> "1" & type <> "2" & type <> "3" & type <> "4" & type <> "5" THEN
  863.       type = 1
  864.  
  865.    set.   = ""                  /* default to empty string */
  866.    /*      decimal pure : decimal : hexadecimal : control-chars : comm-chars */
  867.    set.0  = "32 d032 x20 ^  SP"    /* at first place, so no space-replacements take place at the end */
  868.    set.1  = " 1 d001 x01 ^A SOH"
  869.    set.2  = " 2 d002 x02 ^B STX"
  870.    set.3  = " 3 d003 x03 ^C ETX"
  871.    set.4  = " 4 d004 x04 ^D EOT"
  872.    set.5  = " 5 d005 x05 ^E ENQ"
  873.    set.6  = " 6 d006 x06 ^F ACK"
  874.    set.7  = " 7 d007 x07 ^G BEL"
  875.    set.8  = " 8 d008 x08 ^H BS"
  876.    set.9  = " 9 d009 x09 ^I HT"
  877.    set.10 = "10 d010 x0A ^J LF"
  878.    set.11 = "11 d011 x0B ^K VT"
  879.    set.12 = "12 d012 x0C ^L FF"
  880.    set.13 = "13 d013 x0D ^M CR"
  881.    set.14 = "14 d014 x0E ^N SO"
  882.    set.15 = "15 d015 x0F ^O SI"
  883.    set.16 = "16 d016 x10 ^P DLE"
  884.    set.17 = "17 d017 x11 ^Q DC1"
  885.    set.18 = "18 d018 x12 ^R DC2"
  886.    set.19 = "19 d019 x13 ^S DC3"
  887.    set.20 = "20 d020 x14 ^T DC4"
  888.    set.21 = "21 d021 x15 ^U NAK"
  889.    set.22 = "22 d022 x16 ^V SYN"
  890.    set.23 = "23 d023 x17 ^W ETB"
  891.    set.24 = "24 d024 x18 ^X CAN"
  892.    set.25 = "25 d025 x19 ^Y EM"
  893.    set.26 = "26 d026 x1A ^Z SUB"
  894.    set.27 = "27 d027 x1B ^[ ESC"
  895.    set.28 = "28 d028 x1C ^\ FS"
  896.    set.29 = "29 d029 x1D ^] GS"
  897.    set.30 = "30 d030 x1E ^^ RS"
  898.    set.31 = "31 d031 x1F ^_ US"
  899.    set.32 = " 0 d000 x00 ^@ NUL"
  900.  
  901.    DO i=0 TO 32
  902.       representation = ""
  903.  
  904.       set = SUBSTR(set.i, 4)            /* get encoding strings */
  905.       iChar = WORD(set.i, 1)            /* get decimal value of char in hand */
  906.  
  907.       SELECT
  908.          WHEN type = 5 THEN representation = "<@" || set || ">"       /* all meaningful infos */
  909.          OTHERWISE representation = "<@" || WORD(set, type) || ">"
  910.       END
  911.  
  912.       j = global.eTotalOfNeedles + 1
  913.  
  914.       IF global.eDirection THEN         /* from char ---> representation */
  915.       DO
  916.          global.j.eNeedle_In  = D2C(iChar)
  917.          global.j.eNeedle_Out = representation
  918.       END
  919.       ELSE
  920.       DO
  921.          global.j.eNeedle_In  = representation
  922.          global.j.eNeedle_Out = D2C(iChar)
  923.       END
  924.       global.eTotalOfNeedles = j
  925.    END
  926.  
  927.    RETURN
  928.  
  929.  
  930. /*********************************************************************************
  931.   build high-char needles (80x-FFx)
  932. */
  933. BUILD_HIGH: PROCEDURE EXPOSE global.
  934.    type = ARG(1)                /* should it be decimal (1) or hexadecimal (2) ? */
  935.    IF type <> "1" & type <> "2" THEN
  936.       type = 1
  937.  
  938.    DO i=128 TO 255
  939.       IF type = 1 THEN          /* decimal representation */
  940.          representation = "<@d" || RIGHT(i, 3, 0) || ">"
  941.       ELSE
  942.          representation = "<@x" || d2x(i) || ">"
  943.  
  944.       j = global.eTotalOfNeedles + 1
  945.  
  946.       IF global.eDirection THEN         /* from char ---> representation */
  947.       DO
  948.          global.j.eNeedle_In  = D2C(i)
  949.          global.j.eNeedle_Out = representation
  950.       END
  951.       ELSE
  952.       DO
  953.          global.j.eNeedle_In  = representation
  954.          global.j.eNeedle_Out = D2C(i)
  955.       END
  956.       global.eTotalOfNeedles = j
  957.    END
  958.  
  959.    RETURN
  960.  
  961.  
  962.  
  963.  
  964. /*********************************************************************************
  965.     display errormessage and exit
  966.     ARG(1) ... message
  967.     ARG(2) ... error-level
  968. */
  969. SAY_ERROR:
  970.     CALL BEEP 550, 250
  971.     CALL say2stderr ARG(1)
  972.     EXIT ARG(2)
  973.  
  974. /*********************************************************************************/
  975. HALT:
  976.    IF global.eFilein <> "" THEN
  977.       IF STREAM(global.eFilein, "STATUS") = "READY" THEN
  978.          CALL STREAM global.eFilein, "C", "CLOSE"          /* close input file */
  979.  
  980.    IF global.eFileout <> "" THEN
  981.       IF STREAM(global.eFileout, "STATUS") = "READY" THEN
  982.          CALL STREAM global.eFileout, "C", "CLOSE"         /* close output file */
  983.  
  984.    IF global.eControl <> "" THEN
  985.       IF STREAM(global.eControl, "STATUS") = "READY" THEN
  986.          CALL STREAM global.eFileout, "C", "CLOSE"         /* close output file */
  987.  
  988.    IF global.eTmp <> "" THEN                    /* output file was temporary */
  989.       IF STREAM(global.eTmp, "STATUS") = "READY" THEN
  990.           CALL SysFileDelete(global.eTmp)           /* delete temporary file */
  991.    ELSE
  992.       IF global.eFileout <> "" THEN
  993.          CALL SysFileDelete(global.eFileout)            /* delete output file */
  994.  
  995.    CALL say2stderr "CTL-C pressed, aborting..."
  996.    EXIT
  997.  
  998.  
  999. /*********************************************************************************/
  1000. ERROR:
  1001.    myrc = RC
  1002.    CALL say2stderr 'REXX error' myrc 'in line' SIGL':' ERRORTEXT(myrc)
  1003.    CALL say2stderr SUBSTR('     ',1,6-LENGTH(SIGL))(SIGL)' *-*   'SOURCELINE(SIGL)
  1004.    EXIT -999
  1005.  
  1006. /*
  1007.    calculate elapsed time
  1008. */
  1009. CALC_TIME: PROCEDURE
  1010.    start = ARG(1)
  1011.    end   = ARG(2)
  1012.    diff = DATERGF(end, "-S", start)
  1013.  
  1014.    IF diff >= 1 THEN day = diff % 1 "day(s) "
  1015.                 ELSE day = ""
  1016.  
  1017.    RETURN day || DATERGF(diff, "FR")
  1018.  
  1019.  
  1020.  
  1021. /*
  1022.    replacement for SAY, which uses stdout:
  1023.    =
  1024.    use stderr: rather than stdout:, so redirection works
  1025. */
  1026. SAY2STDERR: PROCEDURE
  1027.    CALL LINEOUT "STDERR:", ARG(1)
  1028.    RETURN
  1029.  
  1030. USAGE:
  1031.    CALL say2stderr "rxMulch.cmd: program to find/replace characters (strings) in file; allows"
  1032.    CALL say2stderr "             definition of hexadecimal or decimal value of single  "
  1033.    CALL say2stderr "             characters                                            "
  1034.    CALL say2stderr
  1035.    CALL say2stderr "usage:"
  1036.    CALL say2stderr
  1037.    CALL say2stderr " rxMulch [infile] [outfile] {[-]controlfile | /[-]switch}      "
  1038.    CALL say2stderr "   infile:      if missing from STDIN:                         "
  1039.    CALL say2stderr
  1040.    CALL say2stderr "   outfile:     if missing, RxMulch will replace infile;       "
  1041.    CALL say2stderr "                if no infile than output to STDOUT:            "
  1042.    CALL say2stderr
  1043.    CALL say2stderr "   controlfile OR switch MUST be present:"
  1044.    CALL say2stderr
  1045.    CALL say2stderr "   controlfile: change for every search-string/replace-string line the  "
  1046.    CALL say2stderr "                'search-string' into 'replace-string'; if more than one "
  1047.    CALL say2stderr "                search/replace-line is given, subsequent search/replaces"
  1048.    CALL say2stderr "                start from the beginning.                      "
  1049.    CALL say2stderr "                If the controlfile is preceded with a minus (-) the  "
  1050.    CALL say2stderr "                meaning of the 'search-string' and 'replace-string' is  "
  1051.    CALL say2stderr "                swapped.                                       "
  1052.    CALL say2stderr "                If a line is empty or if there is a semi-colon (;) at the very"
  1053.    CALL say2stderr "                first column, the line is treated as a comment."
  1054.    CALL say2stderr "                                                               "
  1055.    CALL say2stderr "                                                               "
  1056.    CALL say2stderr "   switch:      If the switch is preceded with a minus (-) the meaning of  "
  1057.    CALL say2stderr "                the 'search-string' and 'replace-string' is swapped. "
  1058.    CALL say2stderr
  1059.    CALL say2stderr "                'C'search-string/replace-string                "
  1060.    CALL say2stderr "                ... Change all occurrences of 'search-string' to  "
  1061.    CALL say2stderr "                    'replace-string'.                          "
  1062.    CALL say2stderr
  1063.    CALL say2stderr "                '[L[1|2|3|4|5]][H[1|2]]'                       "
  1064.    CALL say2stderr "                ...  change low-/high-characters to any of the following"
  1065.    CALL say2stderr "                     representations:                          "
  1066.    CALL say2stderr
  1067.    CALL say2stderr "                     L: change all low-char values c2d(0-32)   "
  1068.    CALL say2stderr "                         L .... defaults to L1                 "
  1069.    CALL say2stderr "                         L1 ... char(0-32) to decimal          "
  1070.    CALL say2stderr "                         L2 ... char(0-32) to hexadecimal      "
  1071.    CALL say2stderr "                         L3 ... char(0-32) to control-sequence "
  1072.    CALL say2stderr "                         L4 ... char(0-32) to abbreviated comm-characters  "
  1073.    CALL say2stderr "                         L5 ... char(0-32) to all representations above "
  1074.    CALL say2stderr
  1075.    CALL say2stderr "                     H: change all high-char values c2d(128-255)  "
  1076.    CALL say2stderr "                         H  ... defaults to H1                 "
  1077.    CALL say2stderr "                         H1 ... char(128-255) to decimal       "
  1078.    CALL say2stderr "                         H2 ... char(128-255) to hexadecimal   "
  1079.    CALL say2stderr
  1080.    CALL say2stderr "                     The appropriate search-string/replace-string pairs are"
  1081.    CALL say2stderr "                     generated automatically.                  "
  1082.    CALL say2stderr
  1083.    CALL say2stderr "                'F'search-string/replace-string                "
  1084.    CALL say2stderr "                ... count the number of occurrences of 'search-string'  "
  1085.    CALL say2stderr
  1086.    CALL say2stderr "   search-string/replace-string:                               "
  1087.    CALL say2stderr "               (delimiter)search-values(delimiter)replace-values(delimiter)"
  1088.    CALL say2stderr
  1089.    CALL say2stderr "         delimiter:                                            "
  1090.    CALL say2stderr "                very first character in search-string/replace-string "
  1091.    CALL say2stderr
  1092.    CALL say2stderr "         search-values                                         "
  1093.    CALL say2stderr "         replace-values:                                       "
  1094.    CALL say2stderr "                any ASCII-string intermixed with the following escape-codes"
  1095.    CALL say2stderr
  1096.    CALL say2stderr "                escape-codes:                                  "
  1097.    CALL say2stderr "                    @C    ... CR                               "
  1098.    CALL say2stderr "                    @L    ... LF                               "
  1099.    CALL say2stderr "                    @T    ... TAB                              "
  1100.    CALL say2stderr "                    @E    ... ESC                              "
  1101.    CALL say2stderr "                    @Z    ... CTL-Z                            "
  1102.    CALL say2stderr "                    @@    ... @ (escape for @)                 "
  1103.    CALL say2stderr "                    @Xnn                                       "
  1104.    CALL say2stderr "                    @Hnn  ... char with the hexadecimal of value 'nn'"
  1105.    CALL say2stderr "                    @Dnnn ... char with the decimal value 'nnn'"
  1106.    CALL say2stderr
  1107.    CALL say2stderr "RxMulch can be called as a function from another REXX-program, e.g."
  1108.    CALL say2stderr
  1109.    CALL say2stderr '   some_variable = RxMulch(REXXstring, "[/][-]switch")'
  1110.    CALL say2stderr
  1111.    CALL say2stderr "examples:                                                          "
  1112.    CALL say2stderr
  1113.    CALL say2stderr "    rxMulch infile outfile controlfile                            "
  1114.    CALL say2stderr "        ... change 'infile' according to 'controlfile', place results into "
  1115.    CALL say2stderr "            'outfile'"
  1116.    CALL say2stderr
  1117.    CALL say2stderr "    rxMulch infile controlfile                                    "
  1118.    CALL say2stderr "        ... change 'infile' according to 'controlfile', place results into "
  1119.    CALL say2stderr "            'infile' (i.e. replace 'infile' itself)               "
  1120.    CALL say2stderr
  1121.    CALL say2stderr "    rxMulch < some_in_file > some_out_file controlfile            "
  1122.    CALL say2stderr "        ... change 'some_in_file' according to 'controlfile', place results"
  1123.    CALL say2stderr "            into 'some_out_file'; 'some_in_file' and 'some_out_file' are"
  1124.    CALL say2stderr "            redirected ('<' and '>'). rxMulch therefore can be used in pipes"
  1125.    CALL say2stderr "            too.                                                  "
  1126.    CALL say2stderr
  1127.    CALL say2stderr "    rxMulch infile outfile1 /C.Microsoft Excel.Lotus 1-2-3.       "
  1128.    CALL say2stderr "        ... change 'infile' according to commandline switch (replace all"
  1129.    CALL say2stderr "            occurrences of 'Microsoft Excel' with 'Lotus 1-2-3'), place "
  1130.    CALL say2stderr "            results into 'outfile1'                               "
  1131.    CALL say2stderr
  1132.    CALL say2stderr "    rxMulch outfile1 outfile2 /-C.Microsoft Excel.Lotus 1-2-3.    "
  1133.    CALL say2stderr "        ... change 'outfile1' according to commandline switch (replace all "
  1134.    CALL say2stderr "            occurrences of 'Lotus 1-2-3' with 'Microsoft Excel', note the  "
  1135.    CALL say2stderr "            minus (-) right before the switch-character), place results into  "
  1136.    CALL say2stderr "            'outfile2'; could be also expressed as:               "
  1137.    CALL say2stderr "                  rxMulch outfile1 outfile2 /C.Lotus 1-2-3.Microsoft Excel."
  1138.    CALL say2stderr
  1139.    CALL say2stderr "    rxMulch infile /C.;.@c@l.                                     "
  1140.    CALL say2stderr "        ... change 'infile' according to commandline switch (replace "
  1141.    CALL say2stderr "            semicolons (;) with a carriage-return/linefeed), replace "
  1142.    CALL say2stderr "            'infile'; could be also expressed as:                 "
  1143.    CALL say2stderr "                  rxMulch infile /C.;.@xd@xa.                     "
  1144.    CALL say2stderr "                  rxMulch infile /C.;.@x0d@x0a.                   "
  1145.    CALL say2stderr "                  rxMulch infile /C.;.@d13@d10.                   "
  1146.    CALL say2stderr
  1147.    CALL say2stderr "    rxMulch infile /C.@c@l@c@l.@c@l.                              "
  1148.    CALL say2stderr "        ... change 'infile' according to commandline switch (replace "
  1149.    CALL say2stderr "            consecutive carriage-return/linefeeds with one        "
  1150.    CALL say2stderr "            carriage-return/linefeed, i.e. remove one empty line), replace "
  1151.    CALL say2stderr "            'infile'; could be also expressed as:                 "
  1152.    CALL say2stderr "                  rxMulch infile /C.@xd@xa@xd@xa.@xd@xa.          "
  1153.    CALL say2stderr "                  rxMulch infile /C!@d13@d10@d13@d10!@d13@d10!    "
  1154.    CALL say2stderr "                  rxMulch infile /C/@d13@d10@d13@d10/@c@l/        "
  1155.    CALL say2stderr
  1156.    CALL say2stderr "    rxMulch infile /-C.@c@l@c@l.@c@l.                             "
  1157.    CALL say2stderr "        ... change 'infile' according to commandline switch (replace a  "
  1158.    CALL say2stderr "            carriage-return/linefeed with two consecutive         "
  1159.    CALL say2stderr "            carriage-return/linefeeds, i.e. insert an empty line after each"
  1160.    CALL say2stderr "            line), replace 'infile'; could be also expressed as:  "
  1161.    CALL say2stderr "                  rxMulch infile /C,@c@l,@c@l@c@l,                "
  1162.    CALL say2stderr "                  rxMulch infile /C=@x0d@x0a=@x0d@x0a@x0dx@0a=    "
  1163.    CALL say2stderr "                  rxMulch infile /C=@d13@d10=@d13@d10@x0dx@0a=    "
  1164.    CALL say2stderr
  1165.    CALL say2stderr "    rxMulch infile /C=@x00@x00@x00@x00=@x01@x01@x01@x01=          "
  1166.    CALL say2stderr "        ... change 'infile' according to commandline switch (replace all"
  1167.    CALL say2stderr "            hexadecimal strings of 0x00000000 with 0x01010101), replace "
  1168.    CALL say2stderr "            'infile'; could be also expressed as:                 "
  1169.    CALL say2stderr "                  rxMulch infile /C=@x0@x0@x0@x0=@x1@x1@x1@x1=    "
  1170.    CALL say2stderr "                  rxMulch infile /C/@d0@d0@d0@d0/@d1@d1@d1@d1/    "
  1171.    CALL say2stderr
  1172.    CALL say2stderr "    rxMulch infile /F.OS/2.                                       "
  1173.    CALL say2stderr "        ... count occurrences of string 'OS/2' in 'infile'          "
  1174.    CALL say2stderr
  1175.    CALL say2stderr "    rxMulch infile /F.@c@l@c@l.                                   "
  1176.    CALL say2stderr "        ... count number of lines in 'infile', which are immediately"
  1177.    CALL say2stderr "            followed by a blank line  "
  1178.    CALL say2stderr
  1179.    CALL say2stderr
  1180.    CALL say2stderr "examples for calling RxMulch from a REXX-procedure:"
  1181.    CALL say2stderr
  1182.    CALL say2stderr "    string1 = 'this is nice'"
  1183.    CALL say2stderr "    string2 = RxMulch(string1, '/c.this.that.')  /* change 'this' to 'that' */"
  1184.    CALL say2stderr "        ... string2 = 'that is nice'"
  1185.    CALL say2stderr "    string2 = RxMulch(string2, '/-c.this.that.') /* change 'that' to 'this' */"
  1186.    CALL say2stderr "        ... string2 = 'this is nice'"
  1187.    CALL say2stderr "    occurrences = RxMulch(string2, 'f.this.')    /* count 'this' in string2 */"
  1188.    CALL say2stderr "        ... occurrences = 1"
  1189.  
  1190.    EXIT
  1191.  
  1192.  
  1193.  
  1194.