File Coverage

blib/lib/BigIP/GTM/ParseConfig.pm
Criterion Covered Total %
statement 11 312 3.5
branch 0 156 0.0
condition 0 98 0.0
subroutine 4 45 8.8
pod 14 37 37.8
total 29 648 4.4


line stmt bran cond sub pod time code
1             package BigIP::GTM::ParseConfig;
2              
3             # CURRENTLY UNDER DEVELOMENT BY WENWU YAN
4             #----------------------------------------------------------------------------
5             # The contents of this file are subject to the iControl Public License
6             # Version 4.5 (the "License"); you may not use this file except in
7             # compliance with the License. You may obtain a copy of the License at
8             # http://www.f5.com/.
9             #
10             # Software distributed under the License is distributed on an "AS IS"
11             # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
12             # the License for the specific language governing rights and limitations
13             # under the License.
14             #
15             # The Original Code is iControl Code and related documentation
16             # distributed by F5.
17             #
18             # The Initial Developer of the Original Code is F5 Networks,
19             # Inc. Seattle, WA, USA. Portions created by F5 are Copyright (C) 1996-2020 F5 Networks,
20             # Inc. All Rights Reserved. iControl (TM) is a registered trademark of F5 Networks, Inc.
21             #
22             # Alternatively, the contents of this file may be used under the terms
23             # of the GNU General Public License (the "GPL"), in which case the
24             # provisions of GPL are applicable instead of those above. If you wish
25             # to allow use of your version of this file only under the terms of the
26             # GPL and not to allow others to use your version of this file under the
27             # License, indicate your decision by deleting the provisions above and
28             # replace them with the notice and other provisions required by the GPL.
29             # If you do not delete the provisions above, a recipient may use your
30             # version of this file under either the License or the GPL.
31             #----------------------------------------------------------------------------
32              
33             #----------------------------------------------------------------------------
34             # Initialize the module
35             #----------------------------------------------------------------------------
36             our $VERSION = '0.83';
37             my $AUTOLOAD;
38              
39 1     1   69734 use 5.012;
  1         4  
40 1     1   6 use Carp;
  1         2  
  1         57  
41 1     1   6 use warnings;
  1         2  
  1         42  
42 1     1   662 use Data::Dumper;
  1         7145  
  1         4415  
