I wrote a module for word-wrapping. First I thougt about using separate. But I guess this one is smarter. Any ideas for improvement?
Usage:
define data local
1 #a10(a10/1:5)
end-define
callnat 'wordwrpn' 'xxx xxxxxxxxx xxxx xxxx xxx' #a10(*)
display #a10(*)
end
* Module: wordwrpn
*
define data parameter
1 #input-parameter
2 #string (A) DYNAMIC BY VALUE
1 #output-parameter
2 #array (A/1:V) DYNAMIC BY VALUE RESULT /* no x-array possible here
local
*
1 #startlength (I4)
1 #idx-array (I4)
1 #i4 (I4)
*
end-define
*
if *OCC(#array) = 0 or *LENGTH(#string) = 0
escape module
end-if
*
#startlength := *LENGTH(#array(1)) + 1
*
repeat1.
repeat while #string ne " "
if *LENGTH(#string) < #startlength
#i4:= *LENGTH(#string)
else
for1.
for #i4 = #startlength to 1 step -1
if substr(#string,#i4,1) = " "
escape bottom(for1.)
end-if
end-for
if #i4 = 0 /* no space found
#i4:= #startlength - 1
end-if
end-if
*
* if output-array is full --> append "..." on last occurance
*
if #idx-array = *UBOUND(#array)
if #startlength <= 4
#array(#idx-array) := "..."
else
#i4 := #startlength - 4
resize dynamic #array(#idx-array) to #i4
compress #array(#idx-array) "..." into #array(#idx-array)
leaving no
end-if
escape bottom(repeat1.)
end-if
*
add 1 to #idx-array
move substr(#string,1,#i4) to #array(#idx-array)
move " " to substr(#string,1,#i4)
* move left #string to #string
#string := *trim(#string)
end-repeat
*
end
I donât want to separate words, I want to do real word wrappring. For example think of Winword. It does an automatical newline if your text reaches the end of the line.
If thereâs a good solution with separate, please let me knowâŚ
Just another thought. If you are on a PC, why not use Word? Write your data to a workfile; invoke Word; let Word not only âorganizeâ your text, but put hyphens where appropriate; save the file; then return to Natural and access the workfile using an array of the form (for example) A79/1:* (xtensible array).
have not actually tried this yet. will let you know if it works.
SET CONTROL âQâ
PRINT #ARRAY (*)
SET CONTROL âCCCL01Aâ
followed by L02, L03, etc.
Then move *COM to an array; or use the S and put the output on the Stack (but probably less efficient).
I donât think WORD would be slower, and it also gives you functionality (e.g. hypenating words) that other approaches would not. Will play with that later today or tomorrow.
Just for information: My WinWord 2002 (German version) does no automatical hyphenating by default. It does the word wrapping like my module - it splits very long words without placing hyphens. Maybe thereâs an additional Word-option anywhere âŚ
sorry - I failed to grasp the intention of your program at first. :oops:
After yours and Steveâs replies I realized that I actually had the same problem: having to break up a lengthy text into n lines of a given size.
I chose to have even the line-size variable and to call the wrapping-sub-pgm recursively.
Main-Program:
DEFINE DATA LOCAL
USING WRAP-A
LOCAL
1 #LINES (I4)
END-DEFINE
* preliminaries:
#LINELENGTH := 30 /* may vary - choose desired length
#ARRAY-SIZE := 4 /* may vary - needed for initialization of x-array
EXPAND OCCURRENCES OF ARRAY #ARRAY TO (1:#ARRAY-SIZE)
* text-string to be wrapped:
#TEXT := 'This rather lenghty text shall be wrapped ' -
'into n lines with 30 bytes each, preferably repr'-
'esented by something like an x-array (a30/1:n)'
CALLNAT 'WRAP-N' #TEXT #ARRAY(*) #LINELENGTH #LINE-CNTR
#ARRAY-SIZE
FOR #LINES 1 #LINE-CNTR
PRINT #ARRAY(#LINES)
END-FOR
END
Sub-Program:
DEFINE DATA PARAMETER
USING WRAP-A
LOCAL
1 #I (I4)
1 #TEXTLENGTH (I4)
1 #NEWLENGTH (I4)
1 #BREAK (I4)
1 #REST (A) DYNAMIC
1 #NEXTBYTE (I4)
END-DEFINE
*
#TEXTLENGTH := *LENGTH(#TEXT)
#NEXTBYTE := #LINELENGTH + 1
* if the line is too short:
EXAMINE #TEXT FOR ' ' GIVING POSITION IN #I
SUBTRACT 1 FROM #I
IF #I > #LINELENGTH
WRITE 'word does not fit on one line'
ESCAPE MODULE
END-IF
IF #LINE-CNTR = *OCC(#ARRAY)
ADD 10 TO #ARRAY-SIZE
EXPAND OCCURRENCES OF ARRAY #ARRAY TO (1:#ARRAY-SIZE)
END-IF
IF *LENGTH(#TEXT) <= #LINELENGTH
ADD 1 TO #LINE-CNTR
#ARRAY(#LINE-CNTR) := #TEXT
ESCAPE MODULE
ELSE
IF SUBSTR(#TEXT, #LINELENGTH, 1) = ' ' /* blank at eol
OR SUBSTR(#TEXT, #NEXTBYTE, 1) = ' '
ADD 1 TO #LINE-CNTR
#ARRAY(#LINE-CNTR) := SUBSTR(#TEXT, 1, #LINELENGTH)
#BREAK := #LINELENGTH + 1
IF SUBSTR(#TEXT, #NEXTBYTE, 1) = ' '
ADD 1 TO #BREAK
END-IF
#NEWLENGTH := #TEXTLENGTH - #BREAK + 1
#TEXT := SUBSTR(#TEXT, #BREAK, #NEWLENGTH)
* rekursiver Aufruf mit Resttext
CALLNAT 'WRAP-N' #TEXT #ARRAY(*) #LINELENGTH
#LINE-CNTR #ARRAY-SIZE
ELSE /* no blank at eol
#BREAK := #LINELENGTH
REPEAT WHILE SUBSTR(#TEXT, #BREAK, 1) NE ' '
SUBTRACT 1 FROM #BREAK
END-REPEAT
SUBTRACT 1 FROM #BREAK /* last no-blank
ADD 1 TO #LINE-CNTR
#ARRAY(#LINE-CNTR) := SUBSTR(#TEXT, 1, #BREAK)
ADD 1 TO #BREAK
#NEWLENGTH := #TEXTLENGTH - #BREAK
ADD 1 TO #BREAK
#TEXT := SUBSTR(#TEXT, #BREAK, #NEWLENGTH)
* rekursiver Aufruf mit Resttext
CALLNAT 'WRAP-N' #TEXT #ARRAY(*) #LINELENGTH
#LINE-CNTR #ARRAY-SIZE
END-IF /* blank am Zeilenende?
END-IF
* *
END
(P.S.: It seems to work for me and itâs rather variable, but I donât really like the fumbling with the breakpoint - hopping from blank to non-blank etc. Problably that could be solved more elegantly.)
The trouble with this code, as with Matthiasâs code, is performance. If you look above at an earlier posting of mine on this thread, the code Matthias had scored 52 versus 1 for a simple SEPARATE. This code scored 50.
You might want to play with the following code from an old issue of Inside Natural. This scored 4. The code was rather specific. It was intended to take strings of A70 and reformat them to strings of A60; however, the technique could easily be adapted to other uses.
DEFINE DATA LOCAL
1 #INPUT (A70/1:2)
1 REDEFINE #INPUT
2 #STRING (A140)
1 #OUTPUT (A60/1:3)
1 #POS (I2)
END-DEFINE
*
INCLUDE AASETC
MOVE
âUndergone heart surgery to correct narrowing or blockage of*â-
â1 or 2 coronary arteries with insertion of bypass graft(s).â
TO #STRING
*
IF SUBSTRING (#INPUT (1),60,1) = â â
MOVE #INPUT (1) TO #OUTPUT (1)
COMPRESS SUBSTRING (#INPUT (1),61) #INPUT (2) INTO #OUTPUT (2)
LEAVING NO SPACE
ELSE
EXAMINE DIRECTION BACKWARD SUBSTRING (#INPUT (1),1,60) FOR â â
GIVING POSITION #POS
MOVE SUBSTRING (#INPUT (1),1,#POS) TO #OUTPUT (1)
ADD 1 TO #POS
COMPRESS SUBSTRING (#INPUT (1),#POS) #INPUT (2)
INTO #OUTPUT (2) LEAVING NO SPACE
END-IF
*
WRITE âoutput-1:â #OUTPUT (1) // âoutput-2:â #OUTPUT (2)
END
PAGE 1 08-02-06 06:42:52
OUTPUT-1: UNDERGONE HEART SURGERY TO CORRECT NARROWING OR BLOCKAGE
OUTPUT-2: OF*1 OR 2 CORONARY ARTERIES WITH INSERTION OF BYPASS GRAFT(S
On my system, it seems to be the callnat by value and dynamics overhead.
Try this:
* Module: wordsepn
*
define data parameter
1 #input-parameter
2 #string (A) DYNAMIC BY VALUE
1 #output-parameter
2 #array (A/1:V) DYNAMIC BY VALUE RESULT /* no x-array possible here
*
end-define
*
separate #string INTO #array(*)
*
end
youâre right in general, BUT: in my case the word-wrapper performance is not really a major issue. After the main program has finished its task of collecting and summing up some data I need to present the data in a list (or rather: several lists). The headers for the respective lists are derived from a db-file where they are stored in full length. Itâs only then that I have to wrap the sometimes rather long headers into several lines of a given length in order to fit them into the standard header list-layout.
So, even if the main program in the end writes like two dozen lists - two dozen calls to the word-wrapper are no substantial loss of performance ( - esp. when compared to the main task of collecting the data). I wonât ever have to do like 10,000 wordwrappings. If that was the case I agree that wrapping performance should be optimized as much as possible, but if I only need it for the presentation of the data I can live with a few more seconds.
But thanks for bringing back to mind the direction-option of the EXAMINE-statement: That really looks more elegant than the stepping backwards byte by byte in a for-loop!
Iâm sorry, I shouldâve included that right away but forgot. Here comes:
DEFINE DATA PARAMETER
1 #TEXT (A) DYNAMIC BY VALUE
1 #ARRAY (A/1:*) DYNAMIC BY VALUE RESULT
1 #LINELENGTH (I4)
1 #LINE-CNTR (I4)
1 #ARRAY-SIZE (I4)
1 #RC (I2) /*8 bei Umbruchfehler
END-DEFINE
*
* module WORDWR2N
*
* todo: - split long words
*
define data parameter
1 #input-parameter
2 #string (A) DYNAMIC BY VALUE
1 #output-parameter
2 #array (A/1:V) DYNAMIC BY VALUE RESULT /* no x-array possible here
*
local
1 #temp-array (A/1:*) DYNAMIC
1 #i4 (I4)
1 #i4-out (I4)
1 #a (A) dynamic
1 #linesize(I4)
end-define
*
#linesize := *LENGTH(#array(1))
*
examine #string for ' ' giving number #i4
add 1 to #i4
expand array #temp-array to (1:#i4)
separate #input-parameter.#string left into #temp-array(*)
*
for #i4=1 to *UBOUND(#temp-array)
if *LENGTH(#temp-array(#i4)) = 0
escape bottom
end-if
if #a = " " or *LENGTH(#a) + 1 + *LENGTH(#temp-array(#i4)) le #linesize
compress #a #temp-array(#i4) into #a
else
add 1 to #i4-out
if #i4-out > *UBOUND(#array)
escape bottom
end-if
#array(#i4-out) := #a
#a := #temp-array(#i4)
end-if
end-for
if #a ne " " and #i4-out < *UBOUND(#array)
add 1 to #i4-out
#array(#i4-out) := #a
end-if
end
after some delay I returned to the word-wrapper and developed a new version which resembles your second suggestion: using a temporary array which is loaded by a SEPARATE and gradually filling up the output-array from this temp-array (see below) with respect to desired line-length.
I then tried to compare performances but I canât get your program to run - it simply freezes. :?:
do you have a constant V defined somewhere?
contrary to what the manual (and your comment) says, I have no problem defining an x-array within a PDA!? why is that??
I donât understand how your pgm determines the linesize: isnât your #output-parameter.#array(1) empty when âWORDWR2Nâ is called?
Hereâs my suggestion. A bit faster without the recursion, but I still keep linesize variable in order to make the module useful for other needs.
Subprogram:
[code]
DEFINE DATA
PARAMETER USING QHAWRAP /* works fine even w/ x-array !?
LOCAL
1 #TEMP-ARRAY (A/1:*) DYNAMIC
1 #WORDS (I4)
1 #LINES (I4)
1 #START (I4)
1 #I (I4)
1 #J (I4)
END-DEFINE
*
IF ##INPUT-TXT = â â
WRITE 'Keine Wortkette
No, please note that my module is a subprogram.
Call it the following way:
define data local
1 #a10(a10/1:5) /* i.e. Make 5 lines with a maximum of 10 characters
end-define
callnat 'wordwrpn' 'xxx xxxxxxxxx xxxx xxxx xxx' #a10(*)
display #a10(*)
end
Youâre rignt. Of course you can define an X-Array in PDA. You can even use a (1:V)-Definition in a subprogram for receiving an X-Array. But if you donât expand it before the callnat, youâll get an NAT1222. Thatâs what I ment with âno x-array possible hereâ. Maybe Itâs better to say: âno x-array with *OCC=0 possible hereâ
Yes, #output-parameter.#array(1) is empty. The Program determines the line size using *LENGTH(#array(1)). The dynamic alpha-field in the subprogram is automatically set to the size of the variable in the calling program - even if itâs empty.