File Coverage

blib/lib/Language/DATR/DATR2XML.pm
Criterion Covered Total %
statement 12 416 2.8
branch 0 248 0.0
condition 0 90 0.0
subroutine 4 33 12.1
pod 18 19 94.7
total 34 806 4.2


line stmt bran cond sub pod time code
1             package Language::DATR::DATR2XML;
2             require 5.005_62;
3 1     1   928 use strict;
  1         2  
  1         32  
4 1     1   5 use warnings;
  1         2  
  1         3591  
5            
6             # Author: Lee Goddard
7             # Copyright: Copyright (C) Lee Goddard; GNU GPL: please see end of file.
8             # Filename: DATR2XML.pm
9            
10             our $VERSION = "0.901"; # Updated dist
11             our $MOD_NAME = "DATR2XML.pm "; # Name of this module
12            
13            
14             =head1 NAME
15            
16             DATR2XML.pm - manipulate DATR .dtr, XML, HTML, XML
17            
18             =head1 SYNOPSIS
19            
20             #! perl -w
21             use DATR2XML;
22            
23             undef $DATR2XML::includeNodePath;
24             $datr -> set_stylesheet('D:/DATR/XSLT/datr.xsl');
25            
26             $datr_eg1 = new DATR2XML('D:\DATR\perl\eg.dtr');
27             $datr_eg2 = new DATR2XML('D:/DATR/perl/eg.dtr', "on");
28             $datr_eg3 = new DATR2XML('http://somewhere/doc.dtr', "verbose");
29            
30             viewAll $datr_eg1;
31             $datr_eg2 -> viewHeader;
32            
33             $datr_eg3 -> printHeader;
34             printOpening $datr_eg3;
35             printNodes $datr_eg3;
36             printClosing $datr_eg3;
37            
38             printAll $datr_eg3;
39            
40             save $datr_eg3;
41            
42             DATR2XML::convert('D:\DATR\XSLT\eg_opening.dtr');
43            
44            
45             =head1 DESCRIPTION
46            
47             This module parses into a Perl struct a DATR C<.dtr>-formatted
48             file, as defined in Gerald Gazdar's I<'DATR By Example'> published
49             on the DATR web-pages at the University of Sussex < http://www.sussex.ac.uk/ >.
50            
51             Particular respect was paid to I, though I
52             confess the formal definitions found elsewhere on the site
53             made no sense to me.
54            
55             =head1 LOGGING
56            
57             Process logging may be set to "off", "on" or "true", and "verbose".
58            
59             =head1 REQUIRED MODULES
60            
61             If internet access is required, the following modules must
62             be installed and on the B<@INC> path:
63            
64             LWP::UserAgent
65             HTTP::Request
66            
67             If no internet access is required, these modules will not be called.
68            
69            
70             =head1 DIAGNOSTICS
71            
72             The usual warnings if it can't read or write.
73            
74             =head1 EXPORTS
75            
76             The module exports nothing to the calling namespace.
77            
78             =head1 CAVEATS
79            
80             The module does not fully support The DATR Standard Library RFC, Version 2.20.
81             Specifically, it does not support the use of the proposed I operator as
82             a full-stop within a path: all full stops are taken to signify the end of a clause.
83            
84             =head1 TO DO
85            
86             * Support The DATR Standard Library RFC, Version 2.20
87             * Change mechanism of _parseOpeningClosing to allow
88             line-spanning of contents.
89             * Support interpoloation of directives within body
90             as specified by the style sheet
91             * Fully support comment printing as specified by DATR XML DTD.
92             Currently lumps all comments together.
93            
94            
95            
96             =head1 GLOBAL VARIABLES
97            
98             These variables can adjust the output of the DTR parser:
99             when they are undefined (using C) they
100             prevent the DTR parser from outputing any element which
101             has a default value, as defined in the DATR DTD; when they
102             are defined with any value, they force XML output in full.
103            
104             =item $printComments
105            
106             Set with any value to print comments, C not to.
107            
108             =cut
109            
110             our $printComments = 1;
111            
112             =item $includeNodePath
113            
114             The DTD provides the default path as a null path, but this can
115             adjusted by setting C<$includeSentenceType> to 1. This can
116             be reset by calling C upon the variable.
117             See also I.
118            
119             =cut
120            
121             our $includeNodePath = 1; # Where decimal 1 is true, and undef is false
122            
123             =item $includeSentenceType
124            
125             The DATR DTD provides the default type as C<==>,
126             and this can be left if this variable is set, which is
127             its defualt state. See also I.
128            
129             =cut
130            
131             our $includeSentenceType = 1; # Again, where dec 1 is true, undef is false
132             our $log = 1; # Ditto; minimal logging by default - see Logging
133            
134             =item $location_xsl
135            
136             The path to the required XSLT stylesheet.
137             The default is C.
138             See also the method and procedure I.
139            
140             =cut
141            
142             our $location_xsl = 'd:/DATR/XSLT/datr.xsl';
143            
144             =item $location_dtd
145            
146             The SYSTEM location of (that is, the path to) the DATR DTD.
147             The default is C.
148             See also the method and procedure I.
149            
150             =cut
151            
152             our $location_dtd = 'http://www.leegoddard.com/DATR/DTD/DATR1.0.dtd';
153            
154             =item $datr_root
155            
156             This is literally the root element as printed, and may contain
157             a references, such as to XML schema.
158            
159             Eg:
160             $datr_root = '';
161            
162             The defualt is simply the opening of the C element.
163             See also I.
164            
165             =cut
166            
167             # $datr_root = '';
168             our $datr_root = '';
169            
170             # System varialbes
171            
172             $| = 1; # Autoflush so STDOUT/STDERR output in chronological order
173            
174            
175            
176            
177            
178             =head1 PUBLIC METHODS
179            
180             =cut
181            
182             =head2 Constructor (new)
183            
184             Creates a new DATR2XML object from file, URI or DATR C<.dtr> source.
185            
186             Accepts: DATR source as scalar, array, scalar/array pointer, or path to a DATR file.
187             If source is scalar or pointer to a scalar, is assumed to be just a list
188             of node definitions, of BODY slot.
189            
190             Optionally accepts a second argument to set logging: see the manual entry
191             for the logging method for details.
192            
193             Returns: reference to object.
194            
195             Object Structure: a hash with the following fields:
196            
197             LOCATION - the name of the file, if any
198            
199             HEADER - the file header (as defined in datrnode44.html#fileheader)
200            
201             OPENING - opening declarations/directives as defined in datrnode45.html#openingdeclarations
202            
203             BODY - node defintions,itself an array of hashes of the format defined in _parseNodes
204            
205             CLOSING - clsoing declarations/directives as defined in datrnode47.html#closingdeclarations
206            
207             =cut
208            
209             sub new{
210 0     0 0   my $pkg = shift; # Get the package/class reference
211 0           my $self = {}; # Define this object
212 0           bless $self,$pkg; # explicitly within this package/class
213            
214             # Reset logging if passed: do now so errors appear after titles
215 0 0         if ($_[1]){ logging($_[1]) }
  0            
216            
217             # Dereference constructor arguments if necessary
218 0 0         if (ref $_[0] =~ /(HASH)/) { # Is a reference to a HASH
    0          
    0          
219 0           die "\nInvalid attempt to construct datr object using a hash reference:\nplease supply a literal scalar or reference to such, an array or a .dtr filename.";
220             }
221             elsif (ref $_[0] eq "SCALAR") { # Is a reference to a scaler
222 0           @_ = ${$_[0]}; # so dereference
  0            
223             }
224             elsif (ref $_[0] =~ /(ARRAY)/) { # Is a reference to an array{
225 0           @_ = @$_[0]; # so coerce dereferenced array to string
226             } # Otherwise assume an string or array passed
227            
228             # Create object 'slots' / struct
229 0           $self -> {OPENING} = [];
230 0           $self -> {HEADER} = {};
231 0           $self -> {BODY} = [];
232 0           $self -> {CLOSING} = [];
233            
234             # Load from internet if necessary
235 0 0         if ($_[0] =~ m|^http://|i) { # Is a URI, possibly ending .dtr
    0          
236 0           $self->{LOCATION} = $_[0];
237 0           @_ = $self -> _loadURI;
238             }
239             # Load from file system if necessary
240             elsif ($_[0] =~ /.*\.dtr/i) { # Is a filepath
241 0           $self->{LOCATION} = $_[0];
242 0           @_ = $self -> _loadFile;
243             }
244            
245 0           $self -> _parseHeader (\@_); # Set self contents
246 0           $self -> _parseOpening (\@_); # "" ""
247 0           $self -> _parseNodes (\@_); # "" ""
248 0           $self -> _parseClosing (\@_); # "" ""
249            
250 0           return $self;
251             } # End sub new
252            
253            
254            
255            
256            
257            
258             =head2 include_sentence_type
259            
260             Sets or resets the C attribute of
261             C elements.
262            
263             Calling with an argument value of C<1> includes the
264             C attribute (I); calling with C<0> forces
265             the C attribute to be omitted.
266            
267             =cut
268            
269             sub include_sentence_type{
270 0 0   0 1   shift if ref $_[0] eq "REF"; # remove (object) ref if called as method
271 0 0         if ($_[0]==1){
    0          
272 0           $includeSentenceType = 1;
273 0 0         print "Shall now print the type attribute os EQUATION sentence type." if $log;
274             }
275             elsif ($_[0]==0) {
276 0           undef $includeSentenceType;
277 0 0         print "Shall not print the type attribute of EQUATION elements." if $log;
278             }
279 0           else { die
280             "You attempted to set the EQUATION element's
281             type attribute, but did not supply a correct
282             value. Please use an argument of 1 to include,
283             0 to ommit.";
284             }
285             }
286            
287            
288            
289             =head2 print_comments
290            
291             Call without a value to stop comment printing;
292             call with a value to restart comment printing.
293             Default is to print comments.
294            
295             =cut
296            
297             sub print_comments{
298 0 0   0 1   shift if ref $_[0] eq "REF"; # Remove (object) reference if called as method
299 0 0         if ($_[0] eq ("" or undef)){
300 0           undef $printComments;
301 0 0         print "Comment printing turned off.\n" if $log; # Notify user if logging
302             }
303             else {
304 0           $printComments= $_[0];
305 0 0         print "Comment printing turned on.\n" if $log; # Notify user if logging
306             }
307             }
308            
309            
310            
311            
312             =head2 set_stylesheet
313            
314             Sets the path to the required XSLT stylesheet.
315             See also I in the section I.
316            
317             =cut
318            
319             sub set_stylesheet{
320 0 0   0 1   shift if ref $_[0] eq "REF"; # Remove (object) reference if called as method
321 0 0         if ($_[0] eq ("" or undef)){ die
  0            
322             'You tried to set the stylesheet location without specifiying a value.
323             http://www.leegoddard.com/DATR/XSLT/datr.xsl;
324             http://www.leegoddard.com/DATR/XSLT/datrHTML.xsl;
325             http://www.leegoddard.com/DATR/XSLT/prolog.xsl;
326             ';
327             }
328 0           $location_xsl = $_[0];
329 0 0         print "Set stylesheet location to$_[0].\n" if $log; # Notify user if logging
330             }
331            
332            
333            
334            
335             =head2 set_dtd
336            
337             Sets the location of the DTD as used in the DOCTYPE SYSTEM declaration.
338             See also I in the section I.
339            
340             =cut
341            
342             sub set_dtd{
343 0 0   0 1   shift if ref $_[0] eq "REF"; # Remove (object) reference if called as method
344 0 0         if ($_[0] eq ("" or undef)){
345 0           die
346             "You tried to set the location of the DATR DTD without specifiying a value.
347             The default is http://www.leegoddard.com/DATR/DTD/DATR1.0.dtd\n";
348             }
349 0           $location_dtd = $_[0];
350 0 0         print "Set XML DTD location to $_[0].\n" if $log; # Notify user if logging
351             }
352            
353             =cut
354            
355            
356             =head2 set_schema
357            
358             Sets the location of the XML Schema as used in the root element.
359             If called with no arguemnt value, removes all references to an
360             XML Schema, setting C<$datr_root> to the opening of the DATR
361             root tag without attributes.
362            
363             Calling with a value of C<1> sets the Schema to the author's,
364             located at C.
365             See also I in the section I.
366            
367             =cut
368            
369             sub set_schema{
370 0 0   0 1   shift if ref $_[0] eq "REF"; # Remove (object) reference if called as method
371 0 0         if ($_[0] eq ("" or undef)){
    0          
372 0           $datr_root = "";
373 0 0         print "Removed reference to an XML Schema" if $log; # Notify user if logging
374             }
375             elsif ($_[0] == 1){
376 0           $datr_root = "http://www.leegoddard.com/DATR/DTD/DATR1.0.xml";
377 0 0         print "Set XML Schema location to $_[0].\n" if $log; # Notify user if logging
378             }
379             else {
380 0           $datr_root = $_[0];
381 0 0         print "Set XML Schema location to $_[0].\n" if $log; # Notify user if logging
382             }
383 0 0         print "Set XML Schema location to $_[0].\n" if $log; # Notify user if logging
384             }
385            
386             =cut
387            
388            
389            
390            
391            
392             =head2 logging
393            
394             Turns logging off or on, verbose or minimal.
395            
396             Accepts: "true|on|minimal" or "verbose" or "off|none|silent"
397             Returns: None
398            
399             =cut
400            
401             sub logging{
402 0 0   0 1   shift if ref $_[0] eq "REF"; # Remove object reference if passed
403 0 0 0       if ($_[0] eq "on" or $_[0] eq "true" or $_[0] eq "minimal"){
    0 0        
404 0           $log = "true";
405             }
406             elsif ($_[0] eq "verbose") {
407 0           $log = "verbose";
408             }
409             # Undefine the variable for 'silent' mode with no output
410 0           else { undef $log }
411             # Output program ID if logging of any kind
412 0 0         if ($log) {
413 0           print "This is $MOD_NAME called by ";
414 0           $0 =~ /.*(\/|\\)+?(.*)$/;
415 0           print "$2.\nCopyright (C) Lee Goddard 2000. All Rights Reserved.\n",
416             }
417             # Output logging state after program ID.
418 0 0         if ($log eq "true"){ print "Minimal logging activated.\n";}
  0 0          
419 0           elsif ($log eq "verbose") { print "Verbose logging activated.\n"; }
420             }
421            
422            
423            
424            
425            
426            
427            
428             =head2 viewAll
429            
430             Provides a rough printout of all records
431            
432             Accepts: object ref;
433             Returns: none
434            
435             =cut
436            
437             sub viewAll {
438 0     0 1   my $self = shift;
439 0           my $t = localtime;
440 0           print "\n==================================\n",
441             "|| DATR DTR DUMP ||\n",
442             "==================================\n",
443             "Document location:\n\t";
444 0 0         if ($self->{LOCATION} ne ""){ print $self->{LOCATION} }
  0            
445 0           else { print "a direct call." }
446 0           print "\nConversion time: $t.\n",
447             "==================================\n";
448 0           $self -> viewHeader;
449 0           print "==================================\n";
450 0           $self -> viewOpening;
451 0           print "==================================\n";
452 0           $self -> viewNodes;
453 0           print "==================================\n";
454 0           $self -> viewClosing;
455 0           print "=============================[END]\n\n";
456             }
457            
458            
459            
460            
461            
462            
463             =head2 viewHeader
464            
465             Provides a rough printout of all nodes
466            
467             Accepts: object ref;
468             Returns: none
469            
470             =cut
471            
472             sub viewHeader {
473 0     0 1   my $self = shift;
474 0           print "File header:\n";
475 0           foreach (keys %{ $self->{HEADER} }){
  0            
476 0           print "\t$_ : ",
477             $self->{HEADER}->{$_},
478             "\n";
479             }
480 0           print "End of file header.\n";
481             }
482            
483            
484            
485            
486            
487            
488            
489            
490             =head2 viewOpening
491            
492             Provides a rough view of the opening directives/definitions
493            
494             Accepts: object ref;
495             Returns: none
496            
497             =cut
498            
499             sub viewOpening {
500 0     0 1   my $self = shift;
501 0 0         if (@{$self->{OPENING}}){
  0            
  0            
502 0           print "Opening declarations and directives:\n";
503 0           foreach (@{$self->{OPENING}}){ print "\t$_\n"; }
  0            
  0            
504 0           print "End of opening.\n";
505             }
506             else {print "Neither opening declarations nor directives present.\n";}
507             }
508            
509            
510            
511            
512            
513            
514            
515             =head2 viewClosing
516            
517             Provides a rough view of the closing directives/definitions
518            
519             Accepts: object ref;
520             Returns: none
521            
522             =cut
523            
524             sub viewClosing {
525 0     0 1   my $self = shift;
526 0 0         if (@{$self->{CLOSING}}){
  0            
  0            
527 0           print "Closing declarations and directives:\n";
528 0           foreach (@{$self->{CLOSING}}){ print "\t$_\n" }
  0            
  0            
529 0           print "End of closing.\n";
530             }
531             else {print "Neither closing declaration nor directives present.\n";}
532             }
533            
534            
535            
536            
537            
538            
539             =head2 viewNodes
540            
541             Provides a rough printout of all nodes
542            
543             Accepts: object ref;
544             Returns: none
545            
546             =cut
547            
548             sub viewNodes {
549 0     0 1   my $self = shift;
550 0           foreach my $hash (@{$self->{BODY}}){
  0            
551 0           foreach ( keys %$hash){ print "$_\t$$hash{$_}\n";}
  0            
552 0           print "----------------------------------\n";
553             }
554             }
555            
556            
557             #-- X M L O U T P U T R O U T I N E S ---------------------------------------------
558            
559            
560            
561            
562             =head2 save
563            
564             Saves to local filesystem an XML printout of all records
565            
566             Accepts: object ref;
567             optional file path to save at
568             or, for internal use, typeglob for PERL filehandle.
569             Returns: none
570             Notes: simply calls printAll, passing filehandle if necessary.
571            
572             =cut
573            
574             sub save {
575 0     0 1   my $self = shift;
576 0           $self -> printAll(shift);
577             } # End sub printAll
578            
579            
580            
581            
582            
583            
584             =head2 convert
585            
586             Convert one or more DATR files to XML.
587            
588             Accepts: I:
589             a filepath with an extension,
590             optionally with an additional destination filepath or directory,
591             I
592             for batch operation, a directory location.
593             Returns: nothing, will die on errors
594             Notes: Does not accept URLs and does not process sub-directories.
595             Minimizes logging during operation.
596            
597             =cut
598            
599             sub convert{
600 0     0 1   my @sourceFiles = shift; # Re-fill if batch
601 0           my $destination = ""; # Destination path for converted data
602 0           my $sourceDir = ""; # Dir of source, possibly first arg
603 0           my $localLog = ""; # Stores state of globabl $log for duration
604            
605 0 0         $destination = shift if $_[0]; # Take a second argument if present
606            
607 0 0         if ($sourceFiles[0] =~ /^http:\/\//){ # if URL passed as first argument
608 0           die "\nDATR2XML::convert does not accept URLs.\n"; # quit the script
609             }
610            
611 0 0         if ($log) { # If package's logging has been set,
612 0           $localLog = $log; # store for restoration on exiting the sub
613 0           $log = ""; # and replace it to minimize output on this routine
614             }
615            
616             # If first argument is a directory path, get batch of filenames:
617 0 0         if (-d $sourceFiles[0]){
618 0           $sourceDir = $sourceFiles[0]; # Store for append later
619 0           opendir DIR, $sourceDir;
620 0           @sourceFiles = grep /\.dtr$/, readdir DIR;
621 0 0         print "Batch processing...." if $localLog;
622 0 0         print "\n" if $localLog;
623             }
624            
625 0           foreach my $sourcePath (@sourceFiles) { # Process all
626 0 0         if ($sourceDir ne ""){ $sourcePath = $sourceDir."/".$sourcePath }
  0            
627             # Warn if loading a file with an xml extension: I do it all the time in error....
628 0 0         if ($sourcePath=~/\.xml$/i){
629 0           warn "** Loading a file with an XML extension:\n $sourcePath.\n";
630             }
631            
632 0           my $datr = new DATR2XML($sourcePath); # Create a DATR-file object, no logging
633            
634             # If the destination wasn't specified as the second argument
635 0 0         if ($destination eq ""){
636             # Destination filepath is source filepath stripped of extension
637 0           $sourcePath =~ /(.*)\.(?=[\w()-]*)/;
638 0           $destination = $& . "xml"; # and with xml extension added
639             }
640 0           $datr -> printAll($destination); # Convert and save to destination path
641 0 0         print "Saved file $destination\n" if $localLog;
642 0           $destination = undef; # Nullify for possible next pass
643 0           $datr = undef;
644             }
645 0 0         print "...done.\n" if $localLog;
646            
647             # Restore package's loggging
648 0           $log = $localLog;
649             } # End0-sub convert
650            
651            
652            
653            
654            
655            
656            
657             =head2 printAll
658            
659             Provides an XML printout of all records
660            
661             Accepts: object ref;
662             optional file path to save at.
663             or, for internal use, typeglob for PERL filehandle
664             Returns: none
665            
666             =cut
667            
668             sub printAll {
669 0     0 1   my $self = shift; # Collect object reference
670             # Set up the output stream, file or STDOUT
671 0           my $FH = _setupOutput(shift);
672             # Print XML declaration and open DATR - may add encoding="ISO-8859-1" or such here.
673 0           print $FH <<"__STOP_PRINTING__";
674            
675            
676            
677            
678             $datr_root
679             __STOP_PRINTING__
680 0           $self -> printHeader ($FH); print $FH "\n\n";
  0            
681 0           $self -> printOpening ($FH); print $FH "\n\n";
  0            
682 0           $self -> printNodes ($FH); print $FH "\n\n";
  0            
683 0           $self -> printClosing ($FH); print $FH "\n\n\n\n";
  0            
684 0           close $FH;
685 0 0         print "Done.\n" if $log;
686            
687             } # End sub printAll
688            
689            
690            
691            
692            
693            
694            
695            
696             =head2 printHeader
697            
698             Provides an rough printout of all nodes
699            
700             Accepts: object ref;
701             optional file path
702             or, for internal use, typeglob for PERL filehandle
703             Returns: none
704            
705             =cut
706            
707             sub printHeader {
708 0     0 1   my $self = shift;
709 0   0       my $FH = shift || *STDOUT; # Output FileHandle to arg2 or standard
710             # The time, for insertion into the file
711 0           my $t = localtime;
712             # Start with this script's META details
713 0           print $FH "
\n",
714             "\t\n",
715             "\t\n";
716 0           print $FH "\t\n";
720             # Continue with DATR file's details
721 0           foreach (keys %{$self->{HEADER}}){
  0            
722 0           print $FH "\t\n";
723             }
724 0           print $FH "\n";
725             }
726            
727            
728            
729            
730             =head2 printOpening; printClosing
731            
732             Provides an XML printout of the opening/closing directives/definitions block element.
733             Without passing a filepath or typeglob for filehandle, outputs to STDOUT.
734             Just a wrapper for _printOpeningClosing.
735            
736             Accepts: object ref;
737             optionally a file path
738             or, for internal use, typeglob for PERL filehandle
739             Returns: none
740            
741             =cut
742            
743             sub printOpening {
744 0     0 1   my $self = shift; # Collect object reference
745 0   0       my $FH = shift || *STDOUT; # Output FileHandle to arg2 or standard
746 0           $self -> _printOpeningClosing($FH,"OPENING");
747             }
748            
749             sub printClosing {
750 0     0 1   my $self = shift; # Collect object reference
751 0   0       my $FH = shift || *STDOUT; # Output FileHandle to arg2 or standard
752 0           $self -> _printOpeningClosing($FH,"CLOSING");
753             }
754            
755            
756            
757            
758            
759             =head2 printNodes
760            
761             Provides an XML printout of all nodes.
762             Basically writes the EQUATION element and calls
763             C<_parsePath> on each value of the object's C<{BODY}> key.
764            
765             Accepts: object ref
766             Returns: none
767            
768             =cut
769            
770             sub printNodes {
771 0     0 1   my $self = shift; # Collect object reference
772 0   0       my $FH = shift || *STDOUT; # Output FileHandle to arg2 or standard
773 0           my $i = -1; # Index to comment array
774             # See &_parseNodes() for details of comment.
775             # Only print comments if flag is set, if they exist as more than whitespace
776 0 0 0       if ($printComments and $self->{COMMENT} and $self->{COMMENT}!~/^\s*$/){
      0        
777 0           print $FH "\n";
778 0           print $FH $self->{COMMENT};
779 0           print $FH "\n\n\n";
780             }
781 0           foreach my $sentence (@{$self->{BODY}}){
  0            
782             # See &_parseNodes() for details of comment.
783             # $i++;
784             # print "i = $i\n";
785             # if ($$sentence{COMMENT}[$i]){
786             # print "commented line $i of ",$#{$$sentence{COMMENT}},"\n";
787             # print $FH "$$sentence{COMMENT}[$i]\n";
788             # }
789 0           print $FH "
790 0 0         if ($includeNodePath){ print $FH "path=\"$$sentence{PATH}\" "}
  0            
791             # if ($includeSentenceType){
792             # print $FH "type=\"";
793             # if ($node{TYPE} and $node{TYPE} eq "="){ print $FH "EXTEND"}
794             # else { print $FH "DEFINE" }
795             # print $FH '"';
796             # }
797 0           print $FH ">\n", _parsePath( \$$sentence{VALUE},\$$sentence{NODE} );
798 0           print $FH "\n\n";
799             }
800             }
801            
802            
803            
804            
805            
806             ###########################################################################################
807            
808             #
809            
810            
811             =head1 PRIVATE METHODS
812            
813             I
814            
815             =cut
816            
817            
818            
819             =head2 _loadFile (private method)
820            
821             Load a dtr file from the local file system.
822            
823             Accepts: object reference
824             Returns: an array of file contents
825            
826             =cut
827            
828             sub _loadFile {
829 0     0     my $self = shift;
830             # Check filename present
831 0 0         if (!$self->{LOCATION}){
832 0           die "\nAttempted to load a file without specifying a filename.\n";
833             }
834             # Explicitly state if file does not exist
835 0 0         if (!-e $self->{LOCATION}){
836 0           die "File $self->{LOCATION} does not exist.\n";
837             }
838 0 0         print "Loading $self->{LOCATION}... " if $log;
839 0 0         open IN,$self->{LOCATION} or die "\nError loading $self->{LOCATION}.\n";
840 0           @_ = ;
841 0           close IN;
842 0 0         print "okay.\n" if $log;
843 0           return @_;
844             }
845            
846            
847            
848            
849            
850            
851            
852             =head2 _loadURI (private method)
853            
854             Load a dtr document from a URI
855            
856             Accepts: object reference
857             Returns: an array of file contents
858            
859             =cut
860            
861             sub _loadURI {
862 0     0     my $self = shift;
863 0 0         if (!$self->{LOCATION}){
864 0           die "\nAttempted to load from the net without specifying a URI.\n";
865             }
866 1     1   5671 use LWP::UserAgent;
  1         67717  
  1         40  
867 1     1   12 use HTTP::Request;
  1         2  
  1         16604  
868 0           my $ua = new LWP::UserAgent; # Create a new UserAgent
869 0           $ua->agent('Mozilla/25.0 (DATR-Agent'); # Give it a type name
870 0 0         print "Attempting to access $self->{LOCATION}..." if $log;
871             # Format URL request
872 0           my $req = new HTTP::Request('GET', $self->{LOCATION});
873 0           my $res = $ua->request($req);
874 0 0         if (!$res->is_success()) { die "failed.\n"}
  0            
875 0 0         else { print "okay." if $log }
876 0           return $res->content; # Return content retrieved
877             }
878            
879            
880            
881            
882            
883            
884            
885             =head2 _parseHeader (private method)
886            
887             Parses a C<.dtr>-format file header into the class record
888            
889             Accepts: object ref;
890             Returns: none
891             Struct: This method fills the hash held in $self->{HEADER}
892             with whatever fields the C<.dtr> file header contains that match
893             a name/value pair delimited with a colon.
894            
895             =cut
896            
897             sub _parseHeader {
898 0     0     my $self = shift; # Collect method's object reference
899             # Do not de-ref second argument
900 0 0         print "Parsing header....\n" if $log;
901             # Loop file until a line with no comment exists: quick and dirty
902             # Could use for/last-if, but this is faster.
903 0           while (@{$_[0]}[0] =~ m/^\s*?%/){
  0            
904 0           shift(@{$_[0]}) =~ / # Match
  0            
905             \s* # Maximum number of spaces
906             % # The DATR comment symbol
907             \s* # Maximum number of spaces
908             # Group 1 - field name
909             ([\w\s\.,()-]*?) # Any number of words, sapces or symbols listed
910             : # before a colon
911             \s* # Maximum number of spaces
912             # Group 2 - field value
913             ([\w\s\.,()-]*?) # Any number of words, sapces or symbols listed
914             \s* # Maximum number of spaces
915             % # The DATR comment symbol
916             \s*? # Minimum number of spaces
917             \n # A new-line, return or form-feed
918             /sgox; # compile Once
919 0 0 0       if ($1 and $2) {
920 0           my $key = uc $1; # Make hash key uppercase
921 0           my $value = $2; # $2 will be lost with substitution below
922 0           $key =~ s/\s/_/sg; # replace whitespace with u/score
923 0           $self->{HEADER}->{$key}= $value;
924 0 0         print "\t$key:\t$value\n" if $log eq "verbose";
925             # Grab any copyright notice and make a hash key
926             }
927             #elsif ($d[0]=~/(Copyright\s\(C\)|\(C\)\sCopyright)\s*(.*?)[.]+/i) {
928             # print "\tCOPYRIGHT_RESERVED:\t$2\n" if $log eq "verbose";
929             # $self->{HEADER}->{"COPYRIGHT_RESERVED"} = $2;
930             #}
931             } # WHend
932 0 0         print "Finished parsing header.\n" if $log;
933             } # End sub _parseHeader
934            
935            
936            
937            
938            
939            
940            
941             =head2 _parseOpening (private method)
942            
943             Extracts opening directives, those occuring B node definitions,
944             and places them into the self-object's OPENING array.
945            
946             Accepts: object ref, ref to DATR data
947             Returns: none
948            
949             =cut
950            
951             sub _parseOpening{
952 0     0     my $self=shift; # Collect method's object reference
953             # Don't dereference DATR data from 2nd argument
954 0           my $lastMatch;
955 0 0         print "Extracting opening directives and definitions....\n" if $log;
956 0           LOOP:
957 0           foreach (@{$_[0]}){ # Loop through whole file
958 0 0 0       next LOOP if $_ eq "" or /^\s*$/;
959 0 0         last LOOP if /^\s*\w*\s*:/; # End if found a node def a line start
960 0           m/ # Match
961             ^\s*\#\s* # At start of scalar, whitespace surronding a directive symbol
962             ( # And store as GROUP 1
963             [\w\s=\$,:"<>-]* #" any number of characters in this class
964             )
965             \s*
966             '?(\w*\.\w*)*'? # In group 2 maybe a single-quoted filename
967             \s*
968             (?!\#) # Catch directives without full-stop terminator
969             \. # Ending in a comment or linefeed of some kind, inc. DATR
970             /ox; # single compile, ignore whitespace
971            
972 0 0         if ($1 ne $lastMatch){
    0          
973 0           $lastMatch = $1; # Prevent duplicates/null finds (nonupdated $1)
974 0 0         if ($2) {$lastMatch .= " $2";}
  0            
975 0           push @{$self->{OPENING}}, $lastMatch; # Store the atomised match
  0            
976 0 0         print "\t$lastMatch\n" if $log eq "verbose";
977             }
978             # elsif (/^\s*\w*[:<]/) { # If the line begins with a node-definition
979             # last LOOP; # then stop looking in the opening
980             # } # Now in first case: faster, but better?
981             elsif (!/^[%\n\r\f]*/) { # Catch source errors, not comments/blanks
982 0 0         print "** Ignoring malformed DATR directive in OPENING: $_\n" if $log;
983             }
984             }
985 0 0         print "Finished extracting opening declarations and directives.\n" if $log;
986             }
987            
988            
989            
990            
991            
992            
993             =head2 _parseClosing (private method)
994            
995             Extracts closing directives, those occuring B node definitions
996            
997             Accepts: object ref; reference to array of DATR data
998             Returns: none
999             Notes: reverses @_ then applies same proc as _parseOpening, then reverses output
1000            
1001             =cut
1002            
1003             sub _parseClosing{
1004             # This has been a swine to write, because directives such
1005             # as show can span lines. We now assume that the
1006             # DATR Stylesheet is implimented: see
1007             # www.datr.org/datrnode38.html, "Style sheet for DATR dtr files"
1008             # Specifically, we rely on the RCS Archive ID comment as defined
1009             # in the stylesheet www.datr.org/datrnode48.html -- at least
1010             # we rely on a comment line appearing as the last element of a file.
1011            
1012 0     0     my $self=shift; # Collect method's object reference
1013             # Don't dereference DATR data from 2nd argument
1014 0           my $lastMatch;
1015 0 0         print "Extracting closing directives and definitions....\n" if $log;
1016 0           LOOP:
1017 0           foreach (reverse @{$_[0]}){ # Loop through whole file
1018 0 0 0       next LOOP if $_ eq "" or /^\s*$/;
1019 0 0         last LOOP if /^\s*\w*\s*:/; # End if found a node def a line start
1020 0           m/ # Match
1021             ^\s*\#\s* # At start of scalar, whitespace surronding a directive symbol
1022             ( # And store as GROUP 1
1023             [\w\s=\$,:"<>-]* #" any number of characters in this class
1024             )
1025             \s*
1026             ('?\w*\.\w*'?)* # In group 2 maybe a single-quoted filename
1027             \s*
1028             (?!\#) # Catch directives without full-stop terminator
1029             \. # Ending in a comment or linefeed of some kind, inc. DATR
1030             /ox; # single compile, ignore whitespace
1031            
1032 0 0         if ($1 ne $lastMatch){
    0          
    0          
1033 0           $lastMatch = $1; # Prevent duplicates/null finds (nonupdated $1)
1034 0 0         if ($2) {$lastMatch .= " $2";}
  0            
1035 0           unshift @{$self->{CLOSING}}, $lastMatch; # Store the atomised match
  0            
1036 0 0         print "\t$lastMatch\n" if $log eq "verbose";
1037             }
1038             elsif (/^\s*\w*[:<]/) { # If the line begins with a node-definition
1039 0           last LOOP; # then stop looking in the opening
1040             } # Now in first case: faster, but better?
1041             elsif (!/^[%\n\r\f\s]$/) { # Catch source errors: not comments/blanks
1042 0 0         print "** Ignoring malformed DATR directive in CLOSING: $_\n" if $log;
1043             }
1044             }
1045 0 0         print "Finished extracting closing delcarations and directives.\n" if $log;
1046             } # End-sub _parseClosing
1047            
1048            
1049            
1050            
1051            
1052            
1053            
1054             =head2 _parseNodes (private method)
1055            
1056             Parse a list of nodes to the class BODY record.
1057            
1058             Accepts: an obj ref and an reference to an array
1059             of DATR data
1060             Returns: none
1061             Struct: This method creates the array of hashes held in $self->{BODY}
1062             with the following fields:
1063            
1064             NODE - the name of the current node
1065             PATH - the (left-hand) path
1066             TYPE - the sentence-type signifier: = or ==
1067             VALUE - the (right-hand) value
1068             COMMENT - an array of comments, index reflecting source line number
1069            
1070             =cut
1071            
1072             sub _parseNodes {
1073 0     0     my $self = shift; # Collect method's object reference
1074 0           my %node;
1075 0           my ($last_line, $last_comment);
1076 0           my $i; # Index to comment array
1077 0 0         print "Parsing nodes....\n" if $log;
1078            
1079             # To support the DATR XML DTD, comments that appear on a line
1080             # by themselves should be contained in a COMMENT element;
1081             # blocks of such should be combined in a single COMMENT element.
1082             # Comments which appear at the end of a line should be included
1083             # in the comment attribute of the last element issued.
1084             # The code below goes part way to this effect, but a rewriting
1085             # of the parser regex is needed along the lines of
1086             # an array gained from the matcher: @_ = m/(groups1-5)/
1087             # with optional groups for the comment at every juncture.
1088             # There's just not enough time right now.
1089             # See also &printNodes()
1090            
1091             # From the DATR, separate comments and the data minus line breaks:
1092 0           foreach(@{$_[0]}){
  0            
1093 0           $i++; # Increment comment array index
1094 0 0         next if not /%/; # Next if no comment: improves speed
1095 0           m/^(.*?)%\s*(.*?)$/o; # Put DATR in group 1, comments in Group 2
1096 0 0         if ($last_comment ne $2) { # If group 3 found a NEW comment
1097             # $node{COMMENT}[$i].="$2 "; # Add new comment with space for supliment
1098 0           $self->{COMMENT}.="$2\n"; # Add new comment with space for supliment
1099 0           $last_comment = $2; # Remember this comment
1100 0 0         if (/^%/){ $_="" } # Catch and stop single line comments
  0            
1101             }
1102 0 0         if ($last_line ne $1) { # If group 3 found a NEW comment
1103 0           $_ = $1;
1104 0           $last_line = $1; # Remember this comment
1105             }
1106             }
1107            
1108             # From the DATR, gather node, path, type symbol and path-value.
1109 0           $_ = join "",@{$_[0]};
  0            
1110 0           while (m/ # Match all occurances
1111             \s*? # Any number of formatting spaces.
1112             # GROUP 1 - optional node name group:
1113             (
1114             [\w]+?[\w\s]*? # Begin with a letter, then any number of words or spaces
1115             (?!<[\w\s]*?>) # that are not right-angle and
1116             : # are before required colon: chop this later (POOR)
1117             )*? # The group is optional
1118             \s*? # Any number of formatting spaces.
1119             # GROUP 2 - the left path, may be empty:
1120             <([\w\s]*?)> # Optional Words or spaces within required angle brackets
1121             \s*? # Any number of formatting spaces.
1122             # GROUP 3 - relationshiop signifier:
1123             (={1,2}) # One or two equality signs
1124             \s* # Any number of formatting spaces.
1125             # GROUP 4 - the value, anything at all
1126             (.*?)
1127             \s* # Any number of formatting spaces.
1128             # TERMINATOR - non-stored group.
1129             (?= # Don't match ending
1130             [.] # with a point
1131             | # or
1132             (?= # a path type definition
1133             <[\w\s]*?>\s*?={1,2} # as Groups 2 and 3
1134             )
1135             )
1136             /gsxo # Search globably, stating where left off, with extended source formatting
1137             ){
1138             # Create hash to push to object; only change node name if new node name present
1139             # Future Expansion: possibly force ucfirst for DATR syntax, depending on switch?
1140 0 0         if ($1) {
1141 0           $node{NODE} = $1;
1142 0           chop $node{NODE}; # Remove trailing whitespace
1143             }
1144 0           $node{PATH} = $2;
1145 0           $node{TYPE} = $3;
1146             # Strip trainling whitespace
1147 0           ($node{VALUE}=$4) =~ s/\s+$//g;
1148             # Error messages for malformed DATR
1149 0 0         if ($5) {warn "*** Malformed DATR source: \n\tParse Error (Group 5 showed $5)\n";}
  0            
1150 0 0         if ($6) {warn "*** Malformed DATR source: \n\tParse Error (Group 6 showed $6)\n";}
  0            
1151 0           push @{$self->{BODY}}, {%node};
  0            
1152             } # Whend
1153 0 0         print "Finsished parsing nodes.\n" if $log;
1154             } # End sub _parseNodes
1155            
1156            
1157            
1158            
1159            
1160            
1161            
1162             =head2 _parsePath (private pseudo-method)
1163            
1164             Decodes path attributes into an XML structure.
1165            
1166             Accepts: a string of DATR path (as in $$hash{VALUE});
1167             optionally a second argument, being the name of a node to
1168             build-out the sentence (cf. geraldg@cogs.susx.ac.uk, 06/07/00)
1169             Returns: a string of XML structure
1170             Notes: a bit of a hack, really.
1171            
1172             =cut
1173            
1174             sub _parsePath{
1175 0     0     my $nodeValue = shift; # reference to the operand
1176 0           $nodeValue = $$nodeValue; # de-ref
1177 0 0         my $nodeName = shift if $_[0]; # name of node if present (as POD above)
1178 0           $nodeName = $$nodeValue; # de-ref
1179             # Reference ot chars in string, for speed
1180 0           my ($next,$last) = "";
1181             # Stack of currently open elements
1182 0           my @open;
1183             # Buffer to store output during parse passes
1184             my $out;
1185             # Character equivelents for first pass substitution
1186 0           my $openQuote = "£l£g££11";
1187 0           my ($openPath, $closePath) = ("£l£g££12", "£l£g££13");
1188 0           my ($openQuotedPath, $closeQuotedPath) = ("£l£g££14", "£l£g££15");
1189 0           my ($openNodePath, $closeNodePath) = ("£l£g££16", "£l£g££17");
1190 0           my ($openQuotedNodePath, $closeQuotedNodePath) = ("£l£g££18", "£l£g££19");
1191            
1192             # First parse
1193 0           for my $i (0 .. length $nodeValue){ # Iterate (through the first argument)
1194 0 0         if ($i>0) { # Avoid negative indexing on first interation
1195 0           $last = substr($nodeValue,$i-1,1);
1196             }
1197 0           else { $last = ""; }
1198            
1199 0           my $this = substr $nodeValue,$i,1; # Take the curent character
1200 0 0         if ($i
1201 0           $next = substr $nodeValue,$i+1,1;
1202             }
1203 0           else { $next = ""; }
1204            
1205             # Cases:
1206            
1207             # query: full XML element inserted
1208 0 0 0       if ($this eq "?"){
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
      0        
1209 0           $out .= "";
1210             }
1211             # open node-path N:<0>
1212             elsif ($this eq "<" and $last eq ":" and $open[$#open] ne $openQuote) {
1213             # Add code to $out, for final pass substitution
1214 0           $out.=$openNodePath;
1215 0           push @open, $openNodePath;
1216             }
1217             # open quoted node-path PART 1 - "N:<0>"
1218             elsif ($this eq "<" and $last eq ":" and $open[$#open] eq $openQuote) {
1219             # Add code to $out in place of :<, for final pass substitution
1220 0           $out.=$openQuotedNodePath;
1221 0           pop @open; # remove 'openQuote' from stack
1222 0           push @open, $openQuotedNodePath;
1223             }
1224            
1225             # open path <0>
1226             elsif ($this eq "<" && $last ne ":" && $last ne '"') {
1227 0 0         if ($nodeName){ # If node name was passed as arg
1228 0           $out.= $nodeName.$openNodePath; # make this a node path
1229 0           push @open, $openNodePath;
1230             }
1231             else {
1232 0           $out.= $openPath;
1233 0           push @open, $openPath;
1234             }
1235             }
1236             # open quoted-path "<0>"
1237             elsif ($this eq '"' && $next eq "<") {
1238 0           $out.= $openQuotedPath;
1239 0           push @open, $openQuotedPath;
1240             }
1241             # open quoted node-path PART 2 - "N:<0>"
1242             elsif ($this eq '"' and $next=~/\w/) {
1243             # $out.= $openQuote; # leave for 2nd parse
1244 0           push @open, $openQuote;
1245             }
1246             # Characters to ignore, as used above
1247             elsif ($this eq "<" or $this eq ":") {
1248             # Already dealt with, so don't add
1249             }
1250             # close node-path
1251             elsif ($this eq ">" && $open[$#open] eq $openNodePath) {
1252 0           $out.= $closeNodePath;
1253 0           pop @open;
1254             }
1255             # close quoted node-path
1256             elsif ($this eq ">" and $next eq '"' and $open[$#open] eq $openQuotedNodePath) {
1257 0           $out.= $closeQuotedNodePath;
1258 0           pop @open;
1259             }
1260             # close quoted-path "<0>"
1261             elsif ($this eq ">" and $next eq '"' and $open[$#open] eq $openQuotedPath) {
1262 0           $out.= $closeQuotedPath;
1263 0           pop @open;
1264             }
1265             # path-closure unless no path is open
1266             elsif ($this eq ">" && $open[$#open] eq $openPath) {
1267 0           $out.= $closePath;
1268 0           pop @open;
1269             }
1270             # Just a plain old character
1271 0           else { $out.= $this }
1272             } # next character
1273            
1274             # Second parse: substitute my symbols with DATR symbols including angle-brackets
1275             # Quoted node path:
1276 0           $out =~ s/([\w]*)$openQuotedNodePath/\n/sg;
1277 0           $out =~ s|$closeQuotedNodePath"|\n|sig; #"
1278            
1279             # Node path:
1280 0           $out =~ s/([\w]*)$openNodePath/\n/sg;
1281 0           $out =~ s/$closeNodePath/<\/NODEPATH>\n/sg;
1282            
1283             # Quoted path:
1284 0           $out =~ s/$openQuotedPath//sg;
1285 0           $out =~ s|$closeQuotedPath"|\n|sig; #"
1286            
1287             # Quoted atoms:
1288             # Find words ending in double-quote not followed by a right angle-bracket or oblique
1289 0           $out =~ s|(\w+)"(?![>/])|\n|sg; #"
1290             # Paths:
1291 0           $out =~ s/$openPath//sg;
1292 0           $out =~ s|$closePath|\n|sg;
1293            
1294             # Replace linefeeds at the begining of attribute values:
1295 0           $out =~ s/"(\n|\r|\f)/"/g;
1296            
1297             # Atoms within all bar (atoms):
1298 0           $out =~ s|(<[^>]*>)([^<]+)| # Group, & grab after element > upto <
1299 0           sprintf( # Format on each parse by regex engine
1300             "%s", # Mix the atom element with ¬
1301             $1, # match group 1 ¬
1302             join('"/> 1303             ) # split at whitespace
1304             |xeg; # NB: eXtended and Evaluated Globally
1305            
1306             # Much the same as the previous ATOM regex.
1307 0           $out =~ s|^([^<]+)|
1308 0           sprintf("",
1309             join('"/> 1310             |xeg;
1311            
1312             # Remove all null-atoms: quicker than checking whether to create in first place
1313 0           $out =~ s|||g;
1314            
1315 0           return $out;
1316             } # end sub _parsePath
1317            
1318            
1319            
1320            
1321            
1322            
1323            
1324             =head2 _preFormatNodes (private method)
1325            
1326             Formats nodes for processing by removing comments/directives/linefeeds
1327            
1328             Accepts: strings or array of DATR node/path/value sentences
1329             Returns: one string of DATR node/path/value sentences, without linebreaks
1330            
1331             =cut
1332            
1333            
1334 0     0     sub _preFormatNodes { $_ = shift; # Collect method's object reference
1335 0           my (@d) = (@_);
1336 0           my ($comment, $last_comment) = ("", ""); # See below
1337 0 0         print "Formatting ... \n" if $log; # Be extra polite if asked to
1338 0           foreach (@d) { # Loop through whole, stripping line feeds
1339 0           tr/\n\r\f//d; # Drop line breaks
1340 0 0         next if not /%/; # Only proceed if a comment symbol present
1341 0           m/^(.*)%\s*(.*?)$/o; # DATR in group 1, comments in Group 2
1342 0           $_ = $1; # Remove comments
1343 0 0         if ($last_comment ne $2) { # If group 3 found a NEW comment
1344 0           $comment .= "$2 "; # Save new comment with space for next
1345 0           $last_comment = $2; # Remember this comment
1346             }
1347             } # Next in array
1348 0 0         print "...done.\n" if $log;
1349             # Return the array coerced to scalar
1350 0           print ">$comment\n";
1351 0           return join ("", @d), $comment;
1352            
1353             }
1354            
1355            
1356            
1357            
1358            
1359            
1360             =head2 _setupOutput (private method)
1361            
1362             Sets up a filehandle for output, whether STDOUT or not
1363            
1364             Accepts: string of a filepath, or a filehandle, or a (ref to a) typeglob, or undef
1365             Returns: a reference to a typeglob that is the filehandle
1366             See also: "Passing Filehandles" in perlfaq7 Perl documentation
1367             Note: Would it be better not to default to STDOUT but
1368             to default to a filename specified at object construction time?
1369            
1370             =cut
1371            
1372             sub _setupOutput{
1373 0   0 0     my $FH = shift || *STDOUT;
1374             # Presence of a second arg to this sub forces a check for a filename as first arg
1375 0 0 0       if (ref \$FH eq "GLOB" && shift){
1376 0           die "\nTried to set-up output to file without a filepath having been specified.\n";
1377             }
1378             # If typeglob not passed or created, assume a filepath was passed
1379 0 0         if (ref \$FH ne "GLOB"){ # Check for FileHandle not being a typeglob like STDOUT
1380 0           my $filepath = $FH;
1381 0 0         print "Attempting to save XML as $filepath....\n" if $log;
1382 0 0 0       print "..overwriting existing file....\n" if (-e $filepath) and $log eq "verbose";
1383 0 0         open $FH,">$filepath" or die "failed. Did you include a filename?(Perl said: '$!')\n";
1384 0 0         print "\tOpened $filepath for writing....\n" if $log;
1385             }
1386 0           return $FH;
1387             }
1388            
1389            
1390            
1391            
1392            
1393            
1394            
1395             =head2 _printOpeningClosing (private pseudo-method)
1396            
1397             Prints as XML contents of opening/clsoing, as requested.
1398            
1399             =cut
1400            
1401             sub _printOpeningClosing {
1402 0     0     my $self = shift; # Collect object reference
1403 0           my $FH = shift; # Output FileHandle
1404 0           my $method = shift; # Key for $self hash: OPENING/CLOSING
1405 0 0         if (@{ $self->{$method} }){ # Only if object slot is defined
  0            
1406 0           print $FH "<$method>\n";
1407 0           foreach (@{$self->{$method}}){ # Do every entry in $method
  0            
1408 0           my ($key, $values) = split/\s/,$_,2; # Split into two at first whitesapce
1409 0 0 0       if ($_ eq ""){next} # Skip null strings
  0 0          
    0          
    0          
1410             elsif (/^vars/i){ # If a dollar-sign is found ¬
1411 0           my ($name,$range) = split/:\s*/,$values,2; # get element attribs,lose whitespace
1412 0           print $FH "\t\n"; # NB: printing dollar
1413             }
1414             elsif (/^load/i){
1415 0           /load\s*(.*)$/i; # Get filename by matching stack
1416 0           print $FH "\t\n";
1417             }
1418             elsif (/^reset/i or /^delete/i){ # Faster than (x|y): see Programming_Perl
1419 0           /($&)\s*(.*)$/i; # Match element name found above & get attribute
1420 0           print $FH "\t<",uc $1; # Open element in upper case
1421 0 0         if ($2) {print $FH " value=\"$2\""} # Print value attribute if present
  0            
1422 0           print $FH "/>\n";
1423             }
1424             else { # If no dollar-sign is found ¬
1425 0           print $FH "\t",&_parsePath(\$values),"\n";
1426             } # End-if dollar-sign found
1427             #else { # If no dollar-sign is found ¬
1428             # print $FH uc "<$key>\n\t", # print the directive as an element ¬
1429             # &_parsePath(\$values), # wrapping directive's value ¬
1430             # uc "\n\n"; # and close the element.
1431             #} # End-if dollar-sign found
1432             }
1433 0           print $FH "\n";
1434             }
1435 0           else { print $FH "<$method/>\n";}
1436             }
1437            
1438            
1439            
1440            
1441            
1442            
1443            
1444             1;# Exit the module
1445            
1446             __END__;