File Coverage

blib/lib/Biblio/EndnoteStyle.pm
Criterion Covered Total %
statement 79 99 79.8
branch 23 38 60.5
condition 6 12 50.0
subroutine 12 16 75.0
pod 3 3 100.0
total 123 168 73.2


line stmt bran cond sub pod time code
1             # $Id: EndnoteStyle.pm,v 1.10 2007/10/04 00:07:03 mike Exp $
2              
3             package Biblio::EndnoteStyle;
4              
5 1     1   40288 use 5.006;
  1         5  
  1         46  
6 1     1   6 use strict;
  1         1  
  1         46  
7 1     1   6 use warnings;
  1         8  
  1         1024  
8              
9             our $VERSION = 0.05;
10              
11             =head1 NAME
12              
13             Biblio::EndnoteStyle - reference formatting using Endnote-like templates
14              
15             =head1 SYNOPSIS
16              
17             use Biblio::EndnoteStyle;
18             $style = new Biblio::EndnoteStyle();
19             ($text, $errmsg) = $style->format($template, \%fields);
20              
21             =head1 DESCRIPTION
22              
23             This small module provides a way of formatting bibliographic
24             references using style templates similar to those used by the popular
25             reference management software Endnote (http://www.endnote.com/). The
26             API is embarrassingly simple: a formatter object is made using the
27             class's constructor, the C method; C may then be
28             repeatedly called on this object, using the same or different
29             templates.
30              
31             (The sole purpose of the object is to cache compiled templates so that
32             multiple C invocations are more efficient than they would
33             otherwise be. Apart from that, the API might just as well have been a
34             single function.)
35              
36             =head1 METHODS
37              
38             =head2 new()
39              
40             $style = new Biblio::EndnoteStyle();
41              
42             Creates a new formatter object. Takes no arguments.
43              
44             =cut
45              
46             # The object is vacuous except that it knows its class, so that
47             # subclasses can be made that override some of the methods.
48             #
49             sub new {
50 1     1 1 51 my $class = shift();
51              
52 1         10 return bless {
53             debug => 0,
54             compiled => {}, # cache of compiled templates
55             }, $class;
56             }
57              
58              
59             =head2 debug()
60              
61             $olddebug = $style->debug(1);
62              
63             Turns debugging on or off and returns the old debugging status. If an
64             argument is provided, then debugging is turned either on or off
65             according to whether then argument is true or false. In any case, the
66             old value of the debugging status is returned, so that a call with no
67             argument is a side-effect-free inquiry.
68              
69             When debugging is turned on, compiled templates are dumped to standard
70             error. It is not pretty.
71              
72             =cut
73              
74             sub debug {
75 0     0 1 0 my $this = shift();
76 0         0 my($val) = @_;
77              
78 0         0 my $old = $this->{debug};
79 0 0       0 $this->{debug} = $val if defined $val;
80 0         0 return $old;
81             }
82              
83              
84             =head2 format()
85              
86             ($text, $errmsg) = $style->format($template, \%fields);
87              
88             Formats a reference, consisting of a hash of fields, according to an
89             Endnote-like template. The template is a string essentially the same
90             as those used in Endnote, as documented in the Endnote X User Guide at
91             http://www.endnote.com/support/helpdocs/EndNoteXWinManual.pdf
92             pages 390ff. In particular, pages 415-210 have details of the recipe
93             format. Because the templates used in this module are plain text, a
94             few special characters are used:
95              
96             =over 4
97              
98             =item ¬
99              
100             Link adjacent words. This is the "non-breaking space"
101             described on page 418 of the EndNote X
102              
103             =item |
104              
105             Forced Seperation of elements that would otherwise be dependent.
106              
107             =item ^
108              
109             Separator for singular/plural aternatives.
110              
111             =cut `
112              
113             =item `
114              
115             Used to prevent literal text from being interpreted as a fieldname.
116              
117             =back
118              
119             The hash of fields is passed by reference: keys are fieldnames, and
120             the corresponding values are the data. PLEASE NOTE AN IMPORTANT
121             DIFFERENCE. Keys that do not appear in the hash at all are not
122             considered to be fields, so that if they appear in the template, they
123             will be interpreted as literal text; keys that appear in the hash but
124             whose values are undefined or empty are considered to be fields with
125             no value, and will be formatted as empty with dependent text omitted.
126             So for example:
127              
128             $style->format(";Author: ", { Author => "Taylor" }) eq ":Taylor: "
129             $style->format(";Author: ", { Author => "" }) eq ";"
130             $style->format(";Author: ", { xAuthor => "" }) eq ";Author: "
131              
132             C returns two values: the formatted reference and an
133             error-message. The error message is defined if and only if the
134             formatted reference is not.
135              
136             =cut
137              
138             sub format {
139 9     9 1 4513 my $this = shift();
140 9         15 my($text, $data) = @_;
141              
142             #use Data::Dumper; print Dumper($data);
143 9         22 my $template = $this->{compiled}->{$text};
144 9 100       25 if (!defined $template) {
145 8         8 my $errmsg;
146 8         28 ($template, $errmsg) =
147             Biblio::EndnoteStyle::Template->new($text, $this->{debug});
148 8 50       21 return (undef, $errmsg) if !defined $template;
149             #print "template '$text'\n", $template->render();
150 8         24 $this->{compiled}->{$text} = $template;
151             }
152              
153 9         20 return $template->format($data);
154             }
155              
156              
157             package Biblio::EndnoteStyle::Template;
158              
159             sub new {
160 8     8   12 my $class = shift();
161 8         11 my($text, $debug) = @_;
162              
163 8         9 my @sequences;
164 8         20 while ($text ne "") {
165 12 50       69 if ($text =~ s/^(\s*[^\s|]*\s?)//) {
166 12         25 my $sequence = $1;
167 12         40 my $obj = Biblio::EndnoteStyle::Sequence->new($sequence);
168 12         38 push @sequences, $obj;
169 12         42 $text =~ s/^\|//;
170             } else {
171 0         0 die "unparseable template fragment '$text'";
172             }
173             }
174              
175 8         38 my $this = bless {
176             text => $text,
177             sequences => \@sequences,
178             }, $class;
179 8 50       20 print STDERR $this->render() if $debug;
180              
181 8         29 return $this;
182             }
183              
184             sub render {
185 0     0   0 my $this = shift();
186              
187 0         0 return join("", map { $_->render() . "\n" } @{ $this->{sequences} });
  0         0  
  0         0  
188             }
189              
190             sub format {
191 9     9   10 my $this = shift();
192 9         16 my($data) = @_;
193              
194 9         12 my $result = "";
195 9         10 foreach my $sequence (@{ $this->{sequences} }) {
  9         24  
196 14         29 my($substr, $errmsg) = $sequence->format($data);
197 14 50       31 return (undef, $errmsg) if !defined $substr;
198 14         31 $result .= $substr;
199             }
200              
201 9         63 return $result;
202             }
203              
204              
205             # ----------------------------------------------------------------------------
206              
207             package Biblio::EndnoteStyle::Sequence;
208              
209 15     15   63 sub WORD { 290168 }
210 47     47   123 sub LITERAL { 120368 }
211             sub typename {
212 0     0   0 my($type) = @_;
213 0 0       0 return "WORD" if $type == WORD;
214 0 0       0 return "LITERAL" if $type == LITERAL;
215 0         0 return "???";
216             }
217              
218             sub new {
219 12     12   17 my $class = shift();
220 12         15 my($text) = @_;
221              
222 1     1   7 use Carp;
  1         2  
  1         780  
223 12 50       28 confess("new($class) with text undefined") if !defined $text;
224 12         16 my $tail = $text;
225 12         22 $tail =~ s/¬/ /g;
226 12         13 my @tokens;
227 12         61 while ($tail =~ s/(.*?)([``a-z_0-9]+)//i) {
228 8         24 my($head, $word) = ($1, $2);
229 8 50       27 push @tokens, [ LITERAL, $head ] if $head ne "";
230 8 100       24 if ($word =~ s/^`(.*)`$/$1/) {
231 1         3 push @tokens, [ LITERAL, $word ];
232             } else {
233 7         15 push @tokens, [ WORD, $word ];
234             }
235             }
236 12 100       33 push @tokens, [ LITERAL, $tail ] if $tail ne "";
237              
238 12         62 return bless {
239             text => $text,
240             tokens => \@tokens,
241             }, $class;
242             }
243              
244             sub render {
245 0     0   0 my $this = shift();
246              
247 0         0 return (sprintf("%24s: ", ("'" . $this->{text} . "'")) .
248             join(", ", map {
249 0         0 my($type, $val) = @$_;
250 0         0 typename($type) . " '$val'";
251 0         0 } @{ $this->{tokens} }));
252             }
253              
254             sub format {
255 14     14   22 my $this = shift();
256 14         15 my($data) = @_;
257              
258 14         14 my $gotField = 0;
259 14         17 my $result = "";
260 14         11 foreach my $token (@{ $this->{tokens} }) {
  14         36  
261 27         40 my($type, $val) = @$token;
262 27 100       44 if ($type == LITERAL) {
    50          
263 19         46 $result .= $val;
264             } elsif ($type != WORD) {
265 0         0 die "unexpected token type '$type'";
266             } else {
267 8         15 my $dval = $data->{$val};
268 8 100       17 $dval = $data->{lc($val)} if !defined $dval;
269 8 50 33     56 $dval = "" if !defined $dval && (exists $data->{$val} ||
      66        
270             exists $data->{lc($val)});
271 8 100 66     61 if (!defined $dval) {
    100          
272             # The word is not a fieldname at all: treat as a literal
273             #print "!defined \$dval\n";
274 1         3 $result .= $val;
275             } elsif (!$gotField && $dval eq "") {
276             #print "\$dval is empty\n";
277             # Field is empty, so whole dependent sequence is omitted
278 5         16 return "";
279             } else {
280             #print "$dval eq '$dval'\n";
281 2         4 $gotField = 1;
282             # Loathesome but useful special case
283 2 50 33     8 $dval = "http://$dval" if $val eq "URL" && $dval !~ /^[a-z]+:/;
284 2         4 $result .= $dval;
285             }
286             }
287             }
288              
289 9         24 return $result;
290             }
291              
292              
293             =head1 AUTHOR
294              
295             Mike Taylor, Emike@miketaylor.org.ukE
296              
297             =head1 COPYRIGHT AND LICENCE
298              
299             Copyright (C) 2007 by Mike Taylor.
300              
301             This library is free software; you can redistribute it and/or modify
302             it under the same terms as Perl itself, either Perl version 5.8.4 or,
303             at your option, any later version of Perl 5 you may have available.
304              
305             =cut
306              
307              
308             1;