Text Flow (like word wrapping to a margin) of free form text lines

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
thefeduke
Posts: 211
Joined: 05 Apr 2015 13:06
Location: MA South Shore, USA

Text Flow (like word wrapping to a margin) of free form text lines

#1 Post by thefeduke » 01 Apr 2016 14:29

I have an application control file in text format from which I use FINDSTR head and tail routines to extract freeform text comments from this:

Code: Select all

[Mouse]
AutoScrl=3
[Comment]
This INI file is customized for John's 'Bride of Frankenstein' PC system
featuring best visibility on the Flatron monitor using 1680 x 1050 resolution.
The File manager is colorized neutrally to remind use of system install
[Keyword]
This comment should not appear once the Head and Tail routines work
The output of the three comment lines flowed to column 50 would look like this:

Code: Select all

This INI file is customized for John's 'Bride of
Frankenstein' PC system featuring best visibility
on the Flatron monitor using 1680 x 1050
resolution.  The File manager is colorized
neutrally to remind use of system install
Does anyone know if a similar batch function has already been invented?

If not, I can see working through the words on these three lines and applying width tracking logic similar to one of my previous posts http://www.dostips.com/forum/viewtopic.php?p=44258#p44258 to a right margin.

Having done that, some doubling of interior white space could make the output right justified as well like:

Code: Select all

This INI file is customized for John's 'Bride  of
Frankenstein' PC system featuring best visibility
on  the  Flatron  monitor  using  1680   x   1050
resolution.   The  File  manager   is   colorized
neutrally to remind use of system install
Is there anything out there that could do parts of this? Or a different approach?
John A.

ShadowThief
Expert
Posts: 1160
Joined: 06 Sep 2013 21:28
Location: Virginia, United States

Re: Text Flow (like word wrapping to a margin) of free form text lines

#2 Post by ShadowThief » 01 Apr 2016 14:49

I'm almost positive I have something for this on my laptop at work. I'll check on Monday.


thefeduke
Posts: 211
Joined: 05 Apr 2015 13:06
Location: MA South Shore, USA

Re: Text Flow (like word wrapping to a margin) of free form text lines

#4 Post by thefeduke » 01 Apr 2016 19:13

Thanks for that pointer, Squashman. I think that I can work with that for a part of it.
John A.

ShadowThief
Expert
Posts: 1160
Joined: 06 Sep 2013 21:28
Location: Virginia, United States

Re: Text Flow (like word wrapping to a margin) of free form text lines

#5 Post by ShadowThief » 04 Apr 2016 01:34

Found it. No idea if it can handle ampersands and other traditional poison characters, but I wrote it three years ago so I doubt it.

Code: Select all

@echo off
setlocal enabledelayedexpansion

:: ******************************************************************
:: * Padded Formatted Text                                          *
:: *                                                                *
:: * USAGE: pftext.bat [text] [width] [pad character]               *
:: *                                                                *
:: * FUNCTION                                                       *
:: * - Formats a string to fit within a column of specified width   *
:: * - Pads and remaining space at the end of each line with spaces *
:: *                                                                *
:: * PARAMETERS                                                     *
:: * - %1 specifies the text to format and pad                      *
:: * - %2 specifies the width of the column                         *
:: * - %3 specifies the character to use to pad extra spaces        *
:: *   - Quotes and carets can not be padding characters            *
:: ******************************************************************

cls
set "unformattedText=The quick brown fox jumps over the lazy dogs."
set "padChar= "
set width=35
set counter=0

:: Strip the quotes from any arguments
if not "%~1"=="" set unformattedText=%~1

if not "%~2"=="" set width=%2

:: Use only the first character from the argument for the pad character
set pChar=%~3
if not [%3]==[] set padChar=%pChar:~0,1%

:: Count the number of words in the sentence
:wordCounter
for /f "tokens=1,* delims= " %%A in ("%unformattedText%") do (
   if not "%%A"=="" (
      set words[!counter!]=%%A
      set /a counter=!counter!+1
      set unformattedText=%%B
      goto wordCounter
   ) else (
      goto loopBreak
   )
)

:: Exit loop via goto. Apologize profusely.
:loopBreak

:: Determine the size of each word
set /a counter=!counter!-1
for /l %%A in (0,1,!counter!) do (
   echo !words[%%A]!>tmp.txt
   for %%J in (tmp.txt) do (
      set /a len[%%A]=%%~zJ
      set /a len[%%A]-=2
      del tmp.txt

      REM width must be a minimum of the longest word
      if !len[%%A]! gtr %width% set /a width=!len[%%A]!+1
   )
)

:: Place the first word
set newString=!words[0]!
set stringLength=!len[0]!
set lineCounter=0

