I've converted the :asc and :chr routines into @asc and @chr macros that are extremely fast (at least by batch standards). See Macros with parameters appended and http://www.dostips.com/forum/viewtopic.php?f=3&t=1827 for more information about batch macros.
There are available techniques that use external executables, but none exist that are universally native to Windows, and they are slower than the macros. For example, see http://www.dostips.com/forum/viewtopic.php?f=3&t=3857. Hybrid batch/powershell or batch/VBScript could be used, but again, this is slower than the macros. Of course a fast solution is available with pure powershell or pure VBScript. But the batch macros are great for anyone that wants to work with batch.
The macro definition script is designed to be used as a TSR program (Terminate and Stay Resident). It defines the macros and necessary character maps as persistent variables. Any script can use the macros, once they are loaded.
There is a slightly faster alternate algorithm that requires definition of one persistent variable for every character. I opted for this slightly slower algorithm that minimizes the number of required variables.
Instead of embedding problematic characters within the script, I use temporary VBScript to dynamically generate the character maps used by the macros. The script now posts perfectly on forums like this. The VBScript is only used during the definition of the macros. Once defined, all further processing is pure native batch.
The macros work on all modern Windows versions, including XP.
Here is the charMacros.bat script that loads the macros
Code: Select all
@echo off
:: charMacros.bat
::
:: This script installs macros that can be used to interconvert between
:: numeric extended ASCII codes and character values.
::
:: The script defines the following variables:
::
:: @asc - A macro used to convert a character into the numeric ASCII code
::
:: @chr - A macro used to convert a numeric ASCII code into a character
::
:: #LF - A variable containing a line feed character
::
:: #CR - A variable containing a carriage return character
::
:: #charMap - A variable used by the @asc macro
::
:: #asciiMap - A variable used by the @chr macro
::
:: \n - used for specifiying the end of line in a macro definition
::
if "!" == "" >&2 echo ERROR: Delayed expansion must be disabled when loading %~nx0&exit /b 1
:: Define a Carriage Return string, only useable as !#CR!
for /f %%a in ('copy /Z "%~dpf0" nul') do set "#CR=%%a"
:: Define a Line Feed (newline) string (normally only used as !#LF!)
set #LF=^
:: Above 2 blank lines are required - do not remove
:: Define a newline with line continuation
set ^"\n=^^^%#LF%%#LF%^%#LF%%#LF%^^"
:: Define character maps used to interconvert between extended ASCII codes
:: and characters.
>"%temp%\charMacros.vbs" (
echo for i=1 to 255
echo if i=10 then WScript.Stdout.Write " " else WScript.Stdout.Write chr(i^)
echo next
echo WScript.Stdout.Write chr(10^)+"#"
echo for i=1 to 255
echo if i^<^>10 then WScript.Stdout.Write chr(i^)+chr(i^)+right("0"+hex(i^),2^)+"#"
echo next
)
set "#charMap="
for /f "delims=" %%A in ('cscript /nologo "%temp%\charMacros.vbs"') do (
if defined #charMap (set "#asciiMap=%%A") else set "#charMap= %%A"
)
del "%temp%\charMacros.vbs"
:: %@asc% StrVar Position [RtnVar]
::
:: Converts a character into the extended ASCII code value.
:: The result is stored in RtnVar, or ECHOed if RtnVar is not specified.
:: The macro is safe to "call" regardless whether delayed expansion is
:: enabled or not.
::
:: StrVar = The name of a variable that contains the character
:: to be converted
::
:: Position = The position of the character within the string
:: to be converted. 0 based.
::
:: RtnVar = The name of the variable used to store the result.
::
set @asc=for %%# in (1 2) do if %%#==2 (%\n%
for /f "eol= tokens=1-3 delims=, " %%a in ("!#args!") do (endlocal%\n%
setlocal enableDelayedExpansion%\n%
if defined %%~a (%\n%
set "str=!%%~a!"%\n%
set /a "n=%%~b" 2^>nul%\n%
for %%N in (!n!) do set "chr=!str:~%%N,1!"%\n%
if defined chr (%\n%
set "rtn="%\n%
if "!chr!"=="=" set rtn=61%\n%
if "!chr!"=="^!" set rtn=33%\n%
if "!chr!"=="!#lf!" set rtn=10%\n%
if not defined rtn for /f delims^^=^^ eol^^= %%c in ("!chr!!#CR!") do (%\n%
set "test=!#asciiMap:*#%%c=!"%\n%
if not "%%c"=="!test:~0,1!" set "test=!test:*#%%c=!"%\n%
set /a "rtn=0x!test:~1,2!"%\n%
)%\n%
)%\n%
for %%v in (!rtn!) do endlocal^&if "%%~c" neq "" (set "%%~c=%%v") else echo(%%v%\n%
) else endlocal%\n%
set "#args=")) else setlocal enableDelayedExpansion^&set #args=,
:: %@chr% AsciiCode [RtnVar]
::
:: Converts an extended ASCII code into the corresponding character.
:: The result is stored in RtnVar, or ECHOed if RtnVar is not specified.
:: The macro is safe to "call" regardless whether delayed expansion is
:: enabled or not.
::
:: AsciiCode - Any value from 1 to 255. The value can be expressed as any
:: numeric expression supported by SET /A.
::
:: RtnVar - The name of the variable used to store the result
::
set @chr=for %%# in (1 2) do if %%#==2 (%\n%
for /f "eol= tokens=1,2 delims=, " %%a in ("!#args!") do (endlocal%\n%
setlocal%\n%
set "NotDelayed=!"%\n%
setlocal EnableDelayedExpansion%\n%
set "n=0"%\n%
set /a "n=%%~a"%\n%
if !n! gtr 255 set "n=0"%\n%
if !n! gtr 0 (%\n%
if !n! equ 10 (%\n%
for %%C in ("!#LF!") do (%\n%
endlocal^&endlocal%\n%
if "%%~b" neq "" (set "%%~b=%%~C") else echo(%%~C%\n%
)%\n%
) else (%\n%
for %%N in (!n!) do set "c=!#charMap:~%%N,1!"%\n%
if "!c!" equ "^!" if not defined NotDelayed set "c=^^^!"%\n%
for /f delims^^=^^ eol^^= %%C in ("!c!!#CR!") do (%\n%
endlocal^&endlocal%\n%
if "%%~b" neq "" (set "%%~b=%%C") else echo(%%C%\n%
)%\n%
)%\n%
) else endlocal^&endlocal%\n%
set "#args=")) else setlocal enableDelayedExpansion^&set #args=,
exit /b 0
And here is a small test script that tests the functionality, and shows how easy it is to use the macros. It loops through codes 1 - 255 and converts the code into a character, and then back to a code again. It prints out each decimal code and resultant character, and prints an error if the starting code does not match the ending code. The script runs the test first with delayed expansion disabled, then again with delayed expansion enabled. Both work perfectly without errors
Code: Select all
@echo off
:: Load the macros. This only needs to be done once per CMD.EXE session
if not defined @chr call charMacros
echo Test "calls" with delayed expansion DISABLED:
setlocal disableDelayedExpansion
for /l %%N in (1 1 255) do (
%@chr% %%N c
%@asc% c 0 n
setlocal enableDelayedExpansion
echo %%N:[!c!]
if !n! neq %%N echo Error at %%N
endlocal
)
echo(
echo Test "calls" with delayed expansion ENABLED
setlocal enableDelayedExpansion
for /l %%N in (1 1 255) do (
%@chr% %%N c
%@asc% c 0 n
echo %%N:[!c!]
if !n! neq %%N echo Error at %%N
)
Dave Benham