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