:: If there's room for a space and the next word on the line, place them
:: If the next word cannot be placed, do not add a space
:: Leading words on new lines are not preceded by spaces
for /l %%A in (1,1,!counter!) do (
   set /a possLength=!stringLength!+!len[%%A]!+1

   REM if the next word fits on the line
   if !possLength! leq %width% (
      set newString=!newString! !words[%%A]!
      set /a stringLength+=!len[%%A]!+1
   ) else (
      set lines[!lineCounter!]=!newString!
      set /a lineCounter+=1
      set newString=!words[%%A]!
      set /a stringLength=!len[%%A]!
   )

   REM if it's the last word
   if %%A equ !counter! (
      set lines[!lineCounter!]=!newString!
   )
)

:: Determine the size of each line
for /l %%A in (0,1,!lineCounter!) do (
   echo !lines[%%A]!>tmp.txt
   for %%J in (tmp.txt) do (
      set /a linelen[%%A]=%%~zJ
      set /a linelen[%%A]-=2
      del tmp.txt
   )
)

:: Pad the lines
for /l %%A in (0,1,!lineCounter!) do (
   for /l %%G in (!linelen[%%A]!,1,%width%) do (
      if %%G lss %width% (
         set lines[%%A]=!lines[%%A]!%padChar%
      )
   )
)

:: See if it worked
for /l %%A in (0,1,!lineCounter!) do (
   echo !lines[%%A]! (!linelen[%%A]!^)
)

Sponge Belly
Posts: 216
Joined: 01 Oct 2012 13:32
Location: Ireland
Contact:

Re: Text Flow (like word wrapping to a margin) of free form text lines

#6 Post by Sponge Belly » 04 Apr 2016 13:17

Hi John A!

Have a look at PrepMessage.cmd posted to alt.msdos.batch.nt by Frank P Westlake in January, 2013.

HTH! :)

- SB

thefeduke
Posts: 211
Joined: 05 Apr 2015 13:06
Location: MA South Shore, USA

Re: Text Flow (like word wrapping to a margin) of free form text lines

#7 Post by thefeduke » 05 Apr 2016 01:18

You people are just great! Four workable sources.

SB - That lead is so sophisticated that I yearn to learn. Another promise to myself. Thank you, so-o-o interesting.

I put a fair bit of effort into Squashman's pointers to two scripts, both of which worked almost out of the box. Antonio's code was elegant, as usual, and flowed the whole input file. The code by MaGoo also worked but flowed only at the record level and not the whole file at a time. I chose not to convert the file to a single string to use it and added a very clumsy back end to Antonio's code to justify the output to both margins, producing output as described in my first post. They both compressed internal white-space and Antonio's technique added a space before the first word which I altered with a small change. This code seems to do what I asked:

Code: Select all

@echo off
::  Author - John Andrejsons - accepting PM to user: thefeduke at DOStips forum
::  acknowledges code and influences from DOStips.com and its forum
:: Squashman - Post subject: Re: Text Flow (like word wrapping to a margin) of free form text lines
:: http://www.dostips.com/forum/viewtopic.php?p=46017#p46017
:: Aacini - In Batch can I make a line of displayed text not split words appart when it goes to a new line?
:: http://stackoverflow.com/a/20944604/1417694
setlocal EnableDelayedExpansion

rem Force the wrap width
    Set /A "width=50"
    If .%2 NEQ . Set /A "width=%2"

rem Read the file given by first param and show its contents with no word split

rem Force the Input file
    set "FileIn=%Temp%\SPFdocINI_Head.txt"
    If .%1 NEQ . Set "FileIn=%1"

    set "output="
rem For each line in input file
    for /F "delims=" %%a in (%FileIn%) do (
       rem For each word in input line
       for %%b in (%%a) do (
          rem Add the new word
          If .!newOutput! EQU . (
              set "newOutput=%%b"
          ) Else (
              set "newOutput=!output! %%b"
          )
          rem If new word don't exceed window width
          if "!newOutput:~%width%,1!" equ "" (
             rem Keep it
             set "output=!newOutput!"
          ) else (
             rem Show the output before the new word
             Call :ReSpace %width% "!output!"
             rem and store the new word
             set "output=%%b"
          )
       )
    )
rem Show the last output, if any
if defined output Echo.!output!

    Exit /B

:Respace
    SET "Width=%~1"
    SET "Wrapped=%~2"
    Call :StrLen Wrapped    columns
    SET /a Short=%Width% - %columns%
    SET /a lastsp=%columns%
    If "%Short%" GTR "0" (
        For /L %%S in (%Short%,-1,1) Do (
            SET /a lastsp-=1
            CALL :StretchLine !lastsp! "!Wrapped!" lastnew Warped
            Set "Wrapped=!Warped!"
            Set "lastsp=!lastnew!"
        )
    )
    Echo.%Wrapped%
    GOTO :eof

