File Coverage

blib/lib/Data/Phrasebook/Loader/XML.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Data::Phrasebook::Loader::XML;
2              
3 7     7   180052 use strict;
  7         19  
  7         325  
4 7     7   41 use warnings FATAL => 'all';
  7         15  
  7         500  
5              
6             our $VERSION = '0.16';
7              
8             #--------------------------------------------------------------------------
9              
10 7     7   36 use base qw( Data::Phrasebook::Loader::Base Data::Phrasebook::Debug );
  7         25  
  7         7724  
11              
12 7     7   14679 use Carp qw( croak );
  7         19  
  7         331  
13 7     7   8118 use XML::Parser;
  0            
  0            
14             use IO::File;
15              
16             #--------------------------------------------------------------------------
17              
18             =head1 NAME
19              
20             Data::Phrasebook::Loader::XML - Absract your phrases with XML.
21              
22             =head1 SYNOPSIS
23              
24             use Data::Phrasebook;
25              
26             my $q = Data::Phrasebook->new(
27             class => 'Fnerk',
28             loader => 'XML',
29             file => 'phrases.xml',
30             dict => 'Dictionary', # optional
31             );
32              
33             OR
34              
35             my $q = Data::Phrasebook->new(
36             class => 'Fnerk',
37             loader => 'XML',
38             file => {
39             file => 'phrases.xml',
40             ignore_whitespace => 1,
41             }
42             );
43              
44             # simple keyword to phrase mapping
45             my $phrase = $q->fetch($keyword);
46              
47             # keyword to phrase mapping with parameters
48             $q->delimiters( qr{ \[% \s* (\w+) \s* %\] }x );
49             my $phrase = $q->fetch($keyword,{this => 'that'});
50              
51             =head1 DESCRIPTION
52              
53             This class loader implements phrasebook patterns using XML.
54              
55             Phrases can be contained within one or more dictionaries, with each phrase
56             accessible via a unique key. Phrases may contain placeholders, please see
57             L for an explanation of how to use these. Groups of phrases
58             are kept in a dictionary. The first dictionary is used as the default, unless
59             a specific dictionary is requested.
60              
61             In this implementation, the dictionaries and phrases are implemented with an
62             XML document. This document is the same as implement by L.
63              
64             The XML document type definition is as followed:
65              
66            
67            
68            
69            
70            
71            
72            
73             ]>
74              
75             An example XML file:
76              
77            
78            
79            
80            
81            
82            
83            
84             ]>
85              
86            
87            
88             Hello World!!!
89             The time now is $hour.
90             add $a and $b and you get $c
91             Barbie
92            
93              
94            
95             Bonjour le Monde!!!
96             Il est maintenant $hour.
97             $a + $b = $c
98             Barbie
99            
100              
101            
102             Hallo Werld!!!
103             Het is nu $hour.
104             $a + $b = $c
105             Barbie
106            
107            
108              
109             Note that, unlike L, this implementation does not search
110             the default dictionary if a phrase is not found in the specified dictionary.
111             This may change in the future.
112              
113             Each phrase should have a unique name within a dictionary, which is then used
114             as a reference key. Within the phrase text placeholders can be used, which are
115             then replaced with the appropriate values once the get() method is called.
116              
117             The parameter 'ignore_whitespace', will remove any extra whitespace from the
118             phrase. This includes leading and trailing whitespace. Whitespace around a
119             newline, including the newline, is replace with a single space.
120              
121             If you need to use the '<' symbol in your XML, you'll need to use '<'
122             instead.
123              
124             # $a < $b
125              
126             my $q = Data::Phrasebook->new(
127             class => 'Fnerk',
128             loader => 'XML',
129             file => 'phrases.xml',
130             );
131              
132             my $phrase = $q->fetch('TEST'); # returns '$a < $b'
133              
134             =head1 INHERITANCE
135              
136             L inherits from the base class
137             L.
138             See that module for other available methods and documentation.
139              
140             =head1 METHODS
141              
142             =head2 load
143              
144             Given a C, load it. C must contain valid XML.
145              
146             $loader->load( $file, $dict );
147              
148             This method is used internally by L's
149             C method, to initialise the data store.
150              
151             =cut
152              
153             my $phrases;
154              
155             sub load {
156             my ($class, $file, $dict) = @_;
157             my ($ignore_whitespace,$ignore_newlines) = (0,0);
158             my @dictionaries;
159              
160             if(ref $file eq 'HASH') {
161             $ignore_whitespace = $file->{ignore_whitespace};
162             $ignore_newlines = $file->{ignore_newlines};
163             $file = $file->{file};
164             }
165             croak "No file given as argument!" unless defined $file;
166             croak "Cannot access file!" unless -r $file;
167              
168             $dict = '' unless($dict); # use default
169              
170             my $read_on = 1;
171             my $default_read = 0;
172             my ($phrase_name,$phrase_value);
173              
174             # create the XML parser object
175             my $parser = XML::Parser->new(ErrorContext => 2);
176             $parser->setHandlers(
177             Start => sub {
178             my $expat = shift;
179             my $element = shift;
180             my %attributes = (@_);
181              
182             # deal with the dictionary element
183             if ($element =~ /dictionary/) {
184             my $name = $attributes{name};
185             croak('The dictionary element must have the name attribute')
186             unless (defined($name));
187             push @dictionaries, $name;
188              
189             # if the default was already read, and the dictionary name
190             # is not the requested one, we should not read on.
191             $read_on = ($default_read && $name ne $dict) ? 0 : 1;
192             }
193              
194             # deal with the phrase element
195             if ($element =~ /^phrase$/) {
196             $phrase_name = $attributes{name};
197             croak('The phrase element must have the name attribute')
198             unless (defined($phrase_name));
199             }
200              
201             $phrase_value = ''; # ensure a clean phrase
202             }, # of Start
203              
204             End => sub {
205             my $expat = shift;
206             my $element = shift;
207             if ($element =~ /^dictionary$/i) {
208             $default_read = 1;
209             }
210              
211             if ($element =~ /^phrase$/i) {
212             if ($read_on) {
213             if($ignore_whitespace) {
214             $phrase_value =~ s/^\s+//;
215             $phrase_value =~ s/\s+$//;
216             $phrase_value =~ s/\s*[\r\n]+\s*/ /gs;
217             }
218             if($ignore_newlines) {
219             $phrase_value =~ s/[\r\n]+/ /gs;
220             }
221             $phrases->{$phrase_name} = $phrase_value;
222             $phrase_value = '';
223             }
224             }
225             }, # of End
226              
227             Char => sub {
228             my $expat = shift;
229             my $string = shift;
230              
231             # if $read_on flag is true and the string is not empty we set the
232             # value of the phrase.
233             if ($read_on && length($string)) {
234             $phrase_value .= $string;
235             }
236             } # of Char
237             ); # of the parser setHandlers class
238              
239             my $fh = IO::File->new($file);
240             croak("Could not open $file for reading.") unless ($fh);
241              
242             eval { $parser->parse($fh) };
243             croak("Could not parse the file [$file]: ".$@) if ($@);
244              
245             $class->{dictionaries} = \@dictionaries;
246             $class->{phrases} = $phrases;
247             }
248              
249             =head2 get
250              
251             Returns the phrase stored in the phrasebook, for a given keyword.
252              
253             my $value = $loader->get( $key );
254              
255             =cut
256              
257             sub get {
258             my ($class, $key) = @_;
259             return unless($key);
260             return $class->{phrases}->{$key} || undef;
261             }
262              
263             =head2 dicts
264              
265             Returns the list of dictionaries available.
266              
267             my @dicts = $loader->dicts();
268              
269             =cut
270              
271             sub dicts {
272             my $class = shift;
273             return @{$class->{dictionaries}};
274             }
275              
276             =head2 keywords
277              
278             Returns the list of keywords available.
279              
280             my @keywords = $loader->keywords();
281              
282             =cut
283              
284             sub keywords {
285             my $class = shift;
286             return () unless($class->{phrases});
287             my @keywords = sort keys %{$class->{phrases}};
288             return @keywords;
289             }
290              
291             1;
292              
293             __END__