43              
44             # Initialize the module
45             sub new {
46 0     0 1   my $class = shift;
47              
48 0           my $self = {};
49 0           bless $self, $class;
50              
51 0           $self->{'ConfigFile'} = shift;
52              
53 0           return $self;
54             }
55              
56             #----------------------------------------------------------------------------
57             # Return a list of objects for ltm
58             #----------------------------------------------------------------------------
59 0     0 0   sub regions { return shift->_objectlist('gtm region'); }
60 0     0 0   sub wideips { return shift->_objectlist('gtm wideip'); }
61 0     0 1   sub pools { return shift->_objectlist('gtm pool'); }
62 0     0 0   sub servers { return shift->_objectlist('gtm server'); }
63 0     0 1   sub monitors { return shift->_objectlist('gtm monitor'); }
64 0     0 1   sub partitions { return shift->_objectlist('partition'); }
65 0     0 1   sub routes { return shift->_objectlist('net route'); }
66 0     0 0   sub selfs { return shift->_objectlist('net self'); }
67 0     0 0   sub vlans { return shift->_objectlist('net vlan'); }
68 0     0 0   sub trunks { return shift->_objectlist('net trunk'); }
69 0     0 0   sub interfaces { return shift->_objectlist('net interface'); }
70 0     0 0   sub mgmt_routes { return shift->_objectlist('sys management-route'); }
71 0     0 1   sub users { return shift->_objectlist('auth user'); }
72              
73             #----------------------------------------------------------------------------
74             # Return a list of objects for net
75             #----------------------------------------------------------------------------
76 0     0 0   sub region { return shift->_object( 'gtm region', shift ); }
77 0     0 0   sub wideip { return shift->_object( 'gtm wideip', shift ); }
78 0     0 1   sub pool { return shift->_object( 'gtm pool', shift ); }
79 0     0 0   sub server { return shift->_object( 'gtm server', shift ); }
80 0     0 1   sub monitor { return shift->_object( 'gtm monitor', shift ); }
81 0     0 1   sub partition { return shift->_object( 'partition', shift ); }
82 0     0 0   sub self { return shift->_object( 'net self', shift ); }
83 0     0 1   sub route { return shift->_object( 'net route', shift ); }
84 0     0 0   sub vlan { return shift->_object( 'net vlan', shift ); }
85 0     0 0   sub trunk { return shift->_object( 'net trunk', shift ); }
86 0     0 0   sub interface { return shift->_object( 'net interface', shift ); }
87 0     0 0   sub mgmt_route { return shift->_object( 'sys management-route', shift ); }
88 0     0 0   sub snmp { return shift->_object( 'sys', 'snmp' ); }
89 0     0 0   sub sshd { return shift->_object( 'sys', 'sshd' ); }
90 0     0 0   sub ntp { return shift->_object( 'sys', 'ntp' ); }
91 0     0 0   sub syslog { return shift->_object( 'sys', 'syslog' ); }
92 0     0 1   sub user { return shift->_object( 'auth user', shift ); }
93              
94             #----------------------------------------------------------------------------
95             # Return a list of objects for others
96             #----------------------------------------------------------------------------
97             sub funcs {
98 0     0 0   my $self = shift;
99              
100 0   0       $self->{'Parsed'} ||= $self->_parse();
101              
102 0   0       return keys %{ $self->{'Parsed'} } || 0;
103             }
104              
105             # Return a list of pool members
106             sub members {
107 0     0 1   my $self = shift;
108 0           my $pool = shift;
109              
110 0           my $members;
111 0   0       $self->{'Parsed'} ||= $self->_parse();
112              
113 0 0         return 0 unless $self->{'Parsed'}->{'gtm pool'}->{$pool}->{'members'};
114              
115 0 0         if ( ref $self->{'Parsed'}->{'gtm pool'}->{$pool}->{'members'} eq 'HASH' )
116             {
117             return
118 0           map {s/\:/\//r}
119             (
120 0           keys %{ $self->{'Parsed'}->{'gtm pool'}->{$pool}->{'members'} } );
  0            
121             }
122             else {
123 0           return $self->{'Parsed'}->{'gtm pool'}->{$pool}->{'members'};
124             }
125             }
126              
127             #----------------------------------------------------------------------------
128             # Return a list of wideips_all
129             #----------------------------------------------------------------------------
130             sub wideips_all {
131 0     0 0   my $self = shift;
132 0   0       $self->{'Parsed'} ||= $self->_parse();
133              
134 0 0         return 0 unless $self->{'Parsed'}->{'gtm wideip'};
135              
136             # Loop for wideips()
137 0           foreach ( $self->wideips() ) {
138 0           my $ret = $self->wideip($_);
139 0           my $pools = $ret->{pools};
140              
141 0           foreach my $pool ( keys %{$pools} ) {
  0            
142 0           my $detail = $self->pool($pool);
143 0 0         my $members = $detail->{members} if defined $detail->{members};
144 0           $ret->{"detail"}{$pool}{"pool_detail"} = $detail;
145              
146 0           foreach my $serverAndVs ( keys %{$members} ) {
  0            
147 0           my ( $server, $vs ) = split( /\:/, $serverAndVs );
148 0           my $detail = $self->server($server);
149 0 0         my $virtual_server = $detail->{"virtual-servers"}{$vs} if $detail;
150 0   0       $ret->{"detail"}{$pool}{"server_detail"} ||= [];
151 0 0         my $mem = $detail if $detail;
152 0           $mem->{"server"} = $server;
153 0           $mem->{"vs"} = $vs;
154 0           push @{$ret->{"detail"}{$pool}{"server_detail"}}, $mem;
  0            
155              
156 0           my $monitor = $virtual_server->{"monitor"};
157 0 0 0       if ( $monitor && $monitor =~ /http|tcp|udp|bigip|gateway_icmp/ ) {
    0 0        
158 0           next;
159             }
160             elsif ( $monitor && $monitor =~ /\S+/ ) {
161 0           my $mon_detail = $self->monitor($monitor);
162 0 0         $ret->{"detail"}{$pool}{"monitor_detail"} = $mon_detail if $mon_detail;
163             }
164             }
165             }
166             }
167              
168 0           return $self->{"Parsed"}{"gtm wideip"};
169             }
170              
171             #----------------------------------------------------------------------------
172             # Return a list of wideip_detail
173             #----------------------------------------------------------------------------
174             sub wideip_detail {
175 0     0 0   my $self = shift;
176 0           my $wideip = shift;
177              
178 0   0       $self->{'Parsed'} ||= $self->_parse();
179              
180 0 0         return 0 unless $self->{'Parsed'}->{'gtm wideip'};
181              
182             # Loop for wideips()
183 0           my $ret = $self->wideip($wideip);
184 0           my $pools = $ret->{pools};
185              
186 0           foreach my $pool ( keys %{$pools} ) {
  0            
187 0           my $detail = $self->pool($pool);
188 0 0         my $members = $detail->{members} if defined $detail->{members};
189 0           $ret->{"detail"}{$pool}{"pool_detail"} = $detail;
190              
191 0           foreach my $serverAndVs ( keys %{$members} ) {
  0            
192 0           my ( $server, $vs ) = split( /\:/, $serverAndVs );
193 0           my $detail = $self->server($server);
194 0 0         my $virtual_server = $detail->{"virtual-servers"}{$vs} if $detail;
195 0   0       $ret->{"detail"}{$pool}{"server_detail"} ||= [];
196 0 0         my $mem = $detail if $detail;
197 0           $mem->{"server"} = $server;
198 0           $mem->{"vs"} = $vs;
199 0           push @{$ret->{"detail"}{$pool}{"server_detail"}}, $mem;
  0            
200              
201 0           my $monitor = $virtual_server->{"monitor"};
202 0 0 0       if ( $monitor && $monitor =~ /http|tcp|udp|bigip|gateway_icmp/ ) {
    0 0        
203 0           next;
204             }
205             elsif ( $monitor && $monitor =~ /\S+/ ) {
206 0           my $mon_detail = $self->monitor($monitor);
207 0 0         $ret->{"detail"}{$pool}{"monitor_detail"} = $mon_detail if $mon_detail;
208             }
209             }
210             }
211              
212 0           return $self->{Parsed}{"gtm wideip"}{$wideip};
213             }
214              
215             # Modify an object
216             sub modify {
217 0     0 1   my $self = shift;
218              
219 0           my ($arg);
220 0           %{$arg} = @_;
  0            
221              
222 0 0 0       return 0 unless $arg->{'type'} && $arg->{'key'};
223              
224 0           my $obj = $arg->{'type'};
225 0           my $key = $arg->{'key'};
226 0           delete $arg->{'type'};
227 0           delete $arg->{'key'};
228              
229 0   0       $self->{'Parsed'} ||= $self->_parse();
230              
231 0 0         return 0 unless $self->{'Parsed'}->{$obj}->{$key};
232              
233 0           foreach my $attr ( keys %{$arg} ) {
  0            
234 0 0         next unless $self->{'Parsed'}->{$obj}->{$key}->{$attr};
235 0           $self->{'Modify'}->{$obj}->{$key}->{$attr} = $arg->{$attr};
236             }
237              
238 0           return 1;
239             }
240              
241             # Write out a new configuration file
242             sub write {
243 0     0 1   my $self = shift;
244 0   0       my $file = shift || $self->{'ConfigFile'};
245              
246 0 0         die "No changes found; no write necessary" unless $self->{'Modify'};
247              
248 0           foreach my $obj (
249             qw( self partition route user monitor auth profile node pool rule virtual )
250             )
251             {
252 0           foreach my $key ( sort keys %{ $self->{'Parsed'}->{$obj} } ) {
  0            
253 0 0         if ( $self->{'Modify'}->{$obj}->{$key} ) {
254 0           $self->{'Output'} .= "$obj $key {\n";
255 0           foreach my $attr ( $self->_order($obj) ) {
256 0 0         next unless $self->{'Parsed'}->{$obj}->{$key}->{$attr};
257             $self->{'Modify'}->{$obj}->{$key}->{$attr}
258 0   0       ||= $self->{'Parsed'}->{$obj}->{$key}->{$attr};
259 0 0         if (ref $self->{'Modify'}->{$obj}->{$key}->{$attr} eq
260             'ARRAY' )
261             {
262 0 0         if ( @{ $self->{'Modify'}->{$obj}->{$key}->{$attr} }
  0            
263             > 1 )
264             {
265 0           $self->{'Output'} .= " $attr\n";
266 0           foreach my $val (
267 0           @{ $self->{'Modify'}->{$obj}->{$key}->{$attr}
268             }
269             )
270             {
271 0           $self->{'Output'} .= " $val\n";
272 0 0         if ( $self->{'Parsed'}->{$obj}->{$key}
273             ->{'_xtra'}->{$val} )
274             {
275             $self->{'Output'}
276             .= ' '
277             . $self->{'Parsed'}->{$obj}->{$key}
278 0           ->{'_xtra'}->{$val} . "\n";
279             }
280             }
281             }
282             else {
283             $self->{'Output'}
284             .= " $attr "
285             . $self->{'Modify'}->{$obj}->{$key}
286 0           ->{$attr}[0] . "\n";
287             }
288             }
289             else {
290             $self->{'Output'}
291             .= " $attr "
292 0           . $self->{'Modify'}->{$obj}->{$key}->{$attr}
293             . "\n";
294             }
295             }
296 0           $self->{'Output'} .= "}\n";
297             }
298             else {
299 0           $self->{'Output'} .= $self->{'Raw'}->{$obj}->{$key};
300             }
301             }
302             }
303              
304 0   0       open FILE, ">$file" || return 0;
305 0           print FILE $self->{'Output'};
306 0           close FILE;
307              
308 0           return 1;
309             }
310              
311             # Return an object hash
312             sub _object {
313 0     0     my $self = shift;
314 0           my $obj = shift;
315 0           my $var = shift;
316              
317 0   0       $self->{'Parsed'} ||= $self->_parse();
318 0 0         return undef unless defined $var;
319 0   0       return $self->{'Parsed'}->{$obj}->{$var} || undef;
320             }
321              
322             # Return a list of objects
323             sub _objectlist {
324 0     0     my $self = shift;
325 0           my $obj = shift;
326              
327 0   0       $self->{'Parsed'} ||= $self->_parse();
328              
329 0 0         if ( $self->{'Parsed'}->{$obj} ) {
330 0           return keys %{ $self->{'Parsed'}->{$obj} };
  0            
331             }
332             else {
333 0           return 0;
334             }
335             }
336              
337             # Define object attribute ordering
338             sub _order {
339 0     0     my $self = shift;
340              
341 0           for (shift) {
342 0 0         /auth/ && return qw( bind login search servers service ssl user );
343 0 0         /gtm monitor/
344             && return
345             qw( default base debug filter mandatoryattrs password security username interval timeout manual dest recv send );
346 0 0         /gtm node/ && return qw( monitor screen );
347 0 0         /gtm pool/ && return qw( lb nat monitor members );
348 0 0         /partition/ && return qw( description );
349 0 0         /net self/ && return qw( netmask unit floating vlan allow );
350 0 0         /auth user/
351             && return qw( password description id group home shell role );
352 0 0         /gtm server/
353             && return
354             qw( translate snat pool destination ip rules profiles persist );
355              
356 0           return 0;
357             }
358             }
359              
360             # Parse the configuration file
361             sub _parse {
362 0     0     my $self = shift;
363 0   0       my $file = shift || $self->{'ConfigFile'};
364              
365             die "File not found: $self->{'ConfigFile'}\n"
366 0 0         unless -e $self->{'ConfigFile'};
367              
368 0   0       open FILE, $file || return 0;
369 0           my @file = ;
370 0           close FILE;
371              
372 0           my ( $parsed, $obj, $key, $attr1, $attr2, $attr3 );
373              
374 0           until ( !$file[0] ) {
375 0           my $ln = shift @file;
376              
377             #policy hit situation with gtm attribute
378 0 0         if ( $ln =~ /^(auth user|patition|cli)\s(.*)\s\{(\s\})?$/ ) {
    0          
    0          
    0          
    0          
    0          
    0          
379 0 0         next if $3;
380 0           $obj = $1;
381 0           $key = $2;
382             }
383              
384             #gtm attribute
385             elsif ( $ln
386             =~ /^(gtm wideip|gtm pool|gtm server|gtm region)\s(.*)\s\{(\s\})?$/
387             )
388             {
389 0 0         next if $3;
390 0           $obj = $1;
391 0           $key = $2;
392             }
393              
394             #net attribute
395             elsif ( $ln
396             =~ /^(net self|net route|net interface|net vlan|net trunk)\s(.*)\s\{(\s\})?$/
397             )
398             {
399 0 0         next if $3;
400 0           $obj = $1;
401 0           $key = $2;
402             }
403              
404             #sys attribute
405             elsif ( $ln =~ /^(sys management-route)\s(.*)\s\{(\s\})?$/ ) {
406 0 0         next if $3;
407 0           $obj = $1;
408 0           $key = $2;
409             }
410              
411             #gtm monitor attribute
412             elsif ( $ln
413             =~ /^((gtm monitor)\s(http|tcp|udp|bigip|gateway_icmp))\s(\S+)\s\{(\s\})?$/
414             )
415             {
416 0 0         next if $5;
417 0           $obj = $2;
418 0           $key = $4;
419 0 0 0       $parsed->{$obj}{$key}{"monitor_method"} = $3 if ( $obj && $key );
420             }
421              
422             #sys management attribute
423             elsif ( $ln
424             =~ /^(sys)\s(snmp|sshd|ntp|syslog|state-mirroring)\s\{(\s\})?$/ )
425             {
426 0 0         next if $3;
427 0           $obj = $1;
428 0           $key = $2;
429             }
430             elsif ( $ln =~ /^\}$/ ) {
431 0           $obj = undef;
432 0           $key = undef;
433             }
434              
435             # mungle data structure
436 0 0 0       if ( $obj && $key ) {
437 0           $self->{'Raw'}->{$obj}{$key} .= $ln;
438              
439             #Indent=4 { not empty }
440 0 0         if ( $ln =~ /^\s{4}(\S+|\".*\")\s\{$/ ) {
441 0           $attr1 = $1;
442 0           next;
443             }
444              
445             #$intdent=8 { not empty }
446 0 0         if ( $ln =~ /^\s{8}(\S+|\".*\")\s\{$/ ) {
447 0           $attr2 = $1;
448 0           next;
449             }
450              
451             #$intdent=12 { not empty }
452 0 0         if ( $ln =~ /^\s{12}(\S+|\".*\")\s\{$/ ) {
453 0           $attr3 = $1;
454 0           next;
455             }
456              
457             #Indent=4 with }$
458 0 0         if ( $ln =~ /^\s{4}\}$/ ) {
459 0           $attr1 = undef;
460 0           next;
461             }
462              
463             #Indent=8 with }$
464 0 0         if ( $ln =~ /^\s{8}\}$/ ) {
465 0           $attr2 = undef;
466 0           next;
467             }
468              
469             #Indent=12 with }$
470 0 0         if ( $ln =~ /^\s{12}\}$/ ) {
471 0           $attr3 = undef;
472 0           next;
473             }
474              
475             #Indent=4 {}
476 0 0         if ( $ln =~ /^\s{4}(\S+)\s\{\s\}$/ ) {
477 0           $parsed->{$obj}{$key}{$1} = undef;
478 0           next;
479             }
480              
481             #Indent=4 { scalar }
482 0 0         if ( $ln =~ /^\s{4}(\S+)\s\{(.*)\}$/ ) {
483 0   0       $parsed->{$obj}{$key}{$1} ||= [];
484 0           push @{ $parsed->{$obj}{$key}{$1} },
485 0           grep { not /^\s*$/ } split( /\s+/, $2 );
  0            
486 0           next;
487             }
488              
489 0 0 0       if ( defined $attr1 && $attr1 ) {
490              
491             #Indent=8 {}
492 0 0         if ( $ln =~ /^\s{8}(\S+)\s\{\s\}$/ ) {
493 0           $parsed->{$obj}{$key}{$attr1}{$1} = undef;
494 0           next;
495             }
496              
497             #Indent=8 { scalar }
498 0 0         if ( $ln =~ /^\s{8}(\S+)\s\{(.*)\}$/ ) {
499 0   0       $parsed->{$obj}{$key}{$attr1}{$1} ||= [];
500 0           push @{ $parsed->{$obj}{$key}{$attr1}{$1} },
501 0           grep { not /^\s*$/ } split( /\s+/, $2 );
  0            
502 0           next;
503             }
504              
505             #Indent=8 match { key => value }
506 0 0         if ( $ln =~ /^\s{8}(\S+)\s(.*)$/ ) {
507 0           $parsed->{$obj}{$key}{$attr1}{$1} = $2;
508 0           next;
509             }
510              
511             #Indent=8 match scalar
512 0 0         if ( $ln =~ /^\s{8}(\S+)$/ ) {
513 0 0         if ( ref( $parsed->{$obj}{$key}{$attr1} ) eq 'HASH' ) {
514 0           $parsed->{$obj}{$key}{$attr1}{$1} = undef;
515             }
516             else {
517 0   0       $parsed->{$obj}{$key}{$attr1} ||= [];
518 0           push @{ $parsed->{$obj}{$key}{$attr1} }, $1;
  0            
519             }
520 0           next;
521             }
522             }
523              
524 0 0 0       if ( defined $attr2 && ( $attr1 && $attr2 ) ) {
      0        
525              
526             #Indent=12 match { not empty }
527 0 0         if ( $ln =~ /^\s{12}(\S+)\s\{\s\}$/ ) {
528 0           $parsed->{$obj}{$key}{$attr1}{$attr2}{$1} = undef;
529 0           next;
530             }
531              
532             #Indent=12 { scalar }
533 0 0         if ( $ln =~ /^\s{12}(\S+)\s\{(.*)\}$/ ) {
534 0   0       $parsed->{$obj}{$key}{$attr1} ||= [];
535 0           push @{ $parsed->{$obj}{$key}{$attr1}{$attr2}{$1} },
536 0           grep { not /^\s*$/ } split( /\s+/, $2 );
  0            
537 0           next;
538             }
539              
540             #Indent=12 match { key => value }
541 0 0         if ( $ln =~ /^\s{12}(\S+)\s(.*)\s?$/ ) {
542 0           $parsed->{$obj}->{$key}{$attr1}{$attr2}{$1} = $2;
543 0           next;
544             }
545              
546             #Indent=12 match scalar
547 0 0         if ( $ln =~ /^\s{12}(.*)\s?$/ ) {
548 0 0         if (ref( $parsed->{$obj}{$key}{$attr1}{$attr2} ) eq
549             'HASH' )
550             {
551 0           $parsed->{$obj}{$key}{$attr1}{$attr2}{$1} = undef;
552             }
553             else {
554 0   0       $parsed->{$obj}{$key}{$attr1}{$attr2} ||= [];
555 0           push @{ $parsed->{$obj}{$key}{$attr1}{$attr2} }, $1;
  0            
556             }
557 0           next;
558             }
559             }
560              
561 0 0 0       if ( defined $attr3 && ( $attr1 && $attr2 && $attr3 ) ) {
      0        
      0        
562              
563             #Indent=16 match { not empty }
564 0 0         if ( $ln =~ /^\s{16}(\S+)\s\{\s\}$/ ) {
565 0           $parsed->{$obj}{$key}{$attr1}{$attr2}{$attr3}{$1} = undef;
566 0           next;
567             }
568              
569             #Indent=16 { scalar }
570 0 0         if ( $ln =~ /^\s{16}(\S+)\s\{(.*)\}$/ ) {
571 0   0       $parsed->{$obj}{$key}{$attr1} ||= [];
572 0           push @{ $parsed->{$obj}{$key}{$attr1}{$attr2}{$attr3}{$1}
573 0           }, grep { not /^\s*$/ } split( /\s+/, $2 );
  0            
574 0           next;
575             }
576              
577             #Indent=16 match { key => value }
578 0 0         if ( $ln =~ /^\s{16}(\S+)\s(.*)\s?$/ ) {
579 0           $parsed->{$obj}->{$key}{$attr1}{$attr2}{$attr3}{$1} = $2;
580 0           next;
581             }
582              
583             #Indent=12 match scalar
584 0 0         if ( $ln =~ /^\s{16}(.*)\s?$/ ) {
585 0 0         if (ref( $parsed->{$obj}{$key}{$attr1}{$attr2}{$attr3} )
586             eq 'HASH' )
587             {
588 0           $parsed->{$obj}{$key}{$attr1}{$attr2}{$attr3}{$1}
589             = undef;
590             }
591             else {
592 0   0       $parsed->{$obj}{$key}{$attr1}{$attr2}{$attr3} ||= [];
593 0           push @{ $parsed->{$obj}{$key}{$attr1}{$attr2}{$attr3}
  0            
594             }, $1;
595             }
596 0           next;
597             }
598             }
599              
600             #Indent=4 match { key => value }
601 0 0         if ( $ln =~ /^\s{4}(\S+)\s(.*)$/ ) {
602 0           $parsed->{$obj}{$key}{$1} = $2;
603 0           next;
604             }
605             }
606             }
607              
608             # Fill in ill-formatted objects
609 0           foreach my $obj ( keys %{ $self->{'Raw'} } ) {
  0            
610 0           foreach my $key ( keys %{ $self->{'Raw'}->{$obj} } ) {
  0            
611 0   0       $parsed->{$obj}{$key} ||= $self->{'Raw'}->{$obj}{$key};
612             }
613             }
614              
615 0           return $parsed;
616             }
617              
618             1;
619              
620             __END__