:StretchLine
setlocal EnableDelayedExpansion
    SET lastsp=%1
    SET savesp=%1
:insert
    SET /a lastsp-=1
    Call SET "Wrapped=%~2"
    CALL SET line=%%Wrapped:~%lastsp%,1%%
    IF "%line%" EQU " " (
        SET "line=!Wrapped:~0,%lastsp%! !Wrapped:~%lastsp%!"
    ) Else (
        GOTO insert
    )
( ENDLOCAL & REM RETURN VALUES
    IF "%~3" NEQ "" SET %~3=%lastsp%
    IF "%~4" NEQ "" SET %~4=%line%
)

    Exit /B

:strLen string len -- returns the length of a string
@Echo Off
::                 -- string [in]  - variable name containing the string being measured for length
::                 -- len    [out] - variable to be used to return the string length
:: Many thanks to 'sowgtsoi', but also 'jeb' and 'amel27' dostips forum users helped making this short and efficient
:$created 20081122 :$changed 20101116 :$categories StringOperation
:$source http://www.dostips.com
(   SETLOCAL ENABLEDELAYEDEXPANSION
    set "str=A!%~1!"&rem keep the A up front to ensure we get the length and not the upper bound
                     rem it also avoids trouble in case of empty string
    set "len=0"
    for /L %%A in (12,-1,0) do (
        set /a "len|=1<<%%A"
        for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"
    )
)
( ENDLOCAL & REM RETURN VALUES
    IF "%~2" NEQ "" SET /a %~2=%len%
)
EXIT /b

The above nearing completion, ShadowThief offered some code, that I found so appealing in approach, comments and coding style that I made the following changes to my copy from his post:

Code: Select all

:: Strip the quotes from any arguments
if not "%~1"=="" (
   set unformattedText=%~1
:: Accept the first argument as a file if supplied
   If Exist "%~1" (
      set unformattedFile=%~1
      set "unformattedText= "
      For /F "usebackq delims=" %%A in ("!unformattedFile!") do (
         Set "unformattedText=!unformattedText! %%A"
      )
   )
)
This was the creation of a long string from an input file that I did not create for the 'MaGoo' code. This produced satisfactory left-justified and right-padded output, so I am now enticed to capitalize on those arrays of words to add the white-space within the lines instead of at the end in a less clumsy method. My thanks.

Just my luck, ShadowThief, that I stumbled upon some poison WORDS in my input. Head-scratching revealed that the spacing counts were thrown awry by the words 'off' or 'on' in the text. You should correct your code when determining word size:

Code: Select all

:: Using a space with Echo makes on and off become poison words in the text
   echo.!words[%%A]!>tmp.txt
because 'echo on' becomes a command and nothing goes to tmp.txt .

Thanks, all, for pointing me to the good stuff.

John A.

thefeduke
Posts: 211
Joined: 05 Apr 2015 13:06
Location: MA South Shore, USA

Re: Text Flow (like word wrapping to a margin) of free form text lines

#8 Post by thefeduke » 06 Apr 2016 12:02

The array structure in ShadowThief's code from above did indeed lend itself to inserting blanks within the text to minimize the ragged right margin. After the array of words and lengths is built, a first pass through the data places words on lines and adjusts an appropriate number of words, by a quite simple change, to advance text to the right margin. The original FOR construct is then used again with no changes to arrange the adjusted array. The fill logic is optional and quite rudimentary, becoming less effective as line length is reduced, especially when using larger words. The accumulated changes are in the code below.

Thanks, again, ShadowThief. I think that if the new options are not used the script runs as original.
John A.

Code: Select all

@echo off
:: ShadowThief - Post subject: Re: Text Flow (like word wrapping to a margin) of free form text lines
:: http://www.dostips.com/forum/viewtopic.php?p=46051#p46051
:: altered by John Andrejsons - accepting PM to user: thefeduke at DOStips forum
setlocal enabledelayedexpansion

:: *******************************************************************
:: * Padded Formatted Text                                           *
:: *                                                                 *
:: * USAGE: pftext.bat [text] [width] [pad character]                *
:: *                                                                 *
:: * FUNCTION                                                        *
:: * - Formats a string or file to fit within a specified line width *
:: * - Pads any remaining space at the end of each line with spaces  *
:: *                                                                 *
:: * PARAMETERS                                                      *
:: * - %1 specifies the text to format and pad                       *
:: * - %2 specifies the width of the column                          *
:: *      (add char 'R' to pad internally instead of ragged right)   *
:: * - %3 specifies the character to use to pad extra spaces         *
:: *   - Quotes and carets can not be padding characters             *
:: *******************************************************************

