App::Changelog2x - A wrapper-class for the functionality of changelog2x


App-Changelog2x documentation Contained in the App-Changelog2x distribution.

Index


Code Index:

NAME

Top

App::Changelog2x - A wrapper-class for the functionality of changelog2x

SYNOPSIS

Top

    use App::Changelog2x;

    my $app = App::Changelog2x->new(xslt_path => [ ... ]);

    $app->transform_changelog(...);

DESCRIPTION

Top

This class provides the core functionality for the changelog2x application. It manages a list of search-paths for locating XSLT stylesheets and performs the transformation of ChangeLogML content using the XML::LibXML and XML::LibXSLT modules.

The transformation of content via XSLT is augmented by the registering of some functions into the XML namespace associated with ChangeLogML, before the XML::LibXSLT instance performs the transformation.

METHODS

Top

The following methods are available:

new [ARGS]

This is the constructor for the class. An optional list of key/value pairs may passed as arguments. The recognized arguments are:

application_tokens
date_format
xslt_path

These parameters are stored on the new object by called the corresponding accessor method (defined below) with the value of the parameter. This allows sub-classes of this class to implement different methods if they desire. The default behavior is to just store the values on the hash reference with the parameter names as keys.

Any other key/value pairs are stored on the hash reference unchanged.

version

Returns the current version of this module (used in the credits method, below).

default_date_format

Returns the default date format, a string that is passed to the strftime method of DateTime. The default format is a slightly more-verbose version of the UNIX "date" format, with full day- and month-names and a 12-hour clock rather than 24-hour. A typical date formatted this way would look like this:

    Friday September 19, 2008, 02:23:12 AM -0700

default_xslt_path

Returns the default path to use when searching for XSLT stylesheets that are not already absolute-path filenames. The default path for this module is a directory called changelog2x that resides in the same directory as this module.

date_format [FORMAT]

Get or set the date-format to use when format_date is called. If the user does not explicitly set a format, the value returned by default_date_format is used.

See "strftime Patterns" in DateTime for a description of the formatting codes to use in a format string.

One special value is recognized: unix. If date_format is called with this value as a format string, a pre-defined format is used that emulates the UNIX date command as closely as possible (but see CAVEATS for notes on DateTime limitations with regards to timezone names and the special patterns recognized in date format strings to try and work around this). A string formatted this way looks like this:

    Mon Aug 10 09:21:46 -0700 2009

xslt_path [DIRS]

Get or set the directories to use when searching for XSLT stylesheets that are not specified by absolute pathname.

If the user passes one or more directories, they are added at the head of the list of paths stored by the object and used internally to resolve templates that are not absolute paths.

If the user passes a list-reference, its contents become the new search path (completely replacing the existing set of directories).

If no values are passed, the return value is either a list-reference to the array of search paths (in scalar context) or the full list itself (in array context).

application_tokens [STRING]

Get or set the string identifying the application that is using this class to transform ChangeLogML content. If the user sets a value with this accessor (or by passing this parameter to the constructor), it is included in the string produced by the credits method (detailed below). If the user does not set this string, nothing is added to the credits.

format_date ISO_8601_DATE

Takes a string containing a date in ISO 8601 format, and re-formats it according to the format pattern specified by either date_format or default_date_format. Returns the (re-)formatted date.

This method is not generally intended for end-user utilization. It is bound to the ChangeLogML namespace URI with the name format-date for use by the XSLT processor.

credits

Produces a string listing the names and versions of all components used in the rendering of the ChangeLogML. This consists of:

    app/ver, mod/ver, LibXML/ver, LibXSLT/ver,
    libxml/ver, libxslt/ver ({ with | without } exslt)

(line broken for clarity only, the string has no embedded newlines)

where:

app/ver

The value of application_tokens, if set by the user. If this was not set there is no content added, not even the comma.

mod/ver

The name of the class this object is a referent of (ref $self), and the value of the version method.

LibXML/ver

The string XML::LibXML and the version of XML::LibXML used at run-time.

LibXSLT/ver

The string XML::LibXSLT and the version of XML::LibXSLT used at run-time.

libxml/ver

The string libxml and the version of the libxml2 C library linked in to XML::LibXML.

libxslt/ver

The string libxslt and the version of the libxslt C library linked in to XML::LibXSLT.

Additionally, whether libxslt was built with support for EXSLT is denoted at the end of this string by one of (with exslt) or (without exslt).

This method is not generally intended for end-user utilization. It is bound to the ChangeLogML namespace URI with the name credits for use by the XSLT processor.

transform_changelog INPUT, OUTPUT, STYLE [, PARAMS]

This method performs the actual transformation of ChangeLogML content. There are three required parameters and one optional parameter:

