package Lire::ReportParser::HTMLDocBookFormatter; use strict; use base qw/ Exporter Lire::DocBookParser /; use Text::Wrap qw/ wrap /; use Lire::Utils qw/deep_copy/; use Lire::I18N qw/ensure_utf8/; our @EXPORT_OK = qw/ dbk2html /; our %dbk2html = ( 'quote' => 'q', 'abbrev' => 'abbr', 'acronym' => 'acronym', 'computeroutput' => 'samp', 'prompt' => 'samp', 'emphasis' => 'em', 'userinput' => 'kbd', 'subscript' => 'sub', 'superscript' => 'sup' ); our @dbk_vartags = qw/ varname envar constant structfield structname function option optional parameter classname symbol property type errorname /; =pod =head1 NAME Lire::ReportParser::HTMLDocBookFormatter - Lire::ReportParser subclass which formats description. =head1 SYNOPSIS To format DocBook: use Lire::ReportParser::HTMLDocBookFormatter qw/dbk2html/; my $txt = dbk2html( "Test" ); In XML Report processors : package MyParser; use base qw/ Lire::ReportParser::HTMLDocBookFormatter Lire::ReportParser /; sub new { my $self = shift->SUPER::new( @_ ); $self->init_docbook_formatter( @_ ); return $self; } sub handle_description { my ( $self, $desc ) = @_; print $desc; return; } =head1 DESCRIPTION Lire::ReportParser::HTMLDocBookFormatter is the counterpart to Lire::ReportParser::AsciiDocBookFormatter for the HTML output format. Please read its documentation to learn how to use this class. Their use is exactly the same. =cut sub new { my $self = shift->SUPER::new( @_ ); $self->init_docbook_formatter( @_ ); return $self; } sub namespaces { my $self = $_[0]; return { "http://www.logreport.org/LRML/" => 'lire' }; } sub elements_spec { my $self = $_[0]; my $spec = deep_copy( $self->Lire::DocBookParser::elements_spec() ); foreach my $admon ( @Lire::DocBookParser::admonitions_mix ) { $spec->{$admon}{'start'} = 'admonition_start'; $spec->{$admon}{'end'} = 'admonition_end'; } foreach my $element ( keys %dbk2html ) { $spec->{$element}{'start'} = 'mapped_start'; $spec->{$element}{'end'} = 'mapped_end'; } foreach my $element ( @dbk_vartags ) { $spec->{$element}{'start'} = 'var_start'; $spec->{$element}{'end'} = 'var_end'; } my %except = map { $_ => 1 } ( keys %dbk2html, @dbk_vartags, 'email', 'term', 'title', 'ulink' ); foreach my $element ( @Lire::DocBookParser::inline_mix ) { next if $except{$element}; $spec->{$element}{'start'} = 'span_start'; $spec->{$element}{'end'} = 'span_end'; } foreach my $element ( keys %$spec ) { next if $element eq 'ulink'; $spec->{$element}{'char'} = 'inline_char' if exists $spec->{$element}{'char'}; } $spec->{'email'}{'start'} = 'collector_start'; $spec->{'email'}{'char'} = 'collector_char'; $spec->{'term'}{'char'} = 'collector_char'; $spec->{'lire:description'} = [ @Lire::DocBookParser::top_levels ]; return $spec; } sub init_docbook_formatter { my ( $self, %args ) = @_; $self->{'dbk_columns'} = $args{'dbk_columns'} || 78; $self->{'dbk_tag_ender'} = ( $args{'xhtml'} ? ' /' : '' ); return $self; } sub dbk_start_processing { my $self = $_[0]; $self->{'dbk_process'} = 1; $self->{'dbk_text'} = ""; $self->{'dbk_text_blocks'} = []; $self->{'dbk_lists'} = []; $self->{'dbk_left_margin'} = 4; return; } sub dbk_end_processing { my ( $self ) = @_; delete $self->{'dbk_process'}; delete $self->{'dbk_text'}; delete $self->{'dbk_text_blocks'}; delete $self->{'dbk_lists'}; return; } sub description_start { $_[0]->dbk_start_processing(); return; } sub description_end { my ( $self ) = @_; return unless $self->{'dbk_process'}; $self->handle_description( $self->{'dbk_text'} ); $self->dbk_end_processing(); return; } sub handle_description { $_[0]{'saved_dbk'} = $_[1]; return; } sub parse_end { return $_[0]{'saved_dbk'}; } sub parent_block { return undef unless @{$_[0]{'dbk_text_blocks'}} > 1; return $_[0]{'dbk_text_blocks'}[$#{$_[0]{'dbk_text_blocks'}} - 1]; } sub current_block { return undef unless @{$_[0]{'dbk_text_blocks'}}; return $_[0]{'dbk_text_blocks'}[$#{$_[0]{'dbk_text_blocks'}}]; } sub print_block { my ( $self ) = @_; my $block = $self->current_block(); return unless $block; return unless length $block->{'dbk_text'}; my $margin = ' ' x $self->{'dbk_left_margin'}; my $initial = $margin . $block->{'dbk_initial_indent'}; my $next = $margin . $block->{'dbk_indent'}; # Squash space and trim the string. $block->{'dbk_text'} =~ tr/\n\t / /s; $block->{'dbk_text'} =~ s/^\s*//; $block->{'dbk_text'} =~ s/\s*$//; return if $block->{'dbk_text'} =~ /^\s*$/; local $Text::Wrap::columns = $self->{'dbk_columns'}; $self->{'dbk_text'} .= wrap( $initial, $next, $block->{'dbk_text'} ); $self->{'dbk_text'} .= "\n"; # Flush text buffer $block->{'dbk_text'} = ""; return; } sub inline_char { my ( $self, $str ) = @_; my $block = $self->current_block(); $block->{'dbk_text'} .= $str if $block; return; } sub span_text { my ( $self, $class, $text ) = @_; $self->inline_char( "$text" ); } sub start_div { my ( $self, $class ) = @_; $self->start_block(); $self->inline_char( "
" ); return; } sub end_div { my $self = $_[0]; $self->inline_char( '
' ); $self->end_block(); return; } sub start_block { my ( $self, $left_margin_indent )= @_; $left_margin_indent ||= 0; # Flush the current block, if there is one $self->print_block(); $self->{'dbk_left_margin'} += $left_margin_indent; push @{$self->{'dbk_text_blocks'}}, { 'dbk_text' => "", 'dbk_initial_indent' => '', 'dbk_indent' => '', 'dbk_left_margin_indent' => $left_margin_indent, 'dbk_children' => 0, }; my $parent = $self->parent_block(); $parent->{'dbk_children'}++ if $parent; return $self->current_block(); } sub end_block { my ( $self ) = @_; # Flush the current block, if there is one $self->print_block(); my $block = $self->current_block(); $self->{'dbk_left_margin'} -= $block->{'dbk_left_margin_indent'}; pop @{$self->{'dbk_text_blocks'}}; return; } sub current_list { return undef unless @{$_[0]{'dbk_lists'}}; return $_[0]{'dbk_lists'}[$#{$_[0]{'dbk_lists'}}]; } sub start_list { my ( $self, $type, %attr )= @_; $self->start_block(); my $list_tag; my $item_tag; if ( $type eq 'ordered' ) { $list_tag = 'ol'; $item_tag = 'li'; } elsif ( $type eq 'itemized' ) { $list_tag = 'ul'; $item_tag = 'li'; } elsif ( $type eq 'variable' ) { $self->start_div( 'variablelist' ); $list_tag = 'dl'; $item_tag = 'dd'; } else { die "unsupported list type: '$type'\n"; } $self->inline_char( "<$list_tag>" ) unless ( $type eq 'variable' ); $self->start_block( 2 ); push @{$self->{'dbk_lists'}}, { 'dbk_type' => $type, 'dbk_list_tag' => $list_tag, 'dbk_listitem_tag' => $item_tag, %attr, }; return $self->current_list(); } sub end_list { my ( $self ) = @_; $self->end_block(); my $list = pop @{$self->{'dbk_lists'}}; my $list_tag = $list->{ 'dbk_list_tag' }; $self->inline_char( "" ); if ( $list->{'dbk_type'} eq 'variable' ) { $self->end_block(); $self->inline_char( "" ); } $self->end_block(); return; } sub dbk_para_start { my ( $self, $name, $attr )= @_; my $block = $self->start_div( 'para' ); if ( $self->in_element( "listitem" ) ) { my $parent = $self->parent_block(); my $list = $self->current_list(); # Copy listitem indent and initial_indent attribute if ( $parent->{'dbk_children'} == 1 ) { $block->{'dbk_initial_indent'} = $parent->{'dbk_initial_indent'}; } else { # Add extra space before the paragraph if it wasn't the first # and the list is compact $self->{'dbk_text'} .= "\n" if ( $parent->{'dbk_children'} > 1 && $list->{'spacing'} eq 'compact' ); # Put mark only on first para $block->{'dbk_initial_indent'} = $parent->{'dbk_indent'}; } $block->{'dbk_indent'} = $parent->{'dbk_indent'}; } return; } sub dbk_para_end { $_[0]->end_div(); return; } sub dbk_itemizedlist_start { my ( $self, $name, $attr )= @_; $self->start_list( 'itemized', 'mark' => '-', 'spacing' => 'normal', %$attr ); return; } sub dbk_itemizedlist_end { $_[0]->end_list(); return; } sub dbk_orderedlist_start { my ( $self, $name, $attr )= @_; $self->start_list( 'ordered', 'spacing' => 'normal', %$attr, 'item_count' => 0 ); return; } sub dbk_orderedlist_end { $_[0]->end_list(); return; } sub dbk_variablelist_start { my ( $self, $name, $attr )= @_; $self->start_list( 'variable', 'spacing' => 'normal', %$attr ); return; } sub dbk_variablelist_end { $_[0]->end_list(); return; } sub dbk_varlistentry_start { my $self = $_[0]; my $list = $self->current_list(); return unless defined( $list ); if ( !$list->{'_list_started'} ) { $self->inline_char( "<$list->{'dbk_list_tag'}>" ); $self->start_block( 2 ); $list->{'_list_started'} = 'yes'; } return; } sub dbk_listitem_start { my ( $self, $name, $attr )= @_; $self->start_block(); my $list = $self->current_list(); return unless defined( $list ); $self->inline_char( "<$list->{'dbk_listitem_tag'}>" ); $self->start_block( 2 ); return; } sub dbk_listitem_end { my $self = $_[0]; $self->end_block(); my $list = $self->current_list(); return unless defined( $list ); $self->inline_char( "{'dbk_listitem_tag'}>" ); $self->end_block(); return; } sub admonition_start { my ( $self, $name, $attr ) = @_; $self->start_div( $name ); $self->span_text( 'admonition', ucfirst( $name . ":" ) ); $self->start_block( 2 ); return; } sub admonition_end { my $self = $_[0]; $self->end_block(); $self->end_div(); return; } sub mapped_start { my ( $self, $name ) = @_; $self->inline_char( "<$dbk2html{$name}>" ); return; } sub mapped_end { my ( $self, $name ) = @_; $self->inline_char( "" ); return; } sub span_start { my ( $self, $name, $attr ) = @_; $self->inline_char( "" ); return; } sub span_end { my ( $self, $name ) = @_; $self->inline_char( "" ); return; } sub var_start { my ( $self, $name, $attr ) = @_; $self->inline_char( "" ); return; } sub var_end { my ( $self, $name, $attr ) = @_; $self->inline_char( "" ); return; } sub dbk_email_end { my ( $self, $name ) = @_; my $email = $self->get_collector( $name ); $_[0]->inline_char( "$email" ); return; } sub dbk_term_start { my ( $self, $name, $attr )= @_; my $block = $self->start_block(); $self->collector_start( $name ); return; } sub dbk_term_end { my ( $self, $name ) = @_; $self->inline_char( "
" . $self->get_collector( $name ). "
" ); $self->end_block(); return; } sub dbk_title_start { my ( $self, $name, $attr )= @_; $self->start_block( 0 ); $self->span_start( $name ); return; } sub dbk_title_end { my ( $self, $name ) = @_; $self->span_end( $name ); $_[0]->end_block(); return; } sub dbk_ulink_start { my ( $self, $name, $attr )= @_; my $url = $attr->{'url'} || ''; $self->{'dbk_curr_url_attr'} = $url; $self->inline_char( "" ); return; } sub dbk_ulink_end { my ( $self ) = @_; $self->inline_char( '' ); delete $self->{'dbk_curr_url_attr'}; delete $self->{'dbk_curr_url'}; return; } sub dbk_ulink_char { my ( $self, $str )= @_; my $cdata = ( $str ne '' ? $str : $self->$self->{'dbk_curr_url_attr'} ); $self->inline_char( $cdata ); return; } =pod =head1 dbk2html( $docbook_str, [$columns], [$xhtml] ) Returns an HTML version of the DocBook XML fragment $docbook_str. The C parameter sets the number of columns in which the DocBook HTML text should be formatted. The C parameter defines whether the generated code will be XHTML-compliant; the default is yes. This method will die() in case of error. =cut sub dbk2html { my ( $docbook_str, $columns, $xhtml ) = @_; $xhtml = 1 unless defined $xhtml; $docbook_str = ensure_utf8( $docbook_str ); my %args = ( 'dbk_columns' => $columns, 'xhtml' => $xhtml ); my $parser = new Lire::ReportParser::HTMLDocBookFormatter( %args ); my $str = < $docbook_str EOD return $parser->parse( $str ); } 1; __END__ =head1 SEE ALSO Lire::ReportParser(3pm) Lire::ReportParser::AsciiDocBookFormatter(3pm) =head1 VERSION $Id: HTMLDocBookFormatter.pm,v 1.10 2006/07/23 13:16:31 vanbaal Exp $ =head1 AUTHORS Francis J. Lacoste Wolfgang Sourdeau =head1 COPYRIGHT Copyright (C) 2001-2004 Stichting LogReport Foundation LogReport@LogReport.org This file is part of Lire. Lire is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program (see COPYING); if not, check with http://www.gnu.org/copyleft/gpl.html. =cut