# $Id: Transforms.pm 5784 2008-12-04 15:41:26Z mnodine $
# Copyright (C) 2002-2005 Freescale Semiconductor, Inc.
# Distributed under terms of the Perl license, which is the disjunction of
# the GNU General Public License (GPL) and the Artistic License.

# Note: These transform names were chosen to correspond with those of
# the original python implementation of docutils.
@Transforms::TRANSFORMS = qw(docutils.transforms.references.MarkReferenced
			     docutils.transforms.references.IndTargets
			     docutils.transforms.frontmatter.DocTitle
			     docutils.transforms.frontmatter.SectionSubTitle
			     docutils.transforms.frontmatter.DocInfo
			     docutils.transforms.references.CitationReferences
			     docutils.transforms.misc.Pending
			     docutils.transforms.universal.EmptyTopics
			     docutils.transforms.references.AutoFootnotes
			     docutils.transforms.references.FootnoteReferences
			     docutils.transforms.references.References
			     docutils.transforms.references.Unreferenced
			     docutils.transforms.universal.Transitions
			     docutils.transforms.universal.ScoopMessages
			     docutils.transforms.universal.Messages
			     docutils.transforms.universal.Decorations
			     );

%Transforms::PENDING_PRIORITY =
    (
     'docutils.transforms.components.Filter'      => -200,
     'docutils.transforms.parts.Class'            =>  100,
     'docutils.transforms.parts.Contents'         => 1000,
     'docutils.transforms.parts.Sectnum'          => -100,
     'docutils.transforms.references.TargetNotes' =>  200,
     );


# Note: These package names were chosen to correspond with those of
# the original python implementation of docutils.
package Text::Restructured::docutils::transforms::components;

($VERSION) = q$Revision: 5784 $ =~ /(\d+)/g;

=pod
=begin reST
=begin Usage
Defines for reStructuredText transforms
---------------------------------------
-D generator=<0|1>     Include a "Generated by" credit at the end of
                       the document (default is 1).
-D date=<0|1>          Include the date at the end of the document
                       (default is 0).
-D docinfo-levels=<number>
                       Indicates how many section levels to go down to
                       process docinfo field lists (default is 0).
                       (Values greater than 0 technically violate the DTD).
-D time=<0|1>          Include the date and time at the end of the
                       document (default is 1, overrides date if 1).
-D source-link=<0|1>   Include a "View document source" link (default
                       is 1).
-D source-url=<URL>    Use the supplied <URL> verbatim for a "View
                       document source" link; implies -D source_link=1.
-D keep-title-section  Keeps the section intact from which the document
                       title is taken.
-D section-subtitles   Promote lone subsection titles to section subtitles.
=end Usage
=end reST
=cut

# Global variables:
#   ``@Transforms::TRANSFORMS``
#     Array of transform names in the order they will be applied.

use vars qw($DOM);
BEGIN {
    *DOM = "Text::Restructured::DOM";
}

use strict;

# Processes a docutils.transforms.components.Filter transform.
# Arguments: pending DOM, parser obj, details hash reference
sub Filter{
    my ($dom, $parser, $details) = @_;

    if ("'$parser->{opt}{w}'" eq $details->{format} ||
	$parser->{opt}{w} eq 'dom') {
	my $nodes = $details->{nodes};
	return $DOM->new($nodes->tag, %{$nodes->{attr}});
    }
    return;
}

package Text::Restructured::docutils::transforms::frontmatter;

use vars qw($DOM);
BEGIN {
    *DOM = "Text::Restructured::DOM";
}

# Create closure for "static" variable

BEGIN {
my @bib_elements = qw(author authors organization address contact version
		      revision status date copyright dedication abstract);
my %BIB_ELEMENTS;
@BIB_ELEMENTS{@bib_elements} = (1) x @bib_elements;

sub process_docinfo {
    my ($dom, $parser) = @_;

     # Create a docinfo if needed
    my @field_lists = grep($_->tag eq 'field_list', $dom->contents);
    my %element_seen;
    if (@field_lists) {
	my $fl = $field_lists[0];
	my @content = $fl->contents;
	# Modify the field list in situ
	$fl->tag('docinfo');
	$fl->replace();
	my $docinfo = $fl;
	my $field;
	my @postdocinfo; # Things to be added to content list after docinfo
	foreach $field (@content) {
	    my $fn = $field->first;
	    my $fb = $field->child(1);
	    my $name = $fn->first->{text};
	    my $origname = $name;
	    $name =~ tr/A-Z/a-z/;
	    my $tname = $name;
	    substr($tname,0,1) =~ tr/ad/AD/;
	    if ($BIB_ELEMENTS{$name}) {
		$element_seen{$name}++;
		if ($element_seen{$name} > 1 && $name =~ /abstract/) {
		    $fb->append
			($parser->system_message(2, $field->{source},
					     $field->{lineno},
					     qq(There can only be one "$tname" field.)));
		    $docinfo->append($field);
		}
		elsif ($name =~ /^(dedication|abstract)$/) {
		    my $topic = $DOM->new('topic', classes=>[ $name ]);
		    my $title = $DOM->new('title');
		    $topic->append($title);
		    $title->append($DOM->newPCDATA($tname));
		    $topic->append($fb->contents);
		    push(@postdocinfo, $topic);
		}
		elsif ($fb->num_contents < 1) {
		    $fb->append
			($parser->system_message(2, $field->{source},
					     $field->{lineno},
					     qq(Cannot extract empty bibliographic field "$origname".)));
		    $docinfo->append($field);
		}
		elsif ($name eq 'authors') {
		    my $bib = $DOM->new($name);
		    my @contents = $fb->first->contents;
		    # There are three cases: bullet_lists,
		    # multiple paragraphs, and string.
		    if (($fb->num_contents == 1 &&
			 ($fb->first->tag !~
			  /paragraph|bullet_list/ ||
			  $fb->first->tag eq 'bullet_list' &&
			  grep($_->num_contents != 1 ||
			       $_->first->tag ne 'paragraph',
			       $fb->first->contents))
			 ) ||
			($fb->num_contents > 1 &&
			 grep($_->tag ne 'paragraph', $fb->contents))
			) {
			$fb->append
			    ($parser->system_message(2, $field->{source},
						 $field->{lineno},
						 qq(Bibliographic field "Authors" incompatible with extraction: it must contain either a single paragraph (with authors separated by one of ";,"), multiple paragraphs (one per author), or a bullet list with one paragraph (one author) per item.)));
			$docinfo->append($field);
		    }
		    elsif ($fb->num_contents > 1) {
			# Multiple paragraphs
			foreach ($fb->contents) {
			    my $author = $DOM->new('author');
			    $bib->append($author);
			    $author->append($_->contents);
			}
		    }
		    elsif ($fb->first->tag eq 'bullet_list') {
			my $bl = $fb->first;
			foreach ($bl->contents) {
			    my $author = $DOM->new('author');
			    $bib->append($author);
			    $author->append($_->first->contents);
			}
		    }
		    else {
			my $text;
			$fb->Recurse(sub {
			    my ($dom) = @_;
			    $text .= $dom->{text}
			    if $dom->tag eq '#PCDATA';
			});
			my @authors = $text =~ /;/ ?
			    split(/\s*;\s*/, $text) :
			    split(/\s*,\s*/, $text);
			foreach (@authors) {
			    my $author = $DOM->new('author');
			    $bib->append($author);
			    $author->append($DOM->newPCDATA($_));
			}
		    }
		    if ($bib->num_contents == 1) {
			$docinfo->append($bib->first);
		    }
		    elsif ($bib->num_contents > 1) {
			$docinfo->append($bib);
		    }
		}
		elsif ($fb->num_contents > 1) {
		    $fb->append
			($parser->system_message(2, $field->{source},
					     $field->{lineno},
					     qq(Cannot extract compound bibliographic field "$origname".)));
		    $docinfo->append($field);
		}
		elsif ($fb->first->tag ne 'paragraph') {
		    $fb->append
			($parser->system_message(2, $field->{source},
					     $field->{lineno},
					     qq(Cannot extract bibliographic field "$origname" containing anything other than a single paragraph.)));
		    $docinfo->append($field);
		}
		else {
		    my $bib = $DOM->new($name);
		    %{$bib->{attr}} = (%Text::Restructured::XML_SPACE)
			if $name =~ /^address$/i;
		    $docinfo->append($bib);
		    my @contents = $fb->first->contents;
		    my $pcdata = $contents[0];
		    $pcdata->{text} =~ s/\$\w+:\s*(.+?)(?:,v)?\s\$/$1/g
			if defined $pcdata->{text};
		    $bib->append(@contents);
		}
	    }
	    else {
		$docinfo->append($field);
	    }
	}

	# Anything before the docinfo that's not a title, subtitle, or
	# decoration has to move after it.
	my $i;
	my $docinfo_seen = 0;
	my @new_content;  
	for ($i=0; $i < $dom->num_contents; $i++) {
	    my $c = $dom->child($i);
	    if ($docinfo_seen || $c->tag =~ /^((sub)?title|decoration)$/) {
		push @new_content, $c;
	    }
	    elsif ($c->tag eq 'docinfo') {
		$docinfo_seen = 1;
		push @new_content, $c, @postdocinfo;
	    }
	    else {
		push @postdocinfo, $c;
	    }
	}
	$dom->replace(@new_content);
#	$dom->{content} = \@new_content;
    }
}
}

