File Coverage

blib/lib/Text/XmlMatch.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Text::XmlMatch;
2 11     11   415863 use strict;
  11         25  
  11         448  
3 11     11   59 use warnings;
  11         21  
  11         459  
4              
5             #XML package designed to provide easy, configurable groups via XML
6             #configuration file
7              
8             # 06/30/06 - Jason A. Lee - Original Code write
9             #
10             # 07/06/06 - JAL - Added new method listGroups();
11             #
12             # 07/13/06 - JAL - Removed unused variables, EXPORT, EXPORT_OK, etc.
13             #
14             # 02/06/07 - JAL - Hotfix 2 -> Correct POD section. No tag means hash
15             # value = '0'
16             #
17             # 02/07/07 - JAL - Hotfix 3 -> Corrected bug where config file with a
18             # single pattern caused the module to fail.
19             #
20             # 02/12/07 - JAL - Hotfix 4 -> Minor POD cleanup, added 'use warnings'
21             #
22             # 06/06/07 - JAL - Hotfix 5 -> Fixed bug in listGroups() failing when
23             # config file contains only a single pattern
24             #
25             # 06/06/07 - JAL - Hotfix 6 -> calling findMatch() prior to
26             # listGroups() was concealing another bug in
27             # listGroups() when only a single pattern name is
28             # specified in a config file.
29              
30              
31             BEGIN {
32 11     11   6992 use XML::Simple;
  0            
  0            
33             use Data::Dumper; #Here for debug/development
34             use vars qw ($VERSION);
35             $VERSION = 1.0006;
36             }
37              
38              
39             sub new { #Standard object constructor
40             my $obj = shift;
41             my $configFile = shift; #This is the filename passed as an argument
42             my $xmlConfig = XMLin($configFile);
43             bless $xmlConfig, $obj;
44             return $xmlConfig;
45             }
46              
47             sub findMatch {
48             my $config = shift; #this holds the XML object
49             our $name = shift; #this holds the string to be matched against, typically a FQDN
50              
51             my $r_matchResults = {}; #this is an intermediate hash, used prior to returning final hash, key->'pattern name'
52              
53             #Now crawl through the hash looking for matches
54             foreach my $group (keys %{$config->{pattern}}) { #walk through each pattern name
55             ############
56             # Hotfix 3 #
57             #################################################################
58             #The block below handles a special case where the user provides a
59             #configuration that contains a single pattern. In such cases,
60             #XML::Simple creates a hash structure that is completely different
61             #than the one that is created when two or more patterns are
62             #specified. This block creates a new hash structure that matches
63             #the expected format. Note that although we are currently in a
64             #foreach loop, the loop gets killed for this special case as there
65             #are no keys to step through.
66             #################################################################
67              
68             #print "DEBUG: Processing pattern $group\n";
69             if (defined $config->{pattern}->{name}) { #if this key is defined then hash is collapsed
70             #print "DEBUG: Hash not found, must be single pattern!\n";
71             #print "Dumper Single: " . Dumper($config->{pattern}->{$group}) . "\n";
72             #rebuild hash structure so that it looks like what's expected
73             my $singlePatternName = $config->{pattern}->{name};
74             my $newHashStructure = {};
75             foreach my $single (keys %{$config->{pattern}}) {
76             next if ($single eq 'name'); # we're rebuilding hash to standard form, don't need this key
77             $newHashStructure->{pattern}->{$singlePatternName}->{$single} = $config->{pattern}->{$single};
78             }
79             #print "DEBUG: new hash structure: " . Dumper($newHashStructure) . "\n";
80             $config->{pattern} = $newHashStructure->{pattern}; #copy newly structured hash over the original
81             #print "DEBUG: new config: " . Dumper($config) . "\n";
82             $group = $singlePatternName;
83             }
84             #End of Hotfix 3 block#########################################
85              
86             #print "DEBUG Standard Dumper: " . Dumper($config->{pattern}->{$group}) . " \n";
87             foreach my $criteria (keys %{$config->{pattern}->{$group}}) { #within each pattern, walk through qualifiers
88             #Now determine the criteria relative to each
89             #Sometimes it's a single element, other times it's an array ref or hash ref
90             #This can't be controlled, as XML::Simple will translate the XML to whatever makes more sense
91             #so must check for ARRAY, HASH, and SCALAR references
92             my $deviceTag;
93             if (ref ($config->{pattern}->{$group}->{$criteria}) eq "ARRAY") {
94             foreach my $matchPattern (@{$config->{pattern}->{$group}->{$criteria}}) { #walk through exclusion, inclusion, tag
95             #print "Array ref: $group $criteria $matchPattern\n";
96             _criteriaSort($r_matchResults,$criteria,$matchPattern,$group);
97             }
98             } elsif (ref ($config->{pattern}->{$group}->{$criteria}) eq "HASH") {
99             foreach my $matchPattern (keys %{$config->{pattern}->{$group}->{$criteria}}) {#walk through exclusion, inclusion, tag
100             #print "hash ref: $group $criteria $matchPattern\n";
101             _criteriaSort($r_matchResults,$criteria,$matchPattern,$group);
102             }
103             } else { #Not array reference, just print value
104             #print "scalar: $group $criteria $config->{pattern}->{$group}->{$criteria}\n";
105             my $matchPattern = $config->{pattern}->{$group}->{$criteria};
106             next if ($matchPattern eq ''); #Don't want to process null values in _criteriaSort
107             _criteriaSort($r_matchResults,$criteria,$matchPattern,$group);
108             }
109             }
110             ##########
111             #Hotfix 3#
112             #################################################################
113             #this line stops the looping through keys, as there are no keys to
114             #step through in the case of a single pattern XML config file.
115              
116             last if (defined $config->{pattern}->{name}); #If case of single pattern, don't try to step through more
117             }
118              
119             #After sifting through the XML mess and walking the trees, time to print final result
120             my $r_returnMatchList = {};
121              
122             foreach my $nameMap (keys %{$r_matchResults->{$name}}) {
123             my $oldNameMap = $nameMap;
124             #my $newNameMap = ''; #we might change group name soon
125             #It's possible the group name became changed if user specified back-references, check for that here
126             if ( $r_matchResults->{$name}->{$nameMap}->{newName} ) { #if group name has changed
127             #print "$nameMap is changing name to $r_matchResults->{$name}->{$nameMap}->{newName}\n";
128             $nameMap = $r_matchResults->{$name}->{$nameMap}->{newName};
129             }
130             if ( ($r_matchResults->{$name}->{$oldNameMap}->{inclusion}) && !($r_matchResults->{$name}->{$oldNameMap}->{exclusion})) {
131             #print "name $name is clearly a member of group $nameMap\n";
132             #print "nameMap is currently $nameMap\n";
133             $r_returnMatchList->{$nameMap} = ($r_matchResults->{$name}->{$oldNameMap}->{tag}) ? $r_matchResults->{$name}->{$oldNameMap}->{tag} : 0; #If it has a tag, store it, if not, give it a default of 0
134             #if ($r_matchResults->{$name}->{$oldNameMap}->{tag}) {
135             #print "and this group is a $r_matchResults->{$name}->{$oldNameMap}->{tag}\n";
136             #}
137             }
138             }
139             #print Dumper($r_matchResults);
140             return $r_returnMatchList;
141              
142             sub _criteriaSort {
143             #differentiate between tag, inclusion, and exclusion, as this sub will walk through all three possible
144             #values
145             my ($r_matchResults,$criteria,$matchPattern,$group) = @_;
146             my $newGroupName; #used in case the name gets updated as a result of a match
147             #because this module supports "back-references," it will allow the user to refer to regex matches
148             #made when defining group names.
149             #print "sub got criteria: $criteria\n";
150              
151             if ($criteria eq 'tag') {
152             my $deviceTag = $matchPattern; #in this case, $matchPattern isn't a pattern, but a group name
153             #print "device tag: $name $deviceTag\n";
154             $$r_matchResults{$name}->{$group}->{tag} = $deviceTag;
155             #if the 'name' matches a regex defined in the inclusion list, then $name is a candidate for
156             #inclusion in this group. We won't know till the end of the XML as it could be excluded
157             } elsif (($criteria eq 'inclusion') && (_match($name,$matchPattern,\$group,\$newGroupName) == 1)) {
158             #print "device inclusion: $name $matchPattern newGroupName: $newGroupName\n";
159             $$r_matchResults{$name}->{$group}->{inclusion} += 1;
160             $$r_matchResults{$name}->{$group}->{newName} = $newGroupName;
161             } elsif (($criteria eq 'exclusion') && (_match($name,$matchPattern,\$group,\$newGroupName) == 1)) {
162             #print "device exclusion: $name $matchPattern newGroupName: $newGroupName\n";
163             $$r_matchResults{$name}->{$group}->{exclusion} += 1;
164             $$r_matchResults{$name}->{$group}->{newName} = $newGroupName;
165             }
166             #print "value of new group is: $newGroupName\n";
167             #foreach (keys %{$$r_matchResults{$name}->{$group}}) {
168             # $$r_matchResults{$name}->{$newGroupName}->{$_} = $$r_matchResults{$name}->{$group}->{$_};
169             #}
170             }
171             sub _match {
172             my $name = shift;
173             my $matchPattern = shift;
174             my $r_group = shift; #like MARKET-MSO, MSO-$1, etc.
175             my $r_newGroupName = shift; #in case the name gets updated
176             #print "comparing $name to $matchPattern\n";
177             if ($name =~ qr /$matchPattern/) {
178             #print "MATCH $name <-> $matchPattern - match variable $1,$2,$3,$4,$5\n";
179             my ($mv1,$mv2,$mv3,$mv4,$mv5) = ($1,$2,$3,$4,$5); #Store up to 5 memory variables from regex
180             my $newGroupName = "$$r_group";
181             $newGroupName =~ s/\$1/$mv1/;
182             $newGroupName =~ s/\$2/$mv2/;
183             $newGroupName =~ s/\$3/$mv3/;
184             $newGroupName =~ s/\$4/$mv4/;
185             #print "derived group name $newGroupName\n";
186             $$r_newGroupName = $newGroupName;
187             return 1
188             } else {
189             return 0;
190             }
191             }
192             }
193              
194             sub listGroups {
195             my $config = shift; #this holds the XML object
196             #our $name = shift; #this holds the item to be matched against, typically a FQDN
197             #setup initialization variables before we start searching through the XML turned into a hash
198             my $r_groupList = []; #reference to anonymous array
199              
200             #Now crawl through the hash and iterate over each group name
201             #print "DEBUG: Original " . Dumper ($config) . "\n";
202             foreach my $group (keys %{$config->{pattern}}) { #walk through each pattern name
203             next if ($group =~ /^(inclusion|tag)$/);
204             ############
205             # Hotfix 6 #
206             #################################################################
207             #The block below handles a special case where the user provides a
208             #configuration that contains a single pattern. In such cases,
209             #XML::Simple creates a hash structure that is completely different
210             #than the one that is created when two or more patterns are
211             #specified. This block creates a new hash structure that matches
212             #the expected format. Note that although we are currently in a
213             #foreach loop, the loop gets killed for this special case as there
214             #are no keys to step through.
215             #################################################################
216              
217             #print "DEBUG: Processing pattern $group\n";
218             if (defined $config->{pattern}->{name}) { #if this key is defined then hash is collapsed
219             #print "DEBUG: Hash not found, must be single pattern!\n";
220             #print "Dumper Single: " . Dumper($config->{pattern}->{$group}) . "\n";
221             #rebuild hash structure so that it looks like what's expected
222             my $singlePatternName = $config->{pattern}->{name};
223             my $newHashStructure = {};
224             foreach my $single (keys %{$config->{pattern}}) {
225             next if ($single eq 'name'); # we're rebuilding hash to standard form, don't need this key
226             $newHashStructure->{pattern}->{$singlePatternName}->{$single} = $config->{pattern}->{$single};
227             }
228             #print "DEBUG: new hash structure: " . Dumper($newHashStructure) . "\n";
229             $config->{pattern} = $newHashStructure->{pattern}; #copy newly structured hash over the original
230             #print "DEBUG: new config: " . Dumper($config) . "\n";
231             $group = $singlePatternName;
232             }
233             #End of Hotfix 6 block#########################################
234              
235             push @$r_groupList, $group; #store each group name in the array
236              
237             ##########
238             #Hotfix 6#
239             #################################################################
240             #this line stops the looping through keys, as there are no keys to
241             #step through in the case of a single pattern XML config file.
242              
243             last if (defined $config->{pattern}->{name}); #If case of single pattern, don't try to step through more
244              
245             }
246             return wantarray ? @$r_groupList : $r_groupList;
247             #return $r_groupList; #pass the reference to the anonymous array back to the caller
248             }
249              
250             1;
251              
252             __END__