Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 11624cb8

Von Sven Schöling vor etwa 1 Jahr hinzugefügt

  • ID 11624cb84760b810b3d883c9f01820a78fdd178b
  • Vorgänger 350c829a
  • Nachfolger 20dc32c0

SL::XMLInvoice: factory pattern etwas umgeschrieben

- braucht jetzt kein Module::Load oder runtime require mehr, weil die
Factory nicht mehr gleichzeitig die Basisklasse der Implementoren ist
- new ist jetzt nur noch in SL::XMLInvoice
- die Basisklasse für die Implementoren ist jetzt SL::XMLInvoice::Base
- _document_modules ist jetzt ein class member der factory statt eine
Methode, so muss das Array nicht ständig neu gebaut werden.
- Im Fehlerfall wird jetzt garnicht blessed, sondern einfach das Fehler
hash zurückgegeben.
- module resolution auf first umgeschrieben.

Unterschiede anzeigen:

SL/XMLInvoice.pm
3 3
use strict;
4 4
use warnings;
5 5

  
6
use Module::Load;
7

  
8
use SL::Locale::String qw(t8);
6
use List::Util qw(first);
9 7
use XML::LibXML;
10
use SL::Locale::String qw(t8);
11 8

  
12
require SL::XMLInvoice::UBL;
13
require SL::XMLInvoice::CrossIndustryInvoice;
9
use SL::Locale::String qw(t8);
10
use SL::XMLInvoice::UBL;
11
use SL::XMLInvoice::CrossIndustryInvoice;
12
use SL::XMLInvoice::CrossIndustryDocument;
14 13

  
15 14
use constant RES_OK => 0;
16 15
use constant RES_XML_PARSING_FAILED => 1;
17 16
use constant RES_UNKNOWN_ROOT_NODE_TYPE => 2;
18 17

  
18
our @document_modules = qw(
19
  SL::XMLInvoice::CrossIndustryDocument
20
  SL::XMLInvoice::CrossIndustryInvoice
21
  SL::XMLInvoice::UBL
22
);
23

  
19 24
=head1 NAME
20 25

  
21 26
SL::XMLInvoice - Top level factory class for XML Invoice parsers.
......
29 34
return an object exposing its data with the standardized structure outlined
30 35
below.
31 36

  
37
See L <SL::XMLInvoice::Base>
38
for details on the shared interface of the returned instances.
39

  
32 40
=head1 SYNOPSIS
33 41

  
34 42
  # $xml_data contains an XML document as flat scalar
