# Copyright (c) 1999-2007 by Steven McDougall. This module is free # software; you can redistribute it and/or modify it under the same # terms as Perl itself. package Pod::Tree::Stream; sub new { my($package, $fh) = @_; my $stream = { fh => $fh, line => '' }; bless $stream, $package } sub get_paragraph { my $stream = shift; my $fh = $stream->{fh}; my $line = $stream->{line}; defined $line or return undef; my(@lines) = ($line); while ($line = $fh->getline) { push @lines, $line; $line =~ /\S/ or last; } while ($line = $fh->getline) { $line =~ /\S/ and last; push @lines, $line; } $stream->{line} = $line; join '', @lines } package Pod::Tree; require Exporter; use strict; use IO::File; use Pod::Tree::Node; use base qw(Exporter); $Pod::Tree::VERSION = '1.13'; sub new { my $class = shift; my $tree = { loaded => 0, paragraphs => [] }; bless $tree, $class } sub load_file { my($tree, $file, %options) = @_; Pod::Tree::Node->set_filename($file); my $fh = new IO::File; $fh->open($file) or return ''; $tree->load_fh($fh, %options); Pod::Tree::Node->set_filename(""); 1 } sub load_fh { my($tree, $fh, %options) = @_; $tree->{in_pod} = 0; $tree->_load_options(%options); my $limit = $tree->{limit}; my $stream = new Pod::Tree::Stream $fh; my $paragraph; my @paragraphs; while ($paragraph = $stream->get_paragraph) { push @paragraphs, $paragraph; $limit and $limit==@paragraphs and last; } $tree->{paragraphs} = \@paragraphs; $tree->_parse; } sub load_string { my($tree, $string, %options) = @_; my @chunks = split /( \n\s*\n | \r\s*\r | \r\n\s*\r\n )/x, $string; my(@paragraphs); while (@chunks) { push @paragraphs, join '', splice @chunks, 0, 2; } $tree->load_paragraphs(\@paragraphs, %options); } sub load_paragraphs { my($tree, $paragraphs, %options) = @_; $tree->{in_pod} = 1; $tree->_load_options(%options); my $limit = $tree->{limit}; my @paragraphs = @$paragraphs; $limit and splice @paragraphs, $limit; $tree->{paragraphs} = \@paragraphs; $tree->_parse; } sub loaded { shift->{'loaded'} } sub _load_options { my($tree, %options) = @_; my($key, $value); while (($key, $value) = each %options) { $tree->{$key} = $value; } } sub _parse { my $tree = shift; $tree->_make_nodes; $tree->_make_for; $tree->_make_sequences; my $root = $tree->{root}; $root->parse_links; $root->unescape; $root->consolidate; $root->make_lists; $tree->{'loaded'} = 1; } sub _add_paragraph { my($tree, $paragraph) = @_; for ($paragraph) { /^=cut/ and do { $tree->{in_pod}=0; last }; $tree->{in_pod} and do { push @{$tree->{paragraphs}}, $paragraph; last }; /^=\w/ and do { $tree->{in_pod}=1; push @{$tree->{paragraphs}}, $paragraph; last }; } } my %Command = map { $_ => 1 } qw(=pod =cut =head1 =head2 =head3 =head4 =over =item =back =for =begin =end); sub _make_nodes { my $tree = shift; my $paragraphs = $tree->{paragraphs}; my $in_pod = $tree->{in_pod}; my @children; for my $paragraph (@$paragraphs) { my($word) = split(/\s/, $paragraph); my $node; if ($in_pod) { if ($paragraph =~ /^\s/) { $node = verbatim Pod::Tree::Node $paragraph; } elsif ($Command{$word}) { $node = command Pod::Tree::Node $paragraph; $in_pod = $word ne '=cut'; } else { $node = ordinary Pod::Tree::Node $paragraph; } } else { if ($Command{$word}) { $node = command Pod::Tree::Node $paragraph; $in_pod = $word ne '=cut'; } else { $node = code Pod::Tree::Node $paragraph; } } push @children, $node; } $tree->{root} = root Pod::Tree::Node \@children; } sub _make_for { my $tree = shift; my $root = $tree->{root}; my $old = $root->get_children; my @new; while (@$old) { my $node = shift @$old; is_c_for $node and $node->force_for; is_c_begin $node and $node->parse_begin($old); push @new, $node; } $root->set_children(\@new); } sub _make_sequences { my $tree = shift; my $root = $tree->{root}; my $nodes = $root->get_children; for my $node (@$nodes) { is_code $node and next; is_verbatim $node and next; is_for $node and next; $node->make_sequences; } } sub dump { my $tree = shift; $tree->{root}->dump } sub get_root { shift->{root} } sub set_root { my($tree, $root) = @_; $tree->{root} = $root; } sub push { my($tree, @nodes) = @_; my $root = $tree->{root}; my $children = $root->get_children; push @$children, @nodes; } sub pop { my $tree = shift; my $root = $tree->get_root; my $children = $root->get_children; pop @$children } sub walk { my($tree, $sub) = @_; my $root = $tree->get_root; _walk($root, $sub); } sub _walk { my $sub = $_[1]; my $descend = &$sub($_[0]); # :TRICKY: sub can modify node $descend or return; my $node = $_[0]; my $children = $node->get_children; for my $child (@$children) { _walk($child, $sub); } my $siblings = $node->get_siblings; for my $sibling (@$siblings) { _walk($sibling, $sub); } } sub has_pod { my $tree = shift; my $root = $tree->get_root; my $children = $root->get_children; scalar grep { $_->get_type ne 'code' } @$children; } 1 __END__ =head1 NAME Pod::Tree - Create a static syntax tree for a POD =head1 SYNOPSIS use Pod::Tree; $tree = new Pod::Tree; $tree->load_file ( $file, %options) $tree->load_fh ( $fh , %options); $tree->load_string ( $pod , %options); $tree->load_paragraphs(\@pod , %options); $loaded = $tree->loaded; $node = $tree->get_root; $tree->set_root ($node); $node = $tree->pop; $tree->push(@nodes); $tree->walk(\&sub); $tree->has_pod and ... print $tree->dump; =head1 REQUIRES Pod::Escapes =head1 EXPORTS Nothing =head1 DESCRIPTION C parses a POD into a static syntax tree. Applications walk the tree to recover the structure and content of the POD. See L> for a description of the tree. =head1 METHODS =over 4 =item I<$tree> = C C Creates a new C object. The syntax tree is initially empty. =item I<$ok> = I<$tree>->C(I<$file>, I<%options>) Parses a POD and creates a syntax tree for it. I<$file> is the name of a file containing the POD. Returns null iff it can't open I<$file>. See L for a description of I<%options> =item I<$tree>->C(I<$fh>, I<%options>) Parses a POD and creates a syntax tree for it. I<$fh> is an C object that is open on a file containing the POD. See L for a description of I<%options> =item I<$tree>->C(I<$pod>, I<%options>) Parses a POD and creates a syntax tree for it. I<$pod> is a single string containing the POD. See L for a description of I<%options> =item I<$tree>->C(\I<@pod>, I<%options>) Parses a POD and creates a syntax tree for it. I<\@pod> is a reference to an array of strings. Each string is one paragraph of the POD. See L for a description of I<%options> =item I<$loaded> = I<$tree>->C Returns true iff one of the C* methods has been called on I<$tree>. =item I<$node> = I<$tree>->C Returns the root node of the syntax tree. See L for a description of the syntax tree. =item I<$tree>->C(I<$node>) Sets the root of the syntax tree to I<$node>. =item I<$tree>->C(I<@nodes>) Pushes I<@nodes> onto the end of the top-level list of nodes in I<$tree>. =item I<$node> = I<$tree>->C Pops I<$node> off of the end of the top-level list of nodes in I<$tree>. =item I<$tree>->C(I<\&sub>) Walks the syntax tree, depth first. Calls I once for each node in the tree. The current node is passed as the first argument to I. C descends to the children and siblings of I<$node> iff I returns true. =item I<$tree>->C Returns true iff I<$tree> contains POD paragraphs. =item I<$tree>->C Pretty prints the syntax tree. This will show you how C interpreted your POD. =back =head1 OPTIONS These options may be passed in the I<%options> hash to the C* methods. =over 4 =item C<< in_pod => 0 >> =item C<< in_pod => 1 >> Sets the initial value of C. When C is false, the parser ignores all text until the next =command paragraph. The initial value of C defaults to false for C and C calls and true for C and C calls. This is usually what you want, unless you want consistency. If this isn't what you want, pass different initial values in the I<%options> hash. =item C => I Only parse the first I paragraphs in the POD. =back =head1 DIAGNOSTICS =over 4 =item C(I<$file>) Returns null iff it can't open I<$file>. =back =head1 NOTES =head2 No round-tripping Currently, C does not provide a complete, exact representation of its input. For example, it doesn't distingish between C<$foo-Ebar> and C<< $foo->bar >> As a result, it is not guaranteed that a file can be exactly reconstructed from its C representation. =head2 LZ<><> markups In the documentation of the L<"sec"> section in this manual page markup, L> has always claimed (the quotes are optional) However, there is no way to decide from the syntax alone whether L is a link to the F man page or a link to the C section of this man page. C parses C<< LZ<> >> as a link to a section if C looks like a section name (e.g. contains whitespace), and as a link to a man page otherswise. In practice, this tends to break links to sections. If you want your section links to work reliably, write them as C<< LZ<><"foo"> >> or C<< LZ<> >>. =head1 SEE ALSO perl(1), L>, L> =head1 ACKNOWLEDGMENTS =over 4 =item * =item * Paul Bettinger =item * Sean M. Burke =item * Brad Choate =item * Rudi Farkas =item * Paul Gibeault =item * Jay Hannah =item * Paul Hawkins =item * Jost Krieger =item * Marc A. Lehmann =item * Jonas Liljegren =item * Thomas Linden =item * Johan Lindstrom =item * Terry Luedtke =item * Rob Napier =item * Kate L Pugh =item * Christopher Shalah =item * Johan Vromans =back =head1 AUTHOR Steven McDougall =head1 COPYRIGHT Copyright (c) 1999-2004 by Steven McDougall. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.