File Coverage

blib/lib/Data/PABX/ParseLex.pm
Criterion Covered Total %
statement 12 98 12.2
branch 0 66 0.0
condition 0 27 0.0
subroutine 4 9 44.4
pod 0 3 0.0
total 16 203 7.8


line stmt bran cond sub pod time code
1             package Data::PABX::ParseLex;
2              
3             # Documentation:
4             # POD-style documentation is at the end. Extract it with pod2html.*.
5             #
6             # Note:
7             # o tab = 4 spaces || die
8             #
9             # Author:
10             # Ron Savage
11             # Home page: http://savage.net.au/index.html
12              
13 1     1   20889 use strict;
  1         2  
  1         30  
14 1     1   5 use warnings;
  1         1  
  1         24  
15 1     1   4 no warnings 'redefine';
  1         1  
  1         29  
16              
17             require Exporter;
18              
19 1     1   6 use Carp;
  1         2  
  1         1703  
20              
21             our @ISA = qw(Exporter);
22              
23             # Items to export into callers namespace by default. Note: do not export
24             # names by default without a very good reason. Use EXPORT_OK instead.
25             # Do not simply export all your public functions/methods/constants.
26              
27             # This allows declaration use Data::PABX::ParseLex ':all';
28             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
29             # will save memory.
30             our %EXPORT_TAGS = ( 'all' => [ qw(
31              
32             ) ] );
33              
34             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
35              
36             our @EXPORT = qw(
37              
38             );
39             our $VERSION = '1.05';
40              
41             # -----------------------------------------------
42              
43             # Preloaded methods go here.
44              
45             # -----------------------------------------------
46              
47             # Encapsulated class data.
48              
49             {
50             my(%_attr_data) =
51             ( # Alphabetical order.
52             );
53              
54             sub _default_for
55             {
56 0     0     my($self, $attr_name) = @_;
57              
58 0           $_attr_data{$attr_name};
59             }
60              
61             sub _standard_keys
62             {
63 0     0     sort keys %_attr_data;
64             }
65              
66             } # End of Encapsulated class data.
67              
68             # -----------------------------------------------
69              
70             sub new
71             {
72 0     0 0   my($class, %arg) = @_;
73 0           my($self) = bless({}, $class);
74              
75 0           for my $attr_name ($self -> _standard_keys() )
76             {
77 0           my($arg_name) = $attr_name =~ /^_(.*)/;
78              
79 0 0         if (exists($arg{$arg_name}) )
80             {
81 0           $$self{$attr_name} = $arg{$arg_name};
82             }
83             else
84             {
85 0           $$self{$attr_name} = $self -> _default_for($attr_name);
86             }
87             }
88              
89 0           $$self{'_service'} = {};
90 0           $$self{'_unexpected'} = {};
91              
92 0           $self;
93              
94             } # End of new.
95              
96             # -----------------------------------------------
97              
98             sub parse
99             {
100 0     0 0   my($self, $file_name) = @_;
101              
102 0 0         open(INX, $file_name) || Carp::croak("Can't open($file_name): $!");
103 0           my(@line) = ;
104 0           close INX;
105 0           chomp @line;
106              
107             # Clean up the input file, in case
108             # multiple 'lex a e' commands were issued:
109             # o Find the last /\??lex/, if any
110             # o Remove all lines prior to that
111              
112 0           my($last) = 0;
113              
114 0           for (0 .. $#line)
115             {
116 0 0         $last = $_ if ($line[$_] =~ /\??\s*lex a e/i);
117             }
118              
119 0           splice(@line, 0, ($last + 1) );
120              
121 0           my(@field, %service, $service_number);
122              
123 0           for (@line)
124             {
125 0           s/^\s+//;
126 0           s/\s+$//;
127              
128             # Skip empty lines and the prompt
129             # after the output of 'lex a e'.
130              
131 0 0 0       next if (! $_ || (/^\?/) );
132              
133             # Check the service number.
134              
135 0 0         if (/^([A-Z]{2})(\d{4,5})\s/)
136             {
137 0           $service_number = $2;
138              
139 0 0         Carp::carp("Warning. Service: $service_number. Duplicate records for service"), next if ($$self{'_service'}{$service_number});
140              
141 0           $$self{'_service'}{$service_number} = {};
142 0           $$self{'_service'}{$service_number}{'number_type'} = $1;
143             }
144             else
145             {
146 0           $$self{'_service'}{$service_number}{'ksgm_id'} = $_;
147              
148 0           next;
149             }
150              
151 0           @field = split(/\s+/, $_);
152              
153 0           shift @field; # Discard service number.
154              
155             # Check the PABX port.
156             # Port digits for types AC, EC, IC and OC:
157             # 0 .. 1 => Shelf
158             # 2 .. 3 => Slot
159             # 4 .. 5 => Access
160             # Port digits for BC and DI:
161             # 0 .. 3 => Unknown
162             # If the service number type was CN or VN, there won't be a port.
163              
164 0 0         if ($field[0] =~ /^([A-Z]{2})(\d{6})$/)
    0          
165             {
166 0           $$self{'_service'}{$service_number}{'pabx_card_type'} = $1;
167 0           $$self{'_service'}{$service_number}{'pabx_port'} = $2;
168              
169 0           shift @field; # Discard port.
170             }
171             elsif ($field[0] =~ /^(BC|DI)(\d{4})$/)
172             {
173 0           $$self{'_service'}{$service_number}{'pabx_card_type'} = $1;
174 0           $$self{'_service'}{$service_number}{'pabx_port'} = $2;
175              
176 0           shift @field; # Discard port.
177             }
178             else
179             {
180 0           $$self{'_service'}{$service_number}{'pabx_card_type'} = '-';
181 0           $$self{'_service'}{$service_number}{'pabx_port'} = '-';
182             }
183              
184             # Check the options.
185              
186 0           while ($_ = shift @field)
187             {
188 0 0         if (/^(\*|\$)$/)
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
189             {
190 0           $$self{'_service'}{$service_number}{$_} = 1;
191             }
192             elsif (/^(A)(0\d)$/)
193             {
194 0           $$self{'_service'}{$service_number}{$1} = $2;
195             }
196             elsif ($_ eq 'ACD')
197             {
198 0 0 0       if (@field && ($field[0] eq 'QUEUE') )
199             {
200 0           $$self{'_service'}{$service_number}{$_} = shift @field;
201             }
202             else
203             {
204 0           Carp::carp("Warning: Service: $service_number. Expected ACD to be followed by QUEUE");
205             }
206             }
207             elsif ($_ eq 'AGENT')
208             {
209 0 0 0       if (@field && ($field[0] eq 'GROUP') )
210             {
211 0           $$self{'_service'}{$service_number}{$_} = shift @field;
212             }
213             else
214             {
215 0           Carp::carp("Warning: Service: $service_number. Expected AGENT to be followed by GROUP");
216             }
217             }
218             elsif (/^(BE|BI|DG|HG|IE|KSGM?|MULT|RE|SPARE)$/)
219             {
220 0           $$self{'_service'}{$service_number}{$_} = 1;
221             }
222             elsif (/^(CS)(\d{2})$/)
223             {
224 0           $$self{'_service'}{$service_number}{$1} = $2;
225             }
226             elsif ($_ eq 'D')
227             {
228 0 0 0       if (@field && ($field[0] =~ /^00(1|2)$/) )
229             {
230 0           $$self{'_service'}{$service_number}{$_} = shift @field;
231             }
232             else
233             {
234 0           $$self{'_service'}{$service_number}{$_} = '001';
235             }
236             }
237             elsif ($_ eq 'II')
238             {
239 0 0 0       if (@field && ($field[0] =~ /^\d+$/) )
240             {
241 0           $$self{'_service'}{$service_number}{$_} = shift @field;
242             }
243             else
244             {
245 0           Carp::carp("Warning: Service: $service_number. Expected II to be followed by N digits");
246             }
247             }
248             elsif ($_ =~ /^(MLG)(\d+)$/)
249             {
250 0           $$self{'_service'}{$service_number}{$1} = $2;
251             }
252             elsif ($_ eq 'MOH')
253             {
254 0 0 0       if (@field && ($field[0] =~ /^000$/) )
255             {
256 0           $$self{'_service'}{$service_number}{$_} = shift @field;
257             }
258             else
259             {
260 0           Carp::carp("Warning: Service: $service_number. Expected MOH to be followed by 000");
261             }
262             }
263             elsif ($_ eq 'OG')
264             {
265 0 0 0       if (@field && ($field[0] =~ /^\d+$/) )
266             {
267 0           $$self{'_service'}{$service_number}{$_} = shift @field;
268             }
269             else
270             {
271 0           Carp::carp("Warning: Service: $service_number. Expected OG to be followed by N digits");
272             }
273             }
274             elsif ($_ eq 'RI')
275             {
276 0 0 0       if (@field && ($field[0] =~ /^\d+$/) )
277             {
278 0           $$self{'_service'}{$service_number}{$_} = shift @field;
279             }
280             else
281             {
282 0           Carp::carp("Warning: Service: $service_number. Expected RI to be followed by N digits");
283             }
284             }
285             elsif (/^(TA)(\d{2})$/)
286             {
287 0           $$self{'_service'}{$service_number}{$1} = $2;
288             }
289             elsif (/^(TP)(\d{2})$/)
290             {
291 0           $$self{'_service'}{$service_number}{$1} = $2;
292             }
293             elsif ($_ eq 'V')
294             {
295 0 0 0       if (@field && ($field[0] =~ /^00(1|2)$/) )
296             {
297 0           $$self{'_service'}{$service_number}{$_} = shift @field;
298             }
299             else
300             {
301 0           $$self{'_service'}{$service_number}{$_} = '001';
302             }
303             }
304             elsif (/^\d+$/)
305             {
306 0 0         if ($$self{'_service'}{$service_number}{'#'})
307             {
308 0           Carp::carp("Warning: Service: $service_number. 2 numbers within options: $_ and $$self{'_service'}{$service_number}{'#'}");
309             }
310             else
311             {
312 0           $$self{'_service'}{$service_number}{'#'} = $_;
313             }
314             }
315             else
316             {
317 0           $$self{'_unexpected'}{$_} = 0;
318             }
319             } # End of while.
320             } # End of for.
321              
322 0           $$self{'_service'};
323              
324             } # End of parse.
325              
326             # -----------------------------------------------
327              
328             sub unexpected
329             {
330 0     0 0   my($self) = @_;
331              
332 0           [sort keys %{$$self{'_unexpected'} }];
  0            
333              
334             } # End of unexpected.
335              
336             # -----------------------------------------------
337              
338             1;
339              
340             =head1 NAME
341              
342             Data::PABX::ParseLex - Parse output of /lex a e/ command for the iSDC PABX
343              
344             =head1 Synopsis
345              
346             #!/usr/bin/env perl
347              
348             use strict;
349             use warnings;
350              
351             use Data::Dumper;
352             use Data::PABX::ParseLex;
353              
354             # -----------------------------------------------
355              
356             sub process
357             {
358             my($parser, $input_file_name) = @_;
359             my($hash) = $parser -> parse($input_file_name);
360              
361             print Data::Dumper -> Dump([$hash], ['PABX']);
362              
363             } # End of process.
364              
365             # -----------------------------------------------
366              
367             $Data::Dumper::Indent = 1;
368             my($parser) = Data::PABX::ParseLex -> new();
369              
370             process($parser, 'pabx-a.txt');
371             process($parser, 'pabx-b.txt');
372              
373             See examples/test-parse.pl for this test program, and the same directory for 2
374             test data files. The output is in examples/test-parse.log.
375              
376             Note: My real data has of course been replaced in these files with random numbers.
377              
378             =head1 Description
379              
380             C is a pure Perl module.
381              
382             This module reads the output file from the 'lex a e' (List Extensions, All Extensions)
383             command given to a PABX of type iSDC.
384              
385             It returns a hash ref keyed by extension.
386              
387             =head1 Distributions
388              
389             This module is available both as a Unix-style distro (*.tgz) and an
390             ActiveState-style distro (*.ppd). The latter is shipped in a *.zip file.
391              
392             See http://savage.net.au/Perl-modules.html for details.
393              
394             See http://savage.net.au/Perl-modules/html/installing-a-module.html for
395             help on unpacking and installing each type of distro.
396              
397             =head1 Constructor and initialization
398              
399             new(...) returns an object of type C.
400              
401             This is the class's contructor.
402              
403             Usage: Data::PABX::ParseLex -> new().
404              
405             This method takes no parameters.
406              
407             =head1 Method: parse($input_file_name)
408              
409             Returns: A hash ref of the data read from the file.
410              
411             The file is assumed to be the output of the 'lex a e' command issued to a
412             PABX of type iSDC.
413              
414             The 'lex a e' command may have been run several times, and the output of all
415             runs concatenated into the file. This module checks for multiple copies of the
416             output, and discards all but the last. It does this by looking for the string
417             /lex a e/i, and deleting all data from the start of the file down to the
418             record containing this string.
419              
420             Typical lines in the input file look like:
421              
422             EN22433 EC141702 KSGM CS11 TA07 MOH 000 BE BI RE RI 54103
423             Ron S
424             DN58903 BC0123 A04 D TP02 CS31 TA31 MOH 000
425              
426             where '22433' and '58903' are the extensions.
427              
428             The other fields on the line are attributes of this extension.
429              
430             Fields are generated from such lines by splitting the lines on spaces, except for lines such as 'Ron S'.
431              
432             The line 'Ron S' needs a bit of an explanation. This field is known in this module as the KSGM id.
433             You see, some lines don't contain extensions. They contain the names people choose to have
434             appear on the caller's display. 'Ron S' is such an id. The KSGM id may be blank.
435              
436             The keys of the returned hash are the 4 or 5 digit extensions.
437              
438             Each of these keys points to another hash ref with the following keys (listed in alphabetical order):
439              
440             =over 4
441              
442             =item o ksgm_id
443              
444             This would be 'Ron S' above.
445              
446             =item o number_type
447              
448             This comes from the first 2 characters of each line containing an extension.
449              
450             This would be 'EN' in the first line above, and 'DN' in the third line.
451              
452             =item o pabx_card_type
453              
454             This would be 'EC' from the field 'EC141702' above.
455              
456             Typical values: AC, BC, DI, EC, IE, OC.
457              
458             =item o pabx_port
459              
460             When the pabx_card_type is AC, EC, IE or OC, this field consists of three sub-fields of 2 digits each:
461              
462             =over 4
463              
464             =item o Shelf
465              
466             =item o Slot
467              
468             =item o Access
469              
470             =back
471              
472             This would be '141702' from the field 'EC141702' above.
473              
474             When the pabx_card_type is BC or DI, this field consists of one field of 4 digits.
475              
476             This would be '0123' from the field 'BC0123' above.
477              
478             If the pabx_card_type is none of the above, then both the pabx_card_type and the pabx_port
479             are set to '-'.
480              
481             The remaining fields on each line are options, and are stored thus:
482              
483             =item o *
484              
485             Store as key '*' and value '1'.
486              
487             =item o $
488              
489             Store as key '$' and value '1'.
490              
491             =item o /A\s+(0\d)/
492              
493             Store as key 'A' and value $1.
494              
495             =item o ACD
496              
497             Store as key 'ACD' and value 'QUEUE'.
498              
499             =item o AGENT
500              
501             Store as key 'AGENT' and value 'GROUP'.
502              
503             =item o /(BE|BI|DG|HG|IE|KSGM?|MULT|RE|SPARE)/
504              
505             Store as key $1 and value '1'.
506              
507             =item o /CS\s+(\d{2})/
508              
509             Store as key 'CS' and value $1.
510              
511             This is the Class of Service attribute.
512              
513             =item o /D\s+(00(1|2))/
514              
515             Store as key 'D' and value $1.
516              
517             =item o /II\s+(\d+)/
518              
519             Store as key 'II' and value $1.
520              
521             =item o /MLG\s+(\d+)/
522              
523             Store as key 'MLG' and value $1.
524              
525             =item o /MOH\s+000/
526              
527             Store as key 'MOH' and value '000' (Zeros).
528              
529             =item o /OG\s+(\d+)/
530              
531             Store as key 'OG' and value $1.
532              
533             =item o /RI\s+(\d+)/
534              
535             Store as key 'RI' and value $1.
536              
537             With RI and some other options, the parsing is a little bit slack, in that several
538             options can be combined and followed by a single extension, which is what the \d+
539             is with RI. Since I did not need to process such data, I have not bothered to combine
540             such options with the single trailing extension.
541              
542             =item o /TA\s+(\d{2})/
543              
544             Store as key 'TA' and value $1.
545              
546             =item o /TP\s+(\d{2})/
547              
548             Store as key 'TP' and value $1.
549              
550             =item o /V\s+(00(1|2))/
551              
552             Store as key 'V' and value $1.
553              
554             Where 'V' is not followed by '00\d', the value used is '001'.
555              
556             =item o /(\d+)/
557              
558             Store as key '#' and value $1.
559              
560             =item o Any other string
561              
562             Store the string as the key in another internal hash, together with the value 0.
563              
564             The keys in this hash can be returned, sorted, by calling the method C.
565              
566             =back
567              
568             =head1 Method: C
569              
570             Parse the given file and return a hash ref as documented in the previous section.
571              
572             =head1 Method: C
573              
574             Return, sorted, the keys of the hash holding extension attributes not recognized
575             by any of the above patterns.
576              
577             =head1 Author
578              
579             C was written by Ron Savage Iron@savage.net.auE>
580             in 2005.
581              
582             Home page: http://savage.net.au/index.html
583              
584             =head1 Copyright
585              
586             Australian copyright (c) 2005, Ron Savage.
587             All Programs of mine are 'OSI Certified Open Source Software';
588             you can redistribute them and/or modify them under the terms of
589             The Artistic License, a copy of which is available at:
590             http://www.opensource.org/licenses/index.html
591              
592             =cut