#============================================================= -*-Perl-*- # # Pod::POM::View::DocBook # # DESCRIPTION # DocBook XML view of a Pod Object Model. # # AUTHOR # Andrew Ford # # Based heavily on Pod::POM::View::HTML by Andy Wardley # # COPYRIGHT # Copyright (C) 2009 Andrew Ford and Ford & Mason Ltd. All Rights Reserved. # Copyright (C) 2000 Andy Wardley. All Rights Reserved. # # This module is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. # # REVISION # $Id: DocBook.pm 4118 2009-03-08 09:25:39Z andrew $ # # TODO # * get all the view_* methods outputting valid DocBook XML # * check all list items for common item formats #======================================================================== package Pod::POM::View::DocBook; require 5.004; use strict; use Pod::POM::View; use Pod::POM::Constants qw( :all ); use base qw( Pod::POM::View ); use Text::Wrap; use List::MoreUtils qw(firstidx); #use Clone; # cloning doesn't seem to work at the moment #use Data::Dumper; # for debugging use constant DEFAULT_ROOT_ELEMENT => 'article'; use constant DEFAULT_TOPSECT_ELEMENT => 'sect1'; ######################################################################### # Don't forget to update the VERSION section in the POD!!! our $VERSION = '0.08'; ######################################################################### our $DEBUG = 0 unless defined $DEBUG; my $XML_PROTECT = 0; my @OVER; my %topsect = ( book => 'chapter', article => 'sect1', chapter => 'sect1', sect1 => 'sect2' ); my @section = qw( part chapter sect1 sect2 sect3 sect4 sect5 ); my $head1off = (firstidx { $_ eq 'sect1' } @section) - 1; my %dont_ucfirst = map { $_ =>1 } qw { a an at as and are but by ere for from in into is of on onto or over per the to that than until unto upon via with while whilst within without de von }; #------------------------------------------------------------------------ # new(%options) # # Constructor for the view. Called implicitly by Pod::POM # Options: # * root - the root element (defaults to 'article') # * topsect - top sectional element # * pubid # * title # * author # * extracttoptitle # * titlecasing #------------------------------------------------------------------------ sub new { my $class = shift; my $self = $class->SUPER::new(@_) || return; # initalise stack for maintaining info for nested lists $self->{ OVER } = []; # Determine the index of the topmost level section if (!exists $self->{topsect}) { if (exists $self->{root}) { my $root = $self->{root}; if (exists $topsect{$root}) { $self->{topsect} = $topsect{$root}; } } } $self->{preservecase} ||= {}; if (!ref $self->{preservecase}) { $self->{preservecase} = { map { lc($_) => 1 } split(/[\,\|\s]+/, $self->{preservecase}) }; } elsif (ref $self->{preservecase} eq 'ARRAY') { $self->{preservecase} = { map { lc($_) => 1 } @{$self->{preservecase}} }; } $self->{forcecase} ||= {}; if (!ref $self->{forcecase}) { $self->{forcecase} = { map { lc($_) => $_ } split(/[\,\|\s]+/, $self->{forcecase}) }; } elsif (ref $self->{forcecase} eq 'ARRAY') { $self->{forcecase} = { map { lc($_) => $_ } @{$self->{forcecase}} }; } $self->{root} ||= DEFAULT_ROOT_ELEMENT; $self->{topsect} ||= DEFAULT_TOPSECT_ELEMENT; $self->{_head1off} = (firstidx { $_ eq $self->{topsect} } @section) - 1; return $self; } #------------------------------------------------------------------------ # view($self, $type, $item) #------------------------------------------------------------------------ sub view { my ($self, $type, $item) = @_; DEBUG("view $type"); if ($type =~ s/^seq_//) { return $item; } elsif (UNIVERSAL::isa($item, 'HASH')) { if (defined $item->{ content }) { return $item->{ content }->present($self); } elsif (defined $item->{ text }) { my $text = $item->{ text }; return ref $text ? $text->present($self) : $text; } else { return ''; } } elsif (! ref $item) { return $item; } else { return ''; } } #------------------------------------------------------------------------ # view_pod($self, $pod) # # View method for top-level node. Outputs the doctype and root element # and its content. #------------------------------------------------------------------------ sub view_pod { my ($self, $pod) = @_; DEBUG("view_pod\n"); my ($root, $author, $pubid, $sysid, $intsubset); my $title = ""; my @content = $pod->content; my $version_msg = sprintf("\n", __PACKAGE__, $VERSION, $Pod::POM::VERSION); if (ref $self) { $root = $self->{root}; if ($self->{suppressversion}) { $version_msg = ""; } } if (ref $content[0] eq 'Pod::POM::Node::Head1' and $content[0]->title eq 'NAME' and int(@{$content[0]->content}) == 1) { my ($titlecontent) = (shift @content)->content; $title = $titlecontent->text->present($self); } $root ||= DEFAULT_ROOT_ELEMENT; $pubid ||= "-//OASIS//DTD DocBook XML V4.5//EN"; $sysid ||= "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"; $intsubset ||= ""; return "\n" . "\n" . $version_msg . "<$root>\n" . "$title\n\n" . join('', ( map { $_->present($self) } @content )) . "\n\n"; } #------------------------------------------------------------------------ # _title_case_text($self, $text, $forcecase, $preservecase, $is_subsequent) # # Convert the case of words in a text string to "title case". There are # a couple of implementations of this (Text::Autoformat and # Text::Capitalize). This is a fairly simple implementation. # # #------------------------------------------------------------------------ sub _title_case_text { my ($self, $text, $forcecase, $preservecase, $is_subsequent) = @_; my @words = grep { $_ } split(/\s+/, $text); foreach my $word (@words) { my ($pre, $theword, $post) = ($word =~ /^(\W)*(\w.*?)(\W*)$/); my $lc_word = lc $theword; if ($forcecase->{$lc_word}) { $theword = $forcecase->{$lc_word}; } elsif (!$preservecase->{$lc_word}) { $theword = $lc_word; $theword = ucfirst $theword unless $dont_ucfirst{$lc_word} and $is_subsequent; } $is_subsequent++; # any of $pre, $theword and $post may be undefined no warnings 'uninitialized'; $word = $pre . $theword . $post; } my $newtext = join(" ", @words); $text =~ s/(\S.*\S)/$newtext/s; return $text; } sub _title_case_seq { my ($self, $node, $forcecase, $preservecase, $is_subsequent) = @_; return unless ref $node; $node = $$node; if ($node->[CMD] =~ /^[BI]?$/) { foreach ( @{$node->[CONTENT]} ) { if (ref $_) { $self->_title_case_seq($_, $forcecase, $preservecase, $is_subsequent); } else { $_ = $self->_title_case_text($_, $forcecase, $preservecase, $is_subsequent); } $is_subsequent = 1; } } } sub _view_headn { my ($self, $head, $level) = @_; DEBUG("view_head$level\n"); my $sect = $section[$level + (ref $self ? $self->{_head1off} : $head1off)]; my $title = $head->title; if (ref $self and $self->{titlecasing}) { # $title = clone($title); $self->_title_case_seq($title, $self->{forcecase}, $self->{preservecase}); } $title = $title->present($self, "head$level"); return "<$sect>\n" . "$title\n\n" . $head->content->present($self) . "\n\n"; } sub view_head1 { my ($self, $head1) = @_; return $self->_view_headn($head1, 1); } sub view_head2 { my ($self, $head2) = @_; return $self->_view_headn($head2, 2); } sub view_head3 { my ($self, $head3) = @_; return $self->_view_headn($head3, 3); } sub view_head4 { my ($self, $head4) = @_; return $self->_view_headn($head4, 4); } #------------------------------------------------------------------------ # view_over($self, $over) # # View method for =over. Maps to some sort of list - except if the content # contains no "=item"s in which case it is a blockquote. #------------------------------------------------------------------------ sub view_over { my ($self, $over) = @_; my ($start, $end, $strip); DEBUG("view_over"); my $items = $over->item(); return '' unless @$items || @{$over->content}; if (@$items) { my $first_title = $items->[0]->title(); if ($first_title =~ /^\s*\*\s*/) { # '=item *' =>