mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 18:28:49 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1750 lines
		
	
	
	
		
			57 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			1750 lines
		
	
	
	
		
			57 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable file
		
	
	
	
	
| #! /usr/bin/env perl
 | |
| # html2texi.pl -- Convert HTML documentation to Texinfo format
 | |
| # Michael Ernst <mernst@cs.washington.edu>
 | |
| # Time-stamp: <1999-01-12 21:34:27 mernst>
 | |
| 
 | |
| # This program converts HTML documentation trees into Texinfo format.
 | |
| # Given the name of a main (or contents) HTML file, it processes that file,
 | |
| # and other files (transitively) referenced by it, into a Texinfo file
 | |
| # (whose name is chosen from the file or directory name of the argument).
 | |
| # For instance:
 | |
| #   html2texi.pl api/index.html
 | |
| # produces file "api.texi".
 | |
| 
 | |
| # Texinfo format can be easily converted to Info format (for browsing in
 | |
| # Emacs or the standalone Info browser), to a printed manual, or to HTML.
 | |
| # Thus, html2texi.pl permits conversion of HTML files to Info format, and
 | |
| # secondarily enables producing printed versions of Web page hierarchies.
 | |
| 
 | |
| # Unlike HTML, Info format is searchable.  Since Info is integrated into
 | |
| # Emacs, one can read documentation without starting a separate Web
 | |
| # browser.  Additionally, Info browsers (including Emacs) contain
 | |
| # convenient features missing from Web browsers, such as easy index lookup
 | |
| # and mouse-free browsing.
 | |
| 
 | |
| # Limitations:
 | |
| # html2texi.pl is currently tuned to latex2html output (and it corrects
 | |
| # several latex2html bugs), but should be extensible to arbitrary HTML
 | |
| # documents.  It will be most useful for HTML with a hierarchical structure
 | |
| # and an index, and it recognizes those features as created by latex2html
 | |
| # (and possibly by some other tools).  The HTML tree to be traversed must
 | |
| # be on local disk, rather than being accessed via HTTP.
 | |
| # This script requires the use of "checkargs.pm".  To eliminate that
 | |
| # dependence, replace calls to check_args* by @_ (which is always the last
 | |
| # argument to those functions).
 | |
| # Also see the "to do" section, below.
 | |
| # Comments, suggestions, bug fixes, and enhancements are welcome.
 | |
| 
 | |
| # Troubleshooting:
 | |
| # Malformed HTML can cause this program to abort, so
 | |
| # you should check your HTML files to make sure they are legal.
 | |
| 
 | |
| 
 | |
| ###
 | |
| ### Typical usage for the Python documentation:
 | |
| ###
 | |
| 
 | |
| # (Actually, most of this is in a Makefile instead.)
 | |
| # The resulting Info format Python documentation is currently available at
 | |
| # ftp://ftp.cs.washington.edu/homes/mernst/python-info.tar.gz
 | |
| 
 | |
| # Fix up HTML problems, eg <DT><DL COMPACT><DD> should be <DT><DL COMPACT><DD>.
 | |
| 
 | |
| # html2texi.pl /homes/fish/mernst/tmp/python-doc/html/api/index.html
 | |
| # html2texi.pl /homes/fish/mernst/tmp/python-doc/html/ext/index.html
 | |
| # html2texi.pl /homes/fish/mernst/tmp/python-doc/html/lib/index.html
 | |
| # html2texi.pl /homes/fish/mernst/tmp/python-doc/html/mac/index.html
 | |
| # html2texi.pl /homes/fish/mernst/tmp/python-doc/html/ref/index.html
 | |
| # html2texi.pl /homes/fish/mernst/tmp/python-doc/html/tut/index.html
 | |
| 
 | |
| # Edit the generated .texi files:
 | |
| #   * change @setfilename to prefix "python-"
 | |
| #   * fix up any sectioning, such as for Abstract
 | |
| #   * make Texinfo menus
 | |
| #   * perhaps remove the @detailmenu ... @end detailmenu
 | |
| # In Emacs, to do all this:
 | |
| #   (progn (goto-char (point-min)) (replace-regexp "\\(@setfilename \\)\\([-a-z]*\\)$" "\\1python-\\2.info") (replace-string "@node Front Matter\n@chapter Abstract\n" "@node Abstract\n@section Abstract\n") (progn (mark-whole-buffer) (texinfo-master-menu 'update-all-nodes)) (save-buffer))
 | |
| 
 | |
| # makeinfo api.texi
 | |
| # makeinfo ext.texi
 | |
| # makeinfo lib.texi
 | |
| # makeinfo mac.texi
 | |
| # makeinfo ref.texi
 | |
| # makeinfo tut.texi
 | |
| 
 | |
| 
 | |
| ###
 | |
| ### Structure of the code
 | |
| ###
 | |
| 
 | |
| # To be written...
 | |
| 
 | |
| 
 | |
| ###
 | |
| ### Design decisions
 | |
| ###
 | |
| 
 | |
| # Source and destination languages
 | |
| # --------------------------------
 | |
| # 
 | |
| # The goal is Info files; I create Texinfo, so I don't have to worry about
 | |
| # the finer details of Info file creation.  (I'm not even sure of its exact
 | |
| # format.)
 | |
| # 
 | |
| # Why not start from LaTeX rather than HTML?
 | |
| # I could hack latex2html itself to produce Texinfo instead, or fix up
 | |
| # partparse.py (which already translates LaTeX to Teinfo).
 | |
| #  Pros:
 | |
| #   * has high-level information such as index entries, original formatting
 | |
| #  Cons:
 | |
| #   * those programs are complicated to read and understand
 | |
| #   * those programs try to handle arbitrary LaTeX input, track catcodes,
 | |
| #     and more:  I don't want to go to that effort.  HTML isn't as powerful
 | |
| #     as LaTeX, so there are fewer subtleties.
 | |
| #   * the result wouldn't work for arbitrary HTML documents; it would be
 | |
| #     nice to eventually extend this program to HTML produced from Docbook,
 | |
| #     Frame, and more.
 | |
| 
 | |
| # Parsing
 | |
| # -------
 | |
| # 
 | |
| # I don't want to view the text as a linear stream; I'd rather parse the
 | |
| # whole thing and then do pattern matching over the parsed representation (to
 | |
| # find idioms such as indices, lists of child nodes, etc.).
 | |
| #  * Perl provides HTML::TreeBuilder, which does just what I want.
 | |
| #     * libwww-perl: http://www.linpro.no/lwp/
 | |
| #     * TreeBuilder: HTML-Tree-0.51.tar.gz
 | |
| #  * Python Parsers, Formatters, and Writers don't really provide the right
 | |
| #    interface (and the version in Grail doesn't correspond to another
 | |
| #    distributed version, so I'm confused about which to be using).  I could
 | |
| #    write something in Python that creates a parse tree, but why bother?
 | |
| 
 | |
| # Other implementation language issues:
 | |
| #  * Python lacks variable declarations, reasonable scoping, and static
 | |
| #    checking tools.  I've written some of the latter for myself that make
 | |
| #    my Perl programming a lot safer than my Python programming will be until
 | |
| #    I have a similar suite for that language.
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### To do
 | |
| ###
 | |
| 
 | |
| # Section names:
 | |
| #   Fix the problem with multiple sections in a single file (eg, Abstract in
 | |
| #     Front Matter section).
 | |
| #   Deal with cross-references, as in /homes/fish/mernst/tmp/python-doc/html/ref/types.html:310
 | |
| # Index:
 | |
| #   Perhaps double-check that every tag mentioned in the index is found
 | |
| #     in the text.
 | |
| # Python:  email to python-docs@python.org, to get their feedback.
 | |
| #   Compare to existing lib/ Info manual
 | |
| #   Write the hooks into info-look; replace pyliblookup1-1.tar.gz.
 | |
| #   Postpass to remove extra quotation marks around typography already in
 | |
| #     a different font (to avoid double delimiters as in "`code'"); or
 | |
| #     perhaps consider using only font-based markup so that we don't get
 | |
| #     the extra *bold* and `code' markup in Info.
 | |
| 
 | |
| ## Perhaps don't rely on automatic means for adding up, next, prev; I have
 | |
| ## all that info available to me already, so it's not so much trouble to
 | |
| ## add it.  (Right?)  But it is *so* easy to use Emacs instead...
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### Strictures
 | |
| ###
 | |
| 
 | |
| # man HTML::TreeBuilder
 | |
| # man HTML::Parser
 | |
| # man HTML::Element
 | |
| 
 | |
| # require HTML::ParserWComment;
 | |
| require HTML::Parser;
 | |
| require HTML::TreeBuilder;
 | |
| require HTML::Element;
 | |
| 
 | |
| use File::Basename;
 | |
| 
 | |
| use strict;
 | |
| # use Carp;
 | |
| 
 | |
| use checkargs;
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### Variables
 | |
| ###
 | |
| 
 | |
| my @section_stack = ();		# elements are chapter/section/subsec nodetitles (I think)
 | |
| my $current_ref_tdf;		# for the file currently being processed;
 | |
| 				#  used in error messages
 | |
| my $html_directory;
 | |
| my %footnotes;
 | |
| 
 | |
| # First element should not be used.
 | |
| my @sectionmarker = ("manual", "chapter", "section", "subsection", "subsubsection");
 | |
| 
 | |
| my %inline_markup = ("b" => "strong",
 | |
| 		     "code" => "code",
 | |
| 		     "i" => "emph",
 | |
| 		     "kbd" => "kbd",
 | |
| 		     "samp" => "samp",
 | |
| 		     "strong" => "strong",
 | |
| 		     "tt" => "code",
 | |
| 		     "var" => "var");
 | |
| 
 | |
| my @deferred_index_entries = ();
 | |
| 
 | |
| my @index_titles = ();		# list of (filename, type) lists
 | |
| my %index_info = ("Index" => ["\@blindex", "bl"],
 | |
| 		  "Concept Index" => ["\@cindex", "cp"],
 | |
| 		  "Module Index" => ["\@mdindex", "md"]);
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### Main/contents page
 | |
| ###
 | |
| 
 | |
| # Process first-level page on its own, or just a contents page?  Well, I do
 | |
| # want the title, author, etc., and the front matter...  For now, just add
 | |
| # that by hand at the end.
 | |
| 
 | |
| 
 | |
