File Coverage

blib/lib/Net/FileMaker/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 Net::FileMaker::XML;
2             {
3             $Net::FileMaker::XML::VERSION = '0.064';
4             }
5              
6 1     1   32259 use strict;
  1         3  
  1         42  
7 1     1   5 use warnings;
  1         2  
  1         34  
8 1     1   6 use Carp;
  1         2  
  1         98  
9 1     1   638 use Net::FileMaker::Error;
  1         3  
  1         26  
10              
11 1     1   609 use XML::Twig;
  0            
  0            
12              
13             =head1 NAME
14              
15             Net::FileMaker::XML - Interact with FileMaker Server's XML Interface.
16              
17             =head1 SYNOPSIS
18              
19             This module provides the interface for communicating with FileMaker Server's XML service.
20              
21             You can simply invoke L directly and specify the 'type'
22             key in the constructor as "xml":
23              
24             use Net::FileMaker;
25            
26             my $fms = Net::FileMaker->new(host => $host, type => 'xml');
27              
28             It's also possible to call this module directly:
29              
30             use Net::FileMaker::XML;
31              
32             my $fms = Net::FileMaker::XML->new(host => $host);
33              
34             my $dbnames = $fms->dbnames;
35             my $fmdb = $fms->database(db => $db, user => $user, pass => $pass);
36              
37              
38             =head1 METHODS
39              
40             =head2 new(host => $host)
41              
42             Creates a new object. The specified must be a valid address or host name.
43              
44             =cut
45              
46             sub new
47             {
48             my($class, %args) = @_;
49              
50             # If the protocol isn't specified, let's assume it's just HTTP.
51             if($args{host} !~/^http/x)
52             {
53             $args{host} = 'http://'.$args{host};
54             }
55             require LWP::UserAgent;
56             my $self = {
57             host => $args{host},
58             ua => LWP::UserAgent->new,
59             xml => XML::Twig->new,
60             uri => URI->new($args{host}),
61             resultset => '/fmi/xml/fmresultset.xml', # Entirely for dbnames();
62             };
63              
64             if($args{error})
65             {
66             $self->{error} = Net::FileMaker::Error->new(lang => $args{error}, type => 'XML');
67             }
68              
69             bless $self , $class;
70             return $self;
71              
72             }
73              
74             =head2 database(db => $database, user => $user, pass => $pass)
75              
76             Initiates a new database object for querying data in the databse.
77              
78             =cut
79              
80             sub database
81             {
82             my($self, %args) = @_;
83              
84             require Net::FileMaker::XML::Database;
85             return Net::FileMaker::XML::Database->new(
86             host => $self->{host},
87             db => $args{db},
88             user => $args{user} || '',
89             pass => $args{pass} || ''
90             );
91             }
92              
93              
94             =head2 dbnames
95              
96             Returns an arrayref containing all XML/XSLT enabled databases for a given host.
97             This method requires no authentication.
98              
99             =cut
100              
101             sub dbnames
102             {
103             my $self = shift;
104             my $xml = $self->_request(
105             resultset => $self->{resultset},
106             query =>'-dbnames'
107             );
108              
109             return $self->_compose_arrayref('DATABASE_NAME', $xml);
110              
111             }
112              
113             =head1 COMPATIBILITY
114              
115             This distrobution is actively tested against FileMaker Advanced Server 10.0.1.59
116             and 11.0.1.95. Older versions are not tested at present, but feedback is
117             welcome. See the messages present in the test suite on how to setup tests
118             against your server.
119              
120             =head1 SEE ALSO
121              
122             L
123              
124             =cut
125              
126             # _request(query => $query, params => $params, resultset => $resultset, user => $user, pass => $pass)
127             #
128             # Performs a request to the FileMaker Server. The query and resultset keys are mandatory,
129             # however user and pass keys are not. The query should always be URI encoded.
130             sub _request
131             {
132             my ($self, %args) = @_;
133              
134             # Construct the URI
135             my $uri = $self->{uri}->clone;
136             $uri->path($args{resultset});
137            
138             my $url;
139             # This kind of defeats the purpose of using URI to begin with, but this
140             # fault has been reported on rt.cpan.org for over 2 years and many releases
141             # with no fix.
142             if($args{params})
143             {
144             $uri->query_form(%{$args{params}});
145             $url = $uri->as_string."&".$args{query};
146             }
147             else
148             {
149             $url = $uri->as_string."?".$args{query};
150             }
151              
152             my $req = HTTP::Request->new(GET => $url);
153              
154             if($args{user} && $args{pass})
155             {
156             $req->authorization_basic( $args{user}, $args{pass});
157             }
158              
159             my $res = $self->{ua}->request($req);
160              
161             my $xml = $self->{xml}->parse($res->content);
162             my $xml_data = $xml->simplify;
163              
164             # Inject localised error message
165             if($self->{error})
166             {
167             $xml_data->{error}->{message} = $self->{error}->get_string($xml_data->{error}->{code});
168             }
169              
170             $xml_data->{http_response} = $res;
171             return $xml_data;
172              
173             }
174              
175              
176             # _compose_arrayref($field_name, $xml)
177             #
178             # A common occurance is recomposing response data so unnecessary structure is removed.
179             sub _compose_arrayref
180             {
181             my ($self, $fieldname, $xml) = @_;
182            
183             if(ref($xml->{resultset}->{record}) eq 'HASH')
184             {
185             return $xml->{resultset}->{record}->{field}->{$fieldname}->{data};
186             }
187             elsif(ref($xml->{resultset}->{record}) eq 'ARRAY')
188             {
189             my @output;
190              
191             for my $record (@{$xml->{resultset}->{record}})
192             {
193             push @output, $record->{field}->{$fieldname}->{data};
194             }
195            
196             return \@output;
197             }
198              
199             }
200              
201              
202             # _assert_param()
203             #
204             # Optional parameters sometimes validation to ensure they are correct.
205             # Warnings are issued if a parameter name is somehow invalid.
206             # single param check
207              
208             sub _assert_param
209             {
210             my($self, $unclean_param, $acceptable_params) = @_;
211             my $param;
212             # if the param is of private type '-something' let's check, otherwise skip
213             # 'cause it could be the name of a field
214             # TODO: we might add a strict control to avoid passing others params than
215             # the ones with "-" like in findall etc
216            
217             if($unclean_param =~ /^-.+$/x)
218             {
219             if($unclean_param =~/$acceptable_params/x)
220             {
221             $param = $unclean_param;
222             }
223             else
224             {
225             # TODO: Localise this error message
226             carp "Invalid parameter specified - $unclean_param";
227             }
228             }else{
229             $param = $unclean_param;
230             }
231              
232             return $param;
233             }
234              
235              
236             # _assert_params
237             # Optional parameters sometimes validation to ensure they are correct.
238             # Warnings are issued if a parameter name is somehow invalid.
239              
240             sub _assert_params
241             {
242             my ($self , %args) = @_;
243            
244             my $params = $args{def_params};
245             my $acceptable_params = $args{acceptable_params};
246             my $type = $args{type};
247            
248             if($args{params} && ref($args{params}) eq 'HASH')
249             {
250             for my $param(keys %{$args{params}})
251             {
252             # Perform or skip parameter checking
253             if($args{nocheck} && $args{nocheck} == 1)
254             {
255             $params->{$param} = $args{params}->{$param};
256             }
257             else
258             {
259             $params->{$param} = $args{params}->{$param}
260             if $self->_assert_param($param, $acceptable_params->{$type});
261             }
262             }
263             }
264             return $params;
265             }
266              
267             1; # End of Net::FileMaker::XML;