word-wrapping

Hello all!

I wrote a module for word-wrapping. First I thougt about using separate. But I guess this one is smarter. :wink: 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
2 Likes

Hi Matthias,

I don’t get it:

Firstly, your program does not seem to do what I thought it would: When I run it I get -


   #A10
----------

xxx
xxxxxxxxx
xxxx xxxx
xxxx

with two “words” in #a10(3) and #a10(5) being empty. :?:

And secondly: Why is your solution “smarter” than just one simple separate-statement (no sub-pgm, no callnat)?


DEFINE DATA LOCAL
1 #A10 (A10/1:5)
1 #STRING (A) DYNAMIC INIT <'xxx xxxxxxxxx xxxx xxxx xxx'>
END-DEFINE
*
SEPARATE #STRING INTO #A10(*)
DISPLAY #A10(*)
*
END

Hi Matthias;

Just to add some performance considerations; here is a timing comparison:

DEFINE DATA LOCAL
1 #A10(A10/1:5)
1 #LOOP (P5)
END-DEFINE
*
INCLUDE AASETC
SETA. SETTIME
FOR #LOOP = 1 TO 10000
CALLNAT ‘wordwrpn’ ‘xxx xxxxxxxxx xxxx xxxx xxx’ #A10(*)
END-FOR
WRITE 5T ‘wrapper time’ TIMD (SETA.)
*
SETB. SETTIME
FOR #LOOP = 1 TO 10000
SEPARATE ‘xxx xxxxxxxxx xxxx xxxx xxx’ INTO #A10(
)
END-FOR
WRITE 5T ‘separate time’ *TIMD (SETB.)
*
END

Page 1 08-06-13 06:43:19

wrapper time       52
separate time        1

What is your routine supposed to be doing that SEPARATE would not do?

steve

Hello djh and Steve Robinson!

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…

Hi Matthias;

After you have SEPARATE’d your text into #ARRAY (*),

              PRINT #ARRAY (*)

Natural does the word wrapping for you.

steve

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.

steve

Good idea, but I want to use it for e-mails, message-boxes and other stuff.

I’m on Solaris. Winword was just an example to explain my intention.

BTW: That Winword-thing would be much slower than my module, wouldn’t it? :wink:

Again, haven’t tried this yet:

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.

steve

Whoops. One to many C’s. Just %CCL01A etc

steve

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 …

Hello Matthias,

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.)

Hi Dirk;

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

steve

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

and call it with your program the following way:

callnat 'wordsepn' 'xxx xxxxxxxxx xxxx xxxx xxx' #A10(*)

my score on open systems is then:

wrapper time       15
separate time       9

Hi Steve,

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!

Hello djh!

Could you please post the content of your Module WRAP-A? I’m too lazy to find it out by myself :wink:

Hi Matthias,

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

So my scores are (with sample text ‘xxx xxxxxxxxx xxxx xxxx xxx’ into 10/5):

wordwrp time       14
wordsep time        9
wrap-n time       43

But I agree! In most cases all runtimes are OK.

another word wrapper (quick’n’dirty)

*
* 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

runtime: 19

Hi Matthias,

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.