| App-Changelog2x documentation | Contained in the App-Changelog2x distribution. |
App::Changelog2x - A wrapper-class for the functionality of changelog2x
use App::Changelog2x;
my $app = App::Changelog2x->new(xslt_path => [ ... ]);
$app->transform_changelog(...);
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.
The following methods are available:
This is the constructor for the class. An optional list of key/value pairs may passed as arguments. The recognized arguments are:
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.
Returns the current version of this module (used in the credits method,
below).
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
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.
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
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).
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.
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.
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:
The value of application_tokens, if set by the user. If this was not set
there is no content added, not even the comma.
The name of the class this object is a referent of (ref $self), and the
value of the version method.
The string XML::LibXML and the version of XML::LibXML used at run-time.
The string XML::LibXSLT and the version of XML::LibXSLT used at run-time.
The string libxml and the version of the libxml2 C library linked in to
XML::LibXML.
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.
This method performs the actual transformation of ChangeLogML content. There are three required parameters and one optional parameter:
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.
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.
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.
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.
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.
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.
changelog2x, https://sourceforge.net/projects/changelogml
Randy J. Ray <rjray@blackperl.com>
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.
This file and the code within are copyright (c) 2008 by Randy J. Ray.
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 2.1 (http://www.opensource.org/licenses/lgpl-2.1.php).
| 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__