......
43 51

  
44 52
=cut
45 53

  
46
=head1 ATTRIBUTES
47

  
48
=over 4
49

  
50
=item dom
51

  
52
A XML::LibXML document object model (DOM) object created from the XML data supplied.
53

  
54
=item message
55

  
56
Will contain a detailed error message if the C<result> attribute is anything
57
other than C<SL::XMLInvoice::RES_OK>.
58

  
59
=item result
60

  
61
A status field indicating whether the supplied XML data could be parsed. It
62
can take the following values:
63

  
64
=item SL::XMLInvoice::RES_OK
65

  
66
File has been parsed successfully.
67

  
68
=item SL::XMLInvoice::RES_XML_PARSING FAILED
69

  
70
Parsing the file failed.
71

  
72
=item SL::XMLInvoice::RES_UNKNOWN_ROOT_NODE_TYPE
73

  
74
The root node is of an unknown type. Currently, C<rsm:CrossIndustryInvoice> and
75
C<ubl:Invoice> are supported.
76

  
77
=back
78

  
79
=cut
80

  
81
=head1 METHODS
82

  
83
=head2 Data structure definition methods (only in C<SL::XMLInvoice>)
84

  
85
These methods are only implemented in C<SL::XMLInvoice> itself and define the
86
data structures to be exposed by any child classes.
87

  
88
=over 4
89

  
90
=item data_keys()
91

  
92
Returns all keys the hash returned by any child's C<metadata()> method must
93
contain. If you add keys to this list, you need to add them to all classes
94
inheriting from C<SL::XMLInvoice> as well. An application may use this method
95
to discover the metadata keys guaranteed to be present.
96

  
97
=cut
98

  
99
sub data_keys {
100
  my @keys = (
101
    'currency',      # The bill's currency, such as "EUR"
102
    'direct_debit',  # Boolean: whether the bill will get paid by direct debit (1) or not (0)
103
    'duedate',       # The bill's due date in YYYY-MM-DD format.
104
    'gross_total',   # The invoice's sum total with tax included
105
    'iban',          # The creditor's IBAN
106
    'invnumber',     # The invoice's number
107
    'net_total',     # The invoice's sum total without tax
108
    'taxnumber',     # The creditor's tax number (Steuernummer in Germany). May be present if
109
                     # there is no VAT ID (USTiD in Germany).
110
    'transdate',     # The date the invoice was issued in YYYY-MM-DD format.
111
    'type',          # Numeric invoice type code, e.g. 380
112
    'ustid',         # The creditor's UStID.
113
    'vendor_name',   # The vendor's company name
114
  );
115
  return \@keys;
116
}
117

  
118
=item item_keys()
119

  
120
Returns all keys the item hashes returned by any child's C<items()> method must
121
contain. If you add keys to this list, you need to add them to all classes
122
inheriting from C<SL::XMLInvoice> as well. An application may use this method
123
to discover the metadata keys guaranteed to be present.
124

  
125
=back
126

  
127
=cut
128

  
129
sub item_keys  {
130
  my @keys = (
131
    'currency',
132
    'description',
133
    'price',
134
    'quantity',
135
    'subtotal',
136
    'tax_rate',
137
    'tax_scheme',
138
    'vendor_partno',
139
  );
140
  return \@keys;
141
}
142

  
143
=head2 User/application facing methods
144

  
145
Any class inheriting from C<SL::XMLInvoice> must implement the following
146
methods. To ensure this happens, C<SL::XMLInvoice> contains stub functions that
147
raise an exception if a child class does not override them.
148

  
149
=over 4
150

  
151
=item new($xml_data)
152

  
153
Constructor for C<SL::XMLInvoice>. This method takes a scalar containing the
154
entire XML document to be parsed as a flat string as its sole argument. It will
155
instantiate the appropriate child class to parse the XML document in question,
156
call its C<parse_xml> method and return the C<SL::XMLInvoice> child object it
157
instantiated. From that point on, the structured data retrieved from the XML
158
document will be available through the object's C<metadata> and C<items()>
159
methods.
160

  
161
=item metadata()
162

  
163
This method returns a hash of document level metadata, such as the invoice
164
number, the total, or the the issuance date. Its keys are the keys returned by
165
the C<(data_keys()> method. Its values are plain scalars containing strings or
166
C<undef> for any data items not present or empty in the XML document.
167

  
168
=cut
169

  
170
sub metadata {
171
  my $self = shift;
172
  die "Children of $self must implement a metadata() method returning the bill's metadata as a hash.";
173
}
174

  
175
=item check_signature($dom)
176

  
177
This static method takes a DOM object and returns 1 if this DOM object can be
178
parsed by the child class in question, 0 otherwise. C<SL::XMLInvoice> uses this
179
method to determine which child class to instantiate for a given document. All
180
child classes must implement this method.
181

  
182
=cut
183

  
184
sub check_signature {
185
  my $self = shift;
186
  die "Children of $self must implement a check_signature() method returning 1 for supported XML, 0 for unsupported XML.";
187
}
188

  
189
=item supported()
190

  
191
This static method returns an array of free-form strings describing XML invoice
192
types parseable by the child class. C<SL::XMLInvoice> uses this method to
193
output a list of supported XML invoice types if its constructor fails to find
194
to find an appropriate child class to parse the given document with. All child
195
classes must implement this method.
196

  
197
=cut
198

  
199
sub supported {
200
  my $self = shift;
201
  die "Children of $self must implement a supported() method returning a list of supported XML invoice types.";
202
}
203

  
204

  
205
=item items()
206

  
207
This method returns an array of hashes containing line item metadata, such as
208
the quantity, price for one unit, or subtotal. These hashes' keys are the keys
209
returned by the C<(item_keys()> method. Its values are plain scalars containing
210
strings or C<undef> for any data items not present or empty in the XML
211
document.
212

  
213
=cut
214

  
215
sub items {
216
  my $self = shift;
217
  die "Children of $self must implement a item() method returning the bill's items as a hash.";
218
}
219

  
220

  
221
=item parse_xml()
222

  
223
This method is only implemented in child classes of C<SL::XMLInvoice> and is
224
called by the C<SL::XMLInvoice> constructor once the appropriate child class has been
225
determined and instantiated. It uses C<$self->{dom}>, an C<XML::LibXML>
226
instance to iterate through the XML document to parse. That XML document is
227
created by the C<SL::XMLInvoice> constructor.
228

  
229
=back
230

  
231
=cut
232

  
233
sub parse_xml {
234
  my $self = shift;
235
  die "Children of $self must implement a parse_xml() method.";
236
}
237

  
238
=head2 Internal methods
239

  
240
These methods' purpose is child classs selection and making sure child classes
241
implent the interface promised by C<SL::XMLInvoice>. You can safely ignore them
242
if you don't plan on implementing any child classes.
243

  
244
=over 4
245

  
246
=item _document_modules()
247

  
248
This method is implemented in C<SL::XMLInvoice> only and returns a list of
249
child classes, each implementing an XML invoice parser. If you add any child
250
classes for new XML document types you need to add them to this list to make it
251
available from C<SL::XMLInvoice>.
252

  
253
=cut
254

  
255
sub _document_modules {
256
  return (
257
    'SL::XMLInvoice::CrossIndustryDocument',
258
    'SL::XMLInvoice::CrossIndustryInvoice',
259
    'SL::XMLInvoice::UBL',
260
  );
261
}
262

  
263
=item _data_keys()
264

  
265
Returns a list of all keys present in the hash returned by the class'
266
C<metadata()> method. Must be implemented in all classes inheriting from
267
C<SL::XMLInvoice> This list must contain the same keys as the list returned by
268
C<data_keys>. Omitting this method from a child class will cause an exception.
269

  
270
=cut
271

  
272
sub _data_keys {
273
  my $self = shift;
274
  die "Children of $self must implement a _data_keys() method returning the keys an invoice item hash will contain.";
275
}
276

  
277
=item _item_keys()
278

  
279
Returns a list of all keys present in the hashes returned by the class'
280
C<items()> method. Must be implemented in all classes inheriting from
281
C<SL::XMLInvoice> This list must contain the same keys as the list returned by
282
C<item_keys>. Omitting this method from a child class will cause an exception.
283

  
284
=back
285

  
286
=head1 AUTHOR
287

  
288
  Johannes Grassler <info@computer-grassler.de>
289

  
290
=cut
291

  
292
sub _item_keys {
293
  my $self = shift;
294
  die "Children of $self must implement a _item_keys() method returning the keys an invoice item hash will contain.";
295
}
296

  
297

  
298 54
sub new {
299
  my ($self, $xml_data) = @_;
300
  my $type = undef;
301

  
302
  $self = {};
303

  
304
  bless $self;
55
  my ($class, $xml_data) = @_;
56
  my $self = {};
305 57

  
306 58
  $self->{message} = '';
307 59
  $self->{dom} = eval { XML::LibXML->load_xml(string => $xml_data) };
......
313 65
  }
314 66

  
315 67
  # Determine parser class to use
316
  foreach my $module ( $self->_document_modules )
317
    {
318
    load $module;
319
    if ( $module->check_signature($self->{dom}) ) {
320
      $type = $module;
321
      last;
322
      }
323
    }
68
  my $type = first {
69
    $_->check_signature($self->{dom})
70
  } @document_modules;
324 71

  
325 72
  unless ( $type ) {
326 73
    $self->{result} = RES_UNKNOWN_ROOT_NODE_TYPE;
327
    my @supported = ();
328 74

  
329
    foreach my $module ( $self->_document_modules ) {
330
      my @module_list = $module->supported();
331
      push @supported, @module_list;
332
    }
75
    my @supported = map { $_->supported } @document_modules;
333 76

  
334
    my $supported_types = join(",\n", @supported);
335 77
    $self->{message} =  t8("Could not parse XML Invoice: unknown XML invoice type\nsupported: #1",
336
                           $supported_types,
78
                           join ",\n", @supported
337 79
                        );
338 80
    return $self;
339 81
  }
SL/XMLInvoice/Base.pm
1
package SL::XMLInvoice::Base;
2

  
3
use strict;
4
use warnings;
5

  
6
=head1 ATTRIBUTES
7

  
8
=over 4
9

  
10
=item dom
11

  
12
A XML::LibXML document object model (DOM) object created from the XML data supplied.
13

  
14
=item message
15

  
16
Will contain a detailed error message if the C<result> attribute is anything
17
other than C<SL::XMLInvoice::RES_OK>.
18

  
19
=item result
20

  
21
A status field indicating whether the supplied XML data could be parsed. It
22
can take the following values:
23

  
24
=item SL::XMLInvoice::RES_OK
25

  
26
File has been parsed successfully.
27

  
28
=item SL::XMLInvoice::RES_XML_PARSING FAILED
29

  
30
Parsing the file failed.
31

  
32
=item SL::XMLInvoice::RES_UNKNOWN_ROOT_NODE_TYPE
33

  
34
The root node is of an unknown type. Currently, C<rsm:CrossIndustryInvoice> and
35
C<ubl:Invoice> are supported.
36

  
37
=back
38

  
39
=head1 METHODS
40

  
41
=head2 Data structure definition methods (only in C<SL::XMLInvoice>)
42

  
43
These methods are only implemented in C<SL::XMLInvoice> itself and define the
44
data structures to be exposed by any child classes.
45

  
46
=over 4
47

  
48
=item data_keys()
49

  
50
Returns all keys the hash returned by any child's C<metadata()> method must
51
contain. If you add keys to this list, you need to add them to all classes
52
inheriting from C<SL::XMLInvoice> as well. An application may use this method
53
to discover the metadata keys guaranteed to be present.
54

  
55
=cut
56

  
57
sub data_keys {
58
  my @keys = (
59
    'currency',      # The bill's currency, such as "EUR"
60
    'direct_debit',  # Boolean: whether the bill will get paid by direct debit (1) or not (0)
61
    'duedate',       # The bill's due date in YYYY-MM-DD format.
62
    'gross_total',   # The invoice's sum total with tax included
63
    'iban',          # The creditor's IBAN
64
    'invnumber',     # The invoice's number
65
    'net_total',     # The invoice's sum total without tax
66
    'taxnumber',     # The creditor's tax number (Steuernummer in Germany). May be present if
67
                     # there is no VAT ID (USTiD in Germany).
68
    'transdate',     # The date the invoice was issued in YYYY-MM-DD format.
69
    'type',          # Numeric invoice type code, e.g. 380
70
    'ustid',         # The creditor's UStID.
71
    'vendor_name',   # The vendor's company name
72
  );
73
  return \@keys;
74
}
75

  
76
=item item_keys()
77

  
78
Returns all keys the item hashes returned by any child's C<items()> method must
79
contain. If you add keys to this list, you need to add them to all classes
80
inheriting from C<SL::XMLInvoice> as well. An application may use this method
81
to discover the metadata keys guaranteed to be present.
82

  
83
=back
84

  
85
=cut
86

  
87
sub item_keys  {
88
  my @keys = (
89
    'currency',
90
    'description',
91
    'price',
92
    'quantity',
93
    'subtotal',
94
    'tax_rate',
95
    'tax_scheme',
96
    'vendor_partno',
97
  );
98
  return \@keys;
99
}
100

  
101
=head2 User/application facing methods
102

  
103
Any class inheriting from C<SL::XMLInvoice> must implement the following
104
methods. To ensure this happens, C<SL::XMLInvoice> contains stub functions that
105
raise an exception if a child class does not override them.
106

  
107
=over 4
108

  
109
=item new($xml_data)
110

  
111
Constructor for C<SL::XMLInvoice>. This method takes a scalar containing the
112
entire XML document to be parsed as a flat string as its sole argument. It will
113
instantiate the appropriate child class to parse the XML document in question,
114
call its C<parse_xml> method and return the C<SL::XMLInvoice> child object it
115
instantiated. From that point on, the structured data retrieved from the XML
116
document will be available through the object's C<metadata> and C<items()>
117
methods.
118

  
119
=item metadata()
120

  
121
This method returns a hash of document level metadata, such as the invoice
122
number, the total, or the the issuance date. Its keys are the keys returned by
123
the C<(data_keys()> method. Its values are plain scalars containing strings or
124
C<undef> for any data items not present or empty in the XML document.
125

  
126
=cut
127

  
128
sub metadata {
129
  my $self = shift;
130
  die "Children of $self must implement a metadata() method returning the bill's metadata as a hash.";
131
}
132

  
133
=item check_signature($dom)
134

  
135
This static method takes a DOM object and returns 1 if this DOM object can be
136
parsed by the child class in question, 0 otherwise. C<SL::XMLInvoice> uses this
137
method to determine which child class to instantiate for a given document. All
138
child classes must implement this method.
139

  
140
=cut
141

  
142
sub check_signature {
143
  my $self = shift;
144
  die "Children of $self must implement a check_signature() method returning 1 for supported XML, 0 for unsupported XML.";
145
}
146

  
147
=item supported()
148

  
149
This static method returns an array of free-form strings describing XML invoice
150
types parseable by the child class. C<SL::XMLInvoice> uses this method to
151
output a list of supported XML invoice types if its constructor fails to find
152
to find an appropriate child class to parse the given document with. All child
153
classes must implement this method.
154

  
155
=cut
156

  
157
sub supported {
158
  my $self = shift;
159
  die "Children of $self must implement a supported() method returning a list of supported XML invoice types.";
160
}
161

  
162

  
163
=item items()
164

  
165
This method returns an array of hashes containing line item metadata, such as
166
the quantity, price for one unit, or subtotal. These hashes' keys are the keys
167
returned by the C<(item_keys()> method. Its values are plain scalars containing
168
strings or C<undef> for any data items not present or empty in the XML
169
document.
170

  
171
=cut
172

  
173
sub items {
174
  my $self = shift;
175
  die "Children of $self must implement a item() method returning the bill's items as a hash.";
176
}
177

  
178

  
179
=item parse_xml()
180

  
181
This method is only implemented in child classes of C<SL::XMLInvoice> and is
182
called by the C<SL::XMLInvoice> constructor once the appropriate child class has been
183
determined and instantiated. It uses C<$self->{dom}>, an C<XML::LibXML>
184
instance to iterate through the XML document to parse. That XML document is
185
created by the C<SL::XMLInvoice> constructor.
186

  
187
=back
188

  
189
=cut
190

  
191
sub parse_xml {
192
  my $self = shift;
193
  die "Children of $self must implement a parse_xml() method.";
194
}
195

  
196
=head2 Internal methods
197

  
198
These methods' purpose is child classs selection and making sure child classes
199
implent the interface promised by C<SL::XMLInvoice::Base>. You can safely ignore them
200
if you don't plan on implementing any child classes.
201

  
202
=over 4
203

  
204
=item _data_keys()
205

  
206
Returns a list of all keys present in the hash returned by the class'
207
C<metadata()> method. Must be implemented in all classes inheriting from
208
C<SL::XMLInvoice> This list must contain the same keys as the list returned by
209
C<data_keys>. Omitting this method from a child class will cause an exception.
210

  
211
=cut
212

  
213
sub _data_keys {
214
  my $self = shift;
215
  die "Children of $self must implement a _data_keys() method returning the keys an invoice item hash will contain.";
216
}
217

  
218
=item _item_keys()
219

  
220
Returns a list of all keys present in the hashes returned by the class'
221
C<items()> method. Must be implemented in all classes inheriting from
222
C<SL::XMLInvoice> This list must contain the same keys as the list returned by
223
C<item_keys>. Omitting this method from a child class will cause an exception.
224

  
225
=back
226

  
227
=head1 AUTHOR
228

  
229
  Johannes Grassler <info@computer-grassler.de>
230

  
231
=cut
232

  
233
sub _item_keys {
234
  my $self = shift;
235
  die "Children of $self must implement a _item_keys() method returning the keys an invoice item hash will contain.";
236
}
237

  
238

  
239

  
240
1;
SL/XMLInvoice/CrossIndustryDocument.pm
3 3
use strict;
4 4
use warnings;
5 5

  
6
use parent qw(SL::XMLInvoice);
6
use parent qw(SL::XMLInvoice::Base);
7 7

  
8 8
use constant ITEMS_XPATH => '//ram:IncludedSupplyChainTradeLineItem';
9 9

  
SL/XMLInvoice/CrossIndustryInvoice.pm
3 3
use strict;
4 4
use warnings;
5 5

  
6
use parent qw(SL::XMLInvoice);
6
use parent qw(SL::XMLInvoice::Base);
7 7

  
8 8
use constant ITEMS_XPATH => '//ram:IncludedSupplyChainTradeLineItem';
9 9

  
SL/XMLInvoice/UBL.pm
3 3
use strict;
4 4
use warnings;
5 5

  
6
use parent qw(SL::XMLInvoice);
6
use parent qw(SL::XMLInvoice::Base);
7 7

  
8 8
use constant ITEMS_XPATH => '//cac:InvoiceLine';
9 9

  

Auch abrufbar als: Unified diff