# Processes a docutils.transforms.frontmatter.DocInfo transform.
# Processes field lists at the beginning of the DOM that are one of
# the docinfo types into a docinfo section.
# Arguments: top-level DOM, parser obj
sub DocInfo {
    my ($dom, $parser, $level) = @_;

    $level = $level || 0;
    process_docinfo($dom, $parser);
    if ($level < ($parser->{opt}{D}{docinfo_levels} || 0)) {
	my @sections = grep($_->tag eq 'section', $dom->contents);
	foreach my $section (@sections) {
	    process_docinfo($section, $parser);
	}
    }
}

# Processes a docutils.transforms.frontmatter.DocTitle transform.
# Creates a document title if the top-level DOM has only one top-level
# section.  Creates a subtitle if a unique top-level section has a
# unique second-level section.
# Arguments: top-level DOM, parser obj
sub DocTitle {
    my ($dom, $parser) = @_;

    create_title($dom, $parser);
    $dom->{attr}{title} = $dom->{'.details'}{title}
        if defined $dom->{'.details'}{title};
    return;
}

# Processes a docutils.transforms.frontmatter.SectionSubTitle transform.
# Creates a subtitle if a section DOM has only one top-level
# subsection.
# Arguments: top-level DOM, parser obj
sub SectionSubTitle {
    my ($topdom, $parser) = @_;
    
    $topdom->Reshape
	(sub {
	     my($dom) = @_;
	     if ($dom->tag eq 'section') {
		 create_title($dom, $parser, 1);
	     }
	     return $dom;
	 }
	 , 'pre') if $parser->{opt}{D}{section_subtitles};
    return;
}

# Used to turn a lone section into a title/subtitle of the given DOM
# Arguments: section DOM object
# Returns: None
# Side-effects: May reorganize the contents to promote a lone section
sub create_title {
    my ($dom, $parser) = @_;
    # If the document has one section, coalesce it with the DOM
    my @sections = grep($_->tag eq 'section', $dom->contents);
    
    if (@sections == 1 && ($parser->{opt}{D}{keep_title_section} ||
			   !grep($_->tag !~
				 /^(section|comment|system_message|target|substitution_definition|title|decoration)$/,
				 $dom->contents))) {
	my $sec = $sections[0];
	push @{$sec->{attr}{classes}}, 'title'
	    if $parser->{opt}{D}{keep_title_section};
	my @non_sections = grep($_->tag !~ /^(?:section|title)$/,
				$dom->contents);
	my ($prev_title) = grep $_->tag eq 'title', $dom->contents;
	# Get the title text
	my $ttext = '';
	$sec->first->Recurse(sub {
	    my ($dom) = @_;
	    $ttext .= $dom->{text} if $dom->tag eq '#PCDATA';
	});
	chomp $ttext;
	my $dom_ids = $dom->{attr}{ids};
	if (! $prev_title) {
	    $dom->{attr}{title} =
		  $ttext; #Text::Restructured::NormalizeName($ttext, 'keepcase');
	    @{$dom->{attr}}{keys %{$sec->{attr}}} = values %{$sec->{attr}};
	}
	if ($parser->{opt}{D}{keep_title_section} && ! defined $prev_title) {
	    # Don't duplicate ids from the section if we keep the section
	    delete $dom->{attr}{ids};
	    my $title = $sec->first;
	    $dom->prepend($title);
	}
	else {
	    $dom->replace($sec->contents);
	    $dom->splice(1, 0, @non_sections);

	    if (defined $prev_title) {
		my $subtitle = $dom->first;
		$subtitle->tag('subtitle');
		$subtitle->{attr}{ids} = [ $parser->NormalizeId($ttext) ];
		$subtitle->{attr}{names} = [ $parser->NormalizeName($ttext) ];
		$dom->prepend($prev_title);
	    }
	    else {
		# Check for a subtitle
		my @sections = grep($_->tag eq 'section', $dom->contents);
		if (@sections == 1 &&
		    !grep($_->tag !~
			  /^(section|comment|system_message|target|substitution_definition|title|decoration)$/,
				 $dom->contents)) {
		    my $sec = $sections[0];
		    my $title = $sec->splice(0, 1);
		    my @non_sections = grep($_->tag !~ /^(section|title)$/,
					    $dom->contents);
		    $sec->prepend(@non_sections);
		    $title->tag('subtitle');
		    $title->{attr} = $sec->{attr};
		    $dom->replace(grep($_->tag eq 'title',
				       $dom->contents));
		    $dom->append($title, $sec->contents);
		}
	    }
	}
    }
}

