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.