Proofread documentation, fix bug in _error().
[perl-pqf.git] / lib / Net / Z3950 / PQF.pm
1 # $Id: PQF.pm,v 1.4 2004-12-17 17:12:05 mike Exp $
2
3 package Net::Z3950::PQF;
4
5 use 5.006;
6 use strict;
7 use warnings;
8
9 use Net::Z3950::PQF::Node;
10
11 our $VERSION = '0.02';
12
13
14 =head1 NAME
15
16 Net::Z3950::PQF - Perl extension for parsing PQF (Prefix Query Format)
17
18 =head1 SYNOPSIS
19
20  use Net::Z3950::PQF;
21  $parser = new Net::Z3950::PQF();
22  $node = $parser->parse('@and @attr 1=1003 kernighan @attr 1=4 unix');
23  print $node->render(0);
24
25 =head1 DESCRIPTION
26
27 This library provides a parser for PQF (Prefix Query Format), an ugly
28 but precise string format for expressing Z39.50 Type-1 queries.  This
29 format is widely used behind the scenes of Z39.50 applications, and is
30 also used extensively with test-harness programs such as the YAZ
31 command-line client, C<yaz-client>.  A few particularly misguided
32 souls have been known to type it by hand.
33
34 Unlike PQF itself, this module
35 is simple to use.  Create a parser object, then pass PQF strings
36 into its C<parse()> method to yield parse-trees.  The trees are made
37 up of nodes whose types are subclasses of
38 C<Net::Z3950::PQF::Node>.
39 and have names of the form
40 C<Net::Z3950::PQF::somethingNode>.  You may find it helpful to use
41 C<Data::Dumper> to visualise the structure of the returned
42 parse-trees.
43
44 What is a PQF parse-tree good for?  Not much.  You can render a
45 human-readable version by invoking the top node's C<render()> method,
46 which is probably useful only for debugging.  If you want to do
47 anything useful, such as implementing an actual query server that
48 understands PQF, you'll have to walk the tree.
49
50 =head1 METHODS
51
52 =head2 new()
53
54  $parser = new Net::Z3950::PQF();
55
56 Creates a new parser object.
57
58 =cut
59
60 sub new {
61     my $class = shift();
62
63     return bless {
64         text => undef,
65         errmsg => undef,
66     }, $class;
67 }
68
69
70 =head2 parse()
71
72  $query = '@and @attr 1=1003 kernighan @attr 1=4 unix';
73  $node = $parser->parse($query);
74  if (!defined $node) {
75      die "parse($query) failed: " . $parser->errmsg();
76  }
77
78 Parses the PQF string provided as its argument.  If an error occurs,
79 then an undefined value is returned, and the error message can be
80 obtained by calling the C<errmsg()> method.  Otherwise, the top node
81 of the parse tree is returned.
82
83  $node2 = $parser->parse($query, "zthes");
84  $node3 = $parser->parse($query, "1.2.840.10003.3.13");
85
86 A second argument may be provided after the query itself.  If it is
87 provided, then it is taken to be either the name or the OID of a
88 default attribute set, which attributes specified in the query belong
89 to if no alternative attribute set is explicitly specified within the
90 query.  When this second argument is absent, the default attribute set
91 is BIB-1.
92
93 =cut
94
95 sub parse {
96     my $this = shift();
97     my($text, $attrset) = @_;
98     $attrset = "bib-1" if !defined $attrset;
99
100     $this->{text} = $text;
101     return $this->_parse($attrset, {});
102 }
103
104
105 # PRIVATE to parse();
106 #
107 # Underlying parse function.  $attrset is the default attribute-set to
108 # use for attributes that are not specified with an explicit set, and
109 # $attrhash is hash of attributes (at most one per type per
110 # attribute-set) to be applied to all nodes below this point.  The
111 # keys of this hash are of the form "<attrset>:<type>" and the values
112 # are the corresponding attribute values.
113 #
114 sub _parse {
115     my $this = shift();
116     my($attrset, $attrhash) = @_;
117
118     ### This rather nasty hack for quoted terms doesn't recognised
119     #   backslash-quoted embedded double quotes.
120     $this->{text} =~ s/^\s+//;
121     if ($this->{text} =~ s/^"(.*?)"//) {
122         return $this->_term($1, $attrhash);
123     }
124
125     my $word = $this->_word();
126     if ($word eq '@attrset') {
127         $attrset = $this->_word();
128         return $this->_parse($attrset, $attrhash);
129
130     } elsif ($word eq '@attr') {
131         $word = $this->_word();
132         if ($word !~ /=/) {
133             $attrset = $word;
134             $word = $this->_word();
135         }
136         my($type, $val) = ($word =~ /(.*)=(.*)/);
137         my %h = %$attrhash;
138         $h{"$attrset:$type"} = $val;
139         return $this->_parse($attrset, \%h);
140
141     } elsif ($word eq '@and' || $word eq '@or' || $word eq '@not') {
142         my $sub1 = $this->_parse($attrset, $attrhash);
143         my $sub2 = $this->_parse($attrset, $attrhash);
144         if ($word eq '@and') {
145             return new Net::Z3950::PQF::AndNode($sub1, $sub2);
146         } elsif ($word eq '@or') {
147             return new Net::Z3950::PQF::OrNode($sub1, $sub2);
148         } elsif ($word eq '@not') {
149             return new Net::Z3950::PQF::NotNode($sub1, $sub2);
150         } else {
151             die "Houston, we have a problem";
152         }
153
154     } elsif ($word eq '@prox') {
155         return $this->_error("proximity not yet implemented");
156
157     }
158
159     # It must be a bareword
160     return $this->_term($word, $attrhash);
161 }
162
163
164 # PRIVATE to _parse();
165 sub _word {
166     my $this = shift();
167
168     $this->{text} =~ s/^\s+//;
169     $this->{text} =~ s/^(\S+)//;
170     return $1;
171 }
172
173
174 # PRIVATE to _parse();
175 sub _error {
176     my $this = shift();
177     my (@msg) = @_;
178
179     $this->{errmsg} = join("", @msg);
180     return undef;
181 }
182
183
184 # PRIVATE to _parse();
185 sub _term {
186     my $this = shift();
187     my($word, $attrhash) = @_;
188
189     my @attrs;
190     foreach my $key (sort keys %$attrhash) {
191         my($attrset, $type) = split /:/, $key;
192         push @attrs, [ $attrset, $type, $attrhash->{$key} ];
193     }
194
195     return new Net::Z3950::PQF::TermNode($word, @attrs);
196 }
197
198
199 =head2 errmsg()
200
201  print $parser->errmsg();
202
203 Returns the last error-message generated by a failed attempt to parse
204 a query.
205
206 =cut
207
208 sub errmsg {
209     my $this = shift();
210     return $this->{errmsg};
211 }
212
213
214 =head1 SEE ALSO
215
216 The C<Net::Z3950::PQF::Node> module.
217
218 The definition of the Type-1 query in the Z39.50 standard, the
219 relevant section of which is on-line at
220 http://www.loc.gov/z3950/agency/markup/09.html#3.7
221
222 The documentation of Prefix Query Format in the YAZ Manual, the
223 relevant section of which is on-line at
224 http://indexdata.com/yaz/doc/tools.tkl#PQF
225
226 =head1 AUTHOR
227
228 Mike Taylor, E<lt>mike@indexdata.comE<gt>
229
230 =head1 COPYRIGHT AND LICENSE
231
232 Copyright 2004 by Index Data ApS.
233
234 This library is free software; you can redistribute it and/or modify
235 it under the same terms as Perl itself.
236
237 =cut
238
239
240 1;