package Text::Restructured::docutils::transforms::misc;

# This package contains routines for transforms of DOM trees

# Processes a docutils.transforms.misc.Pending transform.
# Traverses the DOM tree looking for Pending nodes and applies
# whatever internal transform was specified for them.
# Arguments: top-level DOM, parser obj
sub Pending {
    my ($topdom, $parser) = @_;

    # Collect together all the pending transactions
    my @pendings;
    $topdom->Recurse
	(sub {
	     my($dom) = @_;
	     push @pendings, $dom if $dom->tag eq 'pending';
	 });
    # Sort them by priority
    @pendings = sort {
	($Transforms::PENDING_PRIORITY{$a->{internal}{'.transform'}} || 0) <=>
	($Transforms::PENDING_PRIORITY{$b->{internal}{'.transform'}} || 0)
    } @pendings;
    # Run them in priority order
    foreach my $dom (@pendings) {
	my @result;
	my $transform = $dom->{internal}{'.transform'};
	(my $t = "Text::Restructured::$transform") =~ s/\./::/g;
	# Check the original transform path before giving up
	($t = $transform) =~ s/\./::/g if ! defined &$t;
	if (! defined &$t) {
	    push @result,
	    $parser->system_message(4, $dom->{source}, $dom->{lineno},
				    qq(No transform code found for "$transform".));
	}
	else {
	    my $details = $dom->{internal}{'.details'};
	    no strict 'refs';
	    print STDERR "Debug: Transform $transform\n" if $parser->{opt}{d};
	    @result = eval { &$t($dom, $parser, $details) };
	    push @result,
	    $parser->system_message(4, $dom->{source}, $dom->{lineno},
				    qq(Error in transform code "$transform": $@))
		if $@;
	}
	$dom->substitute(@result);
    }
}

package Text::Restructured::docutils::transforms::parts;

# This package contains routines for pending transforms of DOM trees

use vars qw($DOM);
BEGIN {
    *DOM = "Text::Restructured::DOM";
}

# Processes a docutils.transforms.parts.Class transform.
# Arguments: pending DOM, parser obj, details hash reference
sub Class {
    my ($dom, $parser, $details) = @_;

    my $next = $dom->next('comment|substitution_definition|target|system_message|pending');
    my $tag = $next->tag if defined $next;
    if (defined $tag && $tag =~ /^(?:paragraph|.*_list|section|.*_block|block_quote|table|figure|raw)$/) {
	# It's a classable tag
	push @{$next->{attr}{classes}}, split(/\s+/, $details->{class});
	return;
    }
    return $parser->system_message(2, $dom->{source}, $dom->{lineno},
			       qq(Error in "class" directive: there is no following block element for which a class can be specified.),
			       $dom->{lit})
}

# Processes a docutils.transforms.parts.Contents transform.
# Arguments: pending DOM, parser obj, details hash reference
sub Contents {
    my ($dom, $parser, $details) = @_;
    my @errs;

    my $topdom = $parser->{TOPDOM};
    my $parent = $dom->parent();
    my $backlinks =
	defined $details->{backlinks} ? $details->{backlinks} : '';
    # First we compile the table of contents
    # uncoverable branch false note:parent always has ids
    my $contid = $parent->{attr}{ids}[0] if defined $parent->{attr}{ids};

    my $bl = $DOM->new('bullet_list');
    my $depth = 0; # Used in closure of sub
    my @list = ($bl); # Used in closure of sub
    my $start = defined $details->{local} ? $dom->{section} : $topdom;
    $start->Recurse
	(sub {
	     my($dom, $when) = @_;

	     if ($dom->tag eq 'section' && $dom ne $start) {
		 $depth-- if $when eq 'post';
		 if (! defined $details->{depth} ||
		     $depth < $details->{depth}) {
		     my $bl = $list[-1];
		     my $li; 
		     if ($when eq 'pre') {
			 my $id = $parser->Id();
			 $li = $DOM->new('list_item'); #, ids=>$id);
			 $bl->append($li);
			 if ($backlinks !~ /none/i &&
			     $dom->first->first->tag ne 'reference') {
			     $dom->first->{attr}{refid} =
				 ($backlinks =~ /top/i) ? $contid : $id;
			 }
			 my $para = $DOM->new('paragraph');
			 $li->append($para);
			 my $ref = $DOM->new('reference', ids=> [ $id ],
					   refid=>$dom->{attr}{ids}[0]);
			 $para->append($ref);
			 my @contents;  # Used in the closure of the sub
			 $dom->first->Recurse
			     (sub {
				  my ($dom, $when) = @_;
				  my $tag = $dom->tag;
				  if ($tag =~ /^(?:title|(footnote|citation)_reference|interpreted|problematic|reference|target)$/) {
				      # Ignore
				  }
				  elsif ($tag =~ /image/) {
				      push(@contents,
					   $DOM->newPCDATA($dom->{attr}{alt}))
					  if defined $dom->{attr}{alt} &&
					  $when eq 'pre';
				  }
				  else {
				      # Don't recurse
				      push(@contents, $dom);
				      return 1;
				  }
				  return 0;
			      }
			      , 'both');
			 $ref->append(@contents);
			 $bl->{attr}{classes} = ['auto-toc']
			     if ($dom->first->first->tag
				 eq 'generated');
		     }

		     # Check to see if I have any nested sections
		     if (grep($_->tag eq 'section', $dom->contents)
			 && (! defined $details->{depth} ||
			     $depth < $details->{depth}-1)) {
			 if ($when eq 'pre') {
			     my $new_bl = $DOM->new('bullet_list');
			     $li->append($new_bl);
			     push(@list, $new_bl);
			 }
			 else {
			     pop(@list);
			 }
		     }
		 }
		 $depth++ if $when eq 'pre';
		 return 0;
	     }
	     return $dom ne $start;
	 }
	 , 'both') ;

    # Need to remove all traces of ourselves if the bullet list is empty
    if ($bl->num_contents == 0) {
	$start->splice(0, 1);
	return;
    }
    return $bl, @errs;
}