INPUT

This parameter must be either an open filehandle or a string containing the ChangeLogML XML content to be transformed. If the value is not a reference, it is assumed to be XML content.

OUTPUT

This parameter must be an open filehandle, to which the transformed XML content is written. This may be any object that acts like a filehandle; an IO::File instance, the result of an open call, etc.

STYLE

This parameter specifies the XSLT stylesheet to use. This may be a filename path or a string. A "string" is defined as a value consisting of only alphanumeric characters (those matching the Perl \w regular expression character class).

If the value of this parameter matches the pattern ^\w+$, then the string is used to construct a path to a XSLT file. The file is assumed to be named "changelog2string.xslt", and is looked for in the directory declared as the root for templates (see the xslt_path and default_xslt_path methods).

If the parameter does not match the pattern, it is assumed to be a file name. If it is not an absolute path, it is searched for using the set of XSLT directories. As a special case, if the path starts with a . character, it is not converted to an absolute path.

Once the full path and name of the file has been determined, if it cannot be opened or read an error is reported.

PARAMS

This parameter is optional. If it is passed, it must be a hash-reference. The keys of the hash table represent parameters to the XSLT stylesheet, and the values of the hash are the corresponding values for the stylesheet parameters.

See changelog2x for a detailed list of the stylesheet parameters recognized by the XSLT stylesheets bundled with this distribution.

If an error occurs during any of the processing stages, die is called with the error message from XML::LibXML or XML::LibXSLT, whichever was the source of the problem.

resolve_template TEMPLATE

Takes a template/stylesheet name and resolves it to a full (absolute) file name. If the value passed in does not end in either .xsl or .xslt (case-insensitive), then the value is augmented to changelog2TEMPLATE.xslt before searching for it. This is the naming-pattern used by the default templates packaged with this distribution (html, text, etc.). All directories currently in the XSLT search path (as set by xslt_path) are searched, in order, for the file. The first occurance of the file is used.

If no match is found for TEMPLATE, a null string is returned. If TEMPLATE is already an absolute path, or if the first character of the string is ., then it is considered to already be an absolute path and is returned unchanged.

CAVEATS

Top

The DateTime package does not attempt to map timezone values to the old 3-letter codes that were once the definitive representation of timezones. Because timezones are now much more granular in definition, a timezone offset cannot be canonically mapped to a specific name. The only timezone that can be canonically mapped is UTC. Thus, for now, timezones in dates are given as their offsets from UTC, unless the date is being rendered in UTC directly.

SEE ALSO

Top

changelog2x, https://sourceforge.net/projects/changelogml

AUTHOR

Top

Randy J. Ray <rjray@blackperl.com>

BUGS

Top

Please report any bugs or feature requests to bug-app-changelog2x at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=App-Changelog2x. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

Top

* RT: CPAN's request tracker

http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-Changelog2x

* AnnoCPAN: Annotated CPAN documentation

http://annocpan.org/dist/App-Changelog2x

* CPAN Ratings

http://cpanratings.perl.org/d/App-Changelog2x

* Search CPAN

http://search.cpan.org/dist/App-Changelog2x

COPYRIGHT & LICENSE

Top


App-Changelog2x documentation Contained in the App-Changelog2x distribution.

###############################################################################
#
# This file copyright (c) 2008-2009 by Randy J. Ray, all rights reserved
#
# Copying and distribution are permitted under the terms of the Artistic
# License 2.0 (http://www.opensource.org/licenses/artistic-license-2.0.php) or
# the GNU LGPL (http://www.opensource.org/licenses/lgpl-license.php).
#
###############################################################################
#
#   Description:    A wrapper in the App::* space for the core functionality
#                   provided by the changelog2x script.
#
#   Functions:      new
#                   version
#                   default_xslt_path
#                   default_date_format
#                   date_format
#                   xslt_path
#                   application_tokens
#                   format_date
#                   credits
#                   transform_changelog
#
#   Libraries:      XML::LibXML
#                   XML::LibXSLT
#                   DateTime
#                   DateTime::Format::ISO8601
#                   File::Spec
#
#   Global Consts:  $VERSION
#                   URI
#
###############################################################################

package App::Changelog2x;

use 5.008;
use strict;
use warnings;
use vars qw($VERSION $FORMAT $DEFAULT_XSLT_PATH);
use subs qw(new version default_xslt_path default_date_format date_format
            xslt_path application_tokens format_date credits
            transform_changelog);
use constant URI => 'http://www.blackperl.com/2009/01/ChangeLogML';

use File::Spec;

use XML::LibXML;
use XML::LibXSLT;
use DateTime;
use DateTime::Format::ISO8601;

