home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: WPS_PM / WPS_PM.zip / xfld085s.zip / 001 / tools / html2ipf.cmd
OS/2 REXX Batch file  |  1999-01-10  |  57KB  |  1,860 lines

  1. /*--------------------------------------------------------------------*/
  2. /* REXX script to convert a bunch of .html files into os/2 .ipf files */
  3. /*  which can be converted later into .inf files using ipfc compiler  */
  4. /*                                                                    */
  5. /*               Copyright (c) 1997 by FRIENDS software               */
  6. /*                         All Rights Reserved                        */
  7. /*                                                                    */
  8. /* FidoNet: 2:5030/84.5                                               */
  9. /* e-mail:  Andrew Zabolotny <bit@freya.etu.ru>                       */
  10. /*                                                                    */
  11. /* Updated 1998 by Ulrich Möller. These changes are (mostly) marked   */
  12. /* with UM in this file. Documentation also updated.                  */
  13. /* Modifications     (C) 1998 Ulrich Möller                           */
  14. /*--------------------------------------------------------------------*/
  15.  
  16. /*--------------------------------------------------------------------*/
  17. /* user-customisable section start */
  18.  
  19. /* A command to convert any image file into os/2 bmp format           */
  20. /* This script requires Image Alchemy for os/2, at least demo version */
  21. /* If someone knows of a free proggy which provides at least partial  */
  22. /* or equivalent functionality, please mail me                        */
  23. /* Global.ImageConvert = 'alchemy.exe -o -O -8 <input> <output> >nul'; */
  24.  Global.ImageConvert = 'gbmsize <input> <output>,1.1 >nul';
  25. /* Executable/description of an external WWW browser to launch when   */
  26. /* user selects an URL link. Normally, you shouldn`t change it (even  */
  27. /* if you have Netscape) since WebEx is found on almost every OS/2    */
  28. /* system, and Navigator is not.                                      */
  29.  Global.WWWbrowser = 'netscape.exe*Netscape';
  30. /* Text to be included on the "URL" pages */
  31.  Global.ExternalLinksTitle = 'Resources on the Internet';
  32.  Global.ExternalLinksText = 'This chapter contains all external links referenced in this book.'||'0d0a'x,
  33.     'Each link contained herein is an Unified Resource Locator (URL) to a certain location'||'0d0a'x,
  34.     'on the Internet. Simply double-click on one of them to launch Netscape'||'0d0a'x,
  35.     'with the respective URL.';
  36.  Global.LaunchNetscapeText = 'Click below to launch '||,
  37.     substr(Global.WWWbrowser, pos('*', Global.WWWbrowser) + 1) 'with this URL:'
  38. /* default book font; use warpsans bold for a nicer-looking books     */
  39. Global.DefaultFont = ':font facename=default size=0x0.';
  40. /* Global.DefaultFont = ':font facename=''WarpSans'' size=9x9.'; */
  41. /* fonts for headings (1 through 6)                                   */
  42.  Global.HeaderFont.1 = ':font facename=''Helv'' size=32x20.';
  43.  Global.HeaderFont.2 = ':font facename=''Helv'' size=20x12.';
  44.  Global.HeaderFont.3 = ':font facename=''Tms Rmn'' size=18x10.'
  45.  Global.HeaderFont.4 = ':font facename=''Tms Rmn'' size=16x8.'
  46.  Global.HeaderFont.5 = ':font facename=''Courier'' size=14x8.'
  47.  Global.HeaderFont.6 = ':font facename=''Helv'' size=14x10.'
  48. /* font for url links (which launches WebExplorer)                    */
  49.  Global.URLinkFont  =  ':font facename=''System VIO'' size=14x8.';
  50. /* proportional font (for <tt>...</tt>                                */
  51.  Global.ProportFont =  ':font facename=''System VIO'' size=14x8.';
  52. /* non-proportional font for CITE, CODE, TT tags */
  53.  Global.CITEFont  =    ':font facename=''Courier'' size=18x12.';
  54. /*  Global.CITEFont =  ':font facename=''System VIO'' size=14x8.'; */
  55.  
  56. /* end of user-customisable section                                   */
  57. /*--------------------------------------------------------------------*/
  58. '@echo off'
  59.  call rxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
  60.  call SysLoadFuncs
  61.  
  62.  parse arg _cmdLine
  63.  
  64. /***************** hard-coded variables **********************/
  65. /* maximal line length for ipfc :-( */
  66.  Global.maxLineLength = 256;
  67. /* unix end-of-line constant */
  68.  Global.EOL = d2c(10);
  69. /* file extensions and name of handler procedures for these; */
  70. /* all other file extensions will be ignored */
  71.  Global.TypeHandler = '*.HTML doParseHTML *.SHTML doParseHTML *.HTM doParseHTML',
  72.                       '*.HT3 doParseHTML *.HTM3 doParseHTML *.TXT doParseText',
  73.                       '*.TEXT doParseText *.CMD doParseText *.BAT doParseText',
  74.                       '*.GIF doParseImage *.JPG doParseImage *.PNG doParseImage';
  75. /* Set up some global variables */
  76.  Global.Picture.0 = 0;                     /* keep track of embedded Pictures */
  77.  Global.LinkID = 0;                         /* total number of external links */
  78.  Global.URLinks = 0;                               /* keep track of url links */
  79.  Global.Title = '';                                             /* book Title */
  80.  Global.HREF = '';   /* Speedup: keep all encountered HREFs and IMG_SRCs in a */
  81.  Global.IMGSRC = '';    /* string so we can use Pos() and WordPos() functions */
  82.  Global.SubLinks = 0;           /* This stem keeps track of the SUBLINKS tags */
  83.  Global.NoSubLinks = 0;       /* This stem keeps track of the NOSUBLINKS tags */
  84.  
  85. /* Default state for all switches */
  86.  Global.optCO = 1;  /* COlored output */
  87.  Global.optCE = 1;  /* enable CEntering */
  88.  Global.optCH = 0;  /* disable CHecking */
  89.  Global.optP = 1;   /* embed Pictures */
  90.  Global.optS = 1;   /* Sort links */
  91.  Global.optD = 0;   /* Debug log */
  92.  call AnalyseOptions;
  93.  call DefineQuotes;
  94.  
  95.  call ShowHeader;
  96.  if length(_fName) = 0
  97.   then call ShowHelp;
  98.  
  99.  Global.oName = _oName;
  100.  if length(Global.oName) = 0
  101.   then do
  102.         i = lastPos('.', _fName);
  103.         if i > 0
  104.          then Global.oName = left(_fName, i)||'ipf';
  105.          else Global.oName = _fName||'.ipf';
  106.        end;
  107.  call SetColor lCyan;
  108.  if Global.OptCH
  109.   then say 'Checking the integrity of links for '_fName;
  110.   else do
  111.         say 'Output goes into 'Global.oName;
  112.         call SysFileDelete(Global.oName);
  113.        end;
  114.  
  115.  DateTime = Date(n)', 'Time(c);
  116.  call logError ''
  117.  call logError '--- ['DateTime'] conversion started: index file '_fName;
  118.  
  119.  call putline '.*'copies('-', 76)'*';
  120.  call putline '.*'center('Converted by HTML2IPF from '_fName' at 'DateTime, 76)'*';
  121.  call putline '.*'copies('-', 76)'*';
  122.  call putline ':userdoc.';
  123.  call putline ':docprof toc=12345.';
  124.  
  125.  call time 'R'
  126.  call ParseFile _fName, 1;
  127.  do until ResolveLinks(1) = 0;
  128.   Global.Sublinks = 0;
  129.   Global.NoSublinks = 0;/* Include all unresolved sublinks */
  130.  end;
  131.  call ConvertPictures;
  132.  call OutputURLs;
  133.  
  134.  call putline ':euserdoc.';
  135.  
  136.  call SetColor lCyan;
  137.  elapsed = time('E');
  138.  say 'finished; elapsed time = 'elapsed%3600':'elapsed%60':'trunc(elapsed//60,1);
  139.  DateTime = Date(n)', 'Time(c);
  140.  call logError '--- ['DateTime'] conversion finished';
  141.  call Charout ,d2c(27)'[0m';
  142. exit;
  143.  
  144. AnalyseOptions:
  145.  _fName = ''; _oName = '';
  146.  do i = 1 to words(_cmdLine)
  147.   nw = word(_cmdLine, i);
  148.   if left(nw, 1) = '-'
  149.    then do
  150.          nw = translate(substr(nw, 2));
  151.          OptState = pos(right(nw, 1), '-+');
  152.          if OptState > 0
  153.           then nw = left(nw, length(nw) - 1);
  154.           else OptState = 2;
  155.      OptState = OptState - 1;
  156.      select
  157.           when abbrev('COLORS', nw, 2)
  158.            then Global.OptCO = OptState;
  159.           when abbrev('CENTER', nw, 2)
  160.            then Global.OptCE = OptState;
  161.           when abbrev('CHECK', nw, 2)
  162.            then Global.OptCH = OptState;
  163.           when abbrev('SORT', nw, 1)
  164.            then Global.OptS = OptState;
  165.           when abbrev('PICTURES', nw, 1)
  166.            then Global.OptP = OptState;
  167.           when abbrev('DEBUG', nw, 1)
  168.            then Global.OptD = OptState;
  169.           otherwise
  170.            do
  171.             call ShowHeader;
  172.             call SetColor lRed;
  173.             say 'Invalid option in command line: 'word(_cmdLine, i);
  174.             call ShowHelp;
  175.            end;
  176.          end;
  177.         end
  178.    else if length(_fName) = 0
  179.          then _fName = nw
  180.          else
  181.         if length(_oName) = 0
  182.          then _oName = nw
  183.          else do
  184.                call ShowHeader;
  185.                call SetColor lRed;
  186.                say 'Extra filename in command line: 'word(_cmdLine, i);
  187.                call ShowHelp;
  188.               end;
  189.  end;
  190. return;
  191.  
  192. ShowHeader:
  193.  call SetColor white
  194.  say '─┼─ HTML2IPF ─┼─ Version 0.1.0 ─┼─ Copyright (c) 1997 by FRIENDS software ─┼─'
  195. return;
  196.  
  197. ShowHelp:
  198.  call SetColor Yellow
  199.  say 'Usage: HTML2IPF [IndexFilename.HTML] {OutputFilename.IPF} {conversion options}'
  200.  call SetColor lGreen;
  201.  say '[IndexFilename.HTML]'
  202.  call SetColor Green;
  203.  say '└─┤is the "root" .HTML file to start with'
  204.  call SetColor lGreen;
  205.  say '{OutputFilename.IPF}'
  206.  call SetColor Green;
  207.  say '└─┤is the output filename (usually with the .IPF extension)'
  208.  call SetColor lGreen;
  209.  say '{conversion options}'
  210.  call SetColor Green;
  211.  say '└─┬┤are one or more of the following:'
  212.  say '  └┬┬┤-CO{LORS}{+|-}'
  213.  say '   │└┘use (+) or don`t use (-) ansi [c]olors in output'
  214.  say '   ├┬┤-CE{NTER}{+|-}'
  215.  say '   │└┘enable (+) or disable (-) processing <CENTER> tags'
  216.  say '   ├┬┤-CH{ECK}{+|-}'
  217.  say '   │└┘enable (+) or disable (-) checking files only'
  218.  say '   ├┬┤-S{ORT}{+|-}'
  219.  say '   │└┘sort (+) or don`t sort (-) links alphabetically'
  220.  say '   ├┬┤-P{ICTURES}{+|-}'
  221.  say '   │└┘include (+) or don`t include (-) [p]ictures in .IPF file'
  222.  say '   └┬┤-D{EBUG}{+|-}'
  223.  say '    └┘enable (+) or disable (-) [d]ebug logging into HTML2IPF.LOG'
  224.  call SetColor lCyan;
  225.  say 'default HTML2IPF options:'
  226.  call SetColor Cyan;
  227.  say '└─┤-COLORS+ -CENTER+ -CHECK- -SORT+ -PICTURES+ -DEBUG-'
  228. exit(1);
  229.  
  230. ConvertPictures:
  231.  procedure expose Global.;
  232.  if (\Global.optP) | (Global.OptCH) then return;
  233.  do i = 1 to Global.Picture.0
  234.   if stream(Global.Picture.i.dst, 'c', 'Query Exists') = ''
  235.    then call RunCmd Global.ImageConvert, Global.Picture.i.src, Global.Picture.i.dst;
  236.  end;
  237. return;
  238.  
  239. RunCmd:
  240.  parse arg cmd, in, out;
  241.  
  242.  call SetColor lGreen
  243.  ip = pos('<input>', cmd);
  244.  if ip <> 0 then cmd = left(cmd, ip - 1)||in||substr(cmd, ip + 7);
  245.  op = pos('<output>', cmd);
  246.  if op <> 0 then cmd = left(cmd, op - 1)||out||substr(cmd, op + 8);
  247.  cmd;
  248. return;
  249.  
  250. OutputURLs:
  251. /* make a chapter with links to internet locations */
  252.  if Global.URLinks = 0
  253.   then return;
  254.  call putline ':h1 group=99 x=right width=30%.'Global.ExternalLinksTitle;
  255.  call putline Global.DefaultFont;
  256.  call putline ':p.'Global.ExternalLinksText;
  257. /* Sort URLs alphabetically */
  258.  if Global.OptS
  259.   then do i = 1 to Global.URLinks;
  260.         ii = Global.URLinks.i;
  261.         do j = i + 1 to Global.URLinks;
  262.          ji = Global.URLinks.j;
  263.          if Global.LinkID.ji < Global.LinkID.ii
  264.           then do
  265.                 tmp = Global.URLinks.i;
  266.                 Global.URLinks.i = Global.URLinks.j;
  267.                 Global.URLinks.j = tmp;
  268.                 ii = ji;
  269.                end;
  270.         end;
  271.        end;
  272.  if Global.OptCH
  273.   then do
  274.         call SetColor LGreen;
  275.         do i = 1 to Global.URLinks
  276.          j = Global.URLinks.i;
  277.          say 'Unresolved link: 'Global.LinkID.j.RealName;
  278.          call logError '--- Unresolved link: 'Global.LinkID.j.RealName;
  279.         end;
  280.         return;
  281.        end;
  282.  Global.CurrentDir = '';
  283.  do i = 1 to Global.URLinks
  284.   j = Global.URLinks.i;
  285.   call putline ':h2 res='GetLinkID(Global.LinkID.j)' group=98 x=right y=bottom width=60% height=40%.'IPFstring(Global.LinkID.j.RealName);
  286.   call putline Global.DefaultFont;
  287.   call putline ':p.:lines align=center.';
  288.   call putline IPFstring(Global.LaunchNetscapeText);
  289.   call putline Global.URLinkFont;
  290.   call putline ':p.:link reftype=launch object='''left(Global.WWWbrowser, pos('*', Global.WWWbrowser) - 1),
  291.                              ''' data='''Global.LinkID.j.RealName'''.';
  292.   call putline IPFstring(Global.LinkID.j.RealName);
  293.   call putline ':elink.:elines.';
  294.  end;
  295. return;
  296.  
  297. /*
  298.  * ParseFile:
  299.  *      parse a HTML file; called recursively if needed
  300.  */
  301.  
  302. ParseFile:
  303.     procedure expose Global.;
  304.     parse arg fName, DeepLevel;
  305.     call SetColor Cyan;
  306.     call charout ,'Parsing 'fName' ...';
  307.  
  308.     Global.CurrentDir = '';
  309.     id = GetLinkID(fName);
  310.     if id > 0 then Global.LinkID.id.Resolved = 1;
  311.  
  312.     tmp = translate(stream(fName, 'c', 'Query Exists'), '/', '\');
  313.     if length(tmp) = 0
  314.      then do
  315.            call SetColor lRed;
  316.            say ' not found';
  317.            call logError '--- file 'fName' not found';
  318.            return;
  319.           end;
  320.     fName = Shorten(tmp);
  321.     Global.CurrentDir = fileSpec('P', translate(fName, '\', '/'));
  322.     Global.CurrentFile = fName;
  323.     call logError '--- Parsing file "'fName'" ...';
  324.  
  325.     Global.Article.linkID = '';                   /* --UM: ID for online help linking */
  326.     Global.Article.Group = 0; /* --UM: IPF group to link to for multiple windows */
  327.     Global.Article.XPos = '';                  /* --UM: IPF window display width */
  328.     Global.Article.Width = '';                 /* --UM: IPF window display width */
  329.  
  330.     Global.Article.Title = '';                                  /* Article Title */
  331.     Global.Article.line.0 = 0;                      /* count of lines in Article */
  332.     Global.Article.Hidden = 0;  /* Is current article hidden from book contents? */
  333.     Global.OpenTag.0 = 0;  /* keep track of open tags to close at end of chapter */
  334.     Global.RefEndTag = '';       /* end tag to put at next end-of-reference <\a> */
  335.     Global.IsParagraph = 0;                   /* We`re inside a <P>...</P> pair? */
  336.     Global.IsGraphic = 0;            /* (UM) We`re inside a <PRE>...</PRE> pair? */
  337.     Global.LastWasATag = 0;         /* (UM) last block was not a text, but a tag */
  338.     Global.IsTable = 0;               /* We`re inside a <TABLE>...</TABLE> pair? */
  339.     Global.IsCentered = 0;          /* We`re inside a <CENTER>...</CENTER> pair? */
  340.     Global.IsOutputEnabled = 1; /* A global switch to enable/disable text output */
  341.     Global.SkipSpaces = 0;                   /* set to 1 in lists to skip spaces */
  342.     Global.AfterBreak = 0;            /* set to 1 after .br to avoid empty lines */
  343.     call PutToken Global.EOL;                     /* initialize output subsystem */
  344.     Global.AfterBreak = 1;              /* avoid empty lines at start of Article */
  345.     Global.EOF = 0;
  346.     Global.CurFont = Global.DefaultFont;
  347.    /* Remember the count of SUBLINKS and NOSUBLINKS to restore it later */
  348.     locSublinks = Global.Sublinks;
  349.     locNoSublinks = Global.NoSublinks;
  350.  
  351.     fExt = max(lastPos('/', fName), lastPos('\', fName));
  352.     if lastPos('.', fName) > fExt
  353.      then fExt = translate(substr(fName, lastPos('.', fName) + 1))
  354.      else fExt = '';
  355.     fExt = wordpos('*.'fExt, Global.TypeHandler);
  356.     if fExt > 0
  357.      then fExt = word(Global.TypeHandler, fExt + 1)
  358.      else do
  359.            call SetColor lRed;
  360.            say ' unknown file type';
  361.            call logError '--- File 'fName': unknown type - ignored';
  362.            return;
  363.           end;
  364.  
  365.     select
  366.      when fExt = 'doParseHTML'  then call doParseHTML;
  367.      when fExt = 'doParseImage' then call doParseImage;
  368.      when fExt = 'doParseText'  then call doParseText;
  369.      otherwise call logError 'Unknown file type handler: 'fExt;
  370.     end;
  371.     call ProgressBar;
  372.     call stream Global.CurrentFile, 'c', 'close';            /* close input file */
  373.  
  374.     if length(Global.Article.Title) = 0
  375.      then Global.Article.Title = IPFstring(filespec('N', translate(fName, '\', '/')));
  376.     if (length(Global.Title) = 0)
  377.      then do
  378.            Global.Title = ':title.'Global.Article.Title;
  379.            call putline Global.Title; IndexFile = 'Y';
  380.           end;
  381.  
  382.     /* finally write text to output stream */
  383.     call putline '.* Source filename: 'fName;
  384.     if id > 0
  385.      then do
  386.            if (Global.Article.Hidden) & (IndexFile \= 'Y')
  387.             then do
  388.                   i = max(1, DeepLevel - 1);
  389.                   j = ' hide';
  390.                   Global.SubLinks = 1; Global.Sublinks.1 = '*';
  391.                  end;
  392.             else do
  393.                   i = DeepLevel;
  394.                   j = '';
  395.                  end;
  396.            if (Global.Article.Group > 0) then
  397.                j = j' group='Global.Article.Group;
  398.            if (Global.Article.XPos \= '') then
  399.                j = j' x='Global.Article.XPos;
  400.            if (Global.Article.Width \= '') then
  401.                j = j' width='Global.Article.Width;
  402.            if (Global.Article.linkID \= '') then
  403.                j = j' res='Global.Article.linkID
  404.            else j = j' res='id;
  405.            call putline ':h'i j'.'Global.Article.Title;
  406.           end;
  407.     call putline Global.DefaultFont;
  408.     call putline ':p.';
  409.     /* write all the lines */
  410.     do i = 1 to Global.Article.line.0
  411.         /* avoid empty lines, because this will lead
  412.            to spaces in the IPF viewer (UM) */
  413.         i___ = i+1;
  414.         if (length(Global.Article.line.i) > 0) then
  415.         do
  416.             if (left(Global.Article.line.i, 3) = ":p.") & (left(Global.Article.line.i___, 4) = ":li.")
  417.             then
  418.                 call putline ".br";
  419.             else
  420.                 call putline Global.Article.line.i;
  421.         end
  422.     end;
  423.  
  424.     drop Global.Article.;
  425.  
  426.     call SetColor Blue;
  427.     call charout ,' done';
  428.     call CRLF;
  429.  
  430.     call ResolveLinks DeepLevel+1;
  431.  
  432.     /* Restore the SUBLINKS and NOSUBLINKS counter */
  433.     Global.Sublinks = locSublinks;
  434.     Global.NoSublinks = locNoSublinks;
  435. return;
  436.  
  437. ResolveLinks:
  438.  procedure expose Global.;
  439.  arg DeepLevel;
  440.  LinkCount = 0;
  441.  Links.0 = 0;
  442.  
  443.  do i = 1 to Global.LinkID
  444.   if (\Global.LinkID.i.Resolved)
  445.    then do
  446.          if Global.SubLinks > 0
  447.           then do
  448.                 do j = 1 to Global.SubLinks
  449.                  if Pos(Global.SubLinks.j, translate(Global.LinkID.i.InitialName)) = 1
  450.                   then do; j = -1; leave; end;
  451.                 end;
  452.                 if j \= -1 then Iterate;
  453.                end;
  454.          do j = 1 to Global.NoSubLinks
  455.           if Pos(Global.NoSubLinks.j, translate(Global.LinkID.i.InitialName)) = 1
  456.            then do; j = -1; leave; end;
  457.          end;
  458.          if j = -1 then Iterate;
  459.          Links.0 = Links.0 + 1; j = Links.0;
  460.          Links.j = Global.LinkID.i.RealName;
  461.          Global.LinkID.i.Resolved = 1;
  462.         end;
  463.  end;
  464.  if Global.OptS
  465.   then call SortLinks 1, Links.0;
  466.  if DeepLevel > 6 then DeepLevel = 6;
  467.  do i = 1 to Links.0
  468.   call ParseFile translate(Links.i, '/', '\'), DeepLevel;
  469.   LinkCount = LinkCount + 1;
  470.  end;
  471.  drop Global.SubLinks.;
  472.  drop Global.NoSubLinks.;
  473. return LinkCount;
  474.  
  475. SortLinks:
  476.  procedure expose Links.;
  477.  arg iLeft, iRight;
  478.  
  479.  Left = iLeft; Right = iRight;
  480.  Middle = (Left + Right) % 2;
  481.  MidVar = Links.Middle;
  482.  do until Left > Right
  483.   do while Links.Left < MidVar;
  484.    Left = Left + 1;
  485.   end;
  486.   do while Links.Right > MidVar;
  487.    Right = Right - 1;
  488.   end;
  489.  
  490.   if Left <= Right
  491.    then do
  492.          tmp = Links.Left;
  493.          Links.Left = Links.Right;
  494.          Links.Right = tmp;
  495.          Left = Left + 1;
  496.          Right = Right - 1;
  497.         end;
  498.  end;
  499.  if iLeft < Right
  500.   then call SortLinks iLeft, Right;
  501.  if Left < iRight
  502.   then call SortLinks Left, iRight;
  503. return;
  504.  
  505. doParseHTML:
  506.  Global.FileContents = '';
  507.  Global.FileSize = chars(fName);                                 /* file size */
  508.  call ParseContents 'EMPTY';
  509. return;
  510.  
  511. doParseText:
  512.  Global.SubLinks = 1;
  513.  Global.SubLinks.1 = '*';           /* A plain text file cannot have sublinks */
  514.  Global.FileSize = chars(fName);                                 /* file size */
  515.  call PutToken ':lines align=left.';
  516.  call SetFont Global.ProportFont; /* draw text using proportional font */
  517.  do while chars(fName) > 0;
  518.   call ProgressBar;
  519.   Global.FileContents = charin(fName,,4096);
  520. /* remove all \0x0d Characters from output stream */
  521.   do until i = 0
  522.    i = pos(d2c(13), Global.FileContents);
  523.    if i > 0 then Global.FileContents = delstr(Global.FileContents, i, 1);
  524.   end;
  525.   call PutText Global.FileContents;
  526.  end;
  527.  call PutToken ':elines.';
  528. return;
  529.  
  530. doParseImage:
  531.  _imgBitmap = GetPictureID(fName);
  532.  if (\Global.optP) | (length(_imgBitmap) <= 1)
  533.   then do
  534.         if Global.optP
  535.          then do
  536.                call SetColor Yellow;
  537.                parse value SysCurPos() with row col;
  538.                if col > 0 then call CRLF;
  539.                say 'Warning: Picture "'Global._imgname'" missing';
  540.                call logError 'Picture "'Global._imgname'" missing';
  541.               end;
  542.         call PutText ':lines align=center.';
  543.         call PutText fName;
  544.         call PutText ':elines.';
  545.        end
  546.   else do
  547.         Global.Picture.0 = Global.Picture.0 + 1;
  548.         i = Global.Picture.0;
  549.         Global.Picture.i.dst = left(_imgBitmap, pos('*', _imgBitmap) - 1);
  550.         Global.Picture.i.src = substr(_imgBitmap, pos('*', _imgBitmap) + 1);
  551.         Global.Picture.i.alt = fName;
  552.         call PutToken ':artwork name='''Global.Picture.i.dst''' align=center.';
  553.        end;
  554. return;
  555.  
  556. /*
  557.  * ParseContents:
  558.  *      called for each HTML file.
  559.  *      Arguments: either EMPTY, HEAD, or BODY.
  560.  */
  561.  
  562. ParseContents:
  563.     procedure expose Global.;
  564.     arg TextHandler;
  565.  
  566.     /* loop thru file contents */
  567.     do until (length(Global.FileContents) = 0) & (Global.EOF)
  568.  
  569.         /* read next text chunk */
  570.         Token = GetToken();
  571.  
  572.         if left(Token, 1) = d2c(0) then
  573.         do
  574.             /* ** tag found */
  575.             Token = strip(substr(Token, 2));
  576.             /* assume everything starting with <! is not important */
  577.             if left(Token, 1) = '!'
  578.                 then iterate;
  579.             /* find out the tag */
  580.             Tag = strip(translate(Token, xrange('A','Z')'_!', xrange('a','z')'-/'));
  581.             TagBreakPos = pos(' ', Tag);
  582.             if (TagBreakPos > 0)
  583.                 then Tag = left(Tag, TagBreakPos - 1);
  584.             TagBreakPos = 0;
  585.             select
  586.                 when Tag = 'HTML' then TagBreakPos = doTagHTML();
  587.                 when Tag = '!HTML'    then TagBreakPos = doTag!HTML();
  588.                 when Tag = 'HEAD' then TagBreakPos = doTagHEAD();
  589.                 when Tag = '!HEAD'    then TagBreakPos = doTag!HEAD();
  590.                 when Tag = 'BODY' then TagBreakPos = doTagBODY();
  591.                 when Tag = '!BODY'    then TagBreakPos = doTag!BODY();
  592.                 when Tag = 'META' then TagBreakPos = doTagMETA();
  593.                 when Tag = 'TITLE'    then TagBreakPos = doTagTITLE();
  594.                 when Tag = '!TITLE'   then TagBreakPos = doTag!TITLE();
  595.                 when Tag = 'META' then TagBreakPos = doTagMETA();
  596.                 when Tag = 'A'    then TagBreakPos = doTagA();
  597.                 when Tag = '!A'   then TagBreakPos = doTag!A();
  598.                 when Tag = 'IMG'  then TagBreakPos = doTagIMG();
  599.                 when Tag = 'I'    then TagBreakPos = doTagI();
  600.                 when Tag = '!I'   then TagBreakPos = doTag!I();
  601.                 when Tag = 'B'    then TagBreakPos = doTagB();
  602.                 when Tag = '!B'   then TagBreakPos = doTag!B();
  603.                 when Tag = 'U'    then TagBreakPos = doTagU();
  604.                 when Tag = '!U'   then TagBreakPos = doTag!U();
  605.                 when Tag = 'EM'   then TagBreakPos = doTagEM();
  606.                 when Tag = '!EM'  then TagBreakPos = doTag!EM();
  607.                 when Tag = 'TT'   then TagBreakPos = doTagTT();
  608.                 when Tag = '!TT'  then TagBreakPos = doTag!TT();
  609.                 when Tag = 'P'    then TagBreakPos = doTagP();
  610.                 when Tag = '!P'   then TagBreakPos = doTag!P();
  611.                 when Tag = 'H1'   then TagBreakPos = doTagH1();
  612.                 when Tag = '!H1'  then TagBreakPos = doTag!H1();
  613.                 when Tag = 'H2'   then TagBreakPos = doTagH2();
  614.                 when Tag = '!H2'  then TagBreakPos = doTag!H2();
  615.                 when Tag = 'H3'   then TagBreakPos = doTagH3();
  616.                 when Tag = '!H3'  then TagBreakPos = doTag!H3();
  617.                 when Tag = 'H4'   then TagBreakPos = doTagH4();
  618.                 when Tag = '!H4'  then TagBreakPos = doTag!H4();
  619.                 when Tag = 'H5'   then TagBreakPos = doTagH5();
  620.                 when Tag = '!H5'  then TagBreakPos = doTag!H5();
  621.                 when Tag = 'H6'   then TagBreakPos = doTagH6();
  622.                 when Tag = '!H6'  then TagBreakPos = doTag!H6();
  623.                 when Tag = 'OL'   then TagBreakPos = doTagOL();
  624.                 when Tag = '!OL'  then TagBreakPos = doTag!OL();
  625.                 when Tag = 'UL'   then TagBreakPos = doTagUL();
  626.                 when Tag = '!UL'  then TagBreakPos = doTag!UL();
  627.                 when Tag = 'LI'   then TagBreakPos = doTagLI();
  628.                 when Tag = 'DL'   then TagBreakPos = doTagDL();
  629.                 when Tag = '!DL'  then TagBreakPos = doTag!DL();
  630.                 when Tag = 'DT'   then TagBreakPos = doTagDT();
  631.                 when Tag = 'DD'   then TagBreakPos = doTagDD();
  632.                 when Tag = 'BR'   then TagBreakPos = doTagBR();
  633.                 when Tag = 'CITE' then TagBreakPos = doTagCITE();
  634.                 when Tag = '!CITE'    then TagBreakPos = doTag!CITE();
  635.                 when Tag = 'CENTER'   then TagBreakPos = doTagCENTER();
  636.                 when Tag = '!CENTER'  then TagBreakPos = doTag!CENTER();
  637.                 when Tag = 'PRE'  then TagBreakPos = doTagPRE();
  638.                 when Tag = '!PRE' then TagBreakPos = doTag!PRE();
  639.                 when Tag = 'META' then TagBreakPos = doTagMETA();
  640.                 when Tag = 'MENU' then TagBreakPos = doTagMENU();
  641.                 when Tag = '!MENU'    then TagBreakPos = doTag!MENU();
  642.                 when Tag = 'CODE' then TagBreakPos = doTagCODE();
  643.                 when Tag = '!CODE'    then TagBreakPos = doTag!CODE();
  644.                 when Tag = 'STRONG'   then TagBreakPos = doTagSTRONG();
  645.                 when Tag = '!STRONG'  then TagBreakPos = doTag!STRONG();
  646.                 when Tag = 'ADDRESS'  then TagBreakPos = doTagADDRESS();
  647.                 when Tag = '!ADDRESS' then TagBreakPos = doTag!ADDRESS();
  648.                 when Tag = 'HR'   then TagBreakPos = doTagHR();
  649.                 when Tag = 'TABLE'    then TagBreakPos = doTagTABLE();
  650.                 when Tag = '!TABLE'   then TagBreakPos = doTag!TABLE();
  651.                 when Tag = 'TR'   then TagBreakPos = doTagTR();
  652.                 when Tag = '!TR'  then TagBreakPos = doTag!TR();
  653.                 when Tag = 'TH'   then TagBreakPos = doTagTH();
  654.                 when Tag = '!TH'  then TagBreakPos = doTag!TH();
  655.                 when Tag = 'TD'   then TagBreakPos = doTagTD();
  656.                 when Tag = '!TD'  then TagBreakPos = doTag!TD();
  657.                 when Tag = 'BLOCKQUOTE'then TagBreakPos = doTagBLOCKQUOTE();
  658.                 when Tag = '!BLOCKQUOTE'then TagBreakPos = doTag!BLOCKQUOTE();
  659.                 otherwise call logError 'Unexpected tag <'Token'>';
  660.             end; /* select */
  661.             if TagBreakPos then leave;
  662.               end;
  663.         else /* no token, but text: */
  664.             select
  665.                when TextHandler = 'EMPTY' then call doTextEMPTY;
  666.                when TextHandler = 'HEAD'  then call doTextHEAD;
  667.                when TextHandler = 'BODY'  then call doTextBODY;
  668.             end;
  669.     end;
  670. return;
  671.  
  672. ParseTag:
  673.  procedure expose Global.;
  674.  parse arg Tag;
  675.  parse var Tag Prefix Tag
  676.  Prefix = translate(Prefix);
  677.  do while length(Tag) > 0
  678.   parse value translate(Tag, ' ', Global.EOL) with subTag '=' Tag;
  679.   Tag = strip(Tag, 'leading');
  680.   if left(Tag, 1) = '"'
  681.    then parse var Tag '"' subTagValue '"' Tag
  682.    else parse var Tag subTagValue Tag;
  683.   subTag = translate(strip(subTag));
  684.   subTagValue = strip(subTagValue);
  685.   select
  686.    when Prefix = 'A'
  687.     then select
  688.           when subTag = 'AUTO'      then call doTagA_AUTO;
  689.           when subTag = 'HREF'      then call doTagA_HREF;
  690.           when subTag = 'NAME'      then call doTagA_NAME;
  691.           otherwise call logError 'Unexpected subTag 'subTag'="'subTagValue'"';
  692.          end;
  693.    when Prefix = 'IMG'
  694.     then select
  695.       when subTag = 'SRC'       then call doTagIMG_SRC;
  696.       when subTag = 'ALT'       then call doTagIMG_ALT;
  697.       when subTag = 'ALIGN'     then call doTagIMG_ALIGN;
  698.       when subTag = 'WIDTH'     then call doTagIMG_WIDTH;
  699.       when subTag = 'HEIGHT'    then call doTagIMG_HEIGHT;
  700.           otherwise call logError 'Unexpected subTag 'subTag'="'subTagValue'"';
  701.          end;
  702.    when Prefix = 'HTML'
  703.     then select
  704.       when subTag = 'HIDDEN'    then call doTagHTML_HIDDEN;
  705.       when subTag = 'SUBLINKS'  then call doTagHTML_SUBLINKS;
  706.       when subTag = 'NOSUBLINKS'    then call doTagHTML_NOSUBLINKS;
  707.  
  708.       when subTag = 'ID'        then call doTagHTML_ID;         /* --UM */
  709.       when subTag = 'GROUP'     then call doTagHTML_GROUP;      /* --UM */
  710.       when subTag = 'WIDTH'     then call doTagHTML_WIDTH;      /* --UM*/
  711.       when subTag = 'XPOS'      then call doTagHTML_XPOS;       /* --UM*/
  712.           otherwise call logError 'Unexpected subTag 'subTag'="'subTagValue'"';
  713.          end;
  714.   end;
  715.  end;
  716. return;
  717.  
  718. doTagHTML:
  719.  call ParseTag Token;
  720.  call ParseContents 'EMPTY';
  721. return 0;
  722.  
  723. doTag!HTML:
  724. return 1;
  725.  
  726. doTagHTML_ID:                           /* --UM */
  727.  Global.Article.linkID = SubtagValue;
  728. return 0;
  729.  
  730. doTagHTML_GROUP:                        /* --UM */
  731.  Global.Article.Group = SubtagValue;
  732. return 0;
  733.  
  734. doTagHTML_WIDTH:                        /* --UM */
  735.  Global.Article.Width = SubtagValue;
  736. return 0;
  737.  
  738. doTagHTML_XPOS:                         /* --UM */
  739.  Global.Article.XPos = SubtagValue;
  740. return 0;
  741.  
  742. doTagHTML_HIDDEN:
  743.  Global.Article.Hidden = 1;
  744. return 0;
  745.  
  746. doTagHTML_SUBLINKS:
  747.  Global.SubLinks = Global.SubLinks + 1;
  748.  i = Global.SubLinks;
  749.  Global.SubLinks.i = translate(SubTagValue);
  750. return 0;
  751.  
  752. doTagHTML_NOSUBLINKS:
  753.  Global.NoSubLinks = Global.NoSubLinks + 1;
  754.  i = Global.NoSubLinks;
  755.  Global.NoSubLinks.i = translate(SubTagValue);
  756. return 0;
  757.  
  758. doTagHEAD:
  759.  Global.grabTitle = 0;
  760.  call ParseContents 'HEAD';
  761. return 0;
  762.  
  763. doTag!HEAD:
  764.  Global.grabTitle = 0;
  765. return 1;
  766.  
  767. doTagBODY:
  768.  Global.grabTitle = 0;
  769.  call ParseContents 'BODY';
  770. return 0;
  771.  
  772. doTag!BODY:
  773. return 1;
  774.  
  775. doTagTITLE:
  776.  Global.grabTitle = 1;
  777.  Global.Article.Title = '';
  778. return 0;
  779.  
  780. doTag!TITLE:
  781.  Global.grabTitle = 0;
  782. return 0;
  783.  
  784. doTagEM:
  785. doTagI:
  786.  call PutToken ":hp1.";
  787. return 0;
  788.  
  789. doTag!EM:
  790. doTag!I:
  791.  call PutToken ":ehp1.";
  792. return 0;
  793.  
  794. doTagB:
  795. doTagSTRONG:
  796.  call PutToken ':hp2.';
  797. return 0;
  798.  
  799. doTag!B:
  800. doTag!STRONG:
  801.  call PutToken ':ehp2.';
  802. return 0;
  803.  
  804. doTagU:
  805.  call PutToken ':hp5.';
  806. return 0;
  807.  
  808. doTag!U:
  809.  call PutToken ':ehp5.';
  810. return 0;
  811.  
  812. /* doTagEM:
  813.  call PutToken ':hp3.';
  814. return 0;
  815.  
  816. doTag!EM:
  817.  call PutToken ':ehp3.';
  818. return 0; */
  819.  
  820. doTagCITE:
  821. doTagCODE:
  822. doTagTT:
  823.  call SetFont Global.CITEFont;
  824. return 0;
  825.  
  826. doTag!CITE:
  827. doTag!CODE:
  828. doTag!TT:
  829.  call SetFont Global.DefaultFont;
  830. return 0;
  831.  
  832. doTagBLOCKQUOTE:
  833. doTagP:
  834.  call NewLine;
  835.  call PutToken ':p.';
  836.  Global.IsParagraph = 1;
  837. return 0;
  838.  
  839. doTag!BLOCKQUOTE:
  840. doTag!P:
  841.  call NewLine;
  842.  Global.IsParagraph = 0;
  843. return 0;
  844.  
  845. doTagBR:
  846.     if Global.IsTable then
  847.         return 0; /* IPFC does not allow .br`s in tables */
  848.     Global.AfterBreak = 0;
  849.     call NewLine;
  850.     call PutToken '.br';
  851.     call NewLine;
  852.     Global.AfterBreak = 1;
  853.     if doCheckTag(':eul.') | doCheckTag(':edl.') | doCheckTag(':eol.') then
  854.         Global.SkipSpaces = 1;
  855. return 0;
  856.  
  857. doTagPRE:
  858.     call NewLine;
  859.     call PutToken ':cgraphic.';
  860.     Global.IsGraphic = 1;   /* UM */
  861. return 0;
  862.  
  863. doTag!PRE:
  864.     call PutToken ':ecgraphic.';
  865.     Global.IsGraphic = 0;   /* UM */
  866.     /* HTML compatibility: add extra line break (UM)*/
  867.     Global.AfterBreak = 0;
  868.     call NewLine;
  869.     call PutToken ':p.';
  870.     call NewLine;
  871.     Global.AfterBreak = 1;
  872.     if doCheckTag(':eul.') | doCheckTag(':edl.') | doCheckTag(':eol.') then
  873.         Global.SkipSpaces = 1;
  874.     /* call PutToken ':p.'; */
  875. return 0;
  876.  
  877. doTagH_begin:
  878.     arg i;
  879.     call NewLine;
  880.     if \Global.IsTable
  881.      then do; call PutToken '.br'; call NewLine; end;
  882.     call SetFont Global.HeaderFont.i;
  883.     if \Global.IsTable
  884.      then do; call NewLine; call PutToken '.br'; end;
  885.     call NewLine;
  886.     Global.AfterBreak = 1;
  887. return;
  888.  
  889. doTagH1:
  890.  call doTagH_begin 1;
  891. return 0;
  892.  
  893. doTagH2:
  894.  call doTagH_begin 2;
  895. return 0;
  896.  
  897. doTagH3:
  898.  call doTagH_begin 3;
  899. return 0;
  900.  
  901. doTagH4:
  902.  call doTagH_begin 4;
  903. return 0;
  904.  
  905. doTagH5:
  906.  call doTagH_begin 5;
  907. return 0;
  908.  
  909. doTagH6:
  910.  call doTagH_begin 6;
  911. return 0;
  912.  
  913. doTag!H1:
  914. doTag!H2:
  915. doTag!H3:
  916. doTag!H4:
  917. doTag!H5:
  918. doTag!H6:
  919.  call SetFont Global.DefaultFont;
  920.  if \Global.IsTable
  921.   then do
  922.         call NewLine; call PutToken '.br'; call NewLine;
  923.         call NewLine; call PutToken '.br';
  924.        end;
  925.  call NewLine;
  926.  Global.AfterBreak = 1;
  927. return 0;
  928.  
  929. doTagHR:
  930.  call NewLine;
  931.  call PutToken ':cgraphic.'copies('─', 80)':ecgraphic.';
  932.  call doTagBR;
  933. return 0;
  934.  
  935. doTagOL:
  936.  if Global.IsTable
  937.   then return 0;
  938.  call doOpenOL;
  939. return 0;
  940.  
  941. doTag!OL:
  942.  if Global.IsTable
  943.   then return 0;
  944.  call NewLine;
  945.  call doCloseTag ':eol.';
  946.  call doTagBR;              /* UM */
  947. return 0;
  948.  
  949. doTagMENU:
  950. doTagUL:
  951.  if Global.IsTable
  952.     then return 0;
  953.  call doOpenUL;
  954. return 0;
  955.  
  956. doTag!MENU:
  957. doTag!UL:
  958.  if Global.IsTable
  959.     then return 0;
  960.  call NewLine;
  961.  call doCloseTag ':eul.';
  962.  call doTagBR;              /* UM */
  963. return 0;
  964.  
  965. doTagLI:
  966.     if Global.IsTable
  967.         then return 0;
  968.     if (doCheckTag(':eul.') = 0) & (doCheckTag(':eol.') = 0)
  969.         then call doOpenUL;
  970.     call NewLine;
  971.     call PutToken ':li.';
  972.     call NewLine;
  973.     Global.SkipSpaces = 1;
  974. return 0;
  975.  
  976. doTagDL:
  977.  if Global.IsTable
  978.     then return 0;
  979.  call doOpenDL;
  980. return 0;
  981.  
  982. doTag!DL:
  983.  if Global.IsTable
  984.   then return 0;
  985.  call NewLine;
  986.  if \Global.DLDescDefined
  987.   then call doTagDD;
  988.  call doCloseTag ':edl.';
  989. return 0;
  990.  
  991. doTagDT:
  992.  if Global.IsTable
  993.   then return 0;
  994.  if doCheckTag(':edl.') = 0
  995.   then call doOpenDL;
  996.  call NewLine; call PutToken ':dt.';
  997.  Global.SkipSpaces = 1;
  998.  Global.DLTermDefined = 1;
  999.  Global.DLDescDefined = 0;
  1000. return 0;
  1001.  
  1002. doTagDD:
  1003.  if Global.IsTable
  1004.   then return 0;
  1005.  if doCheckTag(':edl.') = 0
  1006.   then call doOpenDL;
  1007.  call NewLine;
  1008.  if \Global.DLTermDefined
  1009.   then call doTagDT;
  1010.  call PutToken ':dd.';
  1011.  Global.SkipSpaces = 1;
  1012.  Global.DLTermDefined = 0;
  1013.  Global.DLDescDefined = 1;
  1014. return 0;
  1015.  
  1016. doTagA:
  1017.  call CloseRef;
  1018.  call ParseTag Token;
  1019. return 0;
  1020.  
  1021. doTag!A:
  1022.  call CloseRef;
  1023. return 0;
  1024.  
  1025. doTagA_HREF:
  1026.  i = GetLinkID(subTagValue);
  1027.  if i > 0 then do /* changed, --UM */
  1028.     _data = translate(Global.LinkID.i)
  1029.     if (pos('.INF', _data) > 0) then do
  1030.         if (pos('#', _data) > 0) then
  1031.             _data = translate(Global.LinkID.i, " ", "#")
  1032.         else _data = Global.LinkID.i
  1033.         call PutToken ":link reftype=launch object='view.exe' data='"_data"'.";
  1034.         Global.CurLink = i;
  1035.         Global.RefEndTag = ':elink.'||Global.RefEndTag;
  1036.     end
  1037.     else do
  1038.         call PutToken ':link reftype=hd res='i'.';
  1039.         Global.CurLink = i;
  1040.         Global.RefEndTag = ':elink.'||Global.RefEndTag;
  1041.     end;
  1042.   end
  1043. return 0;
  1044.  
  1045. doTagA_AUTO: /* new, --UM */
  1046.  i = GetLinkID(subTagValue);
  1047.  if i > 0 then do /* changed, UM */
  1048.     call PutToken ':link reftype=hd res='i' auto dependent.';
  1049.     Global.CurLink = i;
  1050.   end
  1051. return 0;
  1052.  
  1053. doTagA_NAME:
  1054.  /* ignore */
  1055. return 0;
  1056.  
  1057. doTagIMG:
  1058.  Global._altName = 'missing Picture';
  1059.  Global._imgName = '';
  1060.  if Global.IsCentered /* Choose default picture alignment */
  1061.   then Global._imgAlign = 'center';
  1062.   else Global._imgAlign = 'left';
  1063.  call ParseTag Token;
  1064.  _imgBitmap = GetPictureID(Global._imgName);
  1065.  if (\Global.optP) | (length(_imgBitmap) <= 1),
  1066.     | Global.IsTable       /* Since IPF does not allow pictures in tables :-( */
  1067.   then do
  1068.         if Global.optP & \Global.IsTable
  1069.          then do
  1070.                call SetColor Yellow;
  1071.                parse value SysCurPos() with row col;
  1072.                if col > 0 then call CRLF;
  1073.                say 'Warning: Picture "'Global._imgName'" missing';
  1074.                call logError 'Picture "'Global._imgName'" missing';
  1075.               end;
  1076.         call PutText ' 'Global._altName' ';
  1077.        end
  1078.   else do
  1079.         if pos(':elink.', Global.RefEndTag) > 0
  1080.          then do /* image is a link */
  1081.                call PutToken ':elink.';
  1082.               end;
  1083.         if Global.IsParagraph
  1084.          then call PutToken Global.EOL;
  1085.         Global.Picture.0 = Global.Picture.0 + 1;
  1086.         i = Global.Picture.0;
  1087.         Global.Picture.i.dst = left(_imgBitmap, pos('*', _imgBitmap) - 1);
  1088.         Global.Picture.i.src = substr(_imgBitmap, pos('*', _imgBitmap) + 1);
  1089.         Global.Picture.i.alt = Global._altName;
  1090.         call PutToken ':artwork name='''Global.Picture.i.dst''' align='Global._imgAlign;
  1091.         if Global.IsParagraph
  1092.          then call PutToken ' runin.';
  1093.          else call PutToken '.';
  1094.         if pos(':elink.', Global.RefEndTag) > 0
  1095.          then do /* image is a link */
  1096.                call PutToken ':artlink.:link reftype=hd res='Global.CurLink'.:eartlink.';
  1097.                call PutToken ':link reftype=hd res='Global.CurLink'.';
  1098.               end;
  1099.        end;
  1100. return 0;
  1101.  
  1102. doTagIMG_ALIGN:
  1103.  if pos('<'translate(subTagValue)'>', '<LEFT><RIGHT><CENTER>') > 0
  1104.   then Global._imgAlign = subTagValue;
  1105. return 0;
  1106.  
  1107. doTagIMG_SRC:
  1108.  Global._imgName = subTagValue;
  1109. return 0;
  1110.  
  1111. doTagIMG_ALT:
  1112.  Global._altName = subTagValue;
  1113. return 0;
  1114.  
  1115. doTagIMG_WIDTH:
  1116. doTagIMG_HEIGHT:
  1117. /* nop */
  1118. return 0;
  1119.  
  1120. doTagADDRESS:
  1121. /* nop */
  1122. return 0;
  1123.  
  1124. doTag!ADDRESS:
  1125. /* nop */
  1126. return 0;
  1127.  
  1128. doTagMETA:
  1129. /* nop */
  1130. return 0;
  1131.  
  1132. doTagCENTER:
  1133.  if \Global.OptCE
  1134.   then return 0;
  1135.  Global.IsCentered = 1;
  1136.  call PutToken ':lines align=center.';
  1137. return 0;
  1138.  
  1139. doTag!CENTER:
  1140.  if \Global.OptCE
  1141.   then return 0;
  1142.  if Global.IsCentered
  1143.   then do
  1144.         Global.IsCentered = 0;
  1145.         call NewLine;
  1146.         call PutToken ':elines.';
  1147.        end;
  1148. return 0;
  1149.  
  1150. doTagTABLE:
  1151.  Global.Table.WasCentered = Global.IsCentered;
  1152.  if Global.IsCentered
  1153.   then call doTag!CENTER;
  1154.  call NewLine;
  1155.  Global.AfterBreak = 0;
  1156.  call PutToken '.* table';
  1157.  Global.Table.Begin = Global.Article.Line.0;
  1158.  call NewLine;
  1159.  Global.Table.Width = 0;
  1160.  Global.Table.MaxWidth = 0;
  1161.  Global.AfterBreak = 1;
  1162.  Global.IsTable = 1;
  1163.  Global.IsOutputEnabled = 0;
  1164. return 0;
  1165.  
  1166. doTag!TABLE:
  1167.  call NewLine;
  1168.  if (Global.IsTable)
  1169.   then do
  1170.         i = Global.Table.Begin;
  1171.         if Global.Table.MaxWidth > 0
  1172.          then ColWidth = (79 - Global.Table.MaxWidth) % Global.Table.MaxWidth
  1173.          else tableCols = 78;
  1174.         tableCols = '';
  1175.         do j = 1 to Global.Table.MaxWidth
  1176.          tableCols = tableCols' 'ColWidth;
  1177.         end;
  1178.         if \Global.OptCH
  1179.          then Global.Article.Line.i = ':table cols='''substr(tableCols, 2)'''.';
  1180.         call PutToken ':etable.';
  1181.        end;
  1182.  Global.Table.Begin = 0;
  1183.  Global.IsTable = 0;
  1184.  Global.IsOutputEnabled = 1;
  1185.  if Global.Table.WasCentered
  1186.   then call doTagCENTER;
  1187. return 0;
  1188.  
  1189. doTagTR:
  1190.  call PutToken ':row.';
  1191.  call PutToken Global.EOL;
  1192.  Global.IsOutputEnabled = 0;
  1193. return 0;
  1194.  
  1195. doTag!TR:
  1196.  call CloseRef;
  1197.  if Global.Table.Width > Global.Table.MaxWidth
  1198.   then Global.Table.MaxWidth = Global.Table.Width;
  1199.  Global.Table.Width = 0;
  1200. return 0;
  1201.  
  1202. doTagTH:
  1203.  Global.IsOutputEnabled = 1;
  1204.  Global.Table.Width = Global.Table.Width + 1;
  1205.  call PutToken ':c.'; call doTagU;
  1206. return 0;
  1207.  
  1208. doTag!TH:
  1209.  call CloseRef;
  1210.  call doTag!U;
  1211. return 0;
  1212.  
  1213. doTagTD:
  1214.  Global.IsOutputEnabled = 1;
  1215.  Global.Table.Width = Global.Table.Width + 1;
  1216.  call PutToken ':c.';
  1217. return 0;
  1218.  
  1219. doTag!TD:
  1220.  call CloseRef;
  1221. return 0;
  1222.  
  1223. doTextEMPTY:
  1224.  Token = translate(Token, ' ', xrange(d2c(0),d2c(31)));
  1225.  if length(strip(Token)) > 0
  1226.   then call logError 'Unexpected text 'Token;
  1227. return;
  1228.  
  1229. doTextHEAD:
  1230.  if Global.grabTitle = 1
  1231.   then Global.Article.Title = Global.Article.Title||IPFstring(translate(Token, '  ', d2c(9)d2c(10)))
  1232.   else call dotextempty;
  1233. return;
  1234.  
  1235. /*
  1236.  * doTextBODY:
  1237.  *      called by ParseContents for plain text.
  1238.  *      Token normally contains a whole paragraph (<P>Token</P>.
  1239.  */
  1240.  
  1241. doTextBODY:
  1242.     call PutText Token;
  1243. return;
  1244.  
  1245. doOpenOL:
  1246.  call NewLine;
  1247.  call doOpenTag ':ol compact.',':eol.';
  1248. return;
  1249.  
  1250. doOpenUL:
  1251.  call NewLine;
  1252.  call doOpenTag ':ul compact.',':eul.';
  1253. return;
  1254.  
  1255. doOpenDL:
  1256.  call NewLine;
  1257.  call doOpenTag ':dl compact break=all.', ':edl.';
  1258.  Global.DLTermDefined = 0;
  1259.  Global.DLDescDefined = 0;
  1260. return;
  1261.  
  1262. CloseRef:
  1263.  call PutToken Global.RefEndTag;
  1264.  Global.RefEndTag = '';
  1265. return;
  1266.  
  1267. /* recursive Tags management */
  1268. doOpenTag:
  1269.  parse arg ot, ct;
  1270.  call PutToken ot;
  1271.  Global.OpenTag.0 = Global.OpenTag.0 + 1;
  1272.  i = Global.OpenTag.0;
  1273.  Global.OpenTag.i = ct;
  1274.  Global.OpenTag.i.open = ot;
  1275. return;
  1276.  
  1277. /*
  1278.  * doCloseTag:
  1279.  *      puts out closing tags (</UL> etc.).
  1280.  *      Arguments: IPF closing tag (e.g. ":eul.")
  1281.  */
  1282.  
  1283. doCloseTag:
  1284.     parse arg bottom;
  1285.     if (length(bottom) = 0) then
  1286.         i = 1
  1287.     else do i = Global.OpenTag.0 to 0 by -1
  1288.         if bottom = Global.OpenTag.i
  1289.             then leave;
  1290.         end;
  1291.  
  1292.     if (i > 0)
  1293.     then do
  1294.         call NewLine;
  1295.         do j = Global.OpenTag.0 to i by -1
  1296.             call PutToken Global.OpenTag.j;
  1297.             call PutToken Global.EOL;
  1298.         end;
  1299.         Global.OpenTag.0 = i - 1;
  1300.         return 1;
  1301.     end;
  1302. return 0;
  1303.  
  1304. doCheckTag:
  1305.  parse arg SearchArg;
  1306.  do i = Global.OpenTag.0 to 1 by -1
  1307.   if pos(SearchArg, Global.OpenTag.i) > 0
  1308.    then return 1;
  1309.  end;
  1310. return 0;
  1311.  
  1312. /* Set the current font in output stream */
  1313. SetFont:
  1314.  parse arg Font;
  1315.  if Global.IsTable
  1316.   then return;
  1317.  if Global.CurFont = Font
  1318.   then return;
  1319.  Global.CurFont = Font;
  1320.  call PutToken Font;
  1321. return;
  1322.  
  1323. /* Get id number depending of link value (<A HREF=...>) */
  1324. /* Returns 0 if link belongs to same page (alas, IPF doesn`t permit this...) */
  1325. GetLinkID:
  1326.  procedure expose Global.;
  1327.  parse arg link;
  1328.  
  1329.  InitialLink = link;
  1330.  if (pos('#', link) > 0) & (pos('.INF', translate(link)) = 0)
  1331.   then link = left(link, pos('#', link) - 1);
  1332.  if length(link) = 0
  1333.   then return 0;
  1334.  link = FindFile(link);
  1335.  ulink = translate(link);
  1336.  i = wordpos(ulink, Global.HREF);
  1337.  if i > 0 then return i;
  1338.  Global.LinkID = Global.LinkID + 1;
  1339.  i = Global.LinkID;
  1340.  Global.LinkID.i = ulink;
  1341.  Global.LinkID.i.RealName = link;
  1342.  Global.LinkID.i.InitialName = InitialLink;
  1343.  Global.HREF = Global.HREF||ulink||' ';
  1344.  if (pos(".INF", translate(link)) > 0 ) then do /* inserted, UM */
  1345.         Global.LinkID.i.Resolved = 1;
  1346.  end
  1347.  else if (length(stream(link, 'c', 'query exists')) = 0)
  1348.   then do
  1349.         Global.LinkID.i.Resolved = 1;
  1350.         Global.URLinks = Global.URLinks + 1;
  1351.         j = Global.URLinks;
  1352.         Global.URLinks.j = i;
  1353.         parse var link prot ':' location;
  1354.         if (length(location) = 0) | (pos('/', prot) > 0)
  1355.          then Global.LinkID.i.RealName = filespec('N', translate(link, '\', '/'))
  1356.        end;
  1357.   else Global.LinkID.i.Resolved = 0;
  1358. return i;
  1359.  
  1360. /* transform image extension into .bmp */
  1361. GetPictureID:
  1362.  procedure expose Global.;
  1363.  parse arg PictName;
  1364.  
  1365.  PictName = FindFile(PictName);
  1366.  if length(stream(PictName, 'c', 'query exists')) > 0
  1367.   then do
  1368.         tmp = PictName;
  1369.         i = lastPos('.', tmp);
  1370.         if i > 0
  1371.          then PictName = left(tmp, i)||'bmp';
  1372.          else PictName = tmp||'.bmp';
  1373.        end
  1374.   else do
  1375.         tmp = '';
  1376.         PictName = '';
  1377.        end;
  1378. return PictName||'*'||tmp;
  1379.  
  1380. /* Actively search for file on all possible paths */
  1381. FindFile:
  1382.  parse arg fName;
  1383.  
  1384.  ifName = fName;
  1385.  parse var fName prot ':' location;
  1386.  if (length(location) > 0) & (pos('/', prot) = 0)
  1387.   then fName = location;
  1388.  tmp = '';
  1389.  do while length(fName) > 0
  1390.   do while pos(left(fName, 1), '/\') > 0
  1391.    fName = substr(fName, 2);
  1392.   end;
  1393.   if length(fName) = 0
  1394.    then leave;
  1395.   tmp = stream(fName, 'c', 'query exists');
  1396.   if length(tmp) > 0 then return Shorten(tmp);
  1397.   tmp = stream(Global.CurrentDir||fName, 'c', 'query exists');
  1398.   if length(tmp) > 0 then return Shorten(tmp);
  1399.   tmp1 = Pos('/', fName);
  1400.   tmp2 = Pos('\', fName);
  1401.   if (tmp2 < tmp1) & (tmp2 > 0)
  1402.    then tmp = tmp2
  1403.    else tmp = tmp1;
  1404.   if tmp > 0
  1405.    then fName = substr(fName, tmp)
  1406.    else fName = '';
  1407.  end;
  1408. return ifName;
  1409.  
  1410. /*
  1411.  * GetToken:
  1412.  *      return next Token (a Tag or a text string) from input stream
  1413.  */
  1414.  
  1415. GetToken:
  1416.     procedure expose Global.;
  1417.     if (length(Global.FileContents) < 512) & (\Global.EOF) then
  1418. GetData:
  1419.         do
  1420.             /* read next chunk of file */
  1421.             Global.FileContents = Global.FileContents||charin(Global.CurrentFile,,1024);
  1422.             call ProgressBar;
  1423.             /* remove all \0x0d Characters from input stream */
  1424.             do until i = 0
  1425.                 i = pos('0D'x, Global.FileContents);
  1426.                 if i > 0 then Global.FileContents = delstr(Global.FileContents, i, 1);
  1427.             end;
  1428.             Global.EOF = (chars(Global.CurrentFile) = 0);
  1429.         end;
  1430.  
  1431.     i = pos('<', Global.FileContents);
  1432.     if (i = 0) then
  1433.         if (\Global.EOF)
  1434.             then signal GetData;
  1435.         else do
  1436.               i = length(Global.FileContents) + 1;
  1437.               if i = 1 then return '';
  1438.         end;
  1439.     if (i = 1)
  1440.         then do
  1441.             j = pos('>', Global.FileContents);
  1442.             if (j = 0) then
  1443.                 if \Global.EOF then
  1444.                     signal GetData;
  1445.                 else
  1446.                     j = length(Global.FileContents) + 1;
  1447.             Token = '00'x||substr(Global.FileContents, 2, j - 2);
  1448.             Global.FileContents = substr(Global.FileContents, j + 1);
  1449.         end
  1450.         else do
  1451.             Token = NoQuotes(left(Global.FileContents, i - 1));
  1452.             Global.FileContents = substr(Global.FileContents, i);
  1453.         end;
  1454. return Token;
  1455.  
  1456. /*
  1457.  * PutToken:
  1458.  *      put an IPF Token into Global.Article.Line. stem
  1459.  */
  1460.  
  1461. PutToken:
  1462.     procedure expose Global.;
  1463.     parse arg Output;
  1464.  
  1465.     if Global.OptCH then return;
  1466.  
  1467.     if (Output = Global.EOL) then
  1468.         if (Global.AfterBreak)
  1469.             then Global.AfterBreak = 0;
  1470.             else do
  1471.                  Global.Article.line.0 = Global.Article.line.0 + 1;
  1472.                  i = Global.Article.line.0;
  1473.                  Global.Article.line.i = '';
  1474.             end;
  1475.         else do
  1476.             Global.AfterBreak = 0;
  1477.             i = Global.Article.line.0;
  1478.             if length(Global.Article.line.i) + length(Output) > Global.maxLineLength
  1479.             then do
  1480.                 call PutToken Global.EOL;
  1481.                 i = Global.Article.line.0;
  1482.             end;
  1483.             Global.Article.line.i = Global.Article.line.i||Output;
  1484.         end;
  1485. return;
  1486.  
  1487. /*
  1488.  * PutText:
  1489.  *      add a text line to the Global.Article.Line. stem;
  1490.  *      if EOLs are present, string is subdivided
  1491.  */
  1492.  
  1493. PutText:
  1494.     procedure expose Global.;
  1495.     parse arg Output;  /* text to output */
  1496.  
  1497.     if (Global.OptCH) then
  1498.         return;
  1499.  
  1500.     if (Global.IsTable) & (\Global.IsOutputEnabled)
  1501.         then return; /* Skip everything out of :c. ... :c. or :row. tags */
  1502.  
  1503.     if (Global.SkipSpaces)
  1504.         then Output = strip(strip(Output, 'leading'), 'leading', d2c(9));
  1505.  
  1506.     do while (length(Output) > 0)
  1507.         EOLpos = pos(Global.EOL, Output);
  1508.         if (EOLpos > 0) then
  1509.         do  /* EOL in this block of text: */
  1510.             if (EOLpos > 1) then
  1511.                 _text_ = left(Output, EOLpos - 1);
  1512.             Output = substr(Output, EOLpos + 1);
  1513.             if EOLpos > 1 then
  1514.                 call PutText _text_;
  1515.             call PutToken Global.EOL;
  1516.         end;
  1517.         else do
  1518.             /* no EOL in this block of text: */
  1519.             Global.SkipSpaces = 0;
  1520.             /* replace tab Characters with needed number of spaces */
  1521.             curpos = -1;
  1522.             do forever
  1523.                 tabpos = pos(d2c(9), Output);
  1524.                 if (tabpos = 0) then
  1525.                     /* no tabs found */
  1526.                     leave; /* end do */
  1527.                 /* else tab found: */
  1528.                 if curpos = -1 /* effective position not yet computed? */
  1529.                 then do
  1530.                     i = Global.Article.line.0;
  1531.                     tmpS = Global.Article.line.i;
  1532.                     curpos = 0;
  1533.                     do while length(tmpS) > 0
  1534.                         if pos(left(tmpS, 1), '&:.') > 0
  1535.                         then do
  1536.                             EOLpos = pos('.', tmpS, 2);
  1537.                             if EOLpos = 0 then leave;
  1538.                             if left(tmpS, 1) = '&' then
  1539.                                 tmpS = substr(tmpS, EOLpos);
  1540.                             else do; tmpS = substr(tmpS, EOLpos + 1); iterate; end;
  1541.                         end;
  1542.                         curpos = curpos + 1; tmpS = substr(tmpS, 2);
  1543.                     end;
  1544.                 end;
  1545.                 Output = left(Output, tabpos - 1)||copies(' ',
  1546.                                 ,8 - (curpos + tabpos - 1)//8),
  1547.                                 ||substr(Output, tabpos + 1);
  1548.             end;
  1549.             /* make text IPF-compatible */
  1550.             Output = IPFstring(Output);
  1551.  
  1552.             /* subdivide Output string if it is too long */
  1553.             i = Global.Article.line.0;
  1554.             do while (length(Global.Article.line.i) + length(Output) > Global.maxLineLength)
  1555.                 EOLpos = Global.maxLineLength - length(Global.Article.line.i);
  1556.                 j = EOLpos;
  1557.                 do while (EOLpos > 0)
  1558.                     if (c2d(substr(Output, EOLpos, 1)) <= 32) then Leave;
  1559.                     EOLpos = EOLpos - 1;
  1560.                 end;
  1561.                 if (EOLpos = 0) & (length(Global.Article.line.i) = 0)
  1562.                 then do
  1563.                     /* Line cannot be split on word-bound :-( */
  1564.                     EOLpos = j;
  1565.                     _text_ = left(Output, EOLpos - 1);
  1566.                     Output = substr(Output, EOLpos);
  1567.                 end
  1568.                 else do
  1569.                     if EOLpos > 1 then
  1570.                         _text_ = left(Output, EOLpos - 1)
  1571.                     else _text_ = '';
  1572.                     Output = substr(Output, EOLpos + 1);
  1573.                 end;
  1574.                 Global.Article.line.i = Global.Article.line.i||_text_;
  1575.  
  1576.                 call PutToken Global.EOL;
  1577.  
  1578.                 i = Global.Article.line.0;
  1579.             end;
  1580.             Global.Article.line.i = Global.Article.line.i||Output;
  1581.  
  1582.             Output = '';
  1583.         end;
  1584.     end;
  1585.  
  1586.     /* remove leading spaces, but only if we're not in <PRE> mode (UM) */
  1587.     if (\Global.IsGraphic) then
  1588.             Global.Article.line.i = strip(Global.Article.line.i, "leading");
  1589.     Global.AfterBreak = 0;
  1590. return;
  1591.  
  1592. PutLine:
  1593.  parse arg str;
  1594.  if Global.OptCH then return;
  1595.  call lineout Global.oName, str;
  1596. return;
  1597.  
  1598. NewLine:
  1599.  nli = Global.Article.line.0;
  1600.  if length(Global.Article.line.nli) > 0
  1601.   then do; call PutToken Global.EOL; return 1; end;
  1602. return 0;
  1603.  
  1604. /*
  1605.  * IPFstring:
  1606.  *      this makes a block of regular HTML text
  1607.  *      IPF-compatible
  1608.  */
  1609.  
  1610. IPFstring:
  1611.  parse arg ins;
  1612.  return ChangeStr(d2c(0), ,
  1613.          ChangeStr(':', ,
  1614.           ChangeStr('&', ,
  1615.            ChangeStr('.', ins, d2c(0)'per.'), ,
  1616.           '&.'), ,
  1617.          '&colon.'), ,
  1618.         '&');
  1619. /*
  1620.  ins = StrReplace('.', d2c(0)'per.', ins);
  1621.  ins = StrReplace('&', '&.', ins);
  1622.  ins = StrReplace(':', '&colon.', ins);
  1623. return StrReplace(d2c(0), '&', ins);
  1624. */
  1625. ChangeStr:
  1626.  procedure expose Global.;
  1627.  parse arg src,var,trg;
  1628.  curpos = 1;
  1629.  do forever
  1630.   curpos = pos(src, var, curpos);
  1631.   if curpos = 0 then leave;
  1632.   var = left(var, curpos - 1)||trg||substr(var, curpos + 1);
  1633.   curpos = curpos + length(trg);
  1634.  end;
  1635. return var;
  1636.  
  1637. Shorten:
  1638.  parse arg fName;
  1639.  fName = translate(stream(fName, 'c', 'query exists'), '/', '\');
  1640.  tmp = translate(Directory(), '/', '\');
  1641.  if Pos(tmp, fName) = 1
  1642.   then return substr(fName, length(tmp) + 2);
  1643.  if substr(fName, 2, 1) = ':'
  1644.   then return substr(fName, 3);
  1645. return fName;
  1646.  
  1647. logError:
  1648.  procedure expose Global.;
  1649.  if Global.optD
  1650.   then do
  1651.         parse arg line;
  1652.         call lineout 'HTML2IPF.log', line;
  1653.        end;
  1654. return;
  1655.  
  1656. CRLF:
  1657.  parse value SysTextScreenSize() with maxrow maxcol;
  1658.  parse value SysCurPos() with row col;
  1659.  call charout ,copies(' ', maxcol-col);
  1660. return;
  1661.  
  1662. ProgressBar:
  1663.  parse value SysCurPos() with row col;
  1664.  if col > 79 - 18 then say '';
  1665.  Rest = ((Global.FileSize - chars(Global.CurrentFile)) * 16) % Global.FileSize;
  1666.  call setcolor lcyan; call charOut ,'[';
  1667.  call setcolor white; call charOut ,copies('█', Rest)copies('▒', 16-Rest);
  1668.  call setcolor lcyan; call charOut ,']'copies('08'x, 18);
  1669. return;
  1670.  
  1671. SetColor:
  1672.  arg col;
  1673.  col = ColorNo(col);
  1674.  if \Global.optCO then return;
  1675.  
  1676.  if col = -1 then return -1;
  1677.  if col > 7
  1678.   then col = '1;3'col-8;
  1679.   else col = '0;3'col;
  1680.  call Charout ,d2c(27)'['col'm';
  1681. return 0;
  1682.  
  1683. ColorNo:
  1684.  arg colname;
  1685.  if substr(colname, 1, 1) = 'L'
  1686.   then do
  1687.         colname = right(colname, length(colname) - 1);
  1688.         light = 8;
  1689.        end
  1690.   else light = 0;
  1691.  select
  1692.   when abbrev('BLACK', colname, 3)
  1693.    then return light + 0;
  1694.   when abbrev('BLUE', colname, 3)
  1695.    then return light + 4;
  1696.   when abbrev('GREEN', colname, 3)
  1697.    then return light + 2;
  1698.   when abbrev('CYAN', colname, 3)
  1699.    then return light + 6;
  1700.   when abbrev('RED', colname, 3)
  1701.    then return light + 1;
  1702.   when abbrev('MAGENTA', colname, 3)
  1703.    then return light + 5;
  1704.   when abbrev('BROWN', colname, 3)
  1705.    then return light + 3;
  1706.   when abbrev('GRAY', colname, 3)
  1707.    then return light + 7;
  1708.   when abbrev('DGRAY', colname, 3)
  1709.    then return 8;
  1710.   when abbrev('YELLOW', colname, 3)
  1711.    then return 11;
  1712.   when abbrev('WHITE', colname, 3)
  1713.    then return 15;
  1714.  end;
  1715.  return -1;
  1716.  
  1717. /* these constants have been ripped from    */
  1718. /* HTM2txt v 1.0, mar.11,1997 by otto räder */
  1719. DefineQuotes:
  1720. /* --------------------------------------------- */
  1721. /* constants contributed by tremro@digicom.qc.ca */
  1722. /* --------------------------------------------- */
  1723.  Global.Quotes = ,
  1724.   "COPY   (C)",
  1725.   "SPACE  0x20",
  1726.   "QUOT   0x22",
  1727.   "AMP    0x00",
  1728.   "LT     <",
  1729.   "GT     >",
  1730.   "NBSP   0x20",
  1731.   "#160   0x20",
  1732.   "IEXCL  0xA1",
  1733.   "CENT   0xA2",
  1734.   "POUND  0xA3",
  1735.   "CURREN 0xA4",
  1736.   "YEN    0xA5",
  1737.   "BRVBAR 0xA6",
  1738.   "SECT   0xA7",
  1739.   "UML    0xA8",
  1740.   "COPY   0xA9",
  1741.   "ORDF   0xAA",
  1742.   "LAQNO  0xAB",
  1743.   "NOT    0xAC",
  1744.   "SHY    0xAD",
  1745.   "REG    0xAE",
  1746.   "HIBAR  0xAF",
  1747.   "DEG    0xB0",
  1748.   "PLUSMN 0xB1",
  1749.   "SUP2   0xB2",
  1750.   "SUP3   0xB3",
  1751.   "ACUTE  0xB4",
  1752.   "MICRO  0xB4",
  1753.   "PARA   0xB6",
  1754.   "MIDDOT 0xB7",
  1755.   "CEDIL  0xB8",
  1756.   "SUP1   0xB9",
  1757.   "ORDM   0xBA",
  1758.   "RAQUO  0xBB",
  1759.   "FRAC14 0xBC",
  1760.   "FRAC12 0xBD",
  1761.   "FRAC34 0xBE",
  1762.   "IQUEST 0xBF",
  1763.   "AGRAVE 0xC0",
  1764.   "AACUTE 0xC1",
  1765.   "ACIRC  0xC2",
  1766.   "ATILDE 0xC3",
  1767.   "AUML   0xC4",
  1768.   "ARING  0xC5",
  1769.   "AELIG  0xC6",
  1770.   "CCEDIL 0xC7",
  1771.   "EGRAVE 0xC8",
  1772.   "EACUTE 0xC9",
  1773.   "ECIRC  0xCA",
  1774.   "EUML   0xCB",
  1775.   "IGRAVE 0xCC",
  1776.   "IACUTE 0xCD",
  1777.   "ICIRC  0xCE",
  1778.   "IUML   0xCF",
  1779.   "ETH    0xD0",
  1780.   "NTILDE 0xD1",
  1781.   "OGRAVE 0xD2",
  1782.   "OACUTE 0xD3",
  1783.   "OCIRC  0xD4",
  1784.   "OTILDE 0xD5",
  1785.   "OUML   0xD6",
  1786.   "TIMES  0xD7",
  1787.   "OSLASH 0xD8",
  1788.   "UGRAVE 0xD9",
  1789.   "UACUTE 0xDA",
  1790.   "UCIRC  0xDB",
  1791.   "UUML   0xDC",
  1792.   "YACUTE 0xDD",
  1793.   "THORN  0xDE",
  1794.   "SZLIG  0xDF",
  1795.   "AGRAVE 0xE0",
  1796.   "AACUTE 0xE1",
  1797.   "ACIRC  0xE2",
  1798.   "ATILDE 0xE3",
  1799.   "AUML   0xE4",
  1800.   "ARING  0xE5",
  1801.   "AELIG  0xE6",
  1802.   "CCEDIL 0xE7",
  1803.   "EGRAVE 0xE8",
  1804.   "EACUTE 0xE9",
  1805.   "ECIRC  0xEA",
  1806.   "EUML   0xEB",
  1807.   "IGRAVE 0xEC",
  1808.   "IACUTE 0xED",
  1809.   "ICIRC  0xEE",
  1810.   "IUML   0xEF",
  1811.   "ETH    0xF0",
  1812.   "NTILDE 0xF1",
  1813.   "OGRAVE 0xF2",
  1814.   "OACUTE 0xF3",
  1815.   "OCIRC  0xF4",
  1816.   "OTILDE 0xF5",
  1817.   "OUML   0xF6",
  1818.   "DIVIDE 0xF7",
  1819.   "OSLASH 0xF8",
  1820.   "UGRAVE 0xF9",
  1821.   "UACUTE 0xFA",
  1822.   "UCIRC  0xFB",
  1823.   "UUML   0xFC",
  1824.   "YACUTE 0xFD",
  1825.   "THORN  0xFE",
  1826.   "YUML   0xFF";
  1827. return;
  1828.  
  1829. /* substitute quoted Characters */
  1830. NoQuotes:
  1831.     parse arg text;
  1832.  
  1833.     sPos = 1;
  1834.     do forever
  1835.         qPos = pos('&', text, sPos);
  1836.         if qPos = 0 then
  1837.             leave;
  1838.         parse var text _head '&' Token ';' _tail
  1839.  
  1840.         wordN = wordpos(translate(Token), Global.Quotes);
  1841.         if wordN = 0
  1842.          then do
  1843.                if (left(Token, 1)='#') & (datatype(substr(Token, 2), 'num'))
  1844.                 then do
  1845.                       Token = substr(Token,2);
  1846.                       Token = d2c(Token);
  1847.                      end
  1848.                 else do
  1849.                       Token=d2c(0)||Token||';'
  1850.                      end
  1851.               end
  1852.          else do
  1853.                Token = word(Global.Quotes, wordN + 1);
  1854.                if left(Token, 2)="0x" then Token=x2c(substr(Token, 3));
  1855.               end
  1856.         sPos = length(_head) + length(Token) + 1;
  1857.         text = _head||Token||_tail;
  1858.     end;
  1859. return translate(text, '&', d2c(0));
  1860.