2 # Copyright (C) 2002-2005 Freescale Semiconductor, Inc.
3 # Distributed under terms of the GNU General Public License (GPL).
5 @Transforms::TRANSFORMS
= qw(docutils.transforms.references.MarkReferenced
6 docutils.transforms.references.IndTargets
7 docutils.transforms.frontmatter.DocTitle
8 docutils.transforms.frontmatter.SectionSubTitle
9 docutils.transforms.frontmatter.DocInfo
10 docutils.transforms.references.CitationReferences
11 docutils.transforms.misc.Pending
12 docutils.transforms.universal.EmptyTopics
13 docutils.transforms.references.AutoFootnotes
14 docutils.transforms.references.FootnoteReferences
15 docutils.transforms.references.References
16 docutils.transforms.references.Unreferenced
17 docutils.transforms.universal.Transitions
18 docutils.transforms.universal.ScoopMessages
19 docutils.transforms.universal.Messages
20 docutils.transforms.universal.Decorations
23 package docutils
::transforms
::components
;
28 Defines for reStructuredText transforms
29 ---------------------------------------
30 -D generator=<0|1> Include a "Generated by" credit at the end of
31 the document (default is 1).
32 -D date=<0|1> Include the date at the end of the document
34 -D time=<0|1> Include the date and time at the end of the
35 document (default is 1, overrides date if 1).
36 -D source_link=<0|1> Include a "View document source" link (default
38 -D source_url=<URL> Use the supplied <URL> verbatim for a "View
39 document source" link; implies -D source_link=1.
40 -D keep_title_section Keeps the section intact from which the document
42 -D section_subtitles Activate the promotion of lone subsection titles to
49 # ``@Transforms::TRANSFORMS``
50 # Array of transform names in the order they will be applied.
54 # Processes a docutils.transforms.components.Filter transform.
55 # Arguments: pending DOM, top-level DOM, details hash reference
57 my ($dom, $topdom, $details) = @_;
59 if ($main::opt_w
eq eval($details->{format
}) ||
60 $main::opt_w
eq 'dom') {
61 my $nodes = $details->{nodes
};
62 return new DOM
($nodes->{tag
}, %{$nodes->{attr
}});
67 package docutils
::transforms
::frontmatter
;
69 use vars
qw(%BIB_ELEMENTS);
72 my @bib_elements = qw(author authors organization address contact version
73 revision status date copyright dedication abstract);
74 @BIB_ELEMENTS{@bib_elements} = (1) x
@bib_elements;
77 # Processes a docutils.transforms.frontmatter.DocInfo transform.
78 # Processes field lists at the beginning of the DOM that are one of
79 # the docinfo types into a docinfo section.
80 # Arguments: top-level DOM
84 # Create a docinfo if needed
85 my @field_lists = grep($_->{tag
} eq 'field_list', $dom->contents());
88 my $fl = $field_lists[0];
89 my @content = $fl->contents();
90 # Modify the field list in situ
91 $fl->{tag
} = 'docinfo';
95 my @postdocinfo; # Things to be added to content list after docinfo
96 foreach $field (@content) {
97 my $fn = $field->{content
}[0];
98 my $fb = $field->{content
}[1];
99 my $name = $fn->{content
}[0]{text
};
100 my $origname = $name;
101 $name =~ tr/A-Z/a-z/;
103 substr($tname,0,1) =~ tr/ad/AD/;
104 if ($BIB_ELEMENTS{$name}) {
105 $element_seen{$name}++;
106 if ($element_seen{$name} > 1 && $name =~ /abstract/) {
108 (RST
::system_message
(2, $field->{source
},
110 qq(There can only be one
"$tname" field
.)));
111 $docinfo->append($field);
113 elsif ($name =~ /^(dedication|abstract)$/) {
114 my $topic = new DOM
('topic', classes
=>[ $name ]);
115 my $title = new DOM
('title');
116 $topic->append($title);
117 $title->append(newPCDATA DOM
($tname));
118 $topic->append($fb->contents());
119 push(@postdocinfo, $topic);
121 elsif ($fb->num_contents() < 1) {
123 (RST
::system_message
(2, $field->{source
},
125 qq(Cannot extract empty bibliographic field
"$origname".)));
126 $docinfo->append($field);
128 elsif ($name eq 'authors') {
129 my $bib = new DOM
($name);
130 my @contents = $fb->{content
}[0]->contents();
131 # There are three cases: bullet_lists,
132 # multiple paragraphs, and string.
133 if (($fb->num_contents() == 1 &&
134 ($fb->{content
}[0]{tag
} !~
135 /paragraph|bullet_list/ ||
136 $fb->{content
}[0]{tag
} eq 'bullet_list' &&
137 grep($_->num_contents() != 1 ||
138 $_->{content
}[0]{tag
} ne 'paragraph',
139 $fb->{content
}[0]->contents()))
141 ($fb->num_contents() > 1 &&
142 grep($_->{tag
} ne 'paragraph', $fb->contents()))
145 (RST
::system_message
(2, $field->{source
},
147 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
.)));
148 $docinfo->append($field);
150 elsif ($fb->num_contents() > 1) {
151 # Multiple paragraphs
152 foreach ($fb->contents()) {
153 my $author = new DOM
('author');
154 $bib->append($author);
155 $author->append($_->contents());
158 elsif ($fb->{content
}[0]{tag
} eq 'bullet_list') {
159 my $bl = $fb->{content
}[0];
160 foreach ($bl->contents()) {
161 my $author = new DOM
('author');
162 $bib->append($author);
163 $author->append($_->{content
}[0]->contents());
170 $text .= $dom->{text
}
171 if $dom->{tag
} eq '#PCDATA';
173 my @authors = $text =~ /;/ ?
174 split(/\s*;\s*/, $text) :
175 split(/\s*,\s*/, $text);
177 my $author = new DOM
('author');
178 $bib->append($author);
179 $author->append(newPCDATA DOM
($_));
182 if ($bib->num_contents() == 1) {
183 $docinfo->append($bib->{content
}[0]);
185 elsif ($bib->num_contents() > 1) {
186 $docinfo->append($bib);
189 elsif ($fb->num_contents() > 1) {
191 (RST
::system_message
(2, $field->{source
},
193 qq(Cannot extract compound bibliographic field
"$origname".)));
194 $docinfo->append($field);
196 elsif ($fb->{content
}[0]{tag
} ne 'paragraph') {
198 (RST
::system_message
(2, $field->{source
},
200 qq(Cannot extract bibliographic field
"$origname" containing anything other than a single paragraph
.)));
201 $docinfo->append($field);
204 my $bib = new DOM
($name);
205 %{$bib->{attr
}} = (%RST::XML_SPACE
)
206 if $name =~ /^address$/i;
207 $docinfo->append($bib);
208 my @contents = $fb->{content
}[0]->contents();
209 my $pcdata = $contents[0];
210 $pcdata->{text
} =~ s/\$\w+:\s*(.+?)(?:,v)?\s\$/$1/g
211 if defined $pcdata->{text
};
212 $bib->append(@contents);
216 $docinfo->append($field);
220 # Anything before the docinfo that's not a title, subtitle, or
221 # decoration has to move after it.
223 my $docinfo_seen = 0;
225 for ($i=0; $i < $dom->num_contents(); $i++) {
226 my $c = $dom->{content
}[$i];
227 if ($docinfo_seen || $c->{tag
} =~ /^((sub)?title|decoration)$/) {
228 push @new_content, $c;
230 elsif ($c->{tag
} eq 'docinfo') {
232 push @new_content, $c, @postdocinfo;
235 push @postdocinfo, $c;
238 $dom->replace(@new_content);
239 # $dom->{content} = \@new_content;
243 # Processes a docutils.transforms.frontmatter.DocTitle transform.
244 # Creates a document title if the top-level DOM has only one top-level
245 # section. Creates a subtitle if a unique top-level section has a
246 # unique second-level section.
247 # Arguments: top-level DOM
252 $dom->{attr
}{title
} = $dom->{'.details'}{title
}
253 if defined $dom->{'.details'}{title
};
257 # Processes a docutils.transforms.frontmatter.SectionSubTitle transform.
258 # Creates a subtitle if a section DOM has only one top-level
260 # Arguments: top-level DOM
261 sub SectionSubTitle
{
264 # Link references to their definitions if they exist
268 if ($dom->{tag
} eq 'section') {
269 create_title
($dom, 1);
273 , 'pre') if $main::opt_D
{section_subtitles
};
277 # Used to turn a lone section into a title/subtitle of the given DOM
278 # Arguments: section DOM object
280 # Side-effects: May reorganize the contents to promote a lone section
283 # If the document has one section, coalesce it with the DOM
284 my @sections = grep($_->{tag
} eq 'section', $dom->contents());
286 if (@sections == 1 && ($main::opt_D
{keep_title_section
} ||
288 /^(section|comment|system_message|target|substitution_definition|title|decoration)$/,
289 $dom->contents()))) {
290 my $sec = $sections[0];
291 my @non_sections = grep($_->{tag
} !~ /^(?:section|title)$/,
293 my ($prev_title) = grep $_->{tag
} eq 'title', $dom->contents();
296 $sec->{content
}[0]->Recurse(sub {
298 $ttext .= $dom->{text
} if $dom->{tag
} eq '#PCDATA';
301 my $dom_ids = $dom->{attr
}{ids
};
303 $dom->{attr
}{title
} =
304 $ttext; #RST::NormalizeName($ttext, 'keepcase');
305 @
{$dom->{attr
}}{keys %{$sec->{attr
}}} = values %{$sec->{attr
}};
307 if ($main::opt_D
{keep_title_section
} && ! defined $prev_title) {
308 # Don't duplicate ids from the section if we keep the section
310 $dom->{attr
}{ids
} = $dom_ids;
313 delete $dom->{attr
}{ids
};
315 my $title = $sec->{content
}[0];
316 $dom->prepend($title);
319 $dom->{content
} = $sec->{content
};
320 $dom->splice(1, 0, @non_sections);
322 if (defined $prev_title) {
323 my $subtitle = $dom->{content
}[0];
324 $subtitle->{tag
} = 'subtitle';
325 $subtitle->{attr
}{ids
} = [ RST
::NormalizeId
($ttext) ];
326 $subtitle->{attr
}{names
} = [ RST
::NormalizeName
($ttext) ];
327 $dom->prepend($prev_title);
330 # Check for a subtitle
331 my @sections = grep($_->{tag
} eq 'section', $dom->contents());
332 if (@sections == 1) {
333 my $sec = $sections[0];
334 my $title = $sec->splice(0, 1);
335 my @non_sections = grep($_->{tag
} !~ /^(section|title)$/,
337 $sec->prepend(@non_sections);
338 $title->{tag
} = 'subtitle';
339 $title->{attr
} = $sec->{attr
};
340 $dom->replace(grep($_->{tag
} eq 'title',
342 $dom->append($title, $sec->contents());
349 package docutils
::transforms
::misc
;
351 # This package contains routines for transforms of DOM trees
353 # Processes a docutils.transforms.misc.Pending transform.
354 # Traverses the DOM tree looking for Pending nodes and applies
355 # whatever internal transform was specified for them.
356 # Arguments: top-level DOM
360 # Handle pending transformations
364 if ($dom->{tag
} eq 'pending') {
365 my $transform = $dom->{internal
}{'.transform'};
366 (my $t = $transform) =~ s/\./::/g;
367 return RST
::system_message
(4, $dom->{source
},
369 qq(No transform code found
for "$transform".))
371 my $details = $dom->{internal
}{'.details'};
373 print STDERR
"Debug: Transform $transform\n" if $main::opt_d
;
374 return &$t($dom, $topdom, $details);
380 package docutils
::transforms
::parts
;
382 # This package contains routines for transforms of DOM trees
384 # Processes a docutils.transforms.parts.Class transform.
385 # Arguments: pending DOM, top-level DOM, details hash reference
387 my ($dom, $topdom, $details) = @_;
389 my $next = $dom->next('comment|substitution_definition|target|system_message|pending');
390 my $tag = $next->{tag
} if defined $next;
391 if (defined $tag && $tag =~ /^(?:paragraph|.*_list|section|.*_block|block_quote|table|figure|raw)$/) {
392 # It's a classable tag
393 push @
{$next->{attr
}{classes
}}, split(/\s+/, $details->{class});
396 return RST
::system_message
(2, $dom->{source
}, $dom->{lineno
},
397 qq(Error
in "class" directive
: there is
no following block element
for which a
class can be specified
.),
401 # Processes a docutils.transforms.parts.Contents transform.
402 # Arguments: pending DOM, top-level DOM, details hash reference
404 my ($dom, $topdom, $details) = @_;
407 my $parent = $dom->parent();
409 defined $details->{backlinks
} ?
$details->{backlinks
} : '';
410 # First we compile the table of contents
411 my $contid = $parent->{attr
}{ids
}[0] if defined $parent->{attr
}{ids
};
413 my $bl = new DOM
('bullet_list');
414 my $depth = 0; # Used in closure of sub
415 my @list = ($bl); # Used in closure of sub
416 my $start = defined $details->{local} ?
$dom->{section
} : $topdom;
419 my($dom, $when) = @_;
421 if ($dom->{tag
} eq 'section' && $dom ne $start) {
422 $depth-- if $when eq 'post';
423 if (! defined $details->{depth
} ||
424 $depth < $details->{depth
}) {
427 if ($when eq 'pre') {
429 $li = new DOM
('list_item'); #, ids=>$id);
431 if ($backlinks !~ /none/i &&
432 $dom->{content
}[0]{content
}[0]{tag
} ne 'reference') {
433 $dom->{content
}[0]{attr
}{refid
} =
434 ($backlinks =~ /top/i) ?
$contid : $id;
436 my $para = new DOM
('paragraph');
438 my $ref = new DOM
('reference', ids
=> [ $id ],
439 refid
=>$dom->{attr
}{ids
}[0]);
441 my @contents; # Used in the closure of the sub
442 $dom->{content
}[0]->Recurse
444 my ($dom, $when) = @_;
445 my $tag = $dom->{tag
};
446 if ($tag =~ /^(?:title|(footnote|citation)_reference|interpreted|problematic|reference|target)$/) {
449 elsif ($tag =~ /image/) {
451 newPCDATA DOM
($dom->{attr
}{alt
}))
452 if defined $dom->{attr
}{alt
} &&
456 if ($when eq 'pre') {
458 push(@contents, $dom);
465 $ref->append(@contents);
466 $list[0]{attr
}{classes
} = ['auto-toc']
467 if ($dom->{content
}[0]{content
}[0]{tag
}
471 # Check to see if I have any nested sections
472 if (grep($_->{tag
} eq 'section',$dom->contents())
473 && (! defined $details->{depth
} ||
474 $depth < $details->{depth
}-1)) {
475 if ($when eq 'pre') {
476 my $new_bl = new DOM
('bullet_list');
477 $li->append($new_bl);
478 push(@list, $new_bl);
485 $depth++ if $when eq 'pre';
491 # Need to remove all traces of ourselves if the bullet list is empty
492 if ($bl->num_contents() == 0) {
493 $start->splice(0, 1);
499 # Processes a docutils.transforms.parts.Sectnum transform.
500 # Auto-numbers the sections in the document.
501 # Arguments: pending DOM, top-level DOM, details hash reference
503 my ($dom, $topdom, $details) = @_;
505 # First process the table of contents topic if it exists
506 my ($toc) = grep($_->{tag
} eq 'topic' && defined $_->{attr
}{classes
} &&
507 $_->{attr
}{classes
}[0] eq 'contents',
508 $topdom->contents());
509 my @list; # Used in closure of sub
510 my $prefix = defined $details->{prefix
} ?
$details->{prefix
} : '';
511 my $suffix = defined $details->{suffix
} ?
$details->{suffix
} : '';
512 my $start = $details->{start
} || 1;
516 my($dom, $when) = @_;
517 if ($dom->{tag
} eq 'bullet_list') {
518 if ($when eq 'pre') {
519 push(@list, $start-1);
520 $dom->{attr
}{classes
} = ['auto-toc']
521 if (! defined $details->{depth
} ||
522 @list <= $details->{depth
});
526 elsif ($dom->{tag
} eq 'list_item' && $when eq 'pre') {
529 elsif ($dom->{tag
} eq 'reference' && $when eq 'pre'
530 && (! defined $details->{depth
} ||
531 @list <= $details->{depth
})) {
532 my $gen = new DOM
('generated', classes
=>['sectnum']);
533 $gen->append(newPCDATA DOM
($prefix . join('.',@list)
534 . $suffix . ("\xa0"x3
)));
542 # Next process the sections recursively
546 my($dom, $when) = @_;
547 if ($dom->{tag
} eq 'section') {
548 if ($when eq 'pre') {
549 if (! defined $details->{depth
} ||
550 @list <= $details->{depth
}) {
551 my $title = $dom->{content
}[0];
552 $title->{attr
}{auto
} = 1;
554 my $gen = new DOM
('generated', classes
=>['sectnum']);
555 $gen->append(newPCDATA DOM
($prefix . join('.',@list)
556 . $suffix . ("\xa0"x3
)));
557 $title->prepend($gen);
570 package docutils
::transforms
::references
;
572 # This package contains routines for transforms of DOM trees
574 # Static global variables
575 use vars
qw($NEXT_SYMBOL_FOOTNOTE @FOOTNOTE_SYMBOLS);
577 # Run-time global variables
578 use vars qw($AUTO_FOOTNOTE_REF $LAST_AUTO_FOOTNOTE @AUTO_FOOTNOTES);
581 @FOOTNOTE_SYMBOLS = ("*", chr 0x2020, chr 0x2021, chr 0xa7,
582 chr 0xb6, '#', chr 0x2660, chr 0x2665,
583 chr 0x2666, chr 0x2663);
584 $NEXT_SYMBOL_FOOTNOTE = 0;
587 # Processes a docutils.transforms.references.AutoFootnotes transform.
588 # Computes numbers for autonumbered footnotes.
589 # Arguments: top-level DOM
593 # Compute numbers for autonumbered footnotes
597 my $tag = $dom->{tag};
598 if ($tag eq 'footnote') {
599 if ($dom->{attr}{auto}) {
600 my $label = new DOM('label');
601 $dom->prepend($label);
602 if ($dom->{attr}{auto} eq '1') {
603 while (defined $RST::REFERENCE_DOM{$tag}
604 {++$LAST_AUTO_FOOTNOTE}) { };
605 if (! defined $dom->{attr}{names} &&
606 ! defined $dom->{attr}{dupnames}) {
607 push(@AUTO_FOOTNOTES, $dom);
608 $dom->{attr}{names} = [ $LAST_AUTO_FOOTNOTE ];
609 RST::RegisterName($dom, $dom->{source},
612 $label->append(newPCDATA
613 DOM($LAST_AUTO_FOOTNOTE));
616 push(@AUTO_FOOTNOTES, $dom);
618 int($NEXT_SYMBOL_FOOTNOTE/@FOOTNOTE_SYMBOLS) + 1;
620 $NEXT_SYMBOL_FOOTNOTE % @FOOTNOTE_SYMBOLS;
622 ($FOOTNOTE_SYMBOLS[$index]) x $multiplier;
623 $label->append(newPCDATA DOM($name));
624 $NEXT_SYMBOL_FOOTNOTE++;
626 $RST::REFERENCE_DOM{$tag}{$dom->{attr}{names}[0]} = $dom
627 if defined $dom->{attr}{names};
628 $RST::REFERENCE_DOM{$tag}{$dom->{attr}{ids}[0]} = $dom;
636 # Processes a docutils.transforms.references.IndTargets transform.
637 # Links indirect targets and bare targets to their eventual destination.
638 # Arguments: top-level DOM
643 # Process indirect targets
648 my $parent = $dom->parent();
649 my $tag = $dom->{tag};
650 if ($tag eq 'target' && ! $dom->{forward}) {
652 my $ignores = 'comment|substitution_definition|system_message|pending|image';
655 while ($next->{tag} eq 'target' &&
656 $next->parent()->{tag} ne 'paragraph' &&
657 (defined $next->{attr}{refname} ||
658 ! grep(/ref(uri|id)/, keys %{$next->{attr}}))) {
659 # This is either an indirect target or a bare target
660 return $dom if $next->{badtarget};
662 if (defined $next->{attr}{refname}) {
663 # This is an indirect target
664 $next->{type} = "Indirect"
665 unless defined $next->{type};
666 # Chain until we come to something not indirect
667 while (defined (my $name = $next->{attr}{refname})
671 my @targets = @{$RST::ALL_TARGET_NAMES{$name}}
672 if defined $RST::ALL_TARGET_NAMES{$name};
673 $seen{$next} = $ind{$next} = $next;
675 my $errname = defined $dom->{attr}{names} ?
676 qq("$dom->{attr}{names}[0]" ) : '';
677 my $sm = RST::system_message
678 (3, $dom->{source}, $dom->{lineno},
679 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.));
681 # Mark all the seen targets as bad targets
682 foreach my $bad (keys %seen) {
683 $seen{$bad}{badtarget} = $sm;
688 return $dom unless $next;
692 # This is a chained target. Tie it to what's next
694 my @barechain = ($next);
696 unshift @ids, @{$next->{attr}{ids}};
697 unshift @names, @{$next->{attr}{names}} if
698 $next->{attr}{names};
699 $seen{$next} = $next;
700 $next = $next->next($ignores);
701 return $dom unless $next;
702 while ($next->{tag} eq 'target' &&
703 ! grep(/ref(name|uri|id)/, keys %{$next->{attr}}))
705 $seen{$next} = $next;
707 unshift @ids, @{$next->{attr}{ids}};
708 unshift @names, @{$next->{attr}{names}}
709 if defined $next->{attr}{names};
710 $next = $next->next($ignores);
711 return $dom unless $next;
713 if ($next->{tag} =~ /^(section|paragraph|target|reference)$/) {
714 push @{$next->{attr}{ids}}, @ids;
715 push @{$next->{attr}{names}}, @names
717 foreach (@barechain) {
718 &RST::ReregisterName($_, $next);
719 if ($next->{attr}{refname}) {
720 $_->{attr}{refid} = $_->{attr}{ids}[0];
721 delete $_->{attr}{ids};
722 delete $_->{attr}{names};
730 # Generate a problematic for this dom
731 my $prev = $chain[-1];
732 my ($prob, $refid, $id) =
733 RST::problematic($prev->{lit});
734 # Generate the system message
735 my ($first) = grep($ind{$_}, @chain);
736 my $nextname = $next->{attr}{refname};
737 my $sm = RST::system_message
738 (3, $next->{source}, $next->{lineno},
739 qq(Indirect hyperlink target "$first->{attr}{names}[0]" (id="$first->{attr}{ids}[0]") refers to target "$nextname", forming a circular reference.),
740 undef, ids=>[ $refid ],
743 # Mark all the seen targets as bad targets
744 my $tgtrefid = $next->{attr}{ids}[-1];
745 foreach my $bad (keys %seen) {
746 my $baddom = $seen{$bad};
747 $baddom->{badtarget} = $sm;
748 $baddom->{attr}{refid} = $tgtrefid;
749 delete $baddom->{attr}{refname} if $ind{$bad};
751 $prob->{badtarget} = $sm;
752 $sm->{attr}{backrefs} = [ @ids ];
754 # Convert the last target to a problematic
757 $dom->{attr}{refid} =
758 $dom->{attr}{ids}[0];
759 delete $dom->{attr}{refname};
762 # Return a new problematic
763 ($prob, $refid, $id) =
764 RST::problematic($dom->{lit}, $refid);
765 push @{$sm->{attr}{backrefs}}, $id;
770 if ($next->{tag} eq 'target' &&
771 defined $next->{attr}{refuri}) {
772 foreach my $prev (keys %seen) {
773 my $prevdom = $seen{$prev};
774 if ($ind{$prev} || defined $prevdom->{attr}{refid}) {
775 delete $prevdom->{attr}{refname};
776 delete $prevdom->{attr}{refid};
777 $prevdom->{attr}{refuri} = $next->{attr}{refuri};
780 $prevdom->{attr}{refid} =
781 $prevdom->{attr}{ids}[0];
782 delete $prevdom->{attr}{ids};
783 delete $prevdom->{attr}{names};
788 return $dom if $next->{tag} =~ /^(footnote|citation)$/;
789 if ($next->{tag} =~ /^(section|paragraph|target|topic)$/
790 || defined $next->{attr}{refid}) {
791 my $dest = defined $next->{attr}{refid} ?
792 $next->{forward} : $next;
793 my $refid = $next->{attr}{refid} ||
794 $dest->{attr}{ids}[0];
797 $_->{forward} = $dest;
799 $_->{attr}{refid} = $refid;
800 delete $_->{attr}{refname};
803 my $refid = $_->{attr}{ids} &&
804 $_->{attr}{ids}[0] ||
806 $_->{attr}{names}[0];
807 $_->{attr}{refid} = $refid;
808 delete $_->{attr}{ids};
809 delete $_->{attr}{names};
818 $dom->append(@errs) if @errs;
821 # Processes a docutils.transforms.references.CitationReferences transform.
822 # Links citation references to their targets.
823 # Arguments: top-level DOM
824 sub CitationReferences {
827 # Link references to their definitions if they exist
832 my $tag = $dom->{tag};
833 if ($tag =~ /^(?:(citation|substitution)_reference)$/) {
834 my $what = $1 eq 'citation' ? $1 :
835 'substitution_definition';
836 my $name = main::FirstDefined($dom->{attr}{names} &&
837 $dom->{attr}{names}[0],
838 $dom->{attr}{refname});
839 my $target = $RST::REFERENCE_DOM{$what}{$name};
840 $target = ($RST::REFERENCE_DOM{"$what.lc"}{lc $name})
841 unless defined $target;
842 if (! defined $target) {
843 my ($prob, $refid, $id) =
844 RST::problematic($dom->{lit});
845 my $emsg = $what eq 'citation' ?
846 'Unknown target name' :
847 'Undefined substitution referenced';
848 push @errs, RST::system_message
849 (3, $dom->{source}, $dom->{lineno},
850 qq($emsg: "$name".), '', ids=>[ $refid ],
854 if ($tag eq 'substitution_reference') {
855 if ($target->{attr}{ltrim} || $target->{attr}{rtrim}) {
856 my $parent = $dom->parent();
857 my $idx = $parent->index($dom);
858 $parent->{content}[$idx-1]{text} =~ s/ *$//
859 if $target->{attr}{ltrim} && $idx > 0 &&
860 $parent->{content}[$idx-1]{tag} eq '#PCDATA';
861 $parent->{content}[$idx+1]{text} =~ s/^ *//
862 if $target->{attr}{rtrim} &&
863 $idx < $parent->num_contents() &&
864 $parent->{content}[$idx+1]{tag} eq '#PCDATA';
866 my @content= $target->contents();
868 for ($i=0; $i<@content; $i++) {
869 splice(@content, $i, 1, &$cr($content[$i]))
870 if $content[$i]{tag} eq 'substitution_reference';
874 delete $dom->{attr}{refname};
875 $dom->{attr}{refid} = $target->{attr}{ids}[0];
876 push @{$target->{attr}{backrefs}}, @{$dom->{attr}{ids}};
880 $dom->Reshape ($cr, 'pre');
881 $dom->append(@errs) if @errs;
884 # Processes a docutils.transforms.references.FootnoteReferences transform.
885 # Links footnote references to their targets.
886 # Arguments: top-level DOM
887 sub FootnoteReferences {
890 # Link references to their definitions if they exist
895 my $tag = $dom->{tag};
896 if ($tag eq 'footnote_reference' && !$dom->{resolved}) {
897 my $name = main::FirstDefined($dom->{attr}{names} &&
898 $dom->{attr}{names}[0],
899 $dom->{attr}{refname});
900 my $footnote = defined $name ?
901 $RST::REFERENCE_DOM{footnote}{$name} :
902 $AUTO_FOOTNOTES[$AUTO_FOOTNOTE_REF++];
903 if (! defined $footnote) {
904 my ($prob, $refid, $id) =
905 RST::problematic($dom->{lit});
906 push @errs, RST::system_message
907 (3, $dom->{source}, $dom->{lineno},
908 (defined $name ? qq(Unknown target name: "$name".):
909 qq(Too many autonumbered footnote references: only ${\scalar(@AUTO_FOOTNOTES)} corresponding footnotes available.)),
910 '', ids=>[ $refid ], backrefs=>[ $id ]);
913 if (defined $footnote->{attr}{dupnames}) {
914 my ($prob, $refid, $id) =
915 RST::problematic($dom->{lit});
916 push @errs, RST::system_message
917 (3, $dom->{source}, $dom->{lineno},
918 (qq(Duplicate target name, cannot be used as a unique reference: "$name".)),
919 '', ids=>[ $refid ], backrefs=>[ $id ]);
922 if ($dom->{attr}{auto}) {
923 my $name = $footnote->{content}[0]{content}[0]{text};
924 $dom->append(newPCDATA DOM($name));
926 delete $dom->{attr}{refname};
927 $dom->{attr}{refid} = $footnote->{attr}{ids}[0];
928 push @{$footnote->{attr}{backrefs}}, @{$dom->{attr}{ids}};
929 $dom->{resolved} = 1;
934 $dom->append(@errs) if @errs;
937 # Processes a docutils.transforms.references.MarkReferenced transform.
938 # Marks immediate destinations of references as referenced.
939 # Arguments: top-level DOM
943 # Mark destinations of references as referenced
947 my $tag = $dom->{tag};
948 if (defined $dom->{attr}{refname}) {
950 my $name = $dom->{attr}{refname};
951 my @targets = @{$RST::TARGET_NAME{target}{$name}}
952 if defined $RST::TARGET_NAME{target}{$name};
954 $target = $targets[0];
955 $target->{referenced} = 1;
963 # Processes a docutils.transforms.references.References transform.
964 # Counts anonymous references, links references to their
965 # destinations, produces error messages if the number of anonymous
966 # references is insufficient.
967 # Arguments: top-level DOM
972 # Count how many anonymous references we have
977 if ($dom->{tag} eq 'reference' && $dom->{attr}{anonymous});
981 my $last_anonymous_target = 0;
982 my $anonymous_mismatch_id;
983 my @anonymous_mismatch_refids;
985 # Link references to their definitions if they exist
989 my $tag = $dom->{tag};
990 if ($tag eq 'reference' && ! defined $dom->{attr}{refuri} &&
991 ! defined $dom->{attr}{refid}) {
993 my $name = $dom->{attr}{refname};
995 my @targets = @{$RST::TARGET_NAME{target}{$name}}
996 if defined $RST::TARGET_NAME{target}{$name};
998 my ($prob, $refid, $id) =
999 RST::problematic($dom->{lit});
1001 push @errs, RST::system_message
1002 (3, $dom->{source}, $dom->{lineno},
1003 qq(Duplicate target name, cannot be used as a unique reference: "$name".),
1004 undef, backrefs=>[ $id ], ids=>[ $refid ]);
1007 $target = $targets[0];
1008 if (! defined $target &&
1009 ! defined $RST::ALL_TARGET_IDS{$name}[0]) {
1010 my ($prob, $refid, $id) =
1011 RST::problematic($dom->{lit});
1012 push @errs, RST::system_message
1013 (3, $dom->{source}, $dom->{lineno},
1014 qq(Unknown target name: "$name".),
1015 '', ids=> [ $refid ], backrefs=>[ $id ]);
1019 elsif ($dom->{attr}{anonymous}) {
1020 if ($anonymous_refs > @RST::ANONYMOUS_TARGETS) {
1021 $anonymous_mismatch_id = RST::Id()
1022 if ! defined $anonymous_mismatch_id;
1023 my ($prob, $refid, $id) =
1024 RST::problematic($dom->{lit},
1025 $anonymous_mismatch_id);
1026 push(@anonymous_mismatch_refids, $id);
1030 $RST::ANONYMOUS_TARGETS[$last_anonymous_target++];
1032 while (defined $target) {
1033 if ($target->{badtarget}) {
1034 my $sm = $target->{badtarget};
1035 my ($prob, $refid, $id) =
1036 RST::problematic($dom->{lit},
1037 $sm->{attr}{ids}[0]);
1038 push @{$sm->{attr}{backrefs}}, $id;
1039 $sm->{attr}{ids} = [ $refid ];
1042 my $dest = $target->{forward} || $target;
1043 if ($dest->{tag} eq 'target' &&
1044 defined $dest->{attr}{refuri}) {
1045 $target->{type} = "External"
1046 unless defined $target->{type};
1047 delete $dom->{attr}{refname};
1048 $dom->{attr}{refuri} = $dest->{attr}{refuri};
1050 elsif ($target->{forward}) {
1051 delete $dom->{attr}{refname};
1052 $dom->{attr}{refid} = $target->{attr}{refid};
1054 elsif (defined $target->{attr}{refid}) {
1055 $target->{type} = "Internal"
1056 unless defined $target->{type};
1057 my @targets = @{$RST::ALL_TARGET_IDS
1058 {$target->{attr}{refid}}};
1059 $target = $targets[0];
1062 elsif (defined $target->{attr}{ids}) {
1064 RST::NormalizeId($dom->{attr}{refname});
1065 $dom->{attr}{refid} =
1066 grep($_ eq $refid, @{$target->{attr}{ids}}) ?
1067 $refid : $target->{attr}{ids}[0];
1068 # $dom->{attr}{refid} =
1069 # RST::NormalizeId($dom->{attr}{refname});
1070 delete $dom->{attr}{refname};
1079 # Produce messages if there aren't enough anonymous hyperlink targets
1080 if (defined $anonymous_mismatch_id) {
1081 my $sm = RST::system_message
1082 (3, $dom->{attr}{source}, undef,
1083 qq(Anonymous hyperlink mismatch: $anonymous_refs references but ${\scalar(@RST::ANONYMOUS_TARGETS)} targets.\nSee "backrefs" attribute for IDs.),
1084 '', ids=>[ $anonymous_mismatch_id ],
1085 backrefs=>[ @anonymous_mismatch_refids ]);
1086 delete $sm->{attr}{line};
1090 $dom->append(@errs) if @errs;
1093 # Processes a docutils.transforms.references.Unreferenced transform.
1094 # Produces messages for unreferenced targets.
1095 # Arguments: top-level DOM
1099 # Produce messages for unreferenced targets
1104 my $tag = $dom->{tag};
1105 if ($tag eq 'target' && ! $dom->{referenced} &&
1106 ! $dom->{attr}{anonymous} && ! $dom->{attr}{dupnames}) {
1108 defined $dom->{attr}{names} && $dom->{attr}{names}[0] ||
1109 defined $dom->{attr}{dupnames} &&
1110 $dom->{attr}{dupnames}[0] || $dom->{attr}{refid};
1111 my $id = defined $name ? qq("$name") :
1112 qq(id="$dom->{attr}{ids}[0]");
1113 push @errs, RST::system_message
1114 (1, $dom->{source}, $dom->{lineno},
1115 qq(Hyperlink target $id is not referenced.));
1121 $dom->append(@errs);
1124 # Processes a docutils.transforms.references.TargetNotes transform.
1125 # Constructs a list of external references and creates footnotes
1127 # Arguments: pending DOM, top-level DOM, details hash reference
1129 my ($dom, $topdom, $details) = @_;
1131 my @targets; # Used in closure of sub
1132 # Construct the list of external references.
1136 my $tag = $dom->{tag};
1137 push (@targets, $dom)
1138 if $tag eq 'target' && defined $dom->{attr}{refuri};
1142 # Create the footnotes
1145 foreach (@targets) {
1147 $footnotes{$_->{attr}{names}[0]} = $id;
1148 my $dom = new DOM('footnote', auto=>1, ids=>[ $id ],
1149 names=>[ "TARGET_NOTE: $id" ]);
1150 my $para = new DOM('paragraph');
1151 $dom->append($para);
1152 my $ref = new DOM('reference', refuri=>$_->{attr}{refuri});
1153 $para->append($ref);
1154 $ref->append(newPCDATA DOM($_->{attr}{refuri}));
1158 # Insert the footnote references
1162 my $tag = $dom->{tag};
1163 if ($tag eq 'reference' &&
1164 defined $dom->{attr}{refname} &&
1165 defined $footnotes{$dom->{attr}{refname}}) {
1166 my $refname = $footnotes{$dom->{attr}{refname}};
1167 my $fr = new DOM('footnote_reference', auto=>1,
1168 ids=> [ RST::Id() ],
1169 refname=>"TARGET_NOTE: $refname");
1170 return ($dom, newPCDATA DOM(' '), $fr);
1178 package docutils::transforms::universal;
1180 # This package contains routines for transforms of DOM trees
1182 # Processes a docutils.transforms.universal.Decorations transform.
1183 # Adds the "View docuemnt source", "Generated on" and "Generated by"
1184 # decorations to the end of the document.
1185 # Arguments: top-level DOM
1189 my ($dec) = grep $_->{tag} eq 'decoration', $topdom->contents();
1190 return if defined $dec && ($dec->{content}[0]{tag} eq 'footer' ||
1191 $dec->num_contents() > 1);
1192 my $para = new DOM('paragraph');
1193 my $source_link = main::FirstDefined($main::opt_D{source_link}, 1);
1195 my $source_url = main::FirstDefined($main::opt_D{source_url},
1196 $topdom->{attr}{source});
1197 my $ref = new DOM('reference', refuri=>$source_url);
1198 $ref->append(newPCDATA DOM('View document source'));
1199 $para->append($ref);
1200 $para->append(newPCDATA DOM(".\n"));
1202 my $time = main::FirstDefined($main::opt_D{time}, 1);
1203 my $date = main::FirstDefined($main::opt_D{date}, 0);
1204 if ($date || $time) {
1205 my $format = "%Y/%m/%d" . ($time ? " %H:%M:%S %Z" : "");
1207 my $date = POSIX::strftime($format, localtime);
1208 $para->append(newPCDATA DOM("Generated on: $date.\n"));
1210 my $generator = main::FirstDefined($main::opt_D{generator}, 1);
1212 my $ref = new DOM('reference', refuri=>
1213 'http://docutils.sourceforge.net/rst.html');
1214 $ref->append(newPCDATA DOM("reStructuredText"));
1215 $para->append(newPCDATA DOM("Generated by $main::TOOL_ID from "),
1217 newPCDATA DOM(" source.\n"));
1220 if ($para->num_contents()) {
1221 my $dec = new DOM('decoration');
1222 my $footer = new DOM('footer');
1223 $dec->append($footer);
1224 $footer->append($para);
1225 # Decoration needs to be appended before the document model
1226 # starts, i.e., after the latest of title or subtitle.
1228 for ($i=0; $i<$topdom->num_contents(); $i++) {
1229 if ($topdom->{content}[$i]{tag} !~ /title|docinfo/) {
1230 $topdom->splice($i, 0, $dec);
1237 # Processes a docutils.transforms.universal.EmptyTopics transform.
1238 # Removes any topics that have only a header in their contents.
1239 # Arguments: top-level DOM
1246 return if $dom->{tag} eq 'topic' &&
1247 ($dom->num_contents() == 0 ||
1248 $dom->{content}[-1]{tag} eq 'title');
1253 # Processes a docutils.transforms.universal.Messages transform.
1254 # Moves system messages at the end into "Docutils System Messages" section.
1255 # Arguments: top-level DOM
1259 # Move system messages at the end to a section
1260 my @SYSTEM_MESSAGES;
1264 push (@SYSTEM_MESSAGES, $dom)
1265 if ($dom->{tag} eq 'system_message' &&
1266 $dom->{attr}{level} >= $main::opt_D{report});
1267 return $dom->{tag} ne 'system_message' ? ($dom) : ();
1271 if (@SYSTEM_MESSAGES > 0) {
1272 my $errsec = new DOM('section', classes=>['system-messages']);
1273 $dom->append($errsec);
1274 my $title = new DOM('title');
1275 $errsec->append($title);
1276 $title->append(newPCDATA DOM('Docutils System Messages'));
1277 $errsec->append(@SYSTEM_MESSAGES);
1281 # Processes a docutils.transforms.universal.ScoopMessages transform.
1282 # Moves system messages from anywhere in the DOM tree to the end of
1283 # the top-level DOM.
1284 # Arguments: top-level DOM
1288 # Move system messages into end of top dom's contents
1289 my @SYSTEM_MESSAGES;
1293 if ($dom->{tag} eq 'system_message') {
1294 if (defined $dom->{attr}{ids}) {
1295 push(@SYSTEM_MESSAGES, $dom);
1301 $dom->append(@SYSTEM_MESSAGES);
1304 # Processes a docutils.transforms.universal.Transitions transform.
1305 # Moves transitions at end of sections to top level and creates error
1306 # messages for incorrect transitions.
1307 # Arguments: top-level DOM
1311 # Move system messages at the end to a section
1316 if ($dom->{tag} eq 'transition') {
1317 my $domparent = $dom->parent();
1318 my $idx = $domparent->index($dom);
1320 push @doms, RST::system_message
1321 (3, $dom->{source}, $dom->{lineno},
1322 "Document or section may not begin with a transition.")
1324 $idx == 1 && $domparent->{content}[0]{tag} eq 'title';
1325 my $next = $dom->next();
1326 if ($next && ($next->parent() || 'NONE') != $domparent) {
1327 $next->{transition} = $dom;
1331 if (! defined $next) {
1332 push @doms, RST::system_message
1333 (3, $dom->{source}, $dom->{lineno},
1334 "Document may not end with a transition.");
1336 elsif ($next->{tag} eq 'transition') {
1337 push @doms, RST::system_message
1338 (3, $next->{source}, $next->{lineno},
1339 "At least one body element must separate transitions; adjacent transitions are not allowed.");
1343 elsif (my $t = $dom->{transition}) {
1344 delete $dom->{transition};
1351 $dom->append(@errs);