# Processes a docutils.transforms.parts.Sectnum transform.
# Auto-numbers the sections in the document.
# Arguments: pending DOM, parser obj, details hash reference
sub Sectnum {
    my ($dom, $parser, $details) = @_;

    my $startdom = $dom;
    while ($startdom->tag ne 'section' && $startdom->tag ne 'document') {
	$startdom = $startdom->parent;
    }
    # First process the table of contents topic if it exists
    my @list; # Used in closure of sub
    my $prefix = defined $details->{prefix} ? $details->{prefix} : '';
    my $suffix = defined $details->{suffix} ? $details->{suffix} : '';
    my $start = $details->{start} || 1;
    my $prefix_title = defined $details->{'prefix-title'} ? 1 : 0;
    if ($prefix_title && $prefix ne '') {
	$startdom->{attr}{title} = $prefix . ("\xa0"x3) .
	    $startdom->{attr}{title} if defined $startdom->{attr}{title};
	my $gen = $DOM->new('generated', classes=>['sectnum']);
	$gen->append($DOM->newPCDATA($prefix . ("\xa0"x3)));
	$startdom->first->prepend($gen);
    }

    # Next process the sections recursively
    @list = ($start-1);
    $startdom->Recurse
	(sub {
	     my($dom, $when) = @_;
	     return 0 if $dom eq $startdom;
	     if ($dom->tag eq 'section') {
		 if ($when eq 'pre') {
		     if (! defined $details->{depth} ||
			 @list <= $details->{depth}) {
			 my $title = $dom->first;
			 $title->{attr}{auto} = 1;
			 $list[-1]++;
			 my $gen = $DOM->new('generated', classes=>['sectnum']);
			 $gen->append($DOM->newPCDATA($prefix . join('.',@list)
						    . $suffix . ("\xa0"x3)));
			 $title->prepend($gen);
		     }
		     push(@list, 0);
		 }
		 else { pop(@list); }
	     }
	     return 0;
	 }
	 , 'both') ;
    
    return;
}

package Text::Restructured::docutils::transforms::references;

# This package contains routines for transforms of DOM trees

use vars qw($DOM);
BEGIN {
    *DOM = "Text::Restructured::DOM";
}

# Run-time global variables
use vars qw($AUTO_FOOTNOTE_REF $LAST_AUTO_FOOTNOTE @AUTO_FOOTNOTES
	    @FOOTNOTE_SYMBOLS);

# Create a closure for some "static" variables
BEGIN {
@FOOTNOTE_SYMBOLS = ("*", chr 0x2020, chr 0x2021, chr 0xa7,
		     chr 0xb6, '#', chr 0x2660, chr 0x2665,
		     chr 0x2666, chr 0x2663);
my $NEXT_SYMBOL_FOOTNOTE = 0;

# Processes a docutils.transforms.references.AutoFootnotes transform.
# Computes numbers for autonumbered footnotes.
# Arguments: top-level DOM, parser obj
sub AutoFootnotes {
    my ($dom, $parser) = @_;

    # Compute numbers for autonumbered footnotes
    $dom->Recurse
	(sub {
	     my($dom) = @_;
	     my $tag = $dom->tag;
	     if ($tag eq 'footnote') {
		 if ($dom->{attr}{auto}) {
		     my $label = $DOM->new('label');
		     $dom->prepend($label);
		     if ($dom->{attr}{auto} eq '1') {
			 while (defined $parser->{REFERENCE_DOM}{$tag}
				{++$LAST_AUTO_FOOTNOTE}) { };
			 if (! defined $dom->{attr}{names} &&
			     ! defined $dom->{attr}{dupnames}) {
			     push(@AUTO_FOOTNOTES, $dom);
			     $dom->{attr}{names} = [ $LAST_AUTO_FOOTNOTE ];
			     $parser->RegisterName($dom, $dom->{source},
						   $dom->{lineno});
			 }
			 $label->append($DOM->newPCDATA
					($LAST_AUTO_FOOTNOTE));
		     }
		     else {
			 push(@AUTO_FOOTNOTES, $dom);
			 my $multiplier =
			     int($NEXT_SYMBOL_FOOTNOTE/@FOOTNOTE_SYMBOLS) + 1;
			 my $index =
			     $NEXT_SYMBOL_FOOTNOTE % @FOOTNOTE_SYMBOLS;
			 my $name =
			     ($FOOTNOTE_SYMBOLS[$index]) x $multiplier;
			 $label->append($DOM->newPCDATA($name));
			 $NEXT_SYMBOL_FOOTNOTE++;
		     }
		     $parser->{REFERENCE_DOM}{$tag}{$dom->{attr}{names}[0]} =
			 $dom
			 if defined $dom->{attr}{names};
		     $parser->{REFERENCE_DOM}{$tag}{$dom->{attr}{ids}[0]} =
			 $dom;
		 }
	     }
	     return 0;
	 }
	 , 'pre');
}
}