BEGIN
{
    $VERSION = '0.11';

    $DEFAULT_XSLT_PATH = (File::Spec->splitpath(__FILE__))[1];
    $DEFAULT_XSLT_PATH = File::Spec->catdir($DEFAULT_XSLT_PATH, 'changelog2x');
}

###############################################################################
#
#   Sub Name:       new
#
#   Description:    Dead-simple constructor. We're just a plain blessed
#                   hashref, here.
#
#   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION
#                   $class    in      scalar    Class to bless into
#                   %args     in      hash      Any data to start off with
#
#   Returns:        object referent
#
###############################################################################
sub new
{
    my ($class, %args) = @_;

    my $self = bless {}, $class;

    # If the user didn't pass the xslt_path argument, set up the default
    $args{xslt_path} ||= [ $self->default_xslt_path ];

    foreach (qw(date_format xslt_path))
    {
        # These are the known parameters; if present, call the method to set
        $self->$_(delete $args{$_}) if $args{$_};
    }

    # Copy over any remaining parameters we don't know verbatim
    for (keys %args)
    {
        $self->{$_} = $args{$_};
    }

    $self;
}

# Encapsulated way of retrieving $VERSION, in case someone sub-classes us
sub version             { $VERSION }

# Likewise access to $DEFAULT_XSLT_PATH
sub default_xslt_path   { $DEFAULT_XSLT_PATH }

# And the default date-format
sub default_date_format { '%A %B %e, %Y, %r TZ_SHORT' }

###############################################################################
#
#   Sub Name:       date_format
#
#   Description:    Get or set a default format string for format_date() to
#                   use. If $format is passed, set that as the new format to
#                   use. If no format is set by the user, falls through to
#                   default_date_format().
#
#   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION
#                   $self     in      ref       Object of this class
#                   $format   in      scalar    New format string
#
#   Returns:        Date format
#
###############################################################################
sub date_format
{
    my ($self, $format) = @_;

    if ($format)
    {
        $self->{format} =
            ($format eq 'unix') ? '%a %b %d %T TZ_SHORT %Y' : $format;
    }

    $self->{format} || $self->default_date_format;
}

###############################################################################
#
#   Sub Name:       xslt_path
#
#   Description:    Return the path to where XSLT files should be searched for.
#                   If this is not set by the user, then return the value for
#                   default_xslt_path(). If a value is passed for $path, make
#                   that the new XSLT directory.
#
#   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION
#                   $self     in      ref       Object of this class
#                   $paths    in      list      New directories to use.
#
#   Returns:        path
#
###############################################################################
sub xslt_path
{
    my ($self, @paths) = @_;

    if (@paths)
    {
        if (ref($paths[0]) eq 'ARRAY')
        {
            $self->{xslt_path} = [ @{$paths[0]} ];
        }
        else
        {
            unshift(@{$self->{xslt_path}}, @paths);
        }
    }

    wantarray ? @{$self->{xslt_path}} : $self->{xslt_path};
}

###############################################################################
#
#   Sub Name:       application_tokens
#
#   Description:    Get/set the string that should be present in the "credits"
#                   string, identifying the application that is using this
#                   class to transform ChangeLogML.
#
#   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION
#                   $self     in      ref       Object of this class
#                   $tokens   in      scalar    If present, string/tokens to
#                                                 store for later use
#
#   Returns:        application tokens
#
###############################################################################
sub application_tokens
{
    my ($self, $tokens) = @_;

    $self->{application_tokens} = $tokens if $tokens;

    $self->{application_tokens};
}

###############################################################################
#
#   Sub Name:       format_date
#
#   Description:    Take a date-string in (ISO 8601 format) and return a
#                   more readable format.
#
#   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION
#                   $self     in      scalar    Class name or object ref
#                   $date     in      scalar    Date-string in ISO 8601
#                   $to_utc   in      scalar    Boolean flag, whether to
#                                                 convert times to GMT/UTC
#
#   Returns:        Formatted date/time
#
###############################################################################
sub format_date
{
    my ($self, $date, $to_utc) = @_;

    my $dt = DateTime::Format::ISO8601->parse_datetime($date);
    $dt->set_time_zone('UTC') if $to_utc;

    my $string = $dt->strftime($self->date_format);
    if ($string =~ /TZ_/)
    {
        my %tz_edit = ( TZ_LONG  => $dt->time_zone->name,
                        TZ_SHORT => $dt->time_zone->short_name_for_datetime );
        $string =~ s/(TZ_LONG|TZ_SHORT)/$tz_edit{$1}/ge;
    }

    $string;
}

