home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 18 REXX / 18-REXX.zip / orx8.zip / html_util.cmd < prev    next >
OS/2 REXX Batch file  |  1997-07-21  |  45KB  |  1,109 lines

  1. /*
  2.    Module to allow easier manipulation for generating HTML-files
  3.  
  4. program:   html_util.cmd
  5. type:      Object REXX, REXXSAA 6.0
  6. purpose:   implements functions and classes for easier dealing with HTML
  7. version:   0.80
  8. date:      1995-11
  9. changed:   1997-04-12, split up a little bit for the "8th Int'l Rexx Symposium"
  10.  
  11. author:    Rony G. Flatscher
  12.            Rony.Flatscher@wu-wien.ac.at
  13.            (Wirtschaftsuniversitaet Wien, University of Economics and Business
  14.            Administration, Vienna/Austria/Europe)
  15. needs:     ---
  16.  
  17. usage:     see program :)
  18.  
  19. comments:  not finished yet
  20.  
  21. All rights reserved, copyrighted 1995-1997, no guarantee that it works without
  22. errors, etc. etc.
  23.  
  24. You are granted the right to use this module under the condition that you don't charge money for this module (as you didn't write
  25. it in the first place) or modules directly derived from this module, that you document the original author (to give appropriate
  26. credit) with the original name of the module and that you make the unaltered, original source-code of this module available on
  27. demand.  If that holds, you may even bundle this module (either in source or compiled form) with commercial software.
  28.  
  29. If you find an error, please send me the description (preferably a *very* short example); I'll try to fix it and re-release it to
  30. the net.
  31. */
  32.  
  33.  
  34.  
  35. /*
  36. a = "I am f<rO>Mm Au&s-tria ! "
  37. b = "1You'rENot FRoM AusTRiA ?"
  38.  
  39. SAY "InitCap"
  40. SAY
  41. say "A" pp( a )
  42. say " " pp( InitCap( a, "up", "low" ) )
  43. say
  44.  
  45. say "B" pp( b )
  46. say " " pp( InitCap( b, "up", "low"  ) )
  47. SAY LEFT( "", 79, "-" )
  48.  
  49. SAY "CAPITALIZE"
  50. SAY
  51. say "A" pp( a )
  52. say " " pp( capitalize( a, "up", "low"  ) )
  53. say
  54. say "B" pp( b )
  55. say " " pp( capitalize( b, "up", "low"  ) )
  56. SAY LEFT( "", 79, "-" )
  57. */
  58.  
  59.  
  60.  
  61.  
  62.         /* create directory of tags, which must not have an end tag as per HTML 3.2,
  63.            save it with environment .local      */
  64. .local ~ html.util = .directory ~ new           /* create directory for HTML_UTIL       */
  65. noEndTags = .directory ~ new
  66. tmpString = "BR HR Image Input IsIndex"
  67.  
  68. DO WHILE tmpString <> ""
  69.    PARSE VAR tmpString token tmpString
  70.    noEndTags ~ setentry( token, token )
  71. END
  72. .html.util ~ noEndTags = noEndTags
  73.  
  74.  
  75. :: REQUIRES sgmlentity_util.cmd /* load SGML-entity-translation support */
  76. :: REQUIRES rgf_util.cmd        /* load miscellaneous utilities */
  77. :: REQUIRES class_ref.cmd       /* load class definitions for basic anchor/refernce support     */
  78.  
  79.  
  80. /*
  81.         produce a HTML-string, i.e. embrace argument with tags given as additional arguments,
  82.         ignore attributes on closing tag
  83.  
  84.         arg(1) ... string to work on
  85.  
  86.         arg(2) ... tags, including attributes (innermost)
  87.         ...
  88.         arg(n) ... last tag (outermost)
  89. */
  90. :: ROUTINE WWW_TAG                      PUBLIC
  91.    PARSE ARG string
  92.  
  93. /*
  94. say "in WWW_TAG() ---- ARG(1)" pp( arg(1) ) "string" pp( string )
  95. IF ARG( 2 ) <> "" THEN say "                  ARG(2)" pp( arg(2) )
  96. IF ARG( 3 ) <> "" THEN say "                  ARG(3)" pp( arg(3) )
  97. IF ARG( 4 ) <> "" THEN say "                  ARG(4)" pp( arg(4) )
  98. */
  99.  
  100.     IF ARG(2) = "" THEN RETURN ARG(1)           /* nothing to do */
  101.     tmp = ""
  102.     DO i = 2 TO ARG()
  103.        tmp = tmp || "<" || ARG(i) || ">"
  104.     END
  105.  
  106.     tmp = tmp || string
  107.     noEndTags = .html.util ~ noEndTags          /* get directory of tags with no end tag */
  108.  
  109.     DO i = ARG() TO 2 BY -1                     /* don't use attributes, just the tag */
  110.        tmpTag = WORD( ARG( i ), 1 )                /* get tag      */
  111.        IF noEndTags ~ hasentry( tmpTag ) THEN ITERATE   /* tag must not have an end tag */
  112.        tmp = tmp || "</" || tmpTag || ">"
  113.     END
  114.  
  115.  
  116.     RETURN tmp
  117. /* ------------------------------------------------------------------------------ */
  118.  
  119.  
  120.  
  121.  
  122.  
  123.  
  124. /* ------------------------------------------------------------------------------ */
  125.  
  126. /*
  127.     InitCap( string, First_Char_Markup, Rest_Char_Markup )
  128.  
  129.                 1) translate string to uppercase
  130.                 2) change fontsize of first letter of each word according to second argument
  131.                 3) change fontsize of the rest of the word (except the first char) according to third argument
  132.  
  133.                 4) must be *plain*, i.e. *no* TAGs !
  134.  
  135.  
  136.     First_Char_Markup = ARG(2)                  /* markup of first letter in word     */
  137.     Rest_Char_Markup  = ARG(3)                  /* markup of rest of letters in word  */
  138. */
  139. :: ROUTINE InitCap                     PUBLIC
  140.     PARSE ARG string, First_Char_Markup, Rest_Char_Markup
  141.  
  142.     tmp_in = NLS_UPPER( string )                /* change everything into uppercase */
  143.     crlf   = .rgf.util ~ CRLF                   /* get the representation of CR-LF */
  144.  
  145.     IF First_Char_Markup = "" & Rest_Char_Markup = "" THEN
  146.        Rest_Char_Markup = "FONT SIZE=-1"        /* default markup, leave capitals alone lower rest by 1 pt */
  147.  
  148.     ucase = get_nls_default_object() ~ uppercase            /* get NLS uppercase letters */
  149.     tmp_out = ""                                /* result string */
  150.     non_valid = ""
  151.  
  152.     DO WHILE tmp_in <> ""
  153.        /* check and process non-valid chars-string */
  154.        non_valid = ""
  155.  
  156.        char_pos = VERIFY( tmp_in, UCASE, "M" )  /* get position of a valid capital letter */
  157.  
  158.        IF char_pos > 0 THEN                     /* starts with non-valid letters */
  159.        DO
  160.           non_valid = SUBSTR( tmp_in, 1, char_pos - 1 )     /* extract non-valid char_pos */
  161.           tmp_in    = SUBSTR( tmp_in, char_pos )/* store rest of string, containing valid letters */
  162.        END
  163.        ELSE
  164.        DO
  165.           non_valid = tmp_in                    /* no valid capital letters available */
  166.           tmp_in    = ""
  167.        END
  168.  
  169.        /* look for blank_pos and insert CRLF, if present */
  170.        IF \( non_valid == "" ) THEN             /* string of non-valid chars */
  171.        DO
  172.           /* give non-valid chars size of capital letter */
  173.           pos = LASTPOS( " ", non_valid )       /* find a blank to insert a CRLF */
  174.           IF pos > 0 THEN                       /* insert a CRLF */
  175.           DO
  176.              non_valid = SUBSTR( non_valid, 1, pos - 1 ) || crlf ||,
  177.                          SUBSTR( non_valid, pos + 1 )
  178.           END
  179.           tmp_out = tmp_out || www_tag( x2bare(non_valid), First_Char_Markup )
  180.        END
  181.  
  182.        /* change font-sizes for capitalizing according to arguments */
  183.        not_a_letter_pos = VERIFY( tmp_in, UCASE )                       /* look for a non-char */
  184.        IF not_a_letter_pos > 0 THEN
  185.        DO
  186.           chars_first = SUBSTR( tmp_in, 1, 1 )                          /* first letter    */
  187.           chars_rest  = SUBSTR( tmp_in, 2, not_a_letter_pos - 1 - 1 )   /* rest of letters */
  188.           tmp_in      = SUBSTR( tmp_in, not_a_letter_pos )              /* rest of string  */
  189.           tmp_out = tmp_out || www_tag( x2bare(chars_first), First_Char_Markup )/* markup first char */
  190.           tmp_out = tmp_out || www_tag( x2bare(chars_rest),  Rest_Char_Markup  )/* markup rest of chars */
  191.        END
  192.        ELSE     /* only letters left    */
  193.        DO
  194.           IF tmp_in <> "" THEN
  195.           DO
  196.              IF tmp_out = "" THEN       /* nothing processed so far, just a word in hand        */
  197.              DO
  198.                 tmp_out = www_tag( x2bare( SUBSTR( tmp_in, 1, 1) ), First_Char_Markup )/* markup first char */
  199.  
  200.                 IF LENGTH( tmp_in > 1 ) THEN    /* more than one letter ?       */
  201.                    tmp_out = tmp_out || www_tag( x2bare( SUBSTR(tmp_in, 2) ),  Rest_Char_Markup  )/* markup rest of chars */
  202.              END
  203.              ELSE
  204.              DO
  205.                 tmp_out = tmp_out || www_tag( x2bare(tmp_in),  Rest_Char_Markup  )/* markup rest of chars */
  206.              END
  207.           END
  208.           tmp_in = ""
  209.        END
  210.     END
  211.  
  212.     RETURN tmp_out
  213. /* ------------------------------------------------------------------------------ */
  214.  
  215.  
  216.  
  217.  
  218.  
  219.  
  220. /*
  221.     capitalize( string, First_Char_Markup, Rest_Char_Markup )
  222.  
  223.                 1) show all letters as is (i.e. preserve lower/uppercase letters) in capitals
  224.                    by showing uppercase letters in a larger font-size
  225.  
  226.                 2) must be *plain*, i.e. *no* TAGs !
  227.  
  228.     First_Char_Markup = ARG(2)                  /* markup of uppercase letters     */
  229.     Rest_Char_Markup  = ARG(3)                  /* markup of lowercase letters     */
  230.  
  231. */
  232. :: ROUTINE CAPITALIZE                   PUBLIC
  233.     PARSE ARG string, First_Char_Markup, Rest_Char_Markup
  234.  
  235.     /* handle special markup relevant chars, part 1 */
  236.     Table_In  = '&<>"'                            /* chars with special meaning to HTML */
  237.     Table_Out = '01020304'x                       /* illegal HTML-chars                 */
  238.     string = TRANSLATE( string, Table_Out, Table_In )   /* translate to non-supported HTML-chars */
  239.  
  240.  
  241.     tmp_in = NLS_UPPER( string )                /* change everything into uppercase */
  242.     crlf   = .rgf.util ~ CRLF                   /* get the representation of CR-LF */
  243.  
  244.     IF (First_Char_Markup = "" & Rest_Char_Markup = "") THEN
  245.        Rest_Char_Markup = "FONT SIZE=-1"        /* default markup, leave capitals alone lower rest by 1 pt */
  246.  
  247.  
  248.     ucase = get_nls_default_object() ~ uppercase            /* get NLS uppercase letters */
  249.     lcase = get_nls_default_object() ~ lowercase            /* get NLS lowercase letters */
  250.  
  251.     tmp_out = ""                                /* result string */
  252.  
  253.     DO WHILE tmp_in <> ""
  254.  
  255.        /* check and process non-valid chars-string */
  256.        posLetter = VERIFY( tmp_in, UCASE, "M" ) /* get first position of a letter */
  257.  
  258.        IF posLetter = 0 THEN                            /* no letters found */
  259.        DO
  260.           tmp_out = tmp_out || www_tag( x2bare(tmp_in), First_Char_Markup )  /* give non-valid chars size of capital letter */
  261.           LEAVE
  262.        END
  263.  
  264.        /* save string which has no letters */
  265.        non_valid_letters = SUBSTR( tmp_in, 1, posLetter - 1)    /* extract non-valid letters */
  266.  
  267.        posBlank = LASTPOS( " ", non_valid_letters )             /* try to find a blank to replace with a CRLF */
  268.        IF posBlank > 0 THEN
  269.        DO
  270.           non_valid_letters = SUBSTR( non_valid_letters, 1, posBlank - 1) || crlf ||,
  271.                               SUBSTR( non_valid_letters, posBlank + 1 )
  272.        END
  273.  
  274.        tmp_out = tmp_out || www_tag( x2bare( non_valid_letters ), First_Char_Markup )/* give non-valid chars size of capital letter */
  275.        tmp_in = SUBSTR( tmp_in, posLetter )             /* get rid of no letters */
  276.        string = SUBSTR( string, posLetter )
  277.  
  278.        posNoLetter = VERIFY( tmp_in, UCASE, "N" )       /* get first position of a non-letter */
  279.  
  280.        IF posNoLetter = 0 THEN                          /* string is built of letters only */
  281.        DO
  282.           tmp_out = capitalize_word( tmp_in, string, tmp_out )
  283.           LEAVE                                         /* done */
  284.        END
  285.  
  286.        tmp_out = capitalize_word( SUBSTR( tmp_in, 1, posNoLetter - 1),,
  287.                                   SUBSTR( string, 1, posNoLetter - 1), tmp_out )
  288.  
  289.        tmp_in = SUBSTR( tmp_in, posNoLetter )
  290.        string = SUBSTR( string, posNoLetter )
  291.     END
  292.  
  293.     /* handle special markup relevant chars, part 2, reversal */
  294.     tmp_out = CHANGESTR( "01"x, tmp_out, "&"  )
  295.     tmp_out = CHANGESTR( "02"x, tmp_out, "<"   )
  296.     tmp_out = CHANGESTR( "03"x, tmp_out, ">"   )
  297.     tmp_out = CHANGESTR( "04"x, tmp_out, """ )
  298.  
  299.     RETURN tmp_out
  300.  
  301. /* markup a string consisting of letters only */
  302. CAPITALIZE_WORD: PROCEDURE EXPOSE First_Char_Markup Rest_Char_Markup UCASE LCASE CRLF
  303.     USE ARG tmp_in, string, tmp_out
  304.  
  305.     DO WHILE tmp_in <> ""
  306.        posUpper = VERIFY( string, ucase, "M" )  /* find a capital letter */
  307.  
  308.        IF posUpper = 0 THEN                     /* no capital letter found anymore */
  309.        DO
  310.           tmp_out = tmp_out || www_tag( x2bare(tmp_in),  Rest_Char_Markup  )/* markup lowercase chars */
  311.           LEAVE
  312.        END
  313.  
  314.        IF posUpper > 1 THEN                     /* take care of possible leading lowercase chars */
  315.        DO
  316.           tmp_out  = tmp_out || www_tag( x2bare(SUBSTR( tmp_in, 1, posUpper - 1)) ,  Rest_Char_Markup )  /* markup lowercase chars */
  317.        END
  318.  
  319.        PosNextLow = VERIFY( string, lcase, "M", posUpper )      /* find next lowercase letter */
  320.        IF PosNextLow = 0 THEN                   /* only uppercase letters left */
  321.        DO
  322.           tmp_out = tmp_out || www_tag( x2bare(SUBSTR( tmp_in, posUpper )) , First_Char_Markup )    /* markup first char */
  323.           LEAVE
  324.        END
  325.  
  326.        /* handle uppercase letters */
  327.        tmp_out = tmp_out || www_tag( x2bare( SUBSTR(tmp_in, posUpper, PosNextLow - posUpper )) , First_Char_Markup )
  328.  
  329.        tmp_in  = SUBSTR( tmp_in, PosNextLow )   /* get unhandled string portion */
  330.        string  = SUBSTR( string, PosNextLow )   /* get unhandled string portion */
  331.     END
  332.  
  333.  
  334.     RETURN tmp_out                              /* make a line break after this word */
  335.  
  336.  
  337.  
  338.  
  339.  
  340. /* ------------------------------------------------------------------------------ */
  341. /* first do an X2BARE on plain text and then do a BreakLines, so result is readable
  342.    with editors which can't handle large lines (i.e. > 255);
  343.    makes result easier readable for humans too */
  344. :: ROUTINE PlainText           PUBLIC          /* plain text, do a X2BARE and a BreakLines on it */
  345.    USE ARG plainText, lineLength
  346.  
  347.    IF \VAR( "lineLength" ) THEN lineLength = 100
  348.    RETURN BreakLines( X2BARE( plainText ), lineLength )
  349.  
  350.  
  351.  
  352. /* ------------------------------------------------------------------------------ */
  353. /* plain text needs "&", "<", ">" and sometimes " itself escaped;
  354.    that's what this routine is for */
  355. :: ROUTINE X2BARE               PUBLIC
  356.    PARSE ARG plainText
  357.  
  358.    plainText = CHANGESTR( "&" , plainText,  "&" )   /* translate ampersand */
  359.    plainText = CHANGESTR( "<" , plainText,  "<" )    /* translate smaller */
  360.    plainText = CHANGESTR( ">" , plainText,  ">" )    /* translate larger */
  361.    plainText = CHANGESTR( '"' , plainText,  """ )  /* translate to quote */
  362.    RETURN plainText
  363.  
  364. /* ------------------------------------------------------------------------------ */
  365. /* translate blanks into non-breaking space        */
  366. :: ROUTINE X2BLANK              PUBLIC
  367.    PARSE ARG plainText
  368.  
  369.    RETURN CHANGESTR( " " , plainText,  " " )       /* translate blank to non-breaking space */
  370.  
  371.  
  372.  
  373.  
  374.  
  375. /* ------------------------------------------------------------------------------ */
  376. :: ROUTINE htmlComment                         /* produce a HTML-comment from passed in string */
  377.    RETURN "<!--" ARG( 1 ) "-->"
  378.  
  379.  
  380. /* ------------------------------------------------------------------------------ */
  381. :: ROUTINE BreakLines                  PUBLIC  /* break a string into lines */
  382.    USE ARG string, lineChars
  383.  
  384.    IF \VAR( "lineChars" ) THEN lineChars = 100
  385.  
  386.    tmpString = ""
  387.    crlf = .rgf.util ~ crlf                      /* get CRLF */
  388.    filler = LEFT( "", 5 )
  389.  
  390.    DO WHILE string <> ""
  391.       IF LENGTH( string ) <= lineChars THEN
  392.       DO
  393.          tmpString = tmpString crlf filler string
  394.          LEAVE
  395.       END
  396.  
  397.       pos = LASTPOS( " ", string, lineChars + 1 )       /* look for a blank to break */
  398.       IF pos < 70 THEN                                  /* not found */
  399.       DO
  400.          pos = POS( " ", string, lineChars )            /* look for a blank towards the end */
  401.          IF pos = 0 THEN                                /* no more blanks left ! */
  402.          DO
  403.             tmpString = tmpString crlf filler string
  404.             LEAVE
  405.          END
  406.       END
  407.  
  408.       tmpString = tmpString crlf filler SUBSTR( string, 1, pos )/* insert CRLF */
  409.       string = SUBSTR( string, pos + 1 )                /* remove part already handled */
  410.    END
  411.    RETURN tmpString
  412.  
  413.  
  414. /* ---------------------------------------------------------------------------------------- */
  415. /* purpose: capitalize or initcap plain text, replace blanks by   (non-breaking space) by default */
  416. /*          works with defaults: lowercase letters are FONT SIZE=-1                     */
  417. /* capitalize, translate to html of **PLAIN TEXT** string, blanks will be translated into   ! */
  418. :: ROUTINE SmartCap                     PUBLIC
  419.   USE ARG string, type, bBlanks
  420.  
  421.   bBlanks = ( bBlanks <> .false )               /* default: translate blanks into   */
  422.  
  423.   IF TRANSLATE( LEFT( type, 1 ) ) = "I" THEN type = "I" /* InitCap desired */
  424.                                         ELSE type = "C" /* default to capitalize */
  425.  
  426.   IF bBlanks THEN
  427.      string = CHANGESTR( " ", string, "00"x )
  428.  
  429.   IF type = "C" THEN
  430.       string = CAPITALIZE( string )                     /* capitalize string */
  431.   ELSE
  432.       string = INITCAP( string )                        /* initcap string */
  433.  
  434.  
  435.   IF bBlanks THEN                                       /* translate blanks into   ! */
  436.      string = CHANGESTR( "00"x, string,  " " )     /* translate blank to non-breaking space */
  437.   ELSE
  438.      string = CHANGESTR( "00"x, string,  " "      )     /* translate blank to non-breaking space */
  439.  
  440.   RETURN string
  441.  
  442.  
  443.  
  444. /* ---------------------------------------------------------------------------------------- */
  445. /* put text into given anchor-name-definition */
  446. :: ROUTINE A_NAME               PUBLIC
  447.   PARSE ARG name, text
  448.  
  449.   RETURN '<A NAME="' || name || '">'    text    "</A>"
  450.  
  451.  
  452. /* ---------------------------------------------------------------------------------------- */
  453. /* put text into given anchor-name-definition */
  454. :: ROUTINE A_HREF               PUBLIC
  455.   PARSE ARG doc, name, text
  456.  
  457.   RETURN '<A HREF="' || doc || "#" || name || '">' || text || "</A>"
  458.  
  459.  
  460.  
  461.  
  462.  
  463.  
  464. /* ================================================================================================ */
  465.  
  466.  
  467.  
  468.  
  469. /* ================================================================================================ */
  470. /* ------------------------------------------------------------------------------------------------ */
  471. /* a class for producing HTML-docs */
  472. :: CLASS HTML_DOC               PUBLIC
  473.  
  474. /* ----------------------------------------------- */
  475. :: METHOD init
  476.    EXPOSE file stream bClosed additionalHeader additionalFooter
  477.    USE ARG file, title, bReplace, additionalHeader, additionalFooter
  478.  
  479.    IF \VAR( "file" )                            THEN RAISE SYNTAX 3.901 ARRAY ( file )   /* file missing */
  480.    IF \VAR( "title" )                           THEN title = "No title supplied !"
  481.    IF \VAR( "bReplace") | bReplace = "BREPLACE" THEN bReplace = .false
  482.    IF \VAR( "additionalHeader" )                THEN additionalHeader = ""
  483.    IF \VAR( "additionalFooter" )                THEN additionalFooter = ""
  484.  
  485.    stream = .stream ~ new( file )
  486.    IF \bReplace THEN                                    /* file replacement o.k. ? */
  487.    DO
  488.       IF stream ~ query( "exists" ) <> "" THEN          /* file exists, abort, pretend problems writing to it */
  489.       DO
  490.          stream ~ close                                 /* make sure, it's closed */
  491.          RAISE SYNTAX 3.902 ARRAY ( file )
  492.       END
  493.    END
  494.  
  495.    stream = .stream ~ new( file ) ~~ open( "write replace" )    /* open file for writing, replace existing one */
  496.    bClosed = .false
  497.    self ~ HTML_header( title )
  498.    RETURN
  499.  
  500.  
  501.  
  502. /* ----------------------------------------------- */
  503. /* assumptions:        method_name     ... 1st tag
  504.                        argument[1]     ... string to work on
  505.                        argument[2]     ... attribute for 1st tag, empty else
  506.                        argument[3 ..n] ... additional tags including attributes
  507. */
  508.  
  509. :: METHOD UNKNOWN               /* treat unknown message as HTML-tag(s), except for LINEOUT */
  510.    EXPOSE stream file bClosed
  511.    USE ARG mess_name, mess_args
  512.  
  513.    IF \ bClosed THEN
  514.    DO
  515.       IF mess_name = "LINEOUT" THEN             /* forward LINEOUT to stream */
  516.       DO
  517.          FORWARD MESSAGE (mess_name) ARGUMENTS ( mess_args ) TO ( stream ) CONTINUE
  518.       END
  519.       ELSE                                      /* build and execute arguments for www_tag */
  520.       DO
  521.          /* build string to interpret, encompass string limit by using stem. for substitution */
  522.          IF mess_args ~ items = 0 | mess_args[ 1 ] = .nil THEN          /* no content given */
  523.             a.1 = htmlComment( "empty" )
  524.          ELSE                                   /* use verbatim */
  525.             a.1 = mess_args[ 1 ]
  526.  
  527.          i = 1
  528.          tmpString = "tmp = www_tag( a.1 ,"
  529.  
  530.          IF mess_args[ 2 ] <> .nil THEN         /* message name is first tag */
  531.          DO
  532.             a.2 = mess_args[ 2 ]                /* get second argument, interpreted as attribute for message-tag */
  533.             tmpString = tmpString "mess_name a.2"
  534.             i = 2
  535.          END
  536.          ELSE
  537.             tmpString = tmpString "mess_name"
  538.  
  539.  
  540.          DO i = 3 TO mess_args ~ size           /* build tags */
  541.             a.[i + 1] = mess_args[ i ]
  542.             tmpString = tmpString ", a."|| i + 1   /* save rest of arguments as additional tags */
  543.          END
  544.          tmpString = tmpString ")"              /* supply closing bracket */
  545.  
  546.          INTERPRET tmpString                    /* execute contents, i.e. call www_tag() */
  547.          FORWARD MESSAGE ("LINEOUT") ARRAY ( tmp ) TO ( stream ) CONTINUE
  548.       END
  549.  
  550.       stream ~ lineout( "" )                    /* insert empty line */
  551.    END
  552.    ELSE                                         /* file closed already ! */
  553.       RAISE SYNTAX 3.902 ARRAY ( pp( file) "was closed already!" )
  554.  
  555.    RETURN
  556.  
  557.  
  558.  
  559. /* ----------------------------------------------- */
  560. :: METHOD close                                         /* close stream, invalidate */
  561.    EXPOSE bClosed
  562.  
  563.    IF \ bClosed THEN
  564.    DO
  565.       self ~ html_footer_close                          /* insert closing tags, close stream */
  566.       bClosed = .true                                   /* indicate that closing took place already */
  567.    END
  568.  
  569.  
  570. /* ----------------------------------------------- */
  571. :: METHOD uninit                                        /* make sure file is closed */
  572.    self ~ close                                         /* write footer, close file */
  573.  
  574.  
  575. /* ----------------------------------------------- */
  576. :: METHOD html_header           PRIVATE         /* insert opening HTML-tags */
  577.    EXPOSE stream  additionalHeader
  578.    USE ARG title, htmlVersion
  579.  
  580.    IF \VAR( "htmlVersion" ) THEN htmlVersion = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">'
  581.  
  582.    stream ~ lineout( htmlVersion )
  583.    stream ~ lineout( "<HTML>" )
  584.    stream ~ lineout( "<HEAD>" )
  585.  
  586.    tmpString = "produced with Object REXX using HTML_UTIL.CMD (author: Rony G. Flatscher)"
  587.    stream ~ lineout( htmlComment( tmpString ) )
  588.  
  589.    stream ~ lineout( htmlComment( "production started at:" DATE( "S" ) TIME() ) )
  590.  
  591.    stream ~ lineout( www_tag( x2bare( title ), "TITLE" ) )
  592.  
  593.    IF additionalHeader <> "" THEN               /* write additional header */
  594.       stream ~ lineout( additionalHeader )
  595.  
  596.    stream ~ lineout( "</HEAD>" )
  597.    stream ~ lineout( "<BODY>" )
  598.  
  599.  
  600. /* ----------------------------------------------- */
  601. :: METHOD html_footer_close     PRIVATE         /* insert closing HTML-tags */
  602.    EXPOSE stream  additionalFooter
  603.  
  604.    IF additionalFooter <> "" THEN               /* write additional footer */
  605.       stream ~ lineout( additionalFooter )
  606.  
  607.    stream ~ lineout( "</BODY>" )
  608.    stream ~ lineout( "</HTML>" )                     /* write closing tag */
  609.  
  610.    stream ~ lineout( htmlComment( "production ended at:" DATE( "S" ) TIME() ) )
  611.  
  612.    stream ~ close                                    /* close stream */
  613. /* ------------------------------------------------------------------------------------------------ */
  614.  
  615.  
  616.  
  617. /* ================================================================================================ */
  618. /* define a class for HTML-table handling */
  619. :: CLASS HTML_table             PUBLIC
  620. /* ----------------------------------------------- */
  621. :: METHOD INIT          CLASS                   /* counter to count # of lists generated */
  622.    EXPOSE counter
  623.  
  624.    counter = 0
  625.  
  626. /* ----------------------------------------------- */
  627. :: METHOD counter       CLASS                   /* counter to count # of lists generated */
  628.    EXPOSE counter
  629.  
  630.    counter = counter + 1                        /* increment counter */
  631.    RETURN counter
  632.  
  633.  
  634. /* ----------------------------------------------- */
  635. :: METHOD INIT
  636.    EXPOSE tabArray tabAttributes Caption CaptionAttr,
  637.           lastRow lastCol bEditable htmlText TableNumber maxCol bSkipActive
  638.  
  639.    USE ARG  tabAttributes
  640.  
  641.    /* check & set defaults if necessary */
  642.    IF \VAR( "tabAttributes" ) THEN tabAttributes = "TABLE BORDER CELLPADDING=5" /* default for table */
  643.                               ELSE tabAttributes = "TABLE" tabAttributes
  644.  
  645.    caption = .nil                       /* no caption */
  646.    captionAttr = .nil                   /* no caption position */
  647.    lastRow = 1                          /* last row used, default to row # 1 */
  648.    lastCol = 0                          /* last col used */
  649.    maxCol = 0                           /* maximum nr. of columns on line */
  650.    bEditable = .true                    /* allow table to edit (until close or attach is sent) */
  651.    bSkipActive = .false                 /* indicate that nextRow should insert COLSPAN= */
  652.    htmlText = .nil                      /* no HTML-text of table produced as of yet */
  653.    tabArray = .array ~ new              /* create an array to contain table elements */
  654.    TableNumber = self ~ class ~ counter /* get # of table */
  655.  
  656.  
  657. /* ----------------------------------------------- */
  658. :: METHOD setCaption
  659.    EXPOSE  Caption CaptionAttr
  660.    USE ARG Caption, CaptionAttr
  661.  
  662.    IF \VAR( "CaptionAttr" ) THEN CaptionAttr  = "CAPTION ALIGN=bottom"
  663.                             ELSE CaptionAttr  = "CAPTION" CaptionAttr
  664.  
  665. /* ----------------------------------------------- */
  666. :: METHOD putColumn                                     /* put a new column into table */
  667.    EXPOSE tabArray lastRow lastCol bEditable maxCol
  668.    USE ARG string, attributes
  669.  
  670.    IF \ bEditable THEN RETURN                           /* don't do anything anymore */
  671.    IF \ VAR( "string" ) | string = "" THEN string = htmlComment( "empty" )
  672.    IF \VAR( "attributes" ) THEN attributes = "TD"       /* default for missing argument, i.e. plain table cell */
  673.                            ELSE attributes = "TD" attributes
  674.  
  675.    lastCol = lastCol + 1                                /* insert next column */
  676.    tmpArr = .array ~ of( string, attributes )
  677.    tabArray[ lastRow, lastCol ] = tmpArr                /* insert array containing infos into main array */
  678.    maxCol = MAX( maxCol, lastCol )                      /* save highest column nr. of row */
  679.  
  680. /* ----------------------------------------------- */
  681. :: METHOD putHeader                                     /* put a new header column into table */
  682.    EXPOSE tabArray lastRow lastCol bEditable maxCol
  683.    USE ARG string, attributes
  684.  
  685.    IF \ bEditable THEN RETURN                           /* don't do anything anymore */
  686.    IF \VAR( "attributes" ) THEN attributes = "TH"       /* default for missing argument, i.e. table header cell */
  687.                            ELSE attributes = "TH" attributes
  688.  
  689.    lastCol = lastCol + 1                                /* insert next column */
  690.    tmpArr = .array ~ of( string, attributes )
  691.    tabArray[ lastRow, lastCol ] = tmpArr                /* insert array containing infos into main array */
  692.    maxCol = MAX( maxCol, lastCol )                      /* save highest column nr. of row */
  693.  
  694.  
  695. /* ----------------------------------------------- */
  696. :: METHOD newRow                                /* change to next row of the table */
  697.    EXPOSE lastRow lastCol tabArray maxCol bSkipActive bEditable
  698.    USE ARG Fixup                                /* if bFixup = true, then gaps will be closed via COLSPAN statements */
  699.  
  700.  
  701.    IF \ bEditable THEN RETURN                           /* don't do anything anymore */
  702.    IF \VAR( "Fixup" ) THEN bFixup = .nil        /* if .nil, then use maxCol to determine maximum amount of cols */
  703.  
  704.    IF bSkipActive & bFixup = .nil THEN          /* if a column was skipped, make sure COLSPAN= is inserted */
  705.       bFixup = maxCol
  706.  
  707.    IF Fixup <> .nil THEN                        /* fixup column definitions by adding COLSPAN= to close gaps */
  708.    DO
  709.       IF DATATYPE( Fixup, "W") THEN
  710.       DO
  711.          maxCol = MAX( maxCol, Fixup )          /* use whatever is higher */
  712.       END
  713.  
  714.       startCol = 0
  715.       endCol   = 0
  716.  
  717.       DO col = 1 TO maxCol                              /* find first non-empty entry */
  718.          IF tabArray[ lastRow, col ] <> .nil THEN
  719.          DO
  720.             startCol = col
  721.             LEAVE
  722.          END
  723.       END
  724.  
  725.       IF startCol > 1 THEN                              /* empty columns from beginning */
  726.       DO
  727.         tabArray[ lastRow, 1 ] = .array ~ of( htmlComment( "empty cell, from newRow" ), "TD COLSPAN=" || startCol - 1 )    /* empty column, it was skipped */
  728.       END
  729.  
  730.       DO col = col TO maxCol
  731.          IF tabArray[ lastRow, col ] = .nil THEN        /* skip until an entry was found */
  732.             ITERATE
  733.  
  734.          IF col - startCol > 1 THEN                     /* gap found */
  735.             CALL fixup col                              /* fixup in procedure  */
  736.  
  737.          startCol = col                                 /* save present column */
  738.       END
  739.  
  740.       IF startCol > 0 & startCol <> maxCol THEN         /* fixup to the end     */
  741.          CALL fixup maxCol + 1
  742.    END
  743.  
  744.  
  745.    lastRow = lastRow + 1                                /* increase row counter */
  746.    lastCol = 0                                          /* reset lastCol */
  747.    maxCol  = 0                                          /* reset maxCol for row */
  748.    bSkipActive = .false
  749.    RETURN
  750.  
  751. FIXUP :
  752.    USE ARG tmpCol
  753.  
  754.    tmpArray = tabArray[ lastRow, startCol ]
  755.    attribute = tmpArray[ 2 ]
  756.    attribute = attribute "COLSPAN=" || ( tmpCol - startCol )    /* add spanning attribute */
  757.    tmpArray[ 2 ] = attribute
  758.    tabArray[ lastRow, startCol ] = tmpArray     /* insert array containing infos into main array */
  759.    RETURN
  760.  
  761.  
  762.  
  763. /* ----------------------------------------------- */
  764. :: METHOD skipColumn                            /* skip column, use with newRow( some_content ) */
  765.    EXPOSE tabArray lastRow lastCol maxCol bSkipActive bEditable
  766.    USE ARG nrOfCols
  767.  
  768.    IF \ bEditable THEN RETURN                           /* don't do anything anymore */
  769.    IF \DATATYPE( nrOfCols, "W" ) THEN nrOfCols = 1      /* whole number given ? */
  770.  
  771.    IF nrOfCols < 1 THEN RETURN  /* don't skip, if not a positive number given ...       */
  772.  
  773.    lastCol = lastCol + nrOfCols
  774.    maxCol = MAX( maxCol, lastCol )                      /* save highest column nr. of row */
  775.    bSkipActive = .true
  776.  
  777. /* ----------------------------------------------- */
  778. :: METHOD emptyColumn                           /* add empty column(s) */
  779.    EXPOSE tabArray lastRow lastCol maxCol bEditable
  780.    USE ARG nrOfCols
  781.  
  782.    IF \ bEditable THEN RETURN                           /* don't do anything anymore */
  783.    IF \DATATYPE( nrOfCols, "W" ) THEN nrOfCols = 1      /* whole number given ? */
  784.    IF nrOfCols = 0 THEN RETURN                          /* don't do anything, if explicitly asked to skip no column */
  785.  
  786.    tmpString = htmlComment( "empty" )
  787.    DO i = 1 TO nrOfCols
  788.       lastCol = lastCol + 1
  789.       tabArray[ lastRow, lastCol ] = .array ~ of( tmpString, "TD" )    /* empty column, it was skipped */
  790.    END
  791.    maxCol = MAX( maxCol, lastCol )                      /* save highest column nr. of row */
  792.  
  793.  
  794. /* ----------------------------------------------- */
  795. :: METHOD close                                 /* close editing of table */
  796.    EXPOSE bEditable
  797.  
  798.    bEditable = .false                           /* no editing of table allowed anymore */
  799.    self ~ htmlText                         /* produce HTML text */
  800.  
  801.  
  802. /* ----------------------------------------------- */
  803. :: METHOD htmlText
  804.    EXPOSE tabArray tabAttributes Caption CaptionAttr lastRow lastCol bEditable htmlText TableNumber bSkipActive
  805.  
  806.    IF htmlText <> .nil THEN RETURN htmlText     /* return HTML-text of this table, was already produced */
  807.    IF bSkipActive THEN self ~ newRow            /* make sure a row with skipped column(s) is handled properly */
  808.  
  809.    bEditable = .false                           /* no editing of table allowed anymore */
  810.    crlf = .rgf.util ~ CRLF                      /* get CR-LF */
  811.  
  812.    tmpSupp = tabArray ~ supplier                /* get a supplier of the array */
  813.    rowDim  = tabArray ~ dimension( 1 )          /* get "row" dimensionality */
  814.    colDim  = tabArray ~ dimension( 2 )          /* get "col" dimensionality */
  815.    rowWidth = LENGTH( rowDim )
  816.    colWidth = LENGTH( colDim )
  817.    tmp_String = ""
  818.    EmptyCell = htmlComment( "empty" )
  819.  
  820.    /* produce caption */
  821.    IF caption <> .nil THEN tmp_string = crlf www_tag( Caption, CaptionAttr )
  822.  
  823.  
  824.    oldRow = 0
  825.    oldCol = 0
  826.    tmpRow = ""
  827.  
  828.    DO WHILE tmpSupp ~ AVAILABLE
  829.       tmp = tmpsupp ~ INDEX - 1
  830.       row = tmp %  colDim + 1                   /* get row from index */
  831.       col = tmp // colDim + 1                   /* get col from index */
  832.  
  833.       IF oldRow = 0 THEN
  834.       DO
  835.          oldRow = row
  836.       END
  837.  
  838.       tmpArray = tabArray[ row, col ]           /* get array object */
  839.  
  840.       IF oldRow <> row THEN                     /* a new row arrived */
  841.       DO
  842.          tmp_String = tmp_String crlf www_tag( crlf || tmpRow" ", "TR" ) crlf
  843.          tmpRow = htmlComment( "empty" )
  844.  
  845.          DO oldRow = oldRow + 1 TO row - 1          /* empty rows in between ? */
  846.             tmp_String = tmp_String crlf www_tag( tmpRow , "TR" ) crlf
  847.          END
  848.          tmpRow = ""
  849.          oldRow = row
  850.       END
  851.  
  852.       /* add cell */
  853.       tmpRow = tmpRow rowCol() www_tag( tmpArray[ 1 ], tmpArray[ 2 ] ) crlf
  854.       oldCol = col
  855.  
  856.       tmpSupp ~ next                            /* get next array element */
  857.    END
  858.  
  859.    tmp_String = tmp_String crlf www_tag( crlf || tmpRow" ", "TR" ) crlf
  860.  
  861.    htmlText = www_tag( tmp_string, tabAttributes )
  862.  
  863.    /* make HTML text a little more readable */
  864.    htmlText = crlf crlf || htmlComment( "Begin of" pp( tabAttributes ) "table #" pp( TableNumber ) || "..." ) crlf htmlText crlf ||,
  865.               htmlComment( "end of" pp( tabAttributes ) "table #" pp( TableNumber ) || "." ) crlf
  866.  
  867.    RETURN htmlText
  868.  
  869. rowCol :                                        /* indicate row/col in form of a comment */
  870.    RETURN htmlComment( "row=" || RIGHT( row, rowWidth ) || ", col=" || RIGHT( col, colWidth ) )
  871.  
  872. misty : procedure
  873.    RETURN mist
  874. /* ------------------------------------------------------------------------------------------------ */
  875.  
  876.  
  877.  
  878.  
  879. /* ================================================================================================ */
  880. /* define a class for HTML-list handling (un/numbered and definition lists) */
  881.  
  882. :: CLASS HTML_list              PUBLIC
  883. /* ----------------------------------------------- */
  884. :: METHOD INIT          CLASS                   /* counter to count # of lists generated */
  885.    EXPOSE counter
  886.  
  887.    counter = 0
  888.  
  889. /* ----------------------------------------------- */
  890. :: METHOD counter       CLASS                   /* counter to count # of lists generated */
  891.    EXPOSE counter
  892.  
  893.    counter = counter + 1                        /* increment counter */
  894.    RETURN counter
  895.  
  896. /* ----------------------------------------------- */
  897. :: METHOD INIT
  898.    EXPOSE bEditable htmlText htmlList typeChanged ListNumber
  899.  
  900.    USE ARG  ListTagOpen, ListTagItem, ListTagDescription
  901.  
  902.    FORWARD MESSAGE ("SetListType") CONTINUE     /* let SetListType */
  903.  
  904.    bEditable = .true                            /* allow table to edit (until close or attach is sent) */
  905.    htmlText = .nil                              /* no HTML-text of table produced as of yet */
  906.    htmlList  = .list  ~ new                     /* create a list to contain table elements */
  907.    typeChanged = .false
  908.    ListNumber = self ~ class ~ counter          /* get counter number for this list object */
  909.  
  910. /* ----------------------------------------------- */
  911. :: METHOD SetListType
  912.    EXPOSE ListTagOpen  ListTagItem  ListTagDescription typeChanged
  913.  
  914.    USE ARG  ListTagOpen, ListTagItem, ListTagDescription
  915.  
  916.    /* check & set defaults if necessary */
  917.    IF \VAR( "ListTagOpen" ) THEN                /* default to an unnumbered list */
  918.    DO
  919.       ListTagOpen        = "UL"                 /* unordered list */
  920.       ListTagItem        = "LI"                 /* list-item */
  921.       ListTagDescription  = "LI"                 /* list-item */
  922.    END
  923.    ELSE IF \VAR( "ListTagItem" ) THEN           /* try to determine desired list-type, use LI by default */
  924.    DO
  925.       IF TRANSLATE( WORD( ListTagOpen, 1 ) ) = "DL" THEN        /* a definition list ? */
  926.       DO
  927.          ListTagItem        = "DT"              /* definition term */
  928.          ListTagDescription  = "DD"              /* definition description */
  929.       END
  930.       ELSE                                      /* default to LI, "list item" */
  931.       DO
  932.          ListTagItem        = "LI"              /* list-item */
  933.          ListTagDescription  = "LI"              /* list-item */
  934.       END
  935.    END
  936.    ELSE IF \VAR( "ListTagDescription" ) THEN     /* use ListTagItem for ListTagDescription */
  937.       ListTagDescription = ListTagItem
  938.  
  939.    typeChanged = .true                          /* indicate that type may have been changed */
  940.  
  941.  
  942. /* ----------------------------------------------- */
  943. :: METHOD Item                                          /* put a new listitem into table */
  944.    EXPOSE  bEditable htmlList
  945.    USE ARG string, attributes
  946.  
  947.    IF \ bEditable THEN RETURN                           /* don't do anything anymore */
  948.  
  949.    IF \VAR( "attributes" ) THEN
  950.       attributes = ""                                   /* indicate missing attribute */
  951.  
  952.    tmpArr = .array ~ of( string, "1", attributes )
  953.    htmlList ~ insert( tmpArr )                          /* insert array containing infos into list */
  954.  
  955. /* ----------------------------------------------- */
  956. :: METHOD Term                                          /* put a definition term into list */
  957.    EXPOSE  bEditable  htmlList
  958.    USE ARG string, attributes
  959.  
  960.    IF \ bEditable THEN RETURN                           /* don't do anything anymore */
  961.  
  962.    IF \VAR( "attributes" ) THEN
  963.       attributes = ""                                   /* indicate missing attribute */
  964.  
  965.    tmpArr = .array ~ of( string, "2", attributes )
  966.    htmlList ~ insert( tmpArr )                          /* insert array containing infos into list */
  967.  
  968. /* ----------------------------------------------- */
  969. :: METHOD Description                                   /* put a description for a term into list */
  970.    EXPOSE  bEditable htmlList
  971.    USE ARG string, attributes
  972.  
  973.    IF \ bEditable THEN RETURN                           /* don't do anything anymore */
  974.  
  975.    IF \VAR( "attributes" ) THEN
  976.       attributes = ""                                   /* indicate missing attribute */
  977.  
  978.    tmpArr = .array ~ of( string, "3", attributes )
  979.    htmlList ~ insert( tmpArr )                          /* insert array containing infos into list */
  980.  
  981.  
  982. /* ----------------------------------------------- */
  983. :: METHOD close                                 /* close editing of table */
  984.    EXPOSE bEditable typeChanged
  985.  
  986.    bEditable = .false                           /* no editing of table allowed anymore */
  987.    typeChanged = .true
  988.    self ~ htmlText                         /* produce HTML text */
  989.  
  990.  
  991. /* ----------------------------------------------- */
  992. :: METHOD htmlText
  993.    EXPOSE ListTagOpen  ListTagItem  ListTagDescription bEditable htmlText htmlList typeChanged ListNumber
  994.  
  995.    IF bEditable THEN                            /* was in edit-mode */
  996.    DO
  997.       bEditable = .false                        /* no editing of list itself allowed anymore */
  998.       typeChanged = .true                       /* have a htmlText generated */
  999.    END
  1000.  
  1001.    IF \ typeChanged THEN RETURN htmlText       /* return HTML-text of this table, was already produced */
  1002.    typeChanged = .false                         /* no need to build string a second time */
  1003.  
  1004.    crlf = .rgf.util ~ CRLF                      /* get CR-LF */
  1005.  
  1006.    tmpSupp = htmlList ~ supplier                /* get a supplier of the list */
  1007.    tmp_String = ""
  1008.  
  1009.    /* check whether Item and Descriptions are different tags, if so, then we have a defintion list */
  1010.    bDefinition = \( TRANSLATE(WORD( ListTagItem, 1  )) = TRANSLATE( WORD( ListTagDescription, 1) ) )
  1011.  
  1012.    termPending = .false
  1013.  
  1014.    i = 0
  1015.    DO WHILE tmpSupp ~ AVAILABLE
  1016.       item = tmpSupp ~ item
  1017.       i = i + 1
  1018.       SELECT
  1019.          WHEN item[ 2 ] = 1 THEN                /* an item in hand */
  1020.          DO
  1021.             IF bDefinition THEN                 /* a definition list ? */
  1022.             DO
  1023.                IF termPending THEN              /* is a term (e.g. DT ) pending ? */
  1024.                DO
  1025.                   tmp_String = tmp_String crlf htmlComment( "*Error*: a [" || ListTagItem || "] term without a description! ") crlf
  1026.                END
  1027.                termPending = .true              /* deal as if a term has been supplied ! */
  1028.             END
  1029.  
  1030.             tmpTag = ListTagItem                /* a Listtag-Item in hand */
  1031.          END
  1032.  
  1033.          WHEN item[ 2 ] = 2 THEN                /* a term in hand */
  1034.          DO
  1035.             IF bDefinition THEN                 /* a definition list ? */
  1036.             DO
  1037.                IF termPending THEN              /* is a term (e.g. DT ) pending ? */
  1038.                DO
  1039.                   tmp_String = tmp_String crlf htmlComment( "*Error*: a [" || ListTagItem || "] term without a description! ") crlf
  1040.                END
  1041.                termPending = .true              /* to be handled with a description */
  1042.             END
  1043.  
  1044.             tmpTag = ListTagItem                /* a Listtag-Item in hand */
  1045.          END
  1046.  
  1047.          OTHERWISE                              /* a description in hand */
  1048.          DO
  1049.             IF bDefinition THEN                 /* a definition list ? */
  1050.             DO
  1051.                IF \ termPending THEN            /* is a term (e.g. DT ) pending ? */
  1052.                DO
  1053.                   tmp_String = tmp_String crlf htmlComment( "*Error*: a [" || ListTagItem ||,
  1054.                                "] description, but term is missing!" ) crlf
  1055.                END
  1056.                termPending = .false             /* handled */
  1057.             END
  1058.  
  1059.             tmpTag = ListTagDescription         /* a Listtag-Item in hand */
  1060.          END
  1061.       END
  1062.  
  1063.       /* was an attribute given to this listelement ? */
  1064.       IF item[ 3 ] <> "" THEN tmpTag = WORD( tmpTag, 1 ) item[ 3 ]
  1065.  
  1066.       tmp_String = tmp_String crlf htmlComment( "list entry #" i) www_tag( item[ 1 ], tmpTag )
  1067.       IF bDefinition THEN
  1068.       DO
  1069.          IF item[ 2 ] = 3 THEN                  /* a description in hand, insert a crlf */
  1070.            tmp_String = tmp_String crlf
  1071.       END
  1072.  
  1073.       tmpSupp ~ next
  1074.    END
  1075.  
  1076.    htmlText = www_tag( tmp_string crlf, ListTagOpen )
  1077.    /* make HTML text a little more readable */
  1078.    htmlText = crlf crlf || htmlComment( "Begin of" pp( ListTagOpen ) "list #" pp( ListNumber ) || "..." ) crlf htmlText crlf ||,
  1079.               htmlComment( "end of" pp( ListTagOpen ) "list #" pp( ListNumber ) || "." ) crlf
  1080.  
  1081.    RETURN htmlText
  1082. /* ------------------------------------------------------------------------------------------------ */
  1083.  
  1084.  
  1085.  
  1086.  
  1087.  
  1088.  
  1089. /* ================================================================================================ */
  1090. /* define a class for HTML-list handling (un/numbered and definition lists) */
  1091.  
  1092. :: CLASS html.reference SUBCLASS ref  PUBLIC    /* subclass the anchor-manager */
  1093.  
  1094. :: METHOD A_Name        CLASS                   /* retrieve a reference name, else create it */
  1095.    USE ARG anObject, text
  1096.  
  1097.    RETURN '<A NAME="' || self ~ createReference( anObject ) || '">'  text  "</A>"
  1098.  
  1099.  
  1100. /* ---------------------------------- */
  1101. :: METHOD A_Href        CLASS                   /* retrieve a reference name, else create it */
  1102.    USE ARG anObject,  text, htmlFile
  1103.  
  1104.    IF ARG( 3, "O" ) THEN htmlFile = ""          /* supply default empty string  */
  1105.  
  1106.    RETURN '<A HREF="' || htmlFile || "#" || self ~ getReference( anObject ) || '">' || text || "</A>"
  1107. /* ------------------------------------------------------------------------------------------------ */
  1108.  
  1109.