File Coverage

blib/lib/XML/Dataset.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 XML::Dataset;
2              
3             #-------------------------------------------------------------------------------
4             # Module : XML::Dataset
5             #
6             # Purpose : Extracts XML into Datasets based upon a text profile markup
7             # language
8             #-------------------------------------------------------------------------------
9 20     20   25517 use strict;
  20         50  
  20         795  
10 20     20   109 use warnings;
  20         38  
  20         727  
11              
12 20     20   11675 use XML::LibXML::Reader;
  0            
  0            
13             use Data::Alias;
14              
15             our $VERSION = '0.006';
16             our @ISA = qw(Exporter);
17             our @EXPORT = qw(parse_using_profile);
18              
19             #-------------------------------------------------------------------------------
20             # Call the _run method if the module was called as a script
21             #-------------------------------------------------------------------------------
22             __PACKAGE__->_run(qw ( Parameter1 Parameter2 )) unless caller();
23              
24             #-------------------------------------------------------------------------------
25             # Constructor
26             #
27             # Object constructor parameters are passed directly to the object
28             #-------------------------------------------------------------------------------
29             sub new {
30             my $class = shift;
31             my $self = {@_};
32              
33             # bless REF, CLASSNAME
34             bless $self, $class;
35              
36             # return object
37             return $self;
38             }
39              
40             #-------------------------------------------------------------------------------
41             # Subroutine : _process_record
42             #
43             # Input : $nodeName, $nodeValue, $record
44             #
45             # Purpose : Stores node information based on the profile
46             #-------------------------------------------------------------------------------
47             sub _process_record {
48             my ( $self, $nodeName, $nodeValue, $record ) = @_;
49              
50             #-------------------------------------------------------------------------------
51             # If a 'name' entry is defined we want to use a custom name, replace
52             # the key name accordingly
53             #-------------------------------------------------------------------------------
54             my $name = defined $record->{name} ? $record->{name} : $nodeName;
55              
56             #-------------------------------------------------------------------------------
57             # If a 'prefix' entry is defined we want to prefix with a custom value, update
58             # the key name accordingly
59             #-------------------------------------------------------------------------------
60             $name = defined $record->{prefix} ? $record->{prefix} . ${name} : $name;
61              
62             #-------------------------------------------------------------------------------
63             # If a process declaration is made, pass the current value through the
64             # corresponding process filter prior to storing the value
65             #-------------------------------------------------------------------------------
66             if ( defined $record->{process} ) {
67              
68             # Check for the required process method in the main namespace
69             if ( main->can("$record->{process}") ) {
70             no strict "refs"; ## no critic
71             $nodeValue = &{"main\::$record->{process}"}($nodeValue);
72             }
73             else {
74             die("could not find method $record->{process} for processing");
75             }
76             }
77              
78             #-------------------------------------------------------------------------------
79             # If there is a dataset value
80             #-------------------------------------------------------------------------------
81             if ( defined $record->{dataset} ) {
82              
83             #-------------------------------------------------------------------------------
84             # If we are going to clobber an existing value or if the dataset does not
85             # already exist, create a new dataset
86             #-------------------------------------------------------------------------------
87             if ( ( !defined $self->{current_data}->{ $record->{dataset} } )
88             || ( exists $self->{current_data}->{ $record->{dataset} }->{$name} ) ) {
89              
90             #-------------------------------------------------------------------------------
91             # Create a named hashref - $temporary_hash
92             #-------------------------------------------------------------------------------
93             my $temporary_hash = {};
94              
95             #-------------------------------------------------------------------------------
96             # Copy the existing external references if applicable to $temporary_hash
97             #-------------------------------------------------------------------------------
98             for my $external_reference ( @{ $self->{__EXTERNAL_REFERENCES__} } ) {
99             if ( defined $self->{current_data}->{ $record->{dataset} }->{$external_reference} ) {
100             $temporary_hash->{$external_reference} =
101             $self->{current_data}->{ $record->{dataset} }->{$external_reference};
102             }
103             }
104              
105             #-------------------------------------------------------------------------------
106             # Dispatch Dataset if required
107             #-------------------------------------------------------------------------------
108             if ( ( defined $self->{dispatch} ) && ( defined $self->{current_data}->{ $record->{dataset} } ) ) {
109             $self->_dispatch_dataset( $record->{dataset} );
110             }
111              
112             #-------------------------------------------------------------------------------
113             # Push the $temporary_hash on to the datastructure
114             #-------------------------------------------------------------------------------
115             push @{ $self->{data_structure}->{ $record->{dataset} } }, $temporary_hash;
116              
117             #-------------------------------------------------------------------------------
118             # Update the current reference with the $temporary_hash pointer
119             #-------------------------------------------------------------------------------
120             $self->{current_data}->{ $record->{dataset} } = $temporary_hash;
121             }
122              
123             #-------------------------------------------------------------------------------
124             # Store the data within the dataset
125             #-------------------------------------------------------------------------------
126             $self->{current_data}->{ $record->{dataset} }->{$name} = $nodeValue;
127             }
128              
129             #-------------------------------------------------------------------------------
130             # Store external data
131             #-------------------------------------------------------------------------------
132             elsif ( defined $record->{external_dataset} ) {
133              
134             # If the external doesn't exist, create it with a default value
135             if ( !defined $self->{external_data}->{ $record->{external_dataset} }->{$name} ) {
136             push @{ $self->{external_data}->{ $record->{external_dataset} }->{$name} }, '';
137             }
138              
139             # If the value exists and the last entry has a value which is not '', create a new holder
140             elsif ( ( defined $self->{external_data}->{ $record->{external_dataset} }->{$name}[-1] )
141             && ( $self->{external_data}->{ $record->{external_dataset} }->{$name}[-1] ne '' ) ) {
142             push @{ $self->{external_data}->{ $record->{external_dataset} }->{$name} }, '';
143             }
144              
145             # Store the data
146             $self->{external_data}->{ $record->{external_dataset} }->{$name}[-1] = $nodeValue;
147             }
148             }
149              
150             #-------------------------------------------------------------------------------
151             # Subroutine : _dispatch_all
152             #
153             # Purpose : Dispatches all datasets or all remaining datasets
154             #-------------------------------------------------------------------------------
155             sub _dispatch_all {
156             my $self = shift;
157             if ( defined $self->{dispatch} ) {
158             for my $dataset ( keys %{ $self->{data_structure} } ) {
159             $self->_dispatch_dataset( $dataset, 1 );
160             }
161             }
162             }
163              
164             #-------------------------------------------------------------------------------
165             # Subroutine : _dispatch_dataset
166             #
167             # Input : $self, $dataset, $flush
168             #
169             # Purpose : Dispatches a dataset
170             #-------------------------------------------------------------------------------
171             # Closures used to isolate $dispatch_counter ( equivalent to state )
172             {
173             my $dispatch_counter = {};
174              
175             sub _dispatch_dataset {
176             my ( $self, $dataset, $flush ) = @_;
177              
178             # Increase the counter
179             $dispatch_counter->{$dataset}++;
180              
181             my $counter_trigger = 0;
182             if ( ( defined $self->{dispatch}->{$dataset}->{counter} )
183             || ( defined $self->{dispatch}->{__generic__}->{counter} ) ) {
184             $counter_trigger =
185             defined $self->{dispatch}->{$dataset}->{counter}
186             ? $self->{dispatch}->{$dataset}->{counter}
187             : $self->{dispatch}->{__generic__}->{counter};
188             }
189             else {
190             # Alert
191             }
192              
193             my $counter_coderef;
194             if ( ( defined $self->{dispatch}->{$dataset}->{coderef} )
195             || ( defined $self->{dispatch}->{__generic__}->{coderef} ) ) {
196             $counter_coderef =
197             defined $self->{dispatch}->{$dataset}->{coderef}
198             ? $self->{dispatch}->{$dataset}->{coderef}
199             : $self->{dispatch}->{__generic__}->{coderef};
200             if ( ref($counter_coderef) ne 'CODE' ) {
201              
202             # Alert
203             }
204             }
205             else {
206             # Alert
207             }
208              
209             if ( defined $self->{data_structure}->{$dataset} ) {
210             if ( ( scalar( @{ $self->{data_structure}->{$dataset} } ) >= $counter_trigger ) || ( defined $flush ) ) {
211              
212             # Call the CODEREF with the Payload
213             &{$counter_coderef}( { $dataset => $self->{data_structure}->{$dataset} } );
214              
215             # Delete the processed entries
216             delete $self->{data_structure}->{$dataset};
217             }
218             }
219             }
220             }
221              
222             sub _logging_clobber_external_information {
223             my %args = @_;
224             }
225              
226             sub _log_xml_attribute_not_defined_in_profile {
227             warn "missing profile entry for attribute name=$_[0]->{name} depth=$_[0]->{depth}";
228             }
229              
230             sub _log_xml_element_not_defined_in_profile {
231             warn "missing profile entry for element name=$_[0]->{name} depth=$_[0]->{depth}";
232             }
233              
234             #-------------------------------------------------------------------------------
235             # Subroutine : _process_data
236             #
237             # Input : $data = Perl Structure, $profile = Text Profile
238             #
239             # Output : Perl Structure
240             #
241             # Purpose : Processes perl structures based on input profiles
242             #-------------------------------------------------------------------------------
243             sub _process_data {
244             my $self = shift;
245             my $node = shift;
246              
247             #-------------------------------------------------------------------------------
248             # Dispatch Table - Based on perl reference type
249             #-------------------------------------------------------------------------------
250             my $xml_dispatch_table = {
251             0 => sub { # XML_READER_TYPE_NONE
252              
253             #-------------------------------------------------------------------------------
254             # Process all nodes in the document
255             #-------------------------------------------------------------------------------
256             while ( $node->read ) {
257              
258             #-------------------------------------------------------------------------------
259             # Process data
260             #-------------------------------------------------------------------------------
261             $self->_process_data($node);
262             }
263              
264             },
265             1 => sub { # XML_READER_TYPE_ELEMENT
266              
267             #-------------------------------------------------------------------------------
268             # Store the previous nodeName if applicable
269             #-------------------------------------------------------------------------------
270             $self->{_previous_key} = defined $self->{_current_key} ? $self->{_current_key} : '';
271              
272             #-------------------------------------------------------------------------------
273             # Store the current nodeName
274             #-------------------------------------------------------------------------------
275             $self->{_current_key} = $node->name;
276              
277             if ( $#{ $self->{_profiles} } >= $node->depth ) {
278             $self->{_profile} = $self->{_profiles}[ $node->depth - 1 ];
279             }
280              
281             #-------------------------------------------------------------------------------
282             # Compare the key against the profile
283             #-------------------------------------------------------------------------------
284             if ( defined $self->{_profile}->{ $self->{_current_key} } ) {
285              
286             # Update the profile to the current_key
287             $self->{_profile} = $self->{_profile}->{ $self->{_current_key} };
288              
289             # Store the updated profile
290             $self->{_profiles}[ $node->depth ] = $self->{_profile};
291              
292             #-------------------------------------------------------------------------------
293             # If the key exists and has an __NEW_EXTERNAL_VALUE_HOLDER__ element, configure
294             # external holders
295             #-------------------------------------------------------------------------------
296             if ( defined $self->{_profile}->{__NEW_EXTERNAL_VALUE_HOLDER__} ) {
297              
298             #-------------------------------------------------------------------------------
299             # Process all External Values
300             #-------------------------------------------------------------------------------
301             for my $dataset ( @{ $self->{_profile}->{__NEW_EXTERNAL_VALUE_HOLDER__}->{__record__} } ) {
302             my ( $ext_dataset, $ext_name ) = %{$dataset};
303              
304             push @{ $self->{external_data}->{$ext_dataset}->{$ext_name} }, '';
305             }
306              
307             }
308              
309             #-------------------------------------------------------------------------------
310             # If the key exists and has an __IGNORE__ element, ignore and move to the
311             # next record
312             #-------------------------------------------------------------------------------
313             if ( defined $self->{_profile}->{'__IGNORE__'} ) {
314             }
315              
316             #-------------------------------------------------------------------------------
317             # Otherwise continue
318             #-------------------------------------------------------------------------------
319             else {
320              
321             #-------------------------------------------------------------------------------
322             # Check for a new dataset marker
323             #-------------------------------------------------------------------------------
324             if ( defined $self->{_profile}->{__NEW_DATASET__} ) {
325             for my $dataset ( @{ $self->{_profile}->{__NEW_DATASET__} } ) {
326              
327             #-------------------------------------------------------------------------------
328             # Dispatch Dataset if required
329             #-------------------------------------------------------------------------------
330             if ( ( defined $self->{dispatch} ) && ( defined $self->{current_data}->{$dataset} ) ) {
331             $self->_dispatch_dataset($dataset);
332             }
333              
334             #-------------------------------------------------------------------------------
335             # Create a named hashref
336             #-------------------------------------------------------------------------------
337             my $temporary_hash = {};
338              
339             #-------------------------------------------------------------------------------
340             # Push on to the datastructure
341             #-------------------------------------------------------------------------------
342             push @{ $self->{data_structure}->{$dataset} }, $temporary_hash;
343              
344             #-------------------------------------------------------------------------------
345             # Update the current reference with the same pointer
346             #-------------------------------------------------------------------------------
347             $self->{current_data}->{$dataset} = $temporary_hash;
348             }
349             }
350              
351             #-------------------------------------------------------------------------------
352             # Check for an __EXTERNAL_VALUE__
353             #-------------------------------------------------------------------------------
354             if ( defined $self->{_profile}->{__EXTERNAL_VALUE__} ) {
355              
356             #-------------------------------------------------------------------------------
357             # Initialise default holder for external values
358             #-------------------------------------------------------------------------------
359             $self->{__EXTERNAL_REFERENCES__} = [];
360              
361             #-------------------------------------------------------------------------------
362             # Process all External Values
363             #-------------------------------------------------------------------------------
364             for my $dataset ( @{ $self->{_profile}->{__EXTERNAL_VALUE__} } ) {
365              
366             #-------------------------------------------------------------------------------
367             # Seperate the record
368             #-------------------------------------------------------------------------------
369             my ( $ext_dataset, $ext_name, $forward_dataset, $forward_name ) = split( ':', $dataset );
370              
371             #-------------------------------------------------------------------------------
372             # Use the supplied forwarding name if available, otherwise use the original name
373             #-------------------------------------------------------------------------------
374             if ( !defined $forward_name ) { $forward_name = $ext_name }
375              
376             #-------------------------------------------------------------------------------
377             # Store the external values within the object, if we encounter a clobber
378             # situation where a new dataset is created to prevent data being overwritten
379             # this list will provide a corresponding reference to the external values
380             # to copy
381             #-------------------------------------------------------------------------------
382             push @{ $self->{__EXTERNAL_REFERENCES__} }, $forward_name;
383              
384             #-------------------------------------------------------------------------------
385             # If we've encountered the external data before we've created a dataset holder,
386             # or the external data already exists in the dataset, create a new dataset
387             #-------------------------------------------------------------------------------
388             if ( ( !defined $self->{current_data}->{$forward_dataset} )
389             || ( defined $self->{current_data}->{$forward_dataset}->{$forward_name} ) ) {
390              
391             #-------------------------------------------------------------------------------
392             # Create a named hashref
393             #-------------------------------------------------------------------------------
394             my $temporary_hash = {};
395              
396             #-------------------------------------------------------------------------------
397             # Push on to the datastructure
398             #-------------------------------------------------------------------------------
399             push @{ $self->{data_structure}->{$forward_dataset} }, $temporary_hash;
400              
401             #-------------------------------------------------------------------------------
402             # Update the current reference with the same pointer
403             #-------------------------------------------------------------------------------
404             $self->{current_data}->{$forward_dataset} = $temporary_hash;
405             }
406              
407             #-------------------------------------------------------------------------------
408             # Verify that the external data does not already exist within the dataset,
409             # If it doesn't store accordingly
410             #-------------------------------------------------------------------------------
411             if ( !defined $self->{current_data}->{$forward_dataset}->{$forward_name} ) {
412              
413             #-------------------------------------------------------------------------------
414             # Check for scenarios where we are using external data before the external
415             # data has been processed...
416             #-------------------------------------------------------------------------------
417             if ( !defined $self->{external_data}->{$ext_dataset}->{$ext_name} ) {
418              
419             die(
420             "An attempt has been made to use external data before that data has been processed, this can result in invalid datasets. See the __NEW_EXTERNAL_VALUE_HOLDER__ profile option within the perldoc - $ext_dataset $ext_name"
421             );
422             }
423              
424             #-------------------------------------------------------------------------------
425             # alias through Data::Alias ... example taken from
426             # http://stackoverflow.com/questions/12514234/alias-a-hash-element-in-perl
427             #-------------------------------------------------------------------------------
428             alias $self->{current_data}->{$forward_dataset}->{$forward_name} =
429             $self->{external_data}->{$ext_dataset}->{$ext_name}[-1];
430             }
431             }
432             }
433             }
434              
435             #-------------------------------------------------------------------------------
436             # Process attributes if they exist, these will always be key/value pairs
437             #-------------------------------------------------------------------------------
438             if ( $node->hasAttributes ) {
439             while ( $node->moveToNextAttribute ) {
440              
441             #-------------------------------------------------------------------------------
442             # If the key exists and has an __IGNORE__ element, ignore and move to the
443             # next record
444             #-------------------------------------------------------------------------------
445             if ( defined $self->{_profile}->{ $node->name }->{'__IGNORE__'} ) {
446             }
447              
448             #-------------------------------------------------------------------------------
449             # If the attribute is defined in the profile, process
450             #-------------------------------------------------------------------------------
451             elsif ( defined $self->{_profile}->{ $node->name }->{__record__} ) {
452             for my $record ( @{ $self->{_profile}->{ $node->name }->{__record__} } ) {
453             $self->_process_record( $node->name, $node->value, $record );
454             }
455             }
456              
457             #-------------------------------------------------------------------------------
458             # Alert if the XML Attribute is not defined according to object parameters
459             #-------------------------------------------------------------------------------
460             else {
461             if ( $self->{warn_missing_profile_entry} ) {
462             _log_xml_attribute_not_defined_in_profile(
463             {
464             name => $node->name,
465             depth => $node->depth,
466             }
467             );
468             }
469             }
470             }
471             }
472             }
473              
474             #-------------------------------------------------------------------------------
475             # Otherwise the element is not defined in the profile, warn or die depending
476             # on the constructor options
477             #-------------------------------------------------------------------------------
478             else {
479              
480              
481             if ( $self->{warn_missing_profile_entry} ) {
482             _log_xml_element_not_defined_in_profile(
483             {
484             name => $node->name,
485             depth => $node->depth,
486             }
487             );
488             }
489              
490             # Skip to the next node
491             $node->next;
492              
493             }
494             },
495             2 => sub { # XML_READER_TYPE_ATTRIBUTE
496             # Not needed as attributes are processed within Elements
497             },
498             3 => sub { # XML_READER_TYPE_TEXT
499             if ( defined $self->{_profile}->{__record__} ) {
500             for my $record ( @{ $self->{_profile}->{__record__} } ) {
501             $self->_process_record( $self->{_current_key}, $node->value, $record );
502             }
503             }
504             },
505             4 => sub { },
506             5 => sub { },
507             6 => sub { },
508             7 => sub { },
509             8 => sub { },
510             9 => sub { },
511             10 => sub { },
512             11 => sub { },
513             12 => sub { },
514             13 => sub { },
515             14 => sub { },
516             15 => sub { },
517             16 => sub { },
518             17 => sub { },
519             };
520              
521             # Call the dispatch table with the node type
522             &{ $xml_dispatch_table->{ $node->nodeType } };
523             }
524              
525             #-------------------------------------------------------------------------------
526             # Subroutine : parse_using_profile
527             #
528             # Input : $data = Perl Structure, $profile = Text Profile
529             #
530             # Output : Perl Structure
531             #
532             # Purpose : Processes perl structures based on input profiles
533             #-------------------------------------------------------------------------------
534             sub parse_using_profile {
535             my ( $xml, $profile, %options ) = @_;
536              
537             #-------------------------------------------------------------------------------
538             # Create an internal XML::Dataset object
539             #-------------------------------------------------------------------------------
540             my $self = XML::Dataset->new( %options );
541              
542             #-------------------------------------------------------------------------------
543             # Create an XML::LibXML::Reader Parser object
544             #-------------------------------------------------------------------------------
545             my $doc = XML::LibXML::Reader->new( string => $xml );
546              
547             #-------------------------------------------------------------------------------
548             # Convert the simplified profile to a perl based profile
549             # Store the profile and process
550             #-------------------------------------------------------------------------------
551             $self->{_profile} = $self->_expand_profile($profile);
552             $self->_process_data($doc);
553              
554             #-------------------------------------------------------------------------------
555             # Delete any __external_value__ keys
556             #-------------------------------------------------------------------------------
557             for my $key ( keys %{ $self->{data_structure} } ) {
558             if ( $key =~ m/__external_value__/ ) {
559             delete $self->{data_structure}->{$key};
560             }
561             }
562              
563             #-------------------------------------------------------------------------------
564             # If called with a dispatch logic, dispatch any remaining entries
565             #-------------------------------------------------------------------------------
566             if ( defined $self->{dispatch} ) {
567             $self->_dispatch_all;
568             }
569              
570             else {
571             my $data_structure = $self->{data_structure};
572              
573             #-------------------------------------------------------------------------------
574             # Return the data structure to the caller
575             #-------------------------------------------------------------------------------
576             return $self->{data_structure};
577             }
578              
579             }
580              
581             #-------------------------------------------------------------------------------
582             # Subroutine : _expand_profile
583             #
584             # Input : $profile_input = Text Profile
585             #
586             # Output : Perl Structure
587             #
588             # Purpose : Turns a simple profile into a perl based structure
589             #-------------------------------------------------------------------------------
590             sub _expand_profile {
591             my ( $self, $profile_input ) = @_;
592              
593             #-------------------------------------------------------------------------------
594             # Holder for the complex_profile
595             #-------------------------------------------------------------------------------
596             my $complex_profile = {};
597             my $complex_profile_history = [ \$complex_profile ];
598             my $current_profile_position = ${ $complex_profile_history->[-1] };
599              
600             #-------------------------------------------------------------------------------
601             # Starting indentation
602             #-------------------------------------------------------------------------------
603             my $indentation;
604             my $previous_indentation;
605             my $indentation_history = [];
606              
607             #-------------------------------------------------------------------------------
608             # Capture tokens based on carriage returns
609             #-------------------------------------------------------------------------------
610             my @tokens = split( "\n", $profile_input );
611              
612             #-------------------------------------------------------------------------------
613             # Process all tokens
614             #-------------------------------------------------------------------------------
615             for my $token (@tokens) {
616              
617             #-------------------------------------------------------------------------------
618             # If the does not contain an empty entry, or whitespace only, continue
619             #-------------------------------------------------------------------------------
620             if ( $token !~ m/^(\s+)?$/ ) {
621              
622             #-------------------------------------------------------------------------------
623             # Capture the token data and indentation ( if available )
624             #-------------------------------------------------------------------------------
625             my $token_data;
626              
627             if ( $token =~ m/^(\s+)(.*)/ ) {
628             $indentation = length($1);
629             $token_data = $2;
630             }
631             elsif ( $token =~ m/(.*)/ ) {
632             $indentation = 0;
633             $token_data = $1;
634             }
635              
636             #-------------------------------------------------------------------------------
637             # Capture the previous indentation, if the previous indentation is not
638             # available, mark the previous_indentation as 0
639             #-------------------------------------------------------------------------------
640             $previous_indentation = ( scalar( @{$indentation_history} ) > 0 ) ? $indentation_history->[-1] : 0;
641              
642             #-------------------------------------------------------------------------------
643             # If the indentation has increased, store the indentation in the history
644             #-------------------------------------------------------------------------------
645             if ( $indentation > $previous_indentation ) {
646             push @{$indentation_history}, $indentation;
647             }
648              
649             #-------------------------------------------------------------------------------
650             # Otherwise if the indentation has decreased
651             #-------------------------------------------------------------------------------
652             elsif ( $previous_indentation > $indentation ) {
653             while ( $previous_indentation > $indentation ) {
654             pop @{$indentation_history};
655             $previous_indentation = $indentation_history->[-1];
656             pop @{$complex_profile_history};
657             $current_profile_position = ${ $complex_profile_history->[-1] };
658             }
659             }
660              
661             #-------------------------------------------------------------------------------
662             # Process if the token contains an equals
663             #-------------------------------------------------------------------------------
664             if ( $token_data =~ m/=/ ) {
665              
666             #-------------------------------------------------------------------------------
667             # Seperate the key and record holder based on the equal
668             #-------------------------------------------------------------------------------
669             my ( $key, $record_holder ) = split( '=', $token_data );
670              
671             #-------------------------------------------------------------------------------
672             # Remove any whitespace from the key and record_holder <-- address with common
673             #-------------------------------------------------------------------------------
674             $key =~ s/^\s+//g;
675             $key =~ s/\s+$//g;
676             $record_holder =~ s/^\s+//g;
677             $record_holder =~ s/\s+$//g;
678              
679             #-------------------------------------------------------------------------------
680             # Check for __IGNORE__, place the corresponding marker if required and
681             # move to the next record
682             #-------------------------------------------------------------------------------
683             if ( $record_holder =~ m/__IGNORE__/ ) {
684             $current_profile_position->{$key}{'__IGNORE__'} = 1;
685             next;
686             }
687              
688             #-------------------------------------------------------------------------------
689             # Seperate unprocessed records from the record_holder using whitespace as
690             # the seperator
691             #-------------------------------------------------------------------------------
692             my @records_unprocessed = split( /\s+/, $record_holder );
693              
694             #-------------------------------------------------------------------------------
695             # If there is a marker for a __NEW_DATASET__ store the corresponding dataset
696             # names against the marker
697             #-------------------------------------------------------------------------------
698             if ( $key =~ m/__NEW_DATASET__/ ) {
699             $current_profile_position->{__NEW_DATASET__} = \@records_unprocessed;
700             }
701              
702             #-------------------------------------------------------------------------------
703             # If there is a marker for a __EXTERNAL_VALUE__ store the corresponding dataset
704             # names against the marker
705             #-------------------------------------------------------------------------------
706             elsif ( $key =~ m/__EXTERNAL_VALUE__/ ) {
707             $current_profile_position->{__EXTERNAL_VALUE__} = \@records_unprocessed;
708             }
709              
710             #-------------------------------------------------------------------------------
711             # Otherwise treat as unprocessed records
712             #-------------------------------------------------------------------------------
713             else {
714             for my $record_unprocessed (@records_unprocessed) {
715              
716             #-------------------------------------------------------------------------------
717             # Initialise a hash_ref store
718             #-------------------------------------------------------------------------------
719             my $hash_ref = {};
720              
721             #-------------------------------------------------------------------------------
722             # For each record ( seperated by , ) store key value pair in hash_ref
723             # ( where key values are seperated by : )
724             #-------------------------------------------------------------------------------
725             for my $record ( split( ',', $record_unprocessed ) ) {
726             my ( $record_key, $record_value ) = split( ':', $record );
727              
728             #-------------------------------------------------------------------------------
729             # Store the item in the profile
730             #-------------------------------------------------------------------------------
731             $hash_ref->{$record_key} = $record_value;
732             }
733              
734             #-------------------------------------------------------------------------------
735             # Push the hash_ref onto the profile
736             #-------------------------------------------------------------------------------
737             push( @{ $current_profile_position->{$key}->{__record__} }, $hash_ref );
738             }
739             }
740             }
741              
742             #-------------------------------------------------------------------------------
743             # Otherwise treat the record as a structural marker
744             #-------------------------------------------------------------------------------
745             else {
746             # Initialise the new token as an anonymous hash
747             $current_profile_position->{$token_data} = {};
748              
749             # Push the new position on to the stack
750             push( @{$complex_profile_history}, \$current_profile_position->{$token_data} );
751              
752             # Update the current position
753             $current_profile_position = ${ $complex_profile_history->[-1] };
754             }
755              
756             }
757              
758             #-------------------------------------------------------------------------------
759             # Incompatible markers holder
760             #-------------------------------------------------------------------------------
761             else {
762             }
763              
764             }
765             return $complex_profile;
766             }
767              
768             #-------------------------------------------------------------------------------
769             # Subroutine : run
770             #
771             # Purpose : Testing subroutine
772             #-------------------------------------------------------------------------------
773             sub _run {
774             my $example_data = qq(
775            
776            
777            
778             Gambardella, Matthew
779             XML Developer's Guide
780             Computer
781             44.95
782             2000-10-01
783             An in-depth look at creating applications
784             with XML.
785            
786            
787             Ralls, Kim
788             Midnight Rain
789             Fantasy
790             5.95
791             2000-12-16
792             A former architect battles corporate zombies,
793             an evil sorceress, and her own childhood to become queen
794             of the world.
795            
796            
797             Corets, Eva
798             Maeve Ascendant
799             Fantasy
800             5.95
801             2000-11-17
802             After the collapse of a nanotechnology
803             society in England, the young survivors lay the
804             foundation for a new society.
805            
806            
807             Corets, Eva
808             Oberon's Legacy
809             Fantasy
810             5.95
811             2001-03-10
812             In post-apocalypse England, the mysterious
813             agent known only as Oberon helps to create a new life
814             for the inhabitants of London. Sequel to Maeve
815             Ascendant.
816            
817            
818             Corets, Eva
819             The Sundered Grail
820             Fantasy
821             5.95
822             2001-09-10
823             The two daughters of Maeve, half-sisters,
824             battle one another for control of England. Sequel to
825             Oberon's Legacy.
826            
827            
828             Randall, Cynthia
829             Lover Birds
830             Romance
831             4.95
832             2000-09-02
833             When Carla meets Paul at an ornithology
834             conference, tempers fly as feathers get ruffled.
835            
836            
837             Thurman, Paula
838             Splish Splash
839             Romance
840             4.95
841             2000-11-02
842             A deep sea diver finds true love twenty
843             thousand leagues beneath the sea.
844            
845            
846             Knorr, Stefan
847             Creepy Crawlies
848             Horror
849             4.95
850             2000-12-06
851             An anthology of horror stories about roaches,
852             centipedes, scorpions and other insects.
853            
854            
855             Kress, Peter
856             Paradox Lost
857             Science Fiction
858             6.95
859             2000-11-02
860             After an inadvertant trip through a Heisenberg
861             Uncertainty Device, James Salway discovers the problems
862             of being quantum.
863            
864            
865             O'Brien, Tim
866             Microsoft .NET: The Programming Bible
867             Computer
868             36.95
869             2000-12-09
870             Microsoft's .NET initiative is explored in
871             detail in this deep programmer's reference.
872            
873            
874             O'Brien, Tim
875             MSXML3: A Comprehensive Guide
876             Computer
877             36.95
878             2000-12-01
879             The Microsoft MSXML3 parser is covered in
880             detail, with attention to XML DOM interfaces, XSLT processing,
881             SAX and more.
882            
883            
884             Galos, Mike
885             Visual Studio 7: A Comprehensive Guide
886             Computer
887             49.95
888             2001-04-16
889             Microsoft Visual Studio 7 is explored in depth,
890             looking at how Visual Basic, Visual C++, C#, and ASP+ are
891             integrated into a comprehensive development
892             environment.
893            
894            
895            
896             );
897              
898             my $profile = qq(
899             catalog
900             lowest
901             __NEW_EXTERNAL_VALUE_HOLDER__ = __external_value__1:number
902             number = external_dataset:__external_value__1
903             book
904             __NEW_DATASET__ = 1 2
905             id = dataset:1,name:custom_id
906             author = dataset:1 dataset:2
907             title = dataset:1 dataset:2
908             genre = dataset:1
909             price = dataset:1
910             publish_date = dataset:1
911             description = dataset:1
912             __EXTERNAL_VALUE__ = __external_value__1:number:1
913             );
914              
915             use Data::Dumper;
916             print Dumper( parse_using_profile( $example_data, $profile ) );
917             }
918              
919             1;
920              
921             # ABSTRACT: Extracts XML into Perl Datasets based upon a simple text profile markup language
922              
923             __END__