# Processes a docutils.transforms.references.IndTargets transform.
# Links indirect targets and bare targets to their eventual destination.
# Arguments: top-level DOM, parser obj
sub IndTargets {
    my ($dom, $parser) = @_;

    my @errs;
    # Process indirect targets
    $dom->Reshape
	(sub {
	     my($dom) = @_;

	     my $parent = $dom->parent();
	     my $tag = $dom->tag;
	     if ($tag eq 'target' && ! $dom->{forward}) {
		 my (%seen, %ind);
		 my $ignores = 'comment|substitution_definition|system_message|pending|image';
		 my $next = $dom;
		 my @chain;
		 while ($next->tag eq 'target' &&
			$next->parent()->tag ne 'paragraph' &&
			(defined $next->{attr}{refname} ||
			 ! grep(/ref(uri|id)/, keys %{$next->{attr}}))) {
		     # This is either an indirect target or a bare target
		     # uncoverable branch true note:Assert can't have badtarget
		     return $dom if $next->{badtarget};
		     
		     if (defined $next->{attr}{refname}) {
			 # This is an indirect target
			 $next->{type} = "Indirect";
			 # Chain until we come to something not indirect
			 while (defined (my $name = $next->{attr}{refname})
				&& ! $seen{$next})
			 {
			     push @chain, $next;
			     # Devel::Cover +2 branch 0 1 
			     # uncoverable branch false note:Defensive programming
			     my @targets =
				 @{$parser->{ALL_TARGET_NAMES}{$name}}
			     if defined $parser->{ALL_TARGET_NAMES}{$name};
			     $seen{$next} = $ind{$next} = $next;
			     if (@targets > 1) {
				 my $errname = defined $dom->{attr}{names} ?
				     qq("$dom->{attr}{names}[0]" ) : '';
				 my $sm = $parser->system_message
				     (3, $dom->{source}, $dom->{lineno},
				      qq(Indirect hyperlink target $errname(id="$dom->{attr}{ids}[0]") refers to target "$dom->{attr}{refname}", which is a duplicate, and cannot be used as a unique reference.));
				 push @errs, $sm;
				 # Mark all the seen targets as bad targets
				 foreach my $bad (keys %seen) {
				     $seen{$bad}{badtarget} = $sm;
				 }
				 return $dom;
			     }
			     $next = $targets[0];
			     # uncoverable branch true note:Defensive programming
			     return $dom unless $next;
			 }
		     }
		     else {
			 # This is a chained target.  Tie it to what's next
			 my (@ids, @names);
			 my @barechain = ($next);
			 push @chain, $next;
			 unshift @ids,  @{$next->{attr}{ids}};
			 unshift @names, @{$next->{attr}{names}} if
			     $next->{attr}{names};
			 $seen{$next} = $next;
			 $next = $next->next($ignores);
			 return $dom unless $next;
			 while ($next->tag eq 'target' &&
				! grep(/ref(name|uri|id)/, keys %{$next->{attr}}))
			 {
			     $seen{$next} = $next;
			     push @chain, $next;
			     unshift @ids, @{$next->{attr}{ids}};
			     # uncoverable branch false note:Defensive programming
			     unshift @names, @{$next->{attr}{names}}
			     if defined $next->{attr}{names};
			     $next = $next->next($ignores);
			     return $dom unless $next;
			 }
			 if ($next->tag =~ /^(section|paragraph|target|reference)$/) {
			     push @{$next->{attr}{ids}}, @ids;
			     push @{$next->{attr}{names}}, @names
				 if @names;
			     foreach (@barechain) {
				 $parser->ReregisterName($_, $next);
				 if ($next->{attr}{refname}) {
				     $_->{attr}{refid} = $_->{attr}{ids}[0];
				     delete $_->{attr}{ids};
				     delete $_->{attr}{names};
				 }
			     }
			 }
		     }

		     if ($seen{$next}) {
			 my (@refids, @ids);
			 # Generate a problematic for this dom
			 my $prev = $chain[-1];
			 my ($prob, $refid, $id) =
			     $parser->problematic($prev->{lit});
			 # Generate the system message
			 my ($first) = grep($ind{$_}, @chain);
			 my $nextname = $next->{attr}{refname};
			 my $sm = $parser->system_message
			     (3, $next->{source}, $next->{lineno},
			      qq(Indirect hyperlink target "$first->{attr}{names}[0]" (id="$first->{attr}{ids}[0]") refers to target "$nextname", forming a circular reference.),
			      undef, ids=>[ $refid ],
			      backrefs=>[ $id ]);
			 push @ids, $id;
			 # Mark all the seen targets as bad targets
			 my $tgtrefid = $next->{attr}{ids}[-1];
			 foreach my $bad (keys %seen) {
			     my $baddom = $seen{$bad};
			     $baddom->{badtarget} = $sm;
			     $baddom->{attr}{refid} = $tgtrefid;
			     delete $baddom->{attr}{refname} if $ind{$bad};
			 }
			 $prob->{badtarget} = $sm;
			 $sm->{attr}{backrefs} = [ @ids ];
			 push @errs, $sm;
			 # Convert the last target to a problematic
			 %$prev = %$prob;
			 if ($ind{$dom}) {
			     $dom->{attr}{refid} = $dom->{attr}{ids}[0];
			     delete $dom->{attr}{refname};
			     return $dom;
			 }
			 # Return a new problematic
			 ($prob, $refid, $id) =
			     $parser->problematic($dom->{lit}, $refid);
			 push @{$sm->{attr}{backrefs}}, $id;
			 return $prob;
		     }
		 }

		 if ($next->tag eq 'target' &&
		     defined $next->{attr}{refuri}) {
		     foreach my $prev (keys %seen) {
			 my $prevdom = $seen{$prev};
			 if ($ind{$prev} || defined $prevdom->{attr}{refid}) {
			     delete $prevdom->{attr}{refname};
			     delete $prevdom->{attr}{refid};
			     $prevdom->{attr}{refuri} = $next->{attr}{refuri};
			 }
			 else {
			     $prevdom->{attr}{refid} =$prevdom->{attr}{ids}[0];
			     delete $prevdom->{attr}{ids};
			     delete $prevdom->{attr}{names};
			 }
		     }
		     return $dom;
		 }
		 return $dom if $next->tag =~ /^(footnote|citation)$/;
		 if ($next->tag =~ /^(section|paragraph|target|topic)$/
		     || defined $next->{attr}{refid}) {
		     my $dest = defined $next->{attr}{refid} ?
			 $next->{forward} : $next;
		     my $refid = $next->{attr}{refid} ||
			 $dest->{attr}{ids}[0];
		     # Fill in the refids
		     foreach (@chain) {
			 $_->{forward} = $dest;
			 if ($ind{$_}) {
			     $_->{attr}{refid} = $refid;
			     delete $_->{attr}{refname};
			 }
			 else {
 			     my $new_refid =
 				 ($_->{attr}{ids} && $_->{attr}{ids}[0] ||
 				  $_->{attr}{names} && $_->{attr}{names}[0] ||
				  $refid);
			     $_->{attr}{refid} = $new_refid;
			     delete $_->{attr}{ids};
			     delete $_->{attr}{names};
			 }
		     }
		 }
	     }
	     return $dom;
	 }
	 , 'pre');

    $dom->append(@errs) if @errs;
}

# Processes a docutils.transforms.references.CitationReferences transform.
# Links citation references to their targets.
# Arguments: top-level DOM, parser obj
sub CitationReferences {
    my ($dom, $parser) = @_;

    # Link references to their definitions if they exist
    my (@errs, $cr);
    $cr = sub {
	my($dom) = @_;

	my $tag = $dom->tag;
	if ($tag =~ /^(?:(citation|substitution)_reference)$/) { 
	    my $what = $1 eq 'citation' ? $1 :
		'substitution_definition';
 	    my $name =
 		$dom->{attr}{names} && defined $dom->{attr}{names}[0] ?
		$dom->{attr}{names}[0] : $dom->{attr}{refname};
	    my $target = $parser->{REFERENCE_DOM}{$what}{$name};
	    $target = ($parser->{REFERENCE_DOM}{"$what.lc"}{lc $name})
		unless defined $target;
	    if (! defined $target) {
		my ($prob, $refid, $id) =
		    $parser->problematic($dom->{lit});
		my $emsg = $what eq 'citation' ?
		    'Unknown target name' :
		    'Undefined substitution referenced';
		push @errs, $parser->system_message
		    (3, $dom->{source}, $dom->{lineno},
		     qq($emsg: "$name".), '', ids=>[ $refid ],
		     backrefs=>[ $id ]);
		return $prob;
	    }
	    if ($tag eq 'substitution_reference') {
		if ($target->{attr}{ltrim} || $target->{attr}{rtrim}) {
		    my $parent = $dom->parent();
		    my $idx = $parent->index($dom);
		    $parent->child($idx-1)->{text} =~ s/ *$//
			if $target->{attr}{ltrim} && $idx > 0 &&
			$parent->child($idx-1)->tag eq '#PCDATA';
		    $parent->child($idx+1)->{text} =~ s/^ *//
			if $target->{attr}{rtrim} &&
			$idx < $parent->num_contents &&
			$parent->child($idx+1)->tag eq '#PCDATA';
		}
		my @content= $target->contents;
		my $i;
		for ($i=0; $i<@content; $i++) {
		    splice(@content, $i, 1, &$cr($content[$i]))
			   if $content[$i]->tag eq 'substitution_reference';
		}
		return @content;
	    }
	    delete $dom->{attr}{refname};
	    $dom->{attr}{refid} = $target->{attr}{ids}[0];
	    $dom->{resolved} = $target;
	    push @{$target->{attr}{backrefs}}, @{$dom->{attr}{ids}};
	}
	return $dom;
    };
    $dom->Reshape ($cr, 'pre');
    $dom->append(@errs) if @errs;
}

# Processes a docutils.transforms.references.FootnoteReferences transform.
# Links footnote references to their targets.
# Arguments: top-level DOM, parser obj
sub FootnoteReferences {
    my ($dom, $parser) = @_;

    # Link references to their definitions if they exist
    my @errs;
    $dom->Reshape
	(sub {
	     my($dom) = @_;
	     my $tag = $dom->tag;
	     if ($tag eq 'footnote_reference' && !$dom->{resolved}) {
		 my $name =
		     $dom->{attr}{names} && defined $dom->{attr}{names}[0] ||
		     $dom->{attr}{refname};
		 my $footnote = defined $name ?
		     $parser->{REFERENCE_DOM}{footnote}{$name} :
		     $AUTO_FOOTNOTES[$AUTO_FOOTNOTE_REF++];
		 if (! defined $footnote) {
		     my ($prob, $refid, $id) =
			 $parser->problematic($dom->{lit});
		     push @errs, $parser->system_message
			  (3, $dom->{source}, $dom->{lineno},
			   (defined $name ? qq(Unknown target name: "$name".):
			    qq(Too many autonumbered footnote references: only ${\scalar(@AUTO_FOOTNOTES)} corresponding footnotes available.)),
			   '', ids=>[ $refid ], backrefs=>[ $id ]);
		     return $prob;
		 }
		 if (defined $footnote->{attr}{dupnames}) {
		     my ($prob, $refid, $id) =
			 $parser->problematic($dom->{lit});
		     push @errs, $parser->system_message
			  (3, $dom->{source}, $dom->{lineno},
			   (qq(Duplicate target name, cannot be used as a unique reference: "$name".)),
			   '', ids=>[ $refid ], backrefs=>[ $id ]);
		     return $prob;
		 }
		 if ($dom->{attr}{auto}) {
		     my $name = $footnote->first->first->{text};
		     $dom->append($DOM->newPCDATA($name));
		 }
		 delete $dom->{attr}{refname};
		 $dom->{attr}{refid} = $footnote->{attr}{ids}[0];
		 push @{$footnote->{attr}{backrefs}}, @{$dom->{attr}{ids}};
		 $dom->{resolved} = $footnote;
	     }
	     return $dom;
	 }
	 , 'pre');
    $dom->append(@errs) if @errs;
}

# Processes a docutils.transforms.references.MarkReferenced transform.
# Marks immediate destinations of references as referenced.
# Arguments: top-level DOM, parser obj
sub MarkReferenced {
    my ($dom, $parser) = @_;

    # Mark destinations of references as referenced
    $dom->Recurse
	(sub {
	     my($dom) = @_;
	     my $tag = $dom->tag;
	     if (defined $dom->{attr}{refname}) {
		 my $target;
		 my $name = $dom->{attr}{refname};
		 my @targets = @{$parser->{TARGET_NAME}{target}{$name}}
		 if defined $parser->{TARGET_NAME}{target}{$name};
		 if (@targets == 1) {
		     $target = $targets[0];
		     $target->{referenced} = 1;
		 }
	     }
	     return ;
	 }
	 , 'pre');
}

# Processes a docutils.transforms.references.References transform.
# Counts anonymous references, links references to their
# destinations, produces error messages if the number of anonymous
# references is insufficient.
# Arguments: top-level DOM, parser obj
sub References {
    my ($dom, $parser) = @_;

    my $anonymous_refs;
    # Count how many anonymous references we have
    $dom->Recurse
	(sub {
	     my($dom) = @_;
	     $anonymous_refs++
		 if ($dom->tag eq 'reference' && $dom->{attr}{anonymous});
	     return 0;
	 }
	 , 'pre');
    my $last_anonymous_target = 0;
    my $anonymous_mismatch_id;
    my @anonymous_mismatch_refids;
    my @errs;
    # Link references to their definitions if they exist
    $dom->Reshape
	(sub {
	     my($dom) = @_;
	     my $tag = $dom->tag;
	     if ($tag eq 'reference' && ! defined $dom->{attr}{refuri} &&
		 ! defined $dom->{attr}{refid}) {
		 my $target;
		 my $name = $dom->{attr}{refname};
		 # uncoverable branch false count:2 note:Defensive programming
		 if (defined $name) {
		     my @targets = @{$parser->{TARGET_NAME}{target}{$name}}
		     if defined $parser->{TARGET_NAME}{target}{$name};
		     if (@targets > 1) {
			 my ($prob, $refid, $id) =
			     $parser->problematic($dom->{lit});
			 
			 push @errs, $parser->system_message
			     (3, $dom->{source}, $dom->{lineno},
			      qq(Duplicate target name, cannot be used as a unique reference: "$name".),
			      undef, backrefs=>[ $id ], ids=>[ $refid ]);
			 return $prob;
		     }
		     $target = $targets[0];
		     if (! defined $target &&
			 ! defined $parser->{ALL_TARGET_IDS}{$name}[0]) {
			 my ($prob, $refid, $id) =
			     $parser->problematic($dom->{lit});
			 push @errs, $parser->system_message
			     (3, $dom->{source}, $dom->{lineno},
			      qq(Unknown target name: "$name".),
			      '', ids=> [ $refid ], backrefs=>[ $id ]);
			 return $prob;
		     }
		 }
		 elsif ($dom->{attr}{anonymous}) {
		     if ($anonymous_refs > @{$parser->{ANONYMOUS_TARGETS}}) {
			 $anonymous_mismatch_id = $parser->Id()
			     if ! defined $anonymous_mismatch_id;
			 my ($prob, $refid, $id) =
			     $parser->problematic($dom->{lit},
					      $anonymous_mismatch_id);
			 push(@anonymous_mismatch_refids, $id);
			 return $prob;
		     }
		     $target =
			 $parser->{ANONYMOUS_TARGETS}[$last_anonymous_target++];
		 }
		 while (defined $target) {
		     if ($target->{badtarget}) {
			 my $sm = $target->{badtarget};
			 my ($prob, $refid, $id) =
			     $parser->problematic($dom->{lit},
					      $sm->{attr}{ids}[0]);
			 push @{$sm->{attr}{backrefs}}, $id;
			 $sm->{attr}{ids} = [ $refid ];
			 return $prob;
		     }
		     my $dest = $target->{forward} || $target;
		     # uncoverable branch false count:4 note:Defensive programming
		     if ($dest->tag eq 'target' &&
			 defined $dest->{attr}{refuri}) {
			 $target->{type} = "External"
			     unless defined $target->{type};
			 delete $dom->{attr}{refname};
			 $dom->{attr}{refuri} = $dest->{attr}{refuri};
		     }
		     elsif ($target->{forward}) {
			 delete $dom->{attr}{refname};
			 $dom->{attr}{refid} = $target->{attr}{refid};
		     }
		     elsif (defined $target->{attr}{refid}) {
			 # Anonymous target chained to external target
			 # uncoverable branch false note:Defensive programming
			 $target->{type} = "External"
			     unless defined $target->{type};
			 my @targets = @{$parser->{ALL_TARGET_IDS}
					 {$target->{attr}{refid}}};
			 $target = $targets[0];
			 next;
		     }
		     elsif (defined $target->{attr}{ids}) {
			 my $refid =
			     $parser->NormalizeId($dom->{attr}{refname});
			 $dom->{attr}{refid} =
			     grep($_ eq $refid, @{$target->{attr}{ids}}) ?
			     $refid : $target->{attr}{ids}[0];
			 delete $dom->{attr}{refname};
		     }
		     undef $target;
		 }
	     }
	     return $dom;
	 }
	 , 'pre');

    # Produce messages if there aren't enough anonymous hyperlink targets
    if (defined $anonymous_mismatch_id) {
	my $sm = $parser->system_message
	    (3, $dom->{attr}{source}, undef,
	     qq(Anonymous hyperlink mismatch: $anonymous_refs references but ${\scalar(@{$parser->{ANONYMOUS_TARGETS}})} targets.\nSee "backrefs" attribute for IDs.),
	     '', ids=>[ $anonymous_mismatch_id ],
	     backrefs=>[ @anonymous_mismatch_refids ]);
	delete $sm->{attr}{line};
	$dom->append($sm);
    }

    $dom->append(@errs) if @errs;
}

# Processes a docutils.transforms.references.Unreferenced transform.
# Produces messages for unreferenced targets.
# Arguments: top-level DOM, parser obj
sub Unreferenced {
    my ($dom, $parser) = @_;

    # Produce messages for unreferenced targets
    my @errs;
    $dom->Reshape
	(sub {
	     my($dom) = @_;
	     my $tag = $dom->tag;
	     if ($tag eq 'target' && ! $dom->{referenced} &&
		 ! $dom->{attr}{anonymous} && ! $dom->{attr}{dupnames}) {
		 my $name =
		     defined $dom->{attr}{names} && $dom->{attr}{names}[0] ||
		     $dom->{attr}{refid};
		 # uncoverable branch false note:Assert defined $name
 		 my $id = defined $name ? qq("$name") :
		     qq(id="$dom->{attr}{ids}[0]");
		 push @errs, $parser->system_message
		     (1, $dom->{source}, $dom->{lineno},
		      qq(Hyperlink target $id is not referenced.));
		 return $dom;
	     }
	     return $dom;
	 }
	 , 'pre');
    $dom->append(@errs);
}

# Processes a docutils.transforms.references.TargetNotes transform.
# Constructs a list of external references and creates footnotes
# pointing to them.
# Arguments: pending DOM, parser obj, details hash reference
sub TargetNotes {
    my ($dom, $parser, $details) = @_;

    my $topdom = $parser->{TOPDOM};
    my @targets; # Used in closure of sub
    # Construct the list of external references.
    $topdom->Recurse
	(sub {
	     my($dom) = @_;
	     my $tag = $dom->tag;
	     push (@targets, $dom)
		 if $tag eq 'target' && defined $dom->{attr}{refuri};
	     return 0;
	 }) ;

    # Create the footnotes
    my @doms;
    my %footnotes;
    my $options = $dom->{internal}{'.details'};

    foreach (@targets) {
	my $id = $parser->Id();
	$footnotes{$_->{attr}{names}[0]} = $id;
	my $dom = $DOM->new('footnote', auto=>1, ids=>[ $id ],
			  names=>[ "TARGET_NOTE: $id" ]);
	push @{$dom->{attr}{classes}}, $options->{class}
	    if defined $options && defined $options->{class};
	my $para = $DOM->new('paragraph');
	$dom->append($para);
	my $ref = $DOM->new('reference', refuri=>$_->{attr}{refuri});
	$para->append($ref);
	$ref->append($DOM->newPCDATA($_->{attr}{refuri}));
	push(@doms, $dom);
    }

    # Insert the footnote references
    $topdom->Reshape
	(sub {
	     my($dom) = @_;
	     my $tag = $dom->tag;
	     if ($tag eq 'reference' &&
		 defined $dom->{attr}{refname} &&
		 defined $footnotes{$dom->{attr}{refname}}) {
		 my $refname = $footnotes{$dom->{attr}{refname}};
		 my $fr = $DOM->new('footnote_reference', auto=>1,
				  ids=> [ $parser->Id() ],
				  refname=>"TARGET_NOTE: $refname");
		 return ($dom, $DOM->newPCDATA(' '), $fr);
	     }
	     return $dom;
	 }) ;
    
    return @doms;
}

package Text::Restructured::docutils::transforms::universal;

# This package contains routines for transforms of DOM trees

use vars qw($DOM);
BEGIN {
    *DOM = "Text::Restructured::DOM";
}

# Processes a docutils.transforms.universal.Decorations transform.
# Adds the "View document source", "Generated on" and "Generated by"
# decorations to the end of the document.
# Arguments: top-level DOM, parser obj
sub Decorations {
    my ($topdom, $parser) = @_;

    my ($dec) = grep $_->tag eq 'decoration', $topdom->contents;
    return if defined $dec && ($dec->first->tag eq 'footer' ||
			       $dec->num_contents > 1);
    my $para = $DOM->new('paragraph');
    my $source_link =
	defined $parser->{opt}{D}{source_link} ?
	$parser->{opt}{D}{source_link} : 1;
    if ($source_link) {
	my $source_url = defined $parser->{opt}{D}{source_url} ?
	    $parser->{opt}{D}{source_url} : $topdom->{attr}{source};
	my $ref = $DOM->new('reference', refuri=>$source_url);
	$ref->append($DOM->newPCDATA('View document source'));
	$para->append($ref);
	$para->append($DOM->newPCDATA(".\n"));	
    }
    my $time = defined $parser->{opt}{D}{time} ? $parser->{opt}{D}{time} : 1;
    my $date = defined $parser->{opt}{D}{date} ? $parser->{opt}{D}{date} : 0;
    if ($date || $time) {
	my $format = "%Y/%m/%d" . ($time ? " %H:%M:%S %Z" : "");
	use POSIX;
	my $date = POSIX::strftime($format, localtime);
	$para->append($DOM->newPCDATA("Generated on: $date.\n"));
    }
    my $generator =
	defined $parser->{opt}{D}{generator} ? $parser->{opt}{D}{generator} : 1;
    if ($generator) {
	use Text::Restructured::PrestConfig;
	my $docurl = $Text::Restructured::PrestConfig::DOCURL;
	$para->append($DOM->newPCDATA("Generated by "));
	my $tool_dom = $DOM->newPCDATA($topdom->{TOOL_ID});
	if ($docurl !~ /^none$/i) {
	    my $docref = $DOM->new('reference', refuri=>$docurl);
	    $docref->append($tool_dom);
	    $para->append($docref);
	}
	else {
	    $para->append($tool_dom);
	}
	my $ref = $DOM->new('reference', refuri=>
			  'http://docutils.sourceforge.net/rst.html');
	$ref->append($DOM->newPCDATA("reStructuredText"));
	$para->append($DOM->newPCDATA(" from "),
		      $ref,
		      $DOM->newPCDATA(" source.\n"));
    }

    if ($para->num_contents) {
	my $dec = $DOM->new('decoration');
	my $footer = $DOM->new('footer');
	$dec->append($footer);
	$footer->append($para);
	# Decoration needs to be appended before the document model
	# starts, i.e., after the latest of title or subtitle.
	my $i;
	for ($i=0; $i<$topdom->num_contents; $i++) {
	    if ($topdom->child($i)->tag !~ /title|docinfo/) {
		$topdom->splice($i, 0, $dec);
		last;
	    }
	}
    }
}

# Processes a docutils.transforms.universal.EmptyTopics transform.
# Removes any topics that have only a header in their contents.
# Arguments: top-level DOM, parser obj
sub EmptyTopics {
    my ($dom, $parser) = @_;

    $dom->Reshape
	(sub {
	     my($dom) = @_;
	     return if $dom->tag eq 'topic' &&
		 ($dom->num_contents == 0 ||
		  $dom->child(-1)->tag eq 'title');
	     return $dom;
	 });
}

# Processes a docutils.transforms.universal.Messages transform.
# Moves system messages at the end into "Docutils System Messages" section.
# Arguments: top-level DOM, parser obj
sub Messages {
    my ($dom, $parser) = @_;

    # Move system messages at the end to a section
    my @SYSTEM_MESSAGES;
    $dom->Reshape
	(sub {
	     my($dom) = @_;
	     push (@SYSTEM_MESSAGES, $dom)
		 if ($dom->tag eq 'system_message' &&
		     $dom->{attr}{level} >= $parser->{opt}{D}{report});
	     return $dom->tag ne 'system_message' ? ($dom) : ();
	 }
	 );

   if (@SYSTEM_MESSAGES > 0) {
       my $errsec = $DOM->new('section', classes=>['system-messages']);
       $dom->append($errsec);
       my $title = $DOM->new('title');
       $errsec->append($title);
       $title->append($DOM->newPCDATA('Docutils System Messages'));
       $errsec->append(@SYSTEM_MESSAGES);
    }
}

# Processes a docutils.transforms.universal.ScoopMessages transform.
# Moves system messages from anywhere in the DOM tree to the end of
# the top-level DOM.
# Arguments: top-level DOM, parser obj
sub ScoopMessages {
    my ($dom, $parser) = @_;

    # Move system messages into end of top dom's contents
    my @SYSTEM_MESSAGES;
    $dom->Reshape
	(sub {
	     my($dom) = @_;
	     if ($dom->tag eq 'system_message') {
		 if (defined $dom->{attr}{ids}) {
		     push(@SYSTEM_MESSAGES, $dom);
		     return;
		 }
	     }
	     return $dom;
	 });
    $dom->append(@SYSTEM_MESSAGES);
}

# Processes a docutils.transforms.universal.Transitions transform.
# Moves transitions at end of sections to top level and creates error
# messages for incorrect transitions.
# Arguments: top-level DOM, parser obj
sub Transitions {
    my ($dom, $parser) = @_;

    # Move system messages at the end to a section
    my @errs;
    $dom->Reshape
	(sub {
	     my($dom) = @_;
	     if ($dom->tag eq 'transition') {
		 my $domparent = $dom->parent();
		 my $idx = $domparent->index($dom);
		 my @doms;
		 push @doms, $parser->system_message
		     (3, $dom->{source}, $dom->{lineno},
		      "Document or section may not begin with a transition.")
		     if $idx == 0 ||
		     $idx == 1 && $domparent->first->tag eq 'title';
		 my $next = $dom->next();
		 if ($next && ($next->parent() || 'NONE') != $domparent) {
		     $next->{transition} = $dom;
		     return @doms;
		 }
		 push @doms, $dom;
		 if (! defined $next) {
		     push @doms, $parser->system_message
			 (3, $dom->{source}, $dom->{lineno},
			  "Document may not end with a transition.");
		 }
		 elsif ($next->tag eq 'transition') {
		     push @doms, $parser->system_message
			 (3, $next->{source}, $next->{lineno},
			  "At least one body element must separate transitions; adjacent transitions are not allowed.");
		 }
		 return @doms;
	     }
	     elsif (my $t = $dom->{transition}) {
		 delete $dom->{transition};
		 return ($t, $dom);
	     }
	     return $dom;
	 }
	 , 'post');

    $dom->append(@errs);
}

1;