cls
set "unformattedText=The quick brown fox jumps over the lazy dogs."
set "padChar= "
set width=35
set counter=0

:: Strip the quotes from any arguments
if not "%~1"=="" (
   set unformattedText=%~1
:: Accept the first argument as a file if supplied
   If Exist "%~1" (
      set unformattedFile=%~1
      set "unformattedText= "
      For /F "usebackq delims=" %%A in ("!unformattedFile!") do (
         Set "unformattedText=!unformattedText! %%A"
      )
   )
)

if not "%~2"=="" (
   set width=%2
   if not "%~2"=="!width:r=!" (
      set "width=!width:r=!"
      set "Just=R"
   )
)

:: Use only the first character from the argument for the pad character
set pChar=%~3
if not [%3]==[] set padChar=%pChar:~0,1%

:: Count the number of words in the sentence
:wordCounter
for /f "tokens=1,* delims= " %%A in ("%unformattedText%") do (
   if not "%%A"=="" (
      set words[!counter!]=%%A
      set /a counter=!counter!+1
      set unformattedText=%%B
      goto wordCounter
   ) else (
      goto loopBreak
   )
)

:: Exit loop via goto. Apologize profusely.
:loopBreak

:: Determine the size of each word
set /a counter=!counter!-1
for /l %%A in (0,1,!counter!) do (
:: Using a space with Echo makes on and off become poison words in the text
   echo.!words[%%A]!>tmp.txt
   for %%J in (tmp.txt) do (
      set /a len[%%A]=%%~zJ
      set /a len[%%A]-=2
      del tmp.txt

      REM width must be a minimum of the longest word
      if !len[%%A]! gtr %width% set /a width=!len[%%A]!
   )
)

:: Place the first word
set newString=!words[0]!
set stringLength=!len[0]!
set lineCounter=0

:: If there's room for a space and the next word on the line, place them
:: If the next word cannot be placed, do not add a space
:: Leading words on new lines are not preceded by spaces
:: First pass adds white-space to individual words padding to right margin
If /I "%Just%" NEQ "R" GoTo :Pass2
for /l %%A in (1,1,!counter!) do (
   set /a possLength=!stringLength!+!len[%%A]!+1

   REM if the next word fits on the line
   if !possLength! leq %width% (
      set newString=!newString! !words[%%A]!
      set /a stringLength+=!len[%%A]!+1
   ) else (
      Set    LastSpace=%%A
      Set /A LastSpace-=1
      Set /A FirstSpace=%width%-!StringLength!
      Set /A FirstSpace=!LastSpace!-!FirstSpace!+1
      IF !FirstSpace! LEQ !SavePos! Set /A FirstSpace=!SavePos!+1
      For /l %%N in (!LastSpace!,-1,!FirstSpace!) do (
         SET "Words[%%N]= !words[%%N]!"
         SET /A "len[%%N]+=1"
      )
      set lines[!lineCounter!]=!newString!
      set /a lineCounter+=1
      set newString=!words[%%A]!
      set /a stringLength=!len[%%A]!
      set "SavePos=%%A"
   )
)

:: Place the first word
set newString=!words[0]!
set stringLength=!len[0]!
set lineCounter=0

:: Second pass rearranges words already padded with white-space for right margin
:Pass2
for /l %%A in (1,1,!counter!) do (
   set /a possLength=!stringLength!+!len[%%A]!+1

   REM if the next word fits on the line
   if !possLength! leq %width% (
      set newString=!newString! !words[%%A]!
      set /a stringLength+=!len[%%A]!+1
   ) else (
      set lines[!lineCounter!]=!newString!
      set /a lineCounter+=1
      set newString=!words[%%A]!
      set /a stringLength=!len[%%A]!
   )

   REM if it's the last word
   if %%A equ !counter! (
      set lines[!lineCounter!]=!newString!
   )
)

:: Determine the size of each line
for /l %%A in (0,1,!lineCounter!) do (
   echo !lines[%%A]!>tmp.txt
   for %%J in (tmp.txt) do (
      set /a linelen[%%A]=%%~zJ
      set /a linelen[%%A]-=2
      del tmp.txt
   )
)

:: Pad the lines
for /l %%A in (0,1,!lineCounter!) do (
   for /l %%G in (!linelen[%%A]!,1,%width%) do (
      if %%G lss %width% (
         set lines[%%A]=!lines[%%A]!%padChar%
      )
   )
)

:: See if it worked
:: for /l %%A in (0,1,!lineCounter!) do (
::    echo.[!lines[%%A]!] (!linelen[%%A]!^)
:: )
:: Echo(
:: output just the data
for /l %%A in (0,1,!lineCounter!) do (
   echo(!lines[%%A]!
)

Post Reply