| # data structure possibilities:
 | |
| #  * tree-like (need some kind of stack when processing (or parent pointers))
 | |
| #  * list of name and depth; remember old and new depths.
 | |
| 
 | |
| # Each element is a reference to a list of (nodetitle, depth, filename).
 | |
| my @contents_list = ();
 | |
| 
 | |
| # The problem with doing fixups on the fly is that some sections may have
 | |
| # already been processed (and no longer available) by the time we notice
 | |
| # others with the same name.  It's probably better to fully construct the
 | |
| # contents list (reading in all files of interest) upfront; that will also
 | |
| # let me do a better job with cross-references, because again, all files
 | |
| # will already be read in.
 | |
| my %contents_hash = ();
 | |
| my %contents_fixups = ();
 | |
| 
 | |
| my @current_contents_list = ();
 | |
| 
 | |
| # Merge @current_contents_list into @contents_list,
 | |
| # and set @current_contents_list to be empty.
 | |
| sub merge_contents_lists ( )
 | |
| { check_args(0, @_);
 | |
| 
 | |
|   # Three possibilities:
 | |
|   #  * @contents_list is empty: replace it by @current_contents_list.
 | |
|   #  * prefixes of the two lists are identical: do nothing
 | |
|   #  * @current_contents_list is all at lower level than $contents_list[0];
 | |
|   #    prefix @contents_list by @current_contents_list
 | |
| 
 | |
|   if (scalar(@current_contents_list) == 0)
 | |
|     { die "empty current_contents_list"; }
 | |
| 
 | |
|   #   if (scalar(@contents_list) == 0)
 | |
|   #     { @contents_list = @current_contents_list;
 | |
|   #       @current_contents_list = ();
 | |
|   #       return; }
 | |
| 
 | |
|   #   if (($ {$contents_list[0]}[1]) < ($ {$current_contents_list[0]}[1]))
 | |
|   #     { unshift @contents_list, @current_contents_list;
 | |
|   #       @current_contents_list = ();
 | |
|   #       return; }
 | |
| 
 | |
|   for (my $i=0; $i<scalar(@current_contents_list); $i++)
 | |
|     { my $ref_c_tdf = $current_contents_list[$i];
 | |
|       if ($i >= scalar(@contents_list))
 | |
| 	{ push @contents_list, $ref_c_tdf;
 | |
| 	  my $title = $ {$ref_c_tdf}[0];
 | |
| 	  if (defined $contents_hash{$title})
 | |
| 	    { $contents_fixups{$title} = 1; }
 | |
| 	  else
 | |
| 	    { $contents_hash{$title} = 1; }
 | |
| 	  next; }
 | |
|       my $ref_tdf = $contents_list[$i];
 | |
|       my ($title, $depth, $file) = @{$ref_tdf};
 | |
|       my ($c_title, $c_depth, $c_file) = @{$ref_c_tdf};
 | |
| 
 | |
|       if (($title ne $c_title)
 | |
| 	  && ($depth < $c_depth)
 | |
| 	  && ($file ne $c_file))
 | |
| 	{ splice @contents_list, $i, 0, $ref_c_tdf;
 | |
| 	  if (defined $contents_hash{$c_title})
 | |
| 	    { $contents_fixups{$c_title} = 1; }
 | |
| 	  else
 | |
| 	    { $contents_hash{$c_title} = 1; }
 | |
| 	  next; }
 | |
| 
 | |
|       if (($title ne $c_title)
 | |
| 	  || ($depth != $c_depth)
 | |
| 	  || ($file ne $c_file))
 | |
| 	{ die ("while processing $ {$current_ref_tdf}[2] at depth $ {$current_ref_tdf}[1], mismatch at index $i:",
 | |
| 	       "\n  main:  <<<$title>>> $depth $file",
 | |
| 	       "\n  curr:  <<<$c_title>>> $c_depth $c_file"); }
 | |
|     }
 | |
|   @current_contents_list = ();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| # Set @current_contents_list to a list of (title, href, sectionlevel);
 | |
| #  then merge that list into @contents_list.
 | |
| # Maybe this function should also produce a map
 | |
| #  from title (or href) to sectionlevel (eg "chapter"?).
 | |
| sub process_child_links ( $ )
 | |
| { my ($he) = check_args(1, @_);
 | |
| 
 | |
|   # $he->dump();
 | |
|   if (scalar(@current_contents_list) != 0)
 | |
|     { die "current_contents_list nonempty: @current_contents_list"; }
 | |
|   $he->traverse(\&increment_current_contents_list, 'ignore text');
 | |
| 
 | |
|   # Normalize the depths; for instance, convert 1,3,5 into 0,1,2.
 | |
|   my %depths = ();
 | |
|   for my $ref_tdf (@current_contents_list)
 | |
|     { $depths{$ {$ref_tdf}[1]} = 1; }
 | |
|   my @sorted_depths = sort keys %depths;
 | |
|   my $current_depth = scalar(@section_stack)-1;
 | |
|   my $current_depth_2 = $ {$current_ref_tdf}[1];
 | |
|   if ($current_depth != $current_depth_2)
 | |
|     { die "mismatch in current depths: $current_depth $current_depth_2; ", join(", ", @section_stack); }
 | |
|   for (my $i=0; $i<scalar(@sorted_depths); $i++)
 | |
|     { $depths{$sorted_depths[$i]} = $i + $current_depth+1; }
 | |
|   for my $ref_tdf (@current_contents_list)
 | |
|     { $ {$ref_tdf}[1] = $depths{$ {$ref_tdf}[1]}; }
 | |
| 
 | |
|   # Eliminate uninteresting sections.  Hard-coded hack for now.
 | |
|   if ($ {$current_contents_list[-1]}[0] eq "About this document ...")
 | |
|     { pop @current_contents_list; }
 | |
|   if ((scalar(@current_contents_list) > 1)
 | |
|       && ($ {$current_contents_list[1]}[0] eq "Contents"))
 | |
|     { my $ref_first_tdf = shift @current_contents_list;
 | |
|       $current_contents_list[0] = $ref_first_tdf; }
 | |
| 
 | |
|   for (my $i=0; $i<scalar(@current_contents_list); $i++)
 | |
|     { my $ref_tdf = $current_contents_list[$i];
 | |
|       my $title = $ {$ref_tdf}[0];
 | |
|       if (exists $index_info{$title})
 | |
| 	{ my $index_file = $ {$ref_tdf}[2];
 | |
| 	  my ($indexing_command, $suffix) = @{$index_info{$title}};
 | |
| 	  process_index_file($index_file, $indexing_command);
 | |
| 	  print TEXI "\n\@defindex $suffix\n";
 | |
| 	  push @index_titles, $title;
 | |
| 	  splice @current_contents_list, $i, 1;
 | |
| 	  $i--; }
 | |
|       elsif ($title =~ /\bIndex$/)
 | |
| 	{ print STDERR "Warning: \"$title\" might be an index; if so, edit \%index_info.\n"; } }
 | |
| 
 | |
|   merge_contents_lists();
 | |
| 
 | |
|   # print_contents_list();
 | |
|   # print_index_info();
 | |
| }
 | |
| 
 | |
| 
 | |
| sub increment_current_contents_list ( $$$ )
 | |
| { my ($he, $startflag, $depth) = check_args(3, @_);
 | |
|   if (!$startflag)
 | |
|     { return; }
 | |
| 
 | |
|   if ($he->tag eq "li")
 | |
|     { my @li_content = @{$he->content};
 | |
|       if ($li_content[0]->tag ne "a")
 | |
| 	{ die "first element of <LI> should be <A>"; }
 | |
|       my ($name, $href, @content) = anchor_info($li_content[0]);
 | |
|       # unused $name
 | |
|       my $title = join("", collect_texts($li_content[0]));
 | |
|       $title = texi_remove_punctuation($title);
 | |
|       # The problem with these is that they are formatted differently in
 | |
|       # @menu and @node!
 | |
|       $title =~ s/``/\"/g;
 | |
|       $title =~ s/''/\"/g;
 | |
|       $title =~ s/ -- / /g;
 | |
|       push @current_contents_list, [ $title, $depth, $href ]; }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| # Simple version for section titles
 | |
| sub html_to_texi ( $ )
 | |
| { my ($he) = check_args(1, @_);
 | |
|   if (!ref $he)
 | |
|     { return $he; }
 | |
| 
 | |
|   my $tag = $he->tag;
 | |
|   if (exists $inline_markup{$tag})
 | |
|     { my $result = "\@$inline_markup{$tag}\{";
 | |
|       for my $elt (@{$he->content})
 | |
| 	{ $result .= html_to_texi($elt); }
 | |
|       $result .= "\}";
 | |
|       return $result; }
 | |
|   else
 | |
|     { $he->dump();
 | |
|       die "html_to_texi confused by <$tag>"; }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| sub print_contents_list ()
 | |
| { check_args(0, @_);
 | |
|   print STDERR "Contents list:\n";
 | |
|   for my $ref_tdf (@contents_list)
 | |
|     { my ($title, $depth, $file) = @{$ref_tdf};
 | |
|       print STDERR "$title $depth $file\n"; }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### Index
 | |
| ###
 | |
| 
 | |
| my $l2h_broken_link_name = "l2h-";
 | |
| 
 | |
| 
 | |
| # map from file to (map from anchor name to (list of index texts))
 | |
| # (The list is needed when a single LaTeX command like \envvar
 | |
| # expands to multiple \index commands.)
 | |
| my %file_index_entries = ();
 | |
| my %this_index_entries;		# map from anchor name to (list of index texts)
 | |
| 
 | |
| my %file_index_entries_broken = (); # map from file to (list of index texts)
 | |
| my @this_index_entries_broken;
 | |
| 
 | |
| my $index_prefix = "";
 | |
| my @index_prefixes = ();
 | |
| 
 | |
| my $this_indexing_command;
 | |
| 
 | |
| sub print_index_info ()
 | |
| { check_args(0, @_);
 | |
|   my ($key, $val);
 | |
|   for my $file (sort keys %file_index_entries)
 | |
|     { my %index_entries = %{$file_index_entries{$file}};
 | |
|       print STDERR "file: $file\n";
 | |
|       for my $aname (sort keys %index_entries)
 | |
| 	{ my @entries = @{$index_entries{$aname}};
 | |
| 	  if (scalar(@entries) == 1)
 | |
| 	    { print STDERR "  $aname : $entries[0]\n"; }
 | |
| 	  else
 | |
| 	    { print STDERR "  $aname : ", join("\n     " . (" " x length($aname)), @entries), "\n"; } } }
 | |
|   for my $file (sort keys %file_index_entries_broken)
 | |
|     { my @entries = @{$file_index_entries_broken{$file}};
 | |
|       print STDERR "file: $file\n";
 | |
|       for my $entry (@entries)
 | |
| 	{ print STDERR "  $entry\n"; }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| sub process_index_file ( $$ )
 | |
| { my ($file, $indexing_command) = check_args(2, @_);
 | |
|   # print "process_index_file $file $indexing_command\n";
 | |
| 
 | |
|   my $he = file_to_tree($html_directory . $file);
 | |
|   # $he->dump();
 | |
| 
 | |
|   $this_indexing_command = $indexing_command;
 | |
|   $he->traverse(\&process_if_index_dl_compact, 'ignore text');
 | |
|   undef $this_indexing_command;
 | |
|   # print "process_index_file done\n";
 | |
| }
 | |
| 
 | |
| 
 | |
| sub process_if_index_dl_compact ( $$$ )
 | |
| { my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
 | |
|   if (!$startflag)
 | |
|     { return; }
 | |
| 
 | |
|   if (($he->tag() eq "dl") && (defined $he->attr('compact')))
 | |
|     { process_index_dl_compact($he);
 | |
|       return 0; }
 | |
|   else
 | |
|     { return 1; }
 | |
| }
 | |
| 
 | |
| 
 | |
| # The elements of a <DL COMPACT> list from a LaTeX2HTML index:
 | |
| #  * a single space: text to be ignored
 | |
| #  * <DT> elements with an optional <DD> element following each one
 | |
| #    Two types of <DT> elements:
 | |
| #     * Followed by a <DD> element:  the <DT> contains a single
 | |
| #       string, and the <DD> contains a whitespace string to be ignored, a
 | |
| #       <DL COMPACT> to be recursively processed (with the <DT> string as a
 | |
| #       prefix), and a whitespace string to be ignored.
 | |
| #     * Not followed by a <DD> element:  contains a list of anchors
 | |
| #       and texts (ignore the texts, which are only whitespace and commas).
 | |
| #       Optionally contains a <DL COMPACT> to be recursively processed (with
 | |
| #       the <DT> string as a prefix)
 | |
| sub process_index_dl_compact ( $ )
 | |
| { my ($h) = check_args(1, @_);
 | |
|   my @content = @{$h->content()};
 | |
|   for (my $i = 0; $i < scalar(@content); $i++)
 | |
|     { my $this_he = $content[$i];
 | |
|       if ($this_he->tag ne "dt")
 | |
| 	{ $this_he->dump();
 | |
| 	  die "Expected <DT> tag: " . $this_he->tag; }
 | |
|       if (($i < scalar(@content) - 1) && ($content[$i+1]->tag eq "dd"))
 | |
| 	{ process_index_dt_and_dd($this_he, $content[$i+1]);
 | |
| 	  $i++;	}
 | |
|       else
 | |
| 	{ process_index_lone_dt($this_he); } } }
 | |
| 
 | |
| 
 | |
| 
 | |
| # Argument is a <DT> element.  If it contains more than one anchor, then
 | |
| # the texts of all subsequent ones are "[Link]".  Example:
 | |
| #       <DT>
 | |
| #         <A HREF="embedding.html#l2h-201">
 | |
| #           "$PATH"
 | |
| #         ", "
 | |
| #         <A HREF="embedding.html#l2h-205">
 | |
| #           "[Link]"
 | |
| # Optionally contains a <DL COMPACT> as well.  Example:
 | |
| # <DT>
 | |
| #   <A HREF="types.html#l2h-616">
 | |
| #     "attribute"
 | |
| #   <DL COMPACT>
 | |
| #     <DT>
 | |
| #       <A HREF="assignment.html#l2h-3074">
 | |
| #         "assignment"
 | |
| #       ", "
 | |
| #       <A HREF="assignment.html#l2h-3099">
 | |
| #         "[Link]"
 | |
| #     <DT>
 | |
| #       <A HREF="types.html#l2h-">
 | |
| #         "assignment, class"
 | |
| 
 | |
| sub process_index_lone_dt ( $ )
 | |
| { my ($dt) = check_args(1, @_);
 | |
|   my @dtcontent = @{$dt->content()};
 | |
|   my $acontent;
 | |
|   my $acontent_suffix;
 | |
|   for my $a (@dtcontent)
 | |
|     { if ($a eq ", ")
 | |
| 	{ next; }
 | |
|       if (!ref $a)
 | |
| 	{ $dt->dump;
 | |
| 	  die "Unexpected <DT> string element: $a"; }
 | |
| 
 | |
|       if ($a->tag eq "dl")
 | |
| 	{ push @index_prefixes, $index_prefix;
 | |
| 	  if (!defined $acontent_suffix)
 | |
| 	    { die "acontent_suffix not yet defined"; }
 | |
| 	  $index_prefix .= $acontent_suffix . ", ";
 | |
| 	  process_index_dl_compact($a);
 | |
| 	  $index_prefix = pop(@index_prefixes);
 | |
| 	  return; }
 | |
| 
 | |
|       if ($a->tag ne "a")
 | |
| 	{ $dt->dump;
 | |
| 	  $a->dump;
 | |
| 	  die "Expected anchor in lone <DT>"; }
 | |
| 
 | |
|       my ($aname, $ahref, @acontent) = anchor_info($a);
 | |
|       # unused $aname
 | |
|       if (scalar(@acontent) != 1)
 | |
| 	{ die "Expected just one content of <A> in <DT>: @acontent"; }
 | |
|       if (ref $acontent[0])
 | |
| 	{ $acontent[0]->dump;
 | |
| 	  die "Expected string content of <A> in <DT>: $acontent[0]"; }
 | |
|       if (!defined($acontent))
 | |
| 	{ $acontent = $index_prefix . $acontent[0];
 | |
| 	  $acontent_suffix = $acontent[0]; }
 | |
|       elsif (($acontent[0] ne "[Link]") && ($acontent ne ($index_prefix . $acontent[0])))
 | |
| 	{ die "Differing content: <<<$acontent>>>, <<<$acontent[0]>>>"; }
 | |
| 
 | |
|       if (!defined $ahref)
 | |
| 	{ $dt->dump;
 | |
| 	  die "no HREF in nachor in <DT>"; }
 | |
|       my ($ahref_file, $ahref_name) = split(/\#/, $ahref);
 | |
|       if (!defined $ahref_name)
 | |
| 	{ # Reference to entire file
 | |
| 	  $ahref_name = ""; }
 | |
| 
 | |
|       if ($ahref_name eq $l2h_broken_link_name)
 | |
| 	{ if (!exists $file_index_entries_broken{$ahref_file})
 | |
| 	    { $file_index_entries_broken{$ahref_file} = []; }
 | |
| 	  push @{$file_index_entries_broken{$ahref_file}}, "$this_indexing_command $acontent";
 | |
| 	  next; }
 | |
| 
 | |
|       if (!exists $file_index_entries{$ahref_file})
 | |
| 	{ $file_index_entries{$ahref_file} = {}; }
 | |
|       # Don't do this!  It appears to make a copy, which is not desired.
 | |
|       # my %index_entries = %{$file_index_entries{$ahref_file}};
 | |
|       if (!exists $ {$file_index_entries{$ahref_file}}{$ahref_name})
 | |
| 	{ $ {$file_index_entries{$ahref_file}}{$ahref_name} = []; }
 | |
|       # 	{ my $oldcontent = $ {$file_index_entries{$ahref_file}}{$ahref_name};
 | |
|       # 	  if ($acontent eq $oldcontent)
 | |
|       # 	    { die "Multiple identical index entries?"; }
 | |
|       # 	  die "Trying to add $acontent, but already have index entry pointing at $ahref_file\#$ahref_name: ${$file_index_entries{$ahref_file}}{$ahref_name}"; }
 | |
| 
 | |
|       push @{$ {$file_index_entries{$ahref_file}}{$ahref_name}}, "$this_indexing_command $acontent";
 | |
|       # print STDERR "keys: ", keys %{$file_index_entries{$ahref_file}}, "\n";
 | |
|     }
 | |
| }
 | |
| 
 | |
| sub process_index_dt_and_dd ( $$ )
 | |
| { my ($dt, $dd) = check_args(2, @_);
 | |
|   my $dtcontent;
 | |
|   { my @dtcontent = @{$dt->content()};
 | |
|     if ((scalar(@dtcontent) != 1) || (ref $dtcontent[0]))
 | |
|       { $dd->dump;
 | |
| 	$dt->dump;
 | |
| 	die "Expected single string (actual size = " . scalar(@dtcontent) . ") in content of <DT>: @dtcontent"; }
 | |
|     $dtcontent = $dtcontent[0];
 | |
|     $dtcontent =~ s/ +$//; }
 | |
|   my $ddcontent;
 | |
|   { my @ddcontent = @{$dd->content()};
 | |
|     if (scalar(@ddcontent) != 1)
 | |
|       { die "Expected single <DD> content, got ", scalar(@ddcontent), " elements:\n", join("\n", @ddcontent), "\n "; }
 | |
|     $ddcontent = $ddcontent[0]; }
 | |
|   if ($ddcontent->tag ne "dl")
 | |
|     { die "Expected <DL> as content of <DD>, but saw: $ddcontent"; }
 | |
| 
 | |
|   push @index_prefixes, $index_prefix;
 | |
|   $index_prefix .= $dtcontent . ", ";
 | |
|   process_index_dl_compact($ddcontent);
 | |
|   $index_prefix = pop(@index_prefixes);
 | |
| }
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### Ordinary sections
 | |
| ###
 | |
| 
 | |
| sub process_section_file ( $$$ )
 | |
| { my ($file, $depth, $nodetitle) = check_args(3, @_);
 | |
|   my $he = file_to_tree(($file =~ /^\//) ? $file : $html_directory . $file);
 | |
| 
 | |
|   # print STDERR "process_section_file: $file $depth $nodetitle\n";
 | |
| 
 | |
|   # Equivalently:
 | |
|   #   while ($depth >= scalar(@section_stack)) { pop(@section_stack); }
 | |
|   @section_stack = @section_stack[0..$depth-1];
 | |
| 
 | |
|   # Not a great nodename fixup scheme; need a more global view
 | |
|   if ((defined $contents_fixups{$nodetitle})
 | |
|       && (scalar(@section_stack) > 0))
 | |
|     { my $up_title = $section_stack[$#section_stack];
 | |
|       # hack for Python Standard Library
 | |
|       $up_title =~ s/^(Built-in|Standard) Module //g;
 | |
|       my ($up_first_word) = split(/ /, $up_title);
 | |
|       $nodetitle = "$up_first_word $nodetitle";
 | |
|     }
 | |
| 
 | |
|   push @section_stack, $nodetitle;
 | |
|   # print STDERR "new section_stack: ", join(", ", @section_stack), "\n";
 | |
| 
 | |
|   $he->traverse(\&process_if_child_links, 'ignore text');
 | |
|   %footnotes = ();
 | |
|   # $he->dump;
 | |
|   $he->traverse(\&process_if_footnotes, 'ignore text');
 | |
| 
 | |
|   # $he->dump;
 | |
| 
 | |
|   if (exists $file_index_entries{$file})
 | |
|     { %this_index_entries = %{$file_index_entries{$file}};
 | |
|       # print STDERR "this_index_entries:\n ", join("\n ", keys %this_index_entries), "\n";
 | |
|     }
 | |
|   else
 | |
|     { # print STDERR "Warning: no index entries for file $file\n";
 | |
|       %this_index_entries = (); }
 | |
| 
 | |
|   if (exists $file_index_entries_broken{$file})
 | |
|     { @this_index_entries_broken = @{$file_index_entries_broken{$file}}; }
 | |
|   else
 | |
|     { # print STDERR "Warning: no index entries for file $file\n";
 | |
|       @this_index_entries_broken = (); }
 | |
| 
 | |
| 
 | |
|   if ($he->tag() ne "html")
 | |
|     { die "Expected <HTML> at top level"; }
 | |
|   my @content = @{$he->content()};
 | |
|   if ((!ref $content[0]) or ($content[0]->tag ne "head"))
 | |
|     { $he->dump;
 | |
|       die "<HEAD> not first element of <HTML>"; }
 | |
|   if ((!ref $content[1]) or ($content[1]->tag ne "body"))
 | |
|     { $he->dump;
 | |
|       die "<BODY> not second element of <HTML>"; }
 | |
| 
 | |
|   $content[1]->traverse(\&output_body);
 | |
| }
 | |
| 
 | |
| # stack of things we're inside that are preventing indexing from occurring now.
 | |
| # These are "h1", "h2", "h3", "h4", "h5", "h6", "dt" (and possibly others?)
 | |
| my @index_deferrers = ();
 | |
| 
 | |
| sub push_or_pop_index_deferrers ( $$ )
 | |
| { my ($tag, $startflag) = check_args(2, @_);
 | |
|   if ($startflag)
 | |
|     { push @index_deferrers, $tag; }
 | |
|   else
 | |
|     { my $old_deferrer = pop @index_deferrers;
 | |
|       if ($tag ne $old_deferrer)
 | |
| 	{ die "Expected $tag at top of index_deferrers but saw $old_deferrer; remainder = ", join(" ", @index_deferrers); }
 | |
|       do_deferred_index_entries(); }
 | |
| }
 | |
| 
 | |
| 
 | |
| sub label_add_index_entries ( $;$ )
 | |
| { my ($label, $he) = check_args_range(1, 2, @_);
 | |
|   # print ((exists $this_index_entries{$label}) ? "*" : " "), " label_add_index_entries $label\n";
 | |
|   # $he is the anchor element
 | |
|   if (exists $this_index_entries{$label})
 | |
|     { push @deferred_index_entries, @{$this_index_entries{$label}};
 | |
|       return; }
 | |
| 
 | |
|   if ($label eq $l2h_broken_link_name)
 | |
|     { # Try to find some text to use in guessing which links should point here
 | |
|       # I should probably only look at the previous element, or if that is
 | |
|       # all punctuation, the one before it; collecting all the previous texts
 | |
|       # is a bit of overkill.
 | |
|       my @anchor_texts = collect_texts($he);
 | |
|       my @previous_texts = collect_texts($he->parent, $he);
 | |
|       # 4 elements is arbitrary; ought to filter out punctuation and small words
 | |
|       # first, then perhaps keep fewer.  Perhaps also filter out formatting so
 | |
|       # that we can see a larger chunk of text?  (Probably not.)
 | |
|       # Also perhaps should do further chunking into words, in case the
 | |
|       # index term isn't a chunk of its own (eg, was in <tt>...</tt>.
 | |
|       my @candidate_texts = (@anchor_texts, (reverse(@previous_texts))[0..min(3,$#previous_texts)]);
 | |
| 
 | |
|       my $guessed = 0;
 | |
|       for my $text (@candidate_texts)
 | |
| 	{ # my $orig_text = $text;
 | |
| 	  if ($text =~ /^[\"\`\'().?! ]*$/)
 | |
| 	    { next; }
 | |
| 	  if (length($text) <= 2)
 | |
| 	    { next; }
 | |
| 	  # hack for Python manual; maybe defer until failure first time around?
 | |
| 	  $text =~ s/^sys\.//g;
 | |
| 	  for my $iterm (@this_index_entries_broken)
 | |
| 	    { # I could test for zero:  LaTeX2HTML's failures in the Python
 | |
| 	      # documentation are only for items of the form "... (built-in...)"
 | |
| 	      if (index($iterm, $text) != -1)
 | |
| 		{ push @deferred_index_entries, $iterm;
 | |
| 		  # print STDERR "Guessing index term `$iterm' for text `$orig_text'\n";
 | |
| 		  $guessed = 1;
 | |
| 		} } }
 | |
|       if (!$guessed)
 | |
| 	{ # print STDERR "No guess in `", join("'; `", @this_index_entries_broken), "' for texts:\n `", join("'\n `", @candidate_texts), "'\n";
 | |
| 	}
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| # Need to add calls to this at various places.
 | |
| # Perhaps add HTML::Element argument and do the check for appropriateness
 | |
| # here (ie, no action if inside <H1>, etc.).
 | |
| sub do_deferred_index_entries ()
 | |
| { check_args(0, @_);
 | |
|   if ((scalar(@deferred_index_entries) > 0)
 | |
|       && (scalar(@index_deferrers) == 0))
 | |
|     { print TEXI "\n", join("\n", @deferred_index_entries), "\n";
 | |
|       @deferred_index_entries = (); }
 | |
| }
 | |
| 
 | |
| my $table_columns;		# undefined if not in a table
 | |
| my $table_first_column;		# boolean
 | |
| 
 | |
| sub output_body ( $$$ )
 | |
| { my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
 | |
| 
 | |
|   if (!ref $he)
 | |
|     { my $space_index = index($he, " ");
 | |
|       if ($space_index != -1)
 | |
| 	{ # Why does
 | |
| 	  #   print TEXI texi_quote(substr($he, 0, $space_index+1));
 | |
| 	  # give:  Can't locate object method "TEXI" via package "texi_quote"
 | |
| 	  # (Because the definition texi_quote hasn't been seen yet.)
 | |
| 	  print TEXI &texi_quote(substr($he, 0, $space_index+1));
 | |
| 	  do_deferred_index_entries();
 | |
| 	  print TEXI &texi_quote(substr($he, $space_index+1)); }
 | |
|       else
 | |
| 	{ print TEXI &texi_quote($he); }
 | |
|       return; }
 | |
| 
 | |
|   my $tag = $he->tag();
 | |
| 
 | |
|   # Ordinary text markup first
 | |
|   if (exists $inline_markup{$tag})
 | |
|     { if ($startflag)
 | |
| 	{ print TEXI "\@$inline_markup{$tag}\{"; }
 | |
|       else
 | |
| 	{ print TEXI "\}"; } }
 | |
|   elsif ($tag eq "a")
 | |
|     { my ($name, $href, @content) = anchor_info($he);
 | |
|       if (!$href)
 | |
| 	{ # This anchor is only here for indexing/cross referencing purposes.
 | |
| 	  if ($startflag)
 | |
| 	    { label_add_index_entries($name, $he); }
 | |
| 	}
 | |
|       elsif ($href =~ "^(ftp|http|news):")
 | |
| 	{ if ($startflag)
 | |
| 	    { # Should avoid second argument if it's identical to the URL.
 | |
| 	      print TEXI "\@uref\{$href, "; }
 | |
| 	  else
 | |
| 	    { print TEXI "\}"; }
 | |
| 	}
 | |
|       elsif ($href =~ /^\#(foot[0-9]+)$/)
 | |
| 	{ # Footnote
 | |
| 	  if ($startflag)
 | |
| 	    { # Could double-check name and content, but I'm not
 | |
| 	      # currently storing that information.
 | |
| 	      print TEXI "\@footnote\{";
 | |
| 	      $footnotes{$1}->traverse(\&output_body);
 | |
| 	      print TEXI "\}";
 | |
| 	      return 0; } }
 | |
|       else
 | |
| 	{ if ($startflag)
 | |
| 	    { # cross-references are not active Info links, but no text is lost
 | |
| 	      print STDERR "Can't deal with internal HREF anchors yet:\n";
 | |
| 	      $he->dump; }
 | |
| 	}
 | |
|     }
 | |
|   elsif ($tag eq "br")
 | |
|     { print TEXI "\@\n"; }
 | |
|   elsif ($tag eq "body")
 | |
|     { }
 | |
|   elsif ($tag eq "center")
 | |
|     { if (has_single_content_string($he)
 | |
| 	  && ($ {$he->content}[0] =~ /^ *$/))
 | |
| 	{ return 0; }
 | |
|       if ($startflag)
 | |
| 	{ print TEXI "\n\@center\n"; }
 | |
|       else
 | |
| 	{ print TEXI "\n\@end center\n"; }
 | |
|     }
 | |
|   elsif ($tag eq "div")
 | |
|     { my $align = $he->attr('align');
 | |
|       if (defined($align) && ($align eq "center"))
 | |
| 	{ if (has_single_content_string($he)
 | |
| 	      && ($ {$he->content}[0] =~ /^ *$/))
 | |
| 	    { return 0; }
 | |
| 	  if ($startflag)
 | |
| 	    { print TEXI "\n\@center\n"; }
 | |
| 	  else
 | |
| 	    { print TEXI "\n\@end center\n"; } }
 | |
|     }
 | |
|   elsif ($tag eq "dl")
 | |
|     { # Recognize "<dl><dd><pre> ... </pre></dl>" paradigm for "@example"
 | |
|       if (has_single_content_with_tag($he, "dd"))
 | |
| 	{ my $he_dd = $ {$he->content}[0];
 | |
| 	  if (has_single_content_with_tag($he_dd, "pre"))
 | |
| 	    { my $he_pre = $ {$he_dd->content}[0];
 | |
| 	      print_pre($he_pre);
 | |
| 	      return 0; } }
 | |
|       if ($startflag)
 | |
| 	{ # Could examine the elements, to be cleverer about formatting.
 | |
| 	  # (Also to use ftable, vtable...)
 | |
| 	  print TEXI "\n\@table \@asis\n"; }
 | |
|       else
 | |
| 	{ print TEXI "\n\@end table\n"; }
 | |
|     }
 | |
|   elsif ($tag eq "dt")
 | |
|     { push_or_pop_index_deferrers($tag, $startflag);
 | |
|       if ($startflag)
 | |
| 	{ print TEXI "\n\@item "; }
 | |
|       else
 | |
| 	{ } }
 | |
|   elsif ($tag eq "dd")
 | |
|     { if ($startflag)
 | |
| 	{ print TEXI "\n"; }
 | |
|       else
 | |
| 	{ }
 | |
|       if (scalar(@index_deferrers) != 0)
 | |
| 	{ $he->dump;
 | |
| 	  die "Unexpected <$tag> while inside: (" . join(" ", @index_deferrers) . "); bad HTML?"; }
 | |
|       do_deferred_index_entries();
 | |
|     }
 | |
|   elsif ($tag =~ /^(font|big|small)$/)
 | |
|     { # Do nothing for now.
 | |
|     }
 | |
|   elsif ($tag =~ /^h[1-6]$/)
 | |
|     { # We don't need this because we never recursively enter the heading content.
 | |
|       # push_or_pop_index_deferrers($tag, $startflag);
 | |
|       my $secname = "";
 | |
|       my @seclabels = ();
 | |
|       for my $elt (@{$he->content})
 | |
| 	{ if (!ref $elt)
 | |
| 	    { $secname .= $elt; }
 | |
| 	  elsif ($elt->tag eq "br")
 | |
| 	    { }
 | |
| 	  elsif ($elt->tag eq "a")
 | |
| 	    { my ($name, $href, @acontent) = anchor_info($elt);
 | |
|               if ($href)
 | |
|                 { $he->dump;
 | |
|                   $elt->dump;
 | |
|                   die "Nonsimple anchor in <$tag>"; }
 | |
| 	      if (!defined $name)
 | |
| 		{ die "No NAME for anchor in $tag"; }
 | |
| 	      push @seclabels, $name;
 | |
| 	      for my $subelt (@acontent)
 | |
| 		{ $secname .= html_to_texi($subelt); } }
 | |
| 	  else
 | |
| 	    { $secname .= html_to_texi($elt); } }
 | |
|       if ($secname eq "")
 | |
| 	{ die "No section name in <$tag>"; }
 | |
|       if (scalar(@section_stack) == 1)
 | |
| 	{ if ($section_stack[-1] ne "Top")
 | |
| 	    { die "Not top? $section_stack[-1]"; }
 | |
| 	  print TEXI "\@settitle $secname\n";
 | |
| 	  print TEXI "\@c %**end of header\n";
 | |
| 	  print TEXI "\n";
 | |
| 	  print TEXI "\@node Top\n";
 | |
| 	  print TEXI "\n"; }
 | |
|       else
 | |
| 	{ print TEXI "\n\@node $section_stack[-1]\n";
 | |
| 	  print TEXI "\@$sectionmarker[scalar(@section_stack)-1] ", texi_remove_punctuation($secname), "\n"; }
 | |
|       for my $seclabel (@seclabels)
 | |
| 	{ label_add_index_entries($seclabel); }
 | |
|       # This should only happen once per file.
 | |
|       label_add_index_entries("");
 | |
|       if (scalar(@index_deferrers) != 0)
 | |
| 	{ $he->dump;
 | |
| 	  die "Unexpected <$tag> while inside: (" . join(" ", @index_deferrers) . "); bad HTML?"; }
 | |
|       do_deferred_index_entries();
 | |
|       return 0;
 | |
|     }
 | |
|   elsif ($tag eq "hr")
 | |
|     { }
 | |
|   elsif ($tag eq "ignore")
 | |
|     { # Hack for ignored elements
 | |
|       return 0;
 | |
|     }
 | |
|   elsif ($tag eq "li")
 | |
|     { if ($startflag)
 | |
| 	{ print TEXI "\n\n\@item\n";
 | |
| 	  do_deferred_index_entries(); } }
 | |
|   elsif ($tag eq "ol")
 | |
|     { if ($startflag)
 | |
| 	{ print TEXI "\n\@enumerate \@bullet\n"; }
 | |
|       else
 | |
| 	{ print TEXI "\n\@end enumerate\n"; } }
 | |
|   elsif ($tag eq "p")
 | |
|     { if ($startflag)
 | |
| 	{ print TEXI "\n\n"; }
 | |
|       if (scalar(@index_deferrers) != 0)
 | |
| 	{ $he->dump;
 | |
| 	  die "Unexpected <$tag> while inside: (" . join(" ", @index_deferrers) . "); bad HTML?"; }
 | |
|       do_deferred_index_entries(); }
 | |
|   elsif ($tag eq "pre")
 | |
|     { print_pre($he);
 | |
|       return 0; }
 | |
|   elsif ($tag eq "table")
 | |
|     { # Could also indicate common formatting for first column, or
 | |
|       # determine relative widths for columns (or determine a prototype row)
 | |
|       if ($startflag)
 | |
| 	{ if (defined $table_columns)
 | |
| 	    { $he->dump;
 | |
| 	      die "Can't deal with table nested inside $table_columns-column table"; }
 | |
| 	  $table_columns = table_columns($he);
 | |
| 	  if ($table_columns < 2)
 | |
| 	    { $he->dump;
 | |
| 	      die "Column with $table_columns columns?"; }
 | |
| 	  elsif ($table_columns == 2)
 | |
| 	    { print TEXI "\n\@table \@asis\n"; }
 | |
| 	  else
 | |
| 	    { print TEXI "\n\@multitable \@columnfractions";
 | |
| 	      for (my $i=0; $i<$table_columns; $i++)
 | |
| 		{ print TEXI " ", 1.0/$table_columns; }
 | |
| 	      print TEXI "\n"; } }
 | |
|       else
 | |
| 	{ if ($table_columns == 2)
 | |
| 	    { print TEXI "\n\@end table\n"; }
 | |
| 	  else
 | |
| 	    { print TEXI "\n\@end multitable\n"; }
 | |
| 	  undef $table_columns; } }
 | |
|   elsif (($tag eq "td") || ($tag eq "th"))
 | |
|     { if ($startflag)
 | |
| 	{ if ($table_first_column)
 | |
| 	    { print TEXI "\n\@item ";
 | |
| 	      $table_first_column = 0; }
 | |
| 	  elsif ($table_columns > 2)
 | |
| 	    { print TEXI "\n\@tab "; } }
 | |
|       else
 | |
| 	{ print TEXI "\n"; } }
 | |
|   elsif ($tag eq "tr")
 | |
|     { if ($startflag)
 | |
| 	{ $table_first_column = 1; } }
 | |
|   elsif ($tag eq "ul")
 | |
|     { if ($startflag)
 | |
| 	{ print TEXI "\n\@itemize \@bullet\n"; }
 | |
|       else
 | |
| 	{ print TEXI "\n\@end itemize\n"; } }
 | |
|   else
 | |
|     { # I used to have a newline before "output_body" here.
 | |
|       print STDERR "output_body: ignoring <$tag> tag\n";
 | |
|       $he->dump;
 | |
|       return 0; }
 | |
| 
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| sub print_pre ( $ )
 | |
| { my ($he_pre) = check_args(1, @_);
 | |
|   if (!has_single_content_string($he_pre))
 | |
|     { die "Multiple or non-string content for <PRE>: ", @{$he_pre->content}; }
 | |
|   my $pre_content = $ {$he_pre->content}[0];
 | |
|   print TEXI "\n\@example";
 | |
|   print TEXI &texi_quote($pre_content);
 | |
|   print TEXI "\@end example\n";
 | |
| }
 | |
| 
 | |
| sub table_columns ( $ )
 | |
| { my ($table) = check_args(1, @_);
 | |
|   my $result = 0;
 | |
|   for my $row (@{$table->content})
 | |
|     { if ($row->tag ne "tr")
 | |
| 	{ $table->dump;
 | |
| 	  $row->dump;
 | |
| 	  die "Expected <TR> as table row."; }
 | |
|       $result = max($result, scalar(@{$row->content})); }
 | |
|   return $result;
 | |
| }
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### Utilities
 | |
| ###
 | |
| 
 | |
| sub min ( $$ )
 | |
| { my ($x, $y) = check_args(2, @_);
 | |
|   return ($x < $y) ? $x : $y;
 | |
| }
 | |
| 
 | |
| sub max ( $$ )
 | |
| { my ($x, $y) = check_args(2, @_);
 | |
|   return ($x > $y) ? $x : $y;
 | |
| }
 | |
| 
 | |
| sub file_to_tree ( $ )
 | |
| { my ($file) = check_args(1, @_);
 | |
| 
 | |
|   my $tree = new HTML::TreeBuilder;
 | |
|   $tree->ignore_unknown(1);
 | |
|   # $tree->warn(1);
 | |
|   $tree->parse_file($file);
 | |
|   cleanup_parse_tree($tree);
 | |
|   return $tree
 | |
| }
 | |
| 
 | |
| 
 | |
| sub has_single_content ( $ )
 | |
| { my ($he) = check_args(1, @_);
 | |
|   if (!ref $he)
 | |
|     { # return 0;
 | |
|       die "Non-reference argument: $he"; }
 | |
|   my $ref_content = $he->content;
 | |
|   if (!defined $ref_content)
 | |
|     { return 0; }
 | |
|   my @content = @{$ref_content};
 | |
|   if (scalar(@content) != 1)
 | |
|     { return 0; }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| # Return true if the content of the element contains only one element itself,
 | |
| # and that inner element has the specified tag.
 | |
| sub has_single_content_with_tag ( $$ )
 | |
| { my ($he, $tag) = check_args(2, @_);
 | |
|   if (!has_single_content($he))
 | |
|     { return 0; }
 | |
|   my $content = $ {$he->content}[0];
 | |
|   if (!ref $content)
 | |
|     { return 0; }
 | |
|   my $content_tag = $content->tag;
 | |
|   if (!defined $content_tag)
 | |
|     { return 0; }
 | |
|   return $content_tag eq $tag;
 | |
| }
 | |
| 
 | |
| sub has_single_content_string ( $ )
 | |
| { my ($he) = check_args(1, @_);
 | |
|   if (!has_single_content($he))
 | |
|     { return 0; }
 | |
|   my $content = $ {$he->content}[0];
 | |
|   if (ref $content)
 | |
|     { return 0; }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| # Return name, href, content.  First two may be undefined; third is an array.
 | |
| # I don't see how to determine if there are more attributes.
 | |
| sub anchor_info ( $ )
 | |
| { my ($he) = check_args(1, @_);
 | |
|   if ($he->tag ne "a")
 | |
|     { $he->dump;
 | |
|       die "passed non-anchor to anchor_info"; }
 | |
|   my $name = $he->attr('name');
 | |
|   my $href = $he->attr('href');
 | |
|   my @content = ();
 | |
|   { my $ref_content = $he->content;
 | |
|     if (defined $ref_content)
 | |
|       { @content = @{$ref_content}; } }
 | |
|   return ($name, $href, @content);
 | |
| }
 | |
| 
 | |
| 
 | |
| sub texi_quote ( $ )
 | |
| { my ($text) = check_args(1, @_);
 | |
|   $text =~ s/([\@\{\}])/\@$1/g;
 | |
|   $text =~ s/ -- / --- /g;
 | |
|   return $text;
 | |
| }
 | |
| 
 | |
| # Eliminate bad punctuation (that confuses Makeinfo or Info) for section titles.
 | |
| sub texi_remove_punctuation ( $ )
 | |
| { my ($text) = check_args(1, @_);
 | |
| 
 | |
|   $text =~ s/^ +//g;
 | |
|   $text =~ s/[ :]+$//g;
 | |
|   $text =~ s/^[1-9][0-9.]* +//g;
 | |
|   $text =~ s/,//g;
 | |
|   # Both embedded colons and " -- " confuse makeinfo.  (Perhaps " -- "
 | |
|   # gets converted into " - ", just as "---" would be converted into " -- ",
 | |
|   # so the names end up differing.)
 | |
|   # $text =~ s/:/ -- /g;
 | |
|   $text =~ s/://g;
 | |
|   return $text;
 | |
| }
 | |
| 
 | |
| 
 | |
| ## Do not use this inside `traverse':  it throws off the traversal.  Use
 | |
| ## html_replace_by_ignore or html_replace_by_meta instead.
 | |
| # Returns 1 if success, 0 if failure.
 | |
| sub html_remove ( $;$ )
 | |
| { my ($he, $parent) = check_args_range(1, 2, @_);
 | |
|   if (!defined $parent)
 | |
|     { $parent = $he->parent; }
 | |
|   my $ref_pcontent = $parent->content;
 | |
|   my @pcontent = @{$ref_pcontent};
 | |
|   for (my $i=0; $i<scalar(@pcontent); $i++)
 | |
|     { if ($pcontent[$i] eq $he)
 | |
| 	{ splice @{$ref_pcontent}, $i, 1;
 | |
| 	  $he->parent(undef);
 | |
| 	  return 1; } }
 | |
|   die "Didn't find $he in $parent";
 | |
| }
 | |
| 
 | |
| 
 | |
| sub html_replace ( $$;$ )
 | |
| { my ($orig, $new, $parent) = check_args_range(2, 3, @_);
 | |
|   if (!defined $parent)
 | |
|     { $parent = $orig->parent; }
 | |
|   my $ref_pcontent = $parent->content;
 | |
|   my @pcontent = @{$ref_pcontent};
 | |
|   for (my $i=0; $i<scalar(@pcontent); $i++)
 | |
|     { if ($pcontent[$i] eq $orig)
 | |
| 	{ $ {$ref_pcontent}[$i] = $new;
 | |
| 	  $new->parent($parent);
 | |
| 	  $orig->parent(undef);
 | |
| 	  return 1; } }
 | |
|   die "Didn't find $orig in $parent";
 | |
| }
 | |
| 
 | |
| sub html_replace_by_meta ( $;$ )
 | |
| { my ($orig, $parent) = check_args_range(1, 2, @_);
 | |
|   my $meta = new HTML::Element "meta";
 | |
|   if (!defined $parent)
 | |
|     { $parent = $orig->parent; }
 | |
|   return html_replace($orig, $meta, $parent);
 | |
| }
 | |
| 
 | |
| sub html_replace_by_ignore ( $;$ )
 | |
| { my ($orig, $parent) = check_args_range(1, 2, @_);
 | |
|   my $ignore = new HTML::Element "ignore";
 | |
|   if (!defined $parent)
 | |
|     { $parent = $orig->parent; }
 | |
|   return html_replace($orig, $ignore, $parent);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| ###
 | |
| ### Collect text elements
 | |
| ###
 | |
| 
 | |
| my @collected_texts;
 | |
| my $collect_texts_stoppoint;
 | |
| my $done_collecting;
 | |
| 
 | |
| sub collect_texts ( $;$ )
 | |
| { my ($root, $stop) = check_args_range(1, 2, @_);
 | |
|   # print STDERR "collect_texts: $root $stop\n";
 | |
|   $collect_texts_stoppoint = $stop;
 | |
|   $done_collecting = 0;
 | |
|   @collected_texts = ();
 | |
|   $root->traverse(\&collect_if_text); # process texts
 | |
|   # print STDERR "collect_texts => ", join(";;;", @collected_texts), "\n";
 | |
|   return @collected_texts;
 | |
| }
 | |
| 
 | |
| sub collect_if_text ( $$$ )
 | |
| { my $he = (check_args(3, @_))[0]; #  ignore depth and startflag arguments
 | |
|   if ($done_collecting)
 | |
|     { return 0; }
 | |
|   if (!defined $he)
 | |
|     { return 0; }
 | |
|   if (!ref $he)
 | |
|     { push @collected_texts, $he;
 | |
|       return 0; }
 | |
|   if ((defined $collect_texts_stoppoint) && ($he eq $collect_texts_stoppoint))
 | |
|     { $done_collecting = 1;
 | |
|       return 0; }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### Clean up parse tree
 | |
| ###
 | |
| 
 | |
| sub cleanup_parse_tree ( $ )
 | |
| { my ($he) = check_args(1, @_);
 | |
|   $he->traverse(\&delete_if_navigation, 'ignore text');
 | |
|   $he->traverse(\&delete_extra_spaces, 'ignore text');
 | |
|   $he->traverse(\&merge_dl, 'ignore text');
 | |
|   $he->traverse(\&reorder_dt_and_dl, 'ignore text');
 | |
|   return $he;
 | |
| }
 | |
| 
 | |
| 
 | |
| ## Simpler version that deletes contents but not the element itself.
 | |
| # sub delete_if_navigation ( $$$ )
 | |
| # { my $he = (check_args(3, @_))[0]; # ignore startflag and depth
 | |
| #   if (($he->tag() eq "div") && ($he->attr('class') eq 'navigation'))
 | |
| #     { $he->delete();
 | |
| #       return 0; }
 | |
| #   else
 | |
| #     { return 1; }
 | |
| # }
 | |
| 
 | |
| sub delete_if_navigation ( $$$ )
 | |
| { my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
 | |
|   if (!$startflag)
 | |
|     { return; }
 | |
| 
 | |
|   if (($he->tag() eq "div") && (defined $he->attr('class')) && ($he->attr('class') eq 'navigation'))
 | |
|     { my $ref_pcontent = $he->parent()->content();
 | |
|       # Don't try to modify @pcontent, which appears to be a COPY.
 | |
|       # my @pcontent = @{$ref_pcontent};
 | |
|       for (my $i = 0; $i<scalar(@{$ref_pcontent}); $i++)
 | |
| 	{ if (${$ref_pcontent}[$i] eq $he)
 | |
| 	    { splice(@{$ref_pcontent}, $i, 1);
 | |
| 	      last; } }
 | |
|       $he->delete();
 | |
|       return 0; }
 | |
|   else
 | |
|     { return 1; }
 | |
| }
 | |
| 
 | |
| sub delete_extra_spaces ( $$$ )
 | |
| { my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
 | |
|   if (!$startflag)
 | |
|     { return; }
 | |
| 
 | |
|   my $tag = $he->tag;
 | |
|   if ($tag =~ /^(head|html|table|tr|ul)$/)
 | |
|     { delete_child_spaces($he); }
 | |
|   delete_trailing_spaces($he);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| sub delete_child_spaces ( $ )
 | |
| { my ($he) = check_args(1, @_);
 | |
|   my $ref_content = $he->content();
 | |
|   for (my $i = 0; $i<scalar(@{$ref_content}); $i++)
 | |
|     { if ($ {$ref_content}[$i] =~ /^ *$/)
 | |
| 	{ splice(@{$ref_content}, $i, 1);
 | |
| 	  $i--; } }
 | |
| }
 | |
| 
 | |
| sub delete_trailing_spaces ( $ )
 | |
| { my ($he) = check_args(1, @_);
 | |
|   my $ref_content = $he->content();
 | |
|   if (! defined $ref_content)
 | |
|     { return; }
 | |
|   # Could also check for previous element = /^h[1-6]$/.
 | |
|   for (my $i = 0; $i<scalar(@{$ref_content})-1; $i++)
 | |
|     { if ($ {$ref_content}[$i] =~ /^ *$/)
 | |
| 	{ my $next_elt = $ {$ref_content}[$i+1];
 | |
| 	  if ((ref $next_elt) && ($next_elt->tag =~ /^(br|dd|dl|dt|hr|p|ul)$/))
 | |
| 	    { splice(@{$ref_content}, $i, 1);
 | |
| 	      $i--; } } }
 | |
|   if ($he->tag =~ /^(dd|dt|^h[1-6]|li|p)$/)
 | |
|     { my $last_elt = $ {$ref_content}[$#{$ref_content}];
 | |
|       if ((defined $last_elt) && ($last_elt =~ /^ *$/))
 | |
| 	{ pop @{$ref_content}; } }
 | |
| }
 | |
| 
 | |
| 
 | |
| # LaTeX2HTML sometimes creates
 | |
| #   <DT>text
 | |
| #   <DL COMPACT><DD>text
 | |
| # which should actually be:
 | |
| #   <DL COMPACT>
 | |
| #   <DT>text
 | |
| #   <DD>text
 | |
| # Since a <DL> gets added, this ends up looking like
 | |
| # <P>
 | |
| #   <DL>
 | |
| #     <DT>
 | |
| #       text1...
 | |
| #       <DL COMPACT>
 | |
| #         <DD>
 | |
| #           text2...
 | |
| #         dt_or_dd1...
 | |
| #     dt_or_dd2...
 | |
| # which should become
 | |
| # <P>
 | |
| #   <DL COMPACT>
 | |
| #     <DT>
 | |
| #       text1...
 | |
| #     <DD>
 | |
| #       text2...
 | |
| #     dt_or_dd1...
 | |
| #     dt_or_dd2...
 | |
| 
 | |
| sub reorder_dt_and_dl ( $$$ )
 | |
| { my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
 | |
|   if (!$startflag)
 | |
|     { return; }
 | |
| 
 | |
|   if ($he->tag() eq "p")
 | |
|     { my $ref_pcontent = $he->content();
 | |
|       if (defined $ref_pcontent)
 | |
| 	{ my @pcontent = @{$ref_pcontent};
 | |
| 	  # print "reorder_dt_and_dl found a <p>\n"; $he->dump();
 | |
| 	  if ((scalar(@pcontent) >= 1)
 | |
| 	      && (ref $pcontent[0]) && ($pcontent[0]->tag() eq "dl")
 | |
| 	      && $pcontent[0]->implicit())
 | |
| 	    { my $ref_dlcontent = $pcontent[0]->content();
 | |
| 	      # print "reorder_dt_and_dl found a <p> and implicit <dl>\n";
 | |
| 	      if (defined $ref_dlcontent)
 | |
| 		{ my @dlcontent = @{$ref_dlcontent};
 | |
| 		  if ((scalar(@dlcontent) >= 1)
 | |
| 		      && (ref $dlcontent[0]) && ($dlcontent[0]->tag() eq "dt"))
 | |
| 		    { my $ref_dtcontent = $dlcontent[0]->content();
 | |
| 		      # print "reorder_dt_and_dl found a <p>, implicit <dl>, and <dt>\n";
 | |
| 		      if (defined $ref_dtcontent)
 | |
| 			{ my @dtcontent = @{$ref_dtcontent};
 | |
| 			  if ((scalar(@dtcontent) > 0)
 | |
| 			      && (ref $dtcontent[$#dtcontent])
 | |
| 			      && ($dtcontent[$#dtcontent]->tag() eq "dl"))
 | |
| 			    { my $ref_dl2content = $dtcontent[$#dtcontent]->content();
 | |
| 			      # print "reorder_dt_and_dl found a <p>, implicit <dl>, <dt>, and <dl>\n";
 | |
| 			      if (defined $ref_dl2content)
 | |
| 				{ my @dl2content = @{$ref_dl2content};
 | |
| 				  if ((scalar(@dl2content) > 0)
 | |
| 				      && (ref ($dl2content[0]))
 | |
| 				      && ($dl2content[0]->tag() eq "dd"))
 | |
| 			    {
 | |
| 			      # print "reorder_dt_and_dl found a <p>, implicit <dl>, <dt>, <dl>, and <dd>\n";
 | |
| 			      # print STDERR "CHANGING\n"; $he->dump();
 | |
| 			      html_replace_by_ignore($dtcontent[$#dtcontent]);
 | |
| 			      splice(@{$ref_dlcontent}, 1, 0, @dl2content);
 | |
| 			      # print STDERR "CHANGED TO:\n"; $he->dump();
 | |
| 			      return 0; # don't traverse children
 | |
| 			    } } } } } } } } }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| # If we find a paragraph that looks like
 | |
| # <P>
 | |
| #   <HR>
 | |
| #   <UL>
 | |
| # then accumulate its links into a contents_list and delete the paragraph.
 | |
| sub process_if_child_links ( $$$ )
 | |
| { my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
 | |
|   if (!$startflag)
 | |
|     { return; }
 | |
| 
 | |
|   if ($he->tag() eq "p")
 | |
|     { my $ref_content = $he->content();
 | |
|       if (defined $ref_content)
 | |
| 	{ my @content = @{$ref_content};
 | |
| 	  if ((scalar(@content) == 2)
 | |
| 	      && (ref $content[0]) && $content[0]->tag() eq "hr"
 | |
| 	      && (ref $content[1]) && $content[1]->tag() eq "ul")
 | |
| 	    { process_child_links($he);
 | |
| 	      $he->delete();
 | |
| 	      return 0; } } }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| # If we find
 | |
| #     <H4>
 | |
| #       "Footnotes"
 | |
| #     <DL>
 | |
| #       <DT>
 | |
| #         <A NAME="foot560">
 | |
| #           "...borrow"
 | |
| #         <A HREF="refcountsInPython.html#tex2html2" NAME="foot560">
 | |
| #           "1.2"
 | |
| #       <DD>
 | |
| #         "The metaphor of ``borrowing'' a reference is not completely correct: the owner still has a copy of the reference. "
 | |
| #       ...
 | |
| # then record the footnote information and delete the section and list.
 | |
| 
 | |
| my $process_if_footnotes_expect_dl_next = 0;
 | |
| 
 | |
| sub process_if_footnotes ( $$$ )
 | |
| { my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
 | |
|   if (!$startflag)
 | |
|     { return; }
 | |
| 
 | |
|   if (($he->tag() eq "h4")
 | |
|       && has_single_content_string($he)
 | |
|       && ($ {$he->content}[0] eq "Footnotes"))
 | |
|     { html_replace_by_ignore($he);
 | |
|       $process_if_footnotes_expect_dl_next = 1;
 | |
|       return 0; }
 | |
| 
 | |
|   if ($process_if_footnotes_expect_dl_next && ($he->tag() eq "dl"))
 | |
|     { my $ref_content = $he->content();
 | |
|       if (defined $ref_content)
 | |
| 	{ $process_if_footnotes_expect_dl_next = 0;
 | |
| 	  my @content = @{$ref_content};
 | |
| 	  for (my $i=0; $i<$#content; $i+=2)
 | |
| 	    { my $he_dt = $content[$i];
 | |
| 	      my $he_dd = $content[$i+1];
 | |
| 	      if (($he_dt->tag ne "dt") || ($he_dd->tag ne "dd"))
 | |
| 		{ $he->dump;
 | |
| 		  die "expected <DT> and <DD> at positions $i and ", $i+1; }
 | |
| 	      my @dt_content = @{$he_dt->content()};
 | |
| 	      if ((scalar(@dt_content) != 2)
 | |
| 		  || ($dt_content[0]->tag ne "a")
 | |
| 		  || ($dt_content[1]->tag ne "a"))
 | |
| 		{ $he_dt->dump;
 | |
| 		  die "Expected 2 anchors as content of <DT>"; }
 | |
| 	      my ($dt1_name, $dt1_href, $dt1_content) = anchor_info($dt_content[0]);
 | |
| 	      my ($dt2_name, $dt2_href, $dt2_content) = anchor_info($dt_content[0]);
 | |
| 	      # unused: $dt1_href, $dt1_content, $dt2_href, $dt2_content
 | |
| 	      if ($dt1_name ne $dt2_name)
 | |
| 		{ $he_dt->dump;
 | |
| 		  die "Expected identical names for anchors"; }
 | |
| 	      html_replace_by_ignore($he_dd);
 | |
| 	      $he_dd->tag("div"); # has no effect
 | |
| 	      $footnotes{$dt1_name} = $he_dd; }
 | |
| 	  html_replace_by_ignore($he);
 | |
| 	  return 0; } }
 | |
| 
 | |
|   if ($process_if_footnotes_expect_dl_next)
 | |
|     { $he->dump;
 | |
|       die "Expected <DL> for footnotes next"; }
 | |
| 
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| ## Merge two adjacent paragraphs containing <DL> items, such as:
 | |
| #     <P>
 | |
| #       <DL>
 | |
| #         <DT>
 | |
| #           ...
 | |
| #         <DD>
 | |
| #           ...
 | |
| #     <P>
 | |
| #       <DL>
 | |
| #         <DT>
 | |
| #           ...
 | |
| #         <DD>
 | |
| #           ...
 | |
| 
 | |
| sub merge_dl ( $$$ )
 | |
| { my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
 | |
|   if (!$startflag)
 | |
|     { return; }
 | |
| 
 | |
|   my $ref_content = $he->content;
 | |
|   if (!defined $ref_content)
 | |
|     { return; }
 | |
|   my $i = 0;
 | |
|   while ($i < scalar(@{$ref_content})-1)
 | |
|     { my $p1 = $ {$ref_content}[$i];
 | |
|       if ((ref $p1) && ($p1->tag eq "p")
 | |
| 	  && has_single_content_with_tag($p1, "dl"))
 | |
| 	{ my $dl1 = $ {$p1->content}[0];
 | |
| 	  # In this loop, rhs, not lhs, of < comparison changes,
 | |
| 	  # because we are removing elements from the content of $he.
 | |
| 	  while ($i < scalar(@{$ref_content})-1)
 | |
| 	    { my $p2 = $ {$ref_content}[$i+1];
 | |
| 	      if (!((ref $p2) && ($p2->tag eq "p")
 | |
| 		    && has_single_content_with_tag($p2, "dl")))
 | |
| 		{ last; }
 | |
| 	      # Merge these two elements.
 | |
| 	      splice(@{$ref_content}, $i+1, 1); # remove $p2
 | |
| 	      my $dl2 = $ {$p2->content}[0];
 | |
| 	      $dl1->push_content(@{$dl2->content}); # put $dl2's content in $dl1
 | |
| 	    }
 | |
| 	  # extra increment because next element isn't a candidate for $p1
 | |
| 	  $i++; }
 | |
|       $i++; }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### Testing
 | |
| ###
 | |
| 
 | |
| sub test ( $$ )
 | |
| { my ($action, $file) = check_args(2, @_);
 | |
| 
 | |
|   # General testing
 | |
|   if (($action eq "view") || ($action eq ""))
 | |
|     { # # $file = "/homes/gws/mernst/www/links.html";
 | |
|       # # $file = "/homes/gws/mernst/www/index.html";
 | |
|       # # $file = "/homes/fish/mernst/java/gud/doc/manual.html";
 | |
|       # # $file = "/projects/cecil/cecil/doc/manuals/stdlib-man/stdlib/stdlib.html";
 | |
|       # # $file = "/homes/fish/mernst/tmp/python-doc/html/index.html";
 | |
|       # $file = "/homes/fish/mernst/tmp/python-doc/html/api/complexObjects.html";
 | |
|       my $tree = file_to_tree($file);
 | |
| 
 | |
|       ## Testing
 | |
|       # print STDERR $tree->as_HTML;
 | |
|       $tree->dump();
 | |
| 
 | |
|       # print STDERR $tree->tag(), "\n";
 | |
|       # print STDERR @{$tree->content()}, "\n";
 | |
|       # 
 | |
|       # for (@{ $tree->extract_links(qw(a img)) }) {
 | |
|       #   my ($link, $linkelem) = @$_;
 | |
|       #   print STDERR "$link ", $linkelem->as_HTML;
 | |
|       #   }
 | |
|       # 
 | |
|       # print STDERR @{$tree->extract_links()}, "\n";
 | |
| 
 | |
|       # my @top_level_elts = @{$tree->content()};
 | |
| 
 | |
|       # if scalar(@{$tree->content()})
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   elsif ($action eq "raw")
 | |
|     { my $tree = new HTML::TreeBuilder;
 | |
|       $tree->ignore_unknown(1);
 | |
|       # $tree->warn(1);
 | |
|       $tree->parse_file($file);
 | |
| 
 | |
|       $tree->dump();
 | |
| 
 | |
|       # cleanup_parse_tree($tree);
 | |
|       # $tree->dump();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   # Test dealing with a section.
 | |
|   elsif ($action eq "section")
 | |
|     { # my $file;
 | |
|       # $file = "/homes/fish/mernst/tmp/python-doc/html/api/intro.html";
 | |
|       # $file = "/homes/fish/mernst/tmp/python-doc/html/api/includes.html";
 | |
|       # $file = "/homes/fish/mernst/tmp/python-doc/html/api/complexObjects.html";
 | |
|       process_section_file($file, 0, "Title");
 | |
|     }
 | |
| 
 | |
|   # Test dealing with many sections
 | |
|   elsif (0)
 | |
|     { my @files = ("/homes/fish/mernst/tmp/python-doc/html/api/about.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/abstract.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/api.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/cObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/complexObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/concrete.html",
 | |
| 		   # "/homes/fish/mernst/tmp/python-doc/html/api/contents.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/countingRefs.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/debugging.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/dictObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/embedding.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/exceptionHandling.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/exceptions.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/fileObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/floatObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/front.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/fundamental.html",
 | |
| 		   # "/homes/fish/mernst/tmp/python-doc/html/api/genindex.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/importing.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/includes.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/index.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/initialization.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/intObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/intro.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/listObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/longObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/mapObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/mapping.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/newTypes.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/node24.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/noneObject.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/number.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/numericObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/object.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/objects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/os.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/otherObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/processControl.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/refcountDetails.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/refcounts.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/sequence.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/sequenceObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/standardExceptions.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/stringObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/threads.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/tupleObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/typeObjects.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/types.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/utilities.html",
 | |
| 		   "/homes/fish/mernst/tmp/python-doc/html/api/veryhigh.html");
 | |
|       for my $file (@files)
 | |
| 	{ print STDERR "\n", "=" x 75, "\n", "$file:\n";
 | |
| 	  process_section_file($file, 0, "Title");
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   # Test dealing with index.
 | |
|   elsif ($action eq "index")
 | |
|     { # my $file;
 | |
|       # $file = "/homes/fish/mernst/tmp/python-doc/html/api/genindex.html";
 | |
| 
 | |
|       process_index_file($file, "\@cindex");
 | |
|       print_index_info();
 | |
|     }
 | |
| 
 | |
|   else
 | |
|     { die "Unrecognized action `$action'"; }
 | |
| }
 | |
| 
 | |
| 
 | |
| ###########################################################################
 | |
| ### Main loop
 | |
| ###
 | |
| 
 | |
| sub process_contents_file ( $ )
 | |
| { my ($file) = check_args(1, @_);
 | |
| 
 | |
|   # could also use File::Basename
 | |
|   my $info_file = $file;
 | |
|   $info_file =~ s/(\/?index)?\.html$//;
 | |
|   if ($info_file eq "")
 | |
|     { chomp($info_file = `pwd`); }
 | |
|   $info_file =~ s/^.*\///;	# not the most efficient way to remove dirs
 | |
| 
 | |
|   $html_directory = $file;
 | |
|   $html_directory =~ s/(\/|^)[^\/]+$/$1/;
 | |
| 
 | |
|   my $texi_file = "$info_file.texi";
 | |
|   open(TEXI, ">$texi_file");
 | |
| 
 | |
|   print TEXI "\\input texinfo   \@c -*-texinfo-*-\n";
 | |
|   print TEXI "\@c %**start of header\n";
 | |
|   print TEXI "\@setfilename $info_file\n";
 | |
| 
 | |
|   # 2. Summary Description and Copyright
 | |
|   #      The "Summary Description and Copyright" segment describes the
 | |
|   #      document and contains the copyright notice and copying permissions
 | |
|   #      for the Info file.  The segment must be enclosed between `@ifinfo'
 | |
|   #      and `@end ifinfo' commands so that the formatters place it only in
 | |
|   #      the Info file.
 | |
|   # 
 | |
|   # The summary description and copyright segment does not appear in the
 | |
|   # printed document.
 | |
|   # 
 | |
|   #      @ifinfo
 | |
|   #      This is a short example of a complete Texinfo file.
 | |
|   #      
 | |
|   #      Copyright @copyright{} 1990 Free Software Foundation, Inc.
 | |
|   #      @end ifinfo
 | |
| 
 | |
| 
 | |
|   # 3. Title and Copyright
 | |
|   #      The "Title and Copyright" segment contains the title and copyright
 | |
|   #      pages and copying permissions for the printed manual.  The segment
 | |
|   #      must be enclosed between `@titlepage' and `@end titlepage'
 | |
|   #      commands.  The title and copyright page appear only in the printed
 | |
|   #      manual.
 | |
|   # 
 | |
|   # The titlepage segment does not appear in the Info file.
 | |
|   # 
 | |
|   #      @titlepage
 | |
|   #      @sp 10
 | |
|   #      @comment The title is printed in a large font.
 | |
|   #      @center @titlefont{Sample Title}
 | |
|   #      
 | |
|   #      @c The following two commands start the copyright page.
 | |
|   #      @page
 | |
|   #      @vskip 0pt plus 1filll
 | |
|   #      Copyright @copyright{} 1990 Free Software Foundation, Inc.
 | |
|   #      @end titlepage
 | |
| 
 | |
| 
 | |
|   # 4. `Top' Node and Master Menu
 | |
|   #      The "Master Menu" contains a complete menu of all the nodes in the
 | |
|   #      whole Info file.  It appears only in the Info file, in the `Top'
 | |
|   #      node.
 | |
|   # 
 | |
|   # The `Top' node contains the master menu for the Info file.  Since a
 | |
|   # printed manual uses a table of contents rather than a menu, the master
 | |
|   # menu appears only in the Info file.
 | |
|   # 
 | |
|   #      @node    Top,       First Chapter, ,         (dir)
 | |
|   #      @comment node-name, next,          previous, up
 | |
|   # 
 | |
|   #      @menu
 | |
|   #      * First Chapter::    The first chapter is the
 | |
|   #                           only chapter in this sample.
 | |
|   #      * Concept Index::    This index has two entries.
 | |
|   #      @end menu
 | |
| 
 | |
| 
 | |
| 
 | |
|   $current_ref_tdf = [ "Top", 0, $ARGV[0] ];
 | |
|   process_section_file($file, 0, "Top");
 | |
|   while (scalar(@contents_list))
 | |
|   { $current_ref_tdf = shift @contents_list;
 | |
|     process_section_file($ {$current_ref_tdf}[2], $ {$current_ref_tdf}[1], $ {$current_ref_tdf}[0]);
 | |
|   }
 | |
| 
 | |
|   print TEXI "\n";
 | |
|   for my $indextitle (@index_titles)
 | |
|     { print TEXI "\@node $indextitle\n";
 | |
|       print TEXI "\@unnumbered $indextitle\n";
 | |
|       print TEXI "\@printindex $ {$index_info{$indextitle}}[1]\n";
 | |
|       print TEXI "\n"; }
 | |
| 
 | |
|   print TEXI "\@contents\n";
 | |
|   print TEXI "\@bye\n";
 | |
|   close(TEXI);
 | |
| }
 | |
| 
 | |
| # This needs to be last so global variable initializations are reached.
 | |
| 
 | |
| if (scalar(@ARGV) == 0)
 | |
| { die "No arguments supplied to html2texi.pl"; }
 | |
| 
 | |
| if ($ARGV[0] eq "-test")
 | |
| { my @test_args = @ARGV[1..$#ARGV];
 | |
|   if (scalar(@test_args) == 0)
 | |
|     { test("", "index.html"); }
 | |
|   elsif (scalar(@test_args) == 1)
 | |
|     { test("", $test_args[0]); }
 | |
|   elsif (scalar(@test_args) == 2)
 | |
|     { test($test_args[0], $test_args[1]); }
 | |
|   else
 | |
|     { die "Too many test arguments passed to html2texi: ", join(" ", @ARGV); }
 | |
|   exit();
 | |
| }
 | |
| 
 | |
| if (scalar(@ARGV) != 1)
 | |
| { die "Pass one argument, the main/contents page"; }
 | |
| 
 | |
| process_contents_file($ARGV[0]);
 | |
| 
 | |
| # end of html2texi.pl
 | 