###############################################################################
#
#   Sub Name:       credits
#
#   Description:    Produce a "credits" message for inclusion in transformed
#                   output. Combines app name and version, lib name and
#                   version, etc.
#
#   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION
#                   $self     in      scalar    Class name or object ref
#
#   Globals:        $cmd
#                   $VERSION
#
#   Returns:        credits string
#
###############################################################################
sub credits
{
    my $self = shift;

    my $credits =
        sprintf("%s/%s, XML::LibXML/%s, XML::LibXSLT/%s, libxml/%s, " .
                "libxslt/%s (with%s exslt)",
                ref($self), $self->version,
                $XML::LibXML::VERSION, $XML::LibXSLT::VERSION,
                XML::LibXML::LIBXML_DOTTED_VERSION(),
                XML::LibXSLT::LIBXSLT_DOTTED_VERSION(),
                (XML::LibXSLT::HAVE_EXSLT() ? '' : 'out'));
    if (my $apptokens = $self->application_tokens)
    {
        $credits = "$apptokens, $credits";
    }

    $credits;
}

###############################################################################
#
#   Sub Name:       transform_changelog
#
#   Description:    Take a filehandle or string for input, a filehandle for
#                   output, filename/string of a XSL transform, and optional
#                   parameters. Process the input according to the XSLT and
#                   stream the results to the output handle.
#
#   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION
#                   $self     in      scalar    Class name or object ref
#                   $xmlin    in      scalar    Filehandle to read/parse or
#                                                 string
#                   $xmlout   in      ref       Filehandle to output the
#                                                 transformed XML to
#                   $style    in      scalar    Stylesheet, either a string
#                                                 or the name of a file
#                   $params   in      hashref   If present, parameters that
#                                                 should be converted for use
#                                                 in the XSLT and passed in.
#
#   Globals:        URI
#
#   Returns:        Success:    null
#                   Failure:    dies
#
###############################################################################
sub transform_changelog
{
    my ($self, $xmlin, $xmlout, $style, $params) = @_;
    $params ||= {}; # In case they didn't pass any

    our $parser = XML::LibXML->new();
    our $xslt   = XML::LibXSLT->new();

    $parser->expand_xinclude(1);
    $xslt->register_function(URI, 'format-date',
                             sub { $self->format_date(@_) });
    $xslt->register_function(URI, 'credits',
                             sub { $self->credits(@_) });

    our (%params, $xsltc, $source, $stylesheet, $result);

    # If the template isn't already an absolute path, use the root-dir and add
    # the "changelog2" prefix and ".xslt" suffix
    unless ($style =~ /^<\?xml/)
    {
        $xsltc = $self->resolve_template($style)
            or die "Could not resolve style '$style' to a file";
        $style = $xsltc;
    }

    # First copy over and properly setup/escape the parameters, so that XSLT
    # understands them.
    %params = map { XML::LibXSLT::xpath_to_string($_ => $params->{$_}) }
        (keys %$params);

    # Do the steps of parsing XML documents, creating stylesheet engine and
    # applying the transform. Each throws a die on error, so each has to be
    # eval'd to allow for a cleaner error report:
    eval {
        $source = ref($xmlin) ?
            $parser->parse_fh($xmlin) : $parser->parse_string($xmlin);
    };
    die "Error parsing input-XML content: $@" if $@;
    eval {
        $xsltc = ($style =~ /^<\?xml/) ?
            $parser->parse_string($style) : $parser->parse_file($style);
    };
    die "Error parsing the XML of the XSLT stylesheet '$style': $@" if $@;
    eval { $stylesheet = $xslt->parse_stylesheet($xsltc); };
    die "Error parsing the XSLT syntax of the stylesheet: $@" if $@;
    eval { $result = $stylesheet->transform($source, %params); };
    die "Error applying transform to input content: $@" if $@;

    $stylesheet->output_fh($result, $xmlout);
    return;
}

###############################################################################
#
#   Sub Name:       resolve_template
#
#   Description:    Resolve a non-absolute template name to a complete file.
#                   This may include adding "changelog2" and ".xslt" to the
#                   string. If the name is already absolute or starts with a
#                   '.', it is returned unchanged.
#
#   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION
#                   $self     in      ref       Object of this class
#                   $template in      scalar    Name to resolve
#
#   Returns:        Success:    full path
#                   Failure:    empty string
#
###############################################################################
sub resolve_template
{
    my ($self, $template) = @_;

    return $template if ((substr($template, 0, 1) eq '.') ||
                         File::Spec->file_name_is_absolute($template));

    my @paths = $self->xslt_path;
    my $candidate;

    $template = "changelog2$template.xslt" unless ($template =~ /\.xslt?/i);

    for (@paths)
    {
        $candidate = File::Spec->catfile($_, $template);
        last if -f $candidate;
        undef $candidate;
    }

    $candidate;
}

1;

__END__