File Coverage

blib/lib/DBIx/XMLMessage.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             #
2             # DBIx::XMLMessage
3             #
4             # Copyright (c) 2000-2001 Andrei Nossov. All rights reserved.
5             # This program is free software; you can redistribute it and/or
6             # modify it under the same terms as Perl itself.
7             # _________________________________________________________________________
8             # Modifications Log
9             #
10             # Version Date Author Notes
11             # _________________________________________________________________________
12             # 0.04 3/01 Andrei Nossov Root compound key bug fixed
13             # 0.03 11/00 Andrei Nossov Bug fixes, more documentation
14             # 0.02 10/00 Andrei Nossov Documentation improved
15             # 0.01 8/00 Andrei Nossov First cut
16             # _________________________________________________________________________
17              
18             require 5.003;
19              
20 1     1   34802 use Exporter;
  1         3  
  1         40  
21 1     1   818 use HTML::Entities ();
  1         6174  
  1         45  
22 1     1   12634 use POSIX;
  1         12421  
  1         9  
23 1     1   3212 use DBI;
  1         1  
  1         30  
24 1     1   1071 use Data::Dumper;
  1         10537  
  1         89  
25 1     1   11 use strict;
  1         2  
  1         51  
26              
27             # _________________________________________________________________________
28             # XMLMessage: head package
29             #
30             package DBIx::XMLMessage;
31              
32 1     1   6 use Carp;
  1         2  
  1         77  
33 1     1   2878 use XML::Parser;
  0            
  0            
34             use vars qw (@ISA %EXPORT_TAGS $TRACELEVEL $PACKAGE $VERSION);
35             $PACKAGE = 'DBIx::XMLMessage';
36             $VERSION = '0.04';
37             $TRACELEVEL = 0; # Don't trace by default
38             @ISA = qw ( Exporter );
39              
40             %EXPORT_TAGS = ( 'elements' => ['VERSION', 'TRACELEVEL', '%TEMPLATE::',
41             '%REFERENCE::', '%CHILD::', '%KEY::', '%COLUMN::', '%PARAMETER::']);
42             Exporter::export_ok_tags ('elements');
43              
44             # _________________________________________________________________________
45             # Allow to create via 'new'
46             #
47             sub new {
48             my ($class, %args) = @_;
49             my $self = bless {}, $class;
50              
51             # Check if the external code references are correct
52             # So far have: _OnError, _OnTrace
53             foreach (keys %args) {
54             if ( /^_On/ ) { # Should be a CODE reference
55             if ( (ref $args{$_}) ne 'CODE' ) {
56             $self->error ("Argument $_ should be a CODE reference");
57             } else {
58             $self->{$_} = $args{$_};
59             }
60             } elsif ( /^Handlers$/ ) {
61             $self->set_handlers ($self->{Handlers});
62             } elsif ( /^TemplateString$/ ) {
63             $self->prepare_template ($args{TemplateString});
64             } elsif ( /^TemplateFile$/ ) {
65             $self->prepare_template_from_file ($args{TemplateFile});
66             }
67             }
68             return $self;
69             } # -new
70              
71             # _________________________________________________________________________
72             # Set expat handlers
73             #
74             # This is needed as a separate function, as Handlers for input_xml and
75             # prepare_template can be different
76             #
77             sub set_handlers {
78             my $self = shift;
79             my $handlers_ref = shift;
80              
81             my $old_handlers = $self->{Handlers};
82             # Check if Handlers is a hash referernce
83             if ( $handlers_ref && (ref $handlers_ref) ne 'HASH' ) {
84             $self->error ("Argument Handlers should be a HASH reference");
85             } else {
86             $self->{Handlers} = $handlers_ref;
87             }
88             return $old_handlers;
89             }
90              
91             # _________________________________________________________________________
92             # Error method: invoke $self->{_OnError} and die, otherwise croak
93             #
94             sub error {
95             my $self = shift;
96              
97             if ( $self->{_OnError} ) {
98             &{$self->{_OnError}} (@_);
99             die;
100             } else {
101             croak (@_);
102             }
103             } # -error
104              
105             # _________________________________________________________________________
106             # trace method: invoke $self->{_OnTrace}, otherwise print to STDERR
107             #
108             sub trace {
109             my $self = shift;
110              
111             if ( $TRACELEVEL || defined $self->{_OnTrace} ) {
112             if ( $self->{_OnTrace} ) {
113             &{$self->{_OnTrace}} (@_);
114             } else {
115             print STDERR @_;
116             } }
117             } # -trace
118              
119             # _________________________________________________________________________
120             # Prepare template for the message type
121             #
122             sub prepare_template {
123             my $self = shift;
124             my $tplcontents = shift;
125              
126             my $parser = new XML::Parser (Style => 'Objects',
127             Pkg => $PACKAGE, Handlers => $self->{Handlers});
128             my $parsed;
129             eval { $parsed = $parser->parse ($tplcontents) };
130             if ( $@ ) {
131             $self->error ($@);
132             }
133             $self->mk_refs ($parsed->[0]);
134             $self->{_Template} = $parsed->[0];
135              
136             return $self->{_Template};
137              
138             } # -prepare_template
139              
140             # _________________________________________________________________________
141             # Prepare template for the message type
142             #
143             # If no filename given, try to derive it from the _MessageType set by the
144             # input_xml and SQLM_TEMPLATE_DIR environment variable
145             #
146             sub prepare_template_from_file {
147             my $self = shift; # XMLMessage
148             my $fname = shift; # Template file name
149              
150             if ( ! defined $fname ) { # Full filename expected
151             # If there's no name, try to derive it from the message type.
152             # This hopefully makes things a little bit more flexible
153             $fname = $self->{_MessageType} . '.xml';
154             if ( $ENV{SQLM_TEMPLATE_DIR} ) {
155             $fname = "$ENV{SQLM_TEMPLATE_DIR}/$fname";
156             }
157             $self->error ("Template file name not defined") unless -f $fname;
158             }
159             my $parser = new XML::Parser (Style => 'Objects',
160             Pkg => $PACKAGE, Handlers => $self->{Handlers});
161             my $parsed;
162             eval { $parsed = $parser->parsefile ($fname) };
163             if ( $@ ) {
164             $self->error ($@);
165             }
166             $self->mk_refs ($parsed->[0]);
167             $self->{_Template} = $parsed->[0];
168             return $self->{_Template};
169             } # -prepare_template_from_file
170              
171             #__________________________________________________________________________
172             # Parse the input request
173             #
174             sub input_xml {
175             my $self = shift;
176             my $content = shift;
177              
178             my $p = new XML::Parser (Style => 'Tree',
179             Handlers => $self->{Handlers});
180             $self->{_MessageTree} = $p->parse ($content);
181             $self->{_MessageType} = undef;
182             $self->{_MessageAttr} = undef;
183             $self->{_MessageKids} = undef;
184             foreach my $el (@{$self->{_MessageTree}}) {
185             if ( (ref $el) =~ /HASH/ ) {
186             $self->{_MessageAttr} = $el;
187             } elsif ( (ref $el) =~ /ARRAY/ ) {
188             $self->{_MessageKids} = $el;
189             } elsif ( $el && !(ref $el) ) {
190             $self->{_MessageType} = $el;
191             } else {
192             $self->error ("Unknown element type encountered: $el\n");
193             }
194             }
195              
196             return $self->{_MessageType};
197             } ##input_xml
198              
199             #__________________________________________________________________________
200             # Parse the input file
201             #
202             sub input_xml_file {
203             my $self = shift;
204             my $fname = shift;
205              
206             my $p = new XML::Parser (Style => 'Tree',
207             Handlers => $self->{Handlers});
208             $self->{_MessageTree} = $p->parsefile ($fname);
209             $self->{_MessageType} = undef;
210             $self->{_MessageAttr} = undef;
211             $self->{_MessageKids} = undef;
212             foreach my $el (@{$self->{_MessageTree}}) {
213             if ( (ref $el) =~ /HASH/ ) {
214             $self->{_MessageAttr} = $el;
215             } elsif ( (ref $el) =~ /ARRAY/ ) {
216             $self->{_MessageKids} = $el;
217             } elsif ( $el && !(ref $el) ) {
218             $self->{_MessageType} = $el;
219             } else {
220             $self->error ("Unknown element type encountered: $el\n");
221             }
222             }
223             return $self->{_MessageType};
224             } # -input_xml_file
225              
226             #__________________________________________________________________________
227             #
228             # Store the values in the according objects
229             #
230             # E.g.:
231             # [ ServiceIncident,
232             # [ {VERSION => "1.0"},
233             # Service,
234             # [ {},
235             # 0, "",
236             # Case,
237             # [ {},
238             # 0, "",
239             # ID,
240             # [ {}, 0, "8014"
241             # ],
242             # 0, ""
243             # ]
244             # ],
245             # 0, ""
246             # ServiceTransaction,
247             # [ {},
248             # 0, "",
249             # DispStatus,
250             # [ {}, 0, "In Progress"
251             # ]
252             # ]
253             # 0, ""
254             # ]
255             # 0, ""
256             # ]
257             #
258             # ------------------------------------------------------------------------
259             #
260             # FIXME: Buggy..
261             #
262             sub populate_objects {
263             my $self = shift; # XMLMessage
264             my $ghash = shift; # Global hash
265             my $obj = shift; # The matching object for this tag
266             my $tag = shift; # The tag name
267             my $content = shift; # Reference to the array of kids, hash is attrs
268             my $parix = shift || 0; # Parent input set index
269             my ($el, $attr, $i, $text, $kid, $kidcont, $papa);
270              
271             # Initialize the first object from _Template
272             if ( !defined $obj ) {
273             if ( $self->{_Template} ) {
274             $obj = $self->{_Template};
275             } else {
276             $self->error ("Error: the template is empty"
277             . " (have you run prepare_template?)");
278             } }
279              
280             # Initialize the first tag name from _MessageType and the
281             # first content -- from the _MessageKids
282             if ( ! defined $tag && ! defined $content ) {
283             $tag = $self->{_MessageType};
284             $content = $self->{_MessageKids};
285             }
286             # Log the entry at this point.. Hopefully nothing will happen before..
287             $self->trace ("populate_objects: $tag, $parix\n");
288              
289             # Figure out its own _INIX
290             $obj->{_INIX} = (defined $obj->{_INIX}) ? ++$obj->{_INIX} : 0;
291              
292             # Verify that the object matches w/ the tag
293             if ( $tag ne $obj->{NAME} ) {
294             croak "Error: $tag doesn't match with the template ($obj->{NAME})";
295             }
296             $text = undef;
297              
298             for ( $i=0; defined $content->[$i]; $i++ ) {
299             # while ( defined ($kid = shift @$content) ) {
300             $kid = $content->[$i];
301             if ( (ref $kid) =~ /HASH/ ) { # Attributes -- verify
302             foreach $attr ( keys %$kid ) {
303             if ( $obj->{$attr} && $kid->{$attr} ne $obj->{$attr} ) {
304             $obj->error ("Error: $attr of the message $el->{$attr}"
305             . " don't match with that of the template"
306             . " ($obj->{$attr})");
307             } }
308             } else {
309             #<<<<<<<<
310             $kidcont = $content->[++$i];
311             if ( ref $kid ) { # ?? Error
312             $self->error ("Error: Unexpected reference $kid");
313             } elsif (!$kid) { # 0 -- text
314             $kidcont =~ s/[\n\s]*$//;
315             $text .= $kidcont;
316             } else { # Not 0 -- tag
317             undef $el;
318             foreach my $typ (qw (CHI REF COL PAR KEY)) {
319             if ( $obj->{"_$typ" . 'LIST'} && $obj->{"_$typ".'LIST'}->{$kid} ) {
320             $el = $obj->{"_$typ" . 'LIST'}->{$kid};
321             last;
322             } }
323             if ( $el ) { # Found
324             $self->populate_objects ($ghash,$el,$kid,$kidcont,$obj->{_INIX});
325             } else {
326             # Kid not found -- see if we can dynamically create it..
327             if ( $obj->{TOLERANCE} && $obj->{TOLERANCE} =~ /^CREATE/ ) {
328             # CREATE
329             my $type = 'COLUMN';
330             if ( $obj->{TOLERANCE} =~ /^CREATE (.+)$/ ) {
331             $type = $1;
332             }
333             # Dynamic creation
334             $el = new "$PACKAGE::$type";
335             $el->{NAME} = $kid;
336             $el->{_PARENT_TAG} = $obj;
337             push @{$obj->{Kids}}, $el;
338             $obj->{_COLLIST}->{$kid} = $el;
339             $self->populate_objects ($ghash,$el,$kid,$kidcont,$obj->{_INIX});
340             } elsif ( $obj->{TOLERANCE}
341             && $obj->{TOLERANCE} eq 'REJECT' ) {# REJECT
342             $self->error ("$obj->{NAME} doesn't allow child $kid");
343             } else { # IGNORE
344             $self->trace ("$kid kid not found in the template"
345             . " for $obj->{NAME}, ignoring");
346             } } } }
347             #<<<<<<<<
348             } ## while kid
349             # Tweak up the text if there's a built-in..
350             if ( $text && $obj->{BLTIN} ) {
351             my $bltin = $obj->{BLTIN};
352             @_ = ($self,$obj,$text);
353             my $cmd = '$text = &' . $bltin . ';';
354             eval $cmd || die "Error in BUILT-IN $bltin of $obj->{NAME}: $@";
355             }
356             # Figure out what to do w/ the text..
357             if ( (ref $obj) =~ /::COLUMN$/ || (ref $obj) =~ /::PARAMETER$/
358             || (ref $obj) =~ /::KEY$/ ) {
359             $papa = $obj->{_PARENT_TAG};
360             $papa->{_INVALUES}->[$parix]->{$tag} = $text;
361             }
362              
363             } # -populate_objects
364              
365             #__________________________________________________________________________
366             # Debugging subroutine: Print the tree
367             #
368             sub pr_tree {
369             my $self = shift; # XMLMessage
370             my $ref = shift; # Root node of this subtree
371             my $level = shift || 0; # Level of this root node
372             my ($el, $i);
373              
374             if ( (ref $ref) =~ /ARRAY/ ) {
375             foreach $el (@$ref) {
376             $self->pr_tree ($el, $level+1);
377             }
378             } elsif ( (ref $ref) =~ /HASH/ ) {
379             # Attributes only
380             foreach $el ( keys %$ref ) {
381             for ($i=0; $i<$level; $i++) { $self->trace (" "); }
382             $self->trace ("$el = $ref->$el\n");
383             }
384             } else {
385             if ( $ref ) {
386             for ($i=0; $i<$level; $i++) { $self->trace (" "); }
387             if ( $ref =~ /(.*)(\s+)$/ ) {
388             $ref = $1;
389             }
390             if ( $ref =~ /(.*)(\n+)$/ ) {
391             $ref = $1;
392             }
393             $self->trace ("$ref\n");
394             }
395             }
396             } # -pr_tree
397              
398             # _________________________________________________________________________
399             # Create the necessary internal structures
400             #
401             sub mk_refs {
402             my $self = shift; # XMLMessage
403             my $root = shift; # Element
404              
405             foreach my $el (@{$root->{'Kids'}}) {
406             if ( (ref $el) =~ /::(\w+)$/ && (ref $el) !~ /::Characters/ ) {
407             # Create the parent references
408             $el->{_PARENT_TAG} = $root;
409             # Store the object type lists in hashes
410             # Constructs: _COLLIST, _KEYLIST, _PARLIST, _REFLIST, _CHILIST
411             # The assumption here is that the tag name within an object
412             # type is unique (i.e. there couldn't be two COLUMNs with the
413             # same name)
414             my $listname = "_" . substr($1,0,3) . "LIST";
415             if ( $root->{$listname}->{$el->{NAME}} ) {
416             $self->error ("$1 $el->{NAME} is defined more"
417             . " than once under $root->{NAME}");
418             } else {
419             $root->{$listname}->{$el->{NAME}} = $el;
420             }
421             $self->mk_refs($el);
422             } }
423              
424             } # -mk_refs
425              
426             # _________________________________________________________________________
427             # Get the value from global hash (not a method!)
428             #
429             sub get_hashval {
430             my $href = shift; # Hash reference
431             my $name = shift; # Name to look for
432             my $resix = shift || 0; # Index to look for
433              
434             # Note: This function doesn't have to have a $inix argument, as the only
435             # linkage to the higher level is $resix.
436             #
437             my $val = undef;
438             if ( $href && defined $href->{$name} ) {
439             if ( (ref $href->{$name}) eq 'CODE' ) {
440             $val = &{$href->{$name}}($resix);
441             } elsif ( (ref $href->{$name}) eq 'ARRAY' ) {
442             return $href->{$name}->[$resix];
443             } elsif ( !(ref $href->{$name}) && $resix == 0 ) {
444             # Just a single value, only return if the index is 0
445             $val = $href->{$name};
446             }
447             }
448             return $val;
449             } # -get_hashval
450              
451             # _________________________________________________________________________
452             # THESE ARE METHODS FOR THE ELEMENTS
453             #
454              
455             # _________________________________________________________________________
456             # Get the *parent* result value #n
457             #
458             sub get_resval {
459             my $self = shift; # XMLMessage
460             my $node = shift; # TEMPLATE | REFERENCE | CHILD
461             my $name = shift; # (COLUMN) name
462             my $resix = shift || 0; # Result set index
463              
464             $self->trace (" get_resval ($node->{NAME},$name,$resix)\n");
465             my $papa = $node->{_PARENT_TAG} || return undef;
466             my $rref = $papa->{_RESULTS} || return undef;
467              
468             if ( (ref $rref) eq 'CODE' ) {
469             # Should this work for global hash?
470             return &{$rref}($resix);
471             } elsif ( (ref $rref) eq 'ARRAY' ) {
472             if ( $rref->[$resix] && defined $rref->[$resix]->{$name} ) {
473             return $rref->[$resix]->{$name};
474             }
475             } elsif ( (ref $rref) eq 'HASH' && $rref->{$name} && $resix == 0 ) {
476             # Should work for global hash as well?
477             return $rref->{$name};
478             }
479             return undef;
480             } # -get_resval
481              
482             # _________________________________________________________________________
483             # Get the parameter (input value) #n
484             #
485             sub get_inval {
486             my $self = shift; # XMLMessage
487             my $node = shift; # TEMPLATE|CHILD|REFERENCE
488             my $name = shift; # Name to look for
489             my $ix = shift || 0; # Input value set index
490              
491             $self->trace (" get_inval ($node->{NAME},$name,$ix)\n");
492             my $val = $node->{_INVALUES}
493             ? $node->{_INVALUES}->[$ix]
494             ? $node->{_INVALUES}->[$ix]->{$name}
495             : undef
496             : undef;
497             return $val;
498             } # -get_inval
499              
500             #__________________________________________________________________________
501             # Get the key value #($inix,$resix)
502             #
503             sub get_keyval {
504             my $self = shift; # XMLMessage
505             my $node = shift; # Key reference
506             my $href = shift; # External hash reference
507             my $inix = shift || 0; # Input set index
508             my $resix = shift || 0; # Parent result set index
509              
510             $self->trace (" get_keyval ($node->{NAME},$inix,$resix)\n");
511             my ($tag, $papa, $kname, $val);
512             $tag = $node->{_PARENT_TAG};
513             # Any key should have a parent TEMPLATE|CHILD|REFERENCE
514             if ( !$tag ) {
515             $self->error ("Internal error: Key $node->{NAME} has no parent");
516             }
517             # Find the corresponding name a level up
518             $kname = $node->{PARENT_NAME} ? $node->{PARENT_NAME} : $node->{NAME};
519             # Check itself
520             # Keys are stored in a 2-dimensional array:
521             # _____________________________________________________________________
522             # resix 0 1 2 3 ...
523             # inix
524             # 0 A B C D
525             # 1 E F
526             # 2 G H I
527             # ...
528             # _____________________________________________________________________
529             # Thus, inix 0 should be always there and it's fake..
530             #
531             if ( $tag->{_KEYS} && $tag->{_KEYS}->[$inix]
532             && defined $tag->{_KEYS}->[$inix]->[$resix]
533             && defined $tag->{_KEYS}->[$inix]->[$resix]->{$kname} ) {
534             $val = $self->format_value ($node,$tag->{_KEYS}->[$inix]->[$resix]->{$kname});
535             $self->trace (" *get_keyval = $val\n");
536             return $val;
537             }
538             # Find the tag's parent (all but TEMPLATE should have)
539             if ( $tag->{_PARENT_TAG} ) {
540             $papa = $tag->{_PARENT_TAG};
541             } elsif ( (ref $tag) !~ /::TEMPLATE$/ ) {
542             $self->error ("Internal error: Tag $tag->{NAME} has no parent");
543             }
544             # Try to get from input values and parent results
545             my $val1 = $self->get_inval ($tag, $node->{NAME}, $inix);
546             # Get the parent result
547             my $val2 = $self->get_resval ($tag, $kname, $resix);
548             # Compare values
549             if ( defined $val1 ) {
550             if ( defined $val2 && $val1 ne $val2 ) {
551             $self->error ("Key $node->{NAME} values don't"
552             . " match in parent result set and input");
553             }
554             $val = $val1;
555             } else {
556             $val = $val2;
557             }
558             # If still undefined, then try the global hash
559             if ( !defined $val ) {
560             # None defined -- try the global hash
561             $val = &get_hashval ($href, $kname, $resix);
562             }
563             if ( defined $val ) {
564             $tag->{_KEYS}->[$inix]->[$resix]->{$kname} = $val;
565             }
566             $val = (defined $val) ? $self->format_value($node,$val) : undef;
567             $self->trace (" get_keyval = $val\n");
568             return $val;
569              
570             # Should be able to have two references from two different columns
571             # to the same table.. (I recall this idea seemed important..why?;^)
572              
573             } # -get_keyval
574              
575             #__________________________________________________________________________
576             # Get the parameter value #ix
577             #
578             sub get_parval {
579             my $self = shift; # XMLMessage
580             my $node = shift; # PARAMETER
581             my $href = shift; # External hash reference
582             my $inix = shift || 0; # Input value set index, real starts at 1
583             my $resix = shift || 0; # Parent result set index
584              
585             my $val = undef;
586             my $tag = $node->{_PARENT_TAG}; # Parameter's tag
587             if ( !$tag ) {
588             $self->error ("Parameter $node->{NAME} has no parent tag");
589             }
590              
591             # Try to get from input values and parent results
592             my $val1 = $self->get_inval ($tag, $node->{NAME}, $inix);
593             # Find the corresponding name a level up
594             my $pname = $node->{PARENT_NAME} ? $node->{PARENT_NAME} : $node->{NAME};
595             # Get the parent result
596             my $val2 = $self->get_resval ($tag, $pname, $resix);
597             # Compare values
598             if ( defined $val1 ) {
599             if ( defined $val2 && $val1 ne $val2 ) {
600             $self->error ("Parameter $node->{NAME} values"
601             . " don't match in parent result set and input");
602             }
603             $val = $val1;
604             } else {
605             $val = $val2;
606             }
607             # If still undefined, then try the global hash
608             if ( !defined $val ) {
609             $val = &get_hashval ($href, $pname, $resix);
610             }
611             if ( defined $val ) {
612             $val = $self->format_value($node,$val);
613             } else {
614             if ( !defined $val && defined $node->{DEFAULT} ) {
615             $val = $self->{DEFAULT};
616             } }
617             return $val;
618             } ##get_parval
619              
620             #__________________________________________________________________________
621             # Get and format the column value #($inix,$resix)
622             #
623             sub get_colval {
624             my $self = shift; # XMLMessage
625             my $node = shift; # COLUMN
626             my $dbh = shift; # Database handle
627             my $href = shift; # External hash reference
628             my $inix = shift || 0; # Input value set index
629             my $resix = shift || 0; # Parent result set index
630              
631             $self->trace (" get_colval ($node->{NAME},$inix,$resix)\n");
632             my $tag = $node->{_PARENT_TAG}; # Parameter's tag
633             if ( !$tag ) {
634             $self->error ("Internal error: Column $node->{NAME} has no parent");
635             }
636             my $val = undef;
637             # Find the tag's parent (all but TEMPLATE should have)
638             my $papa;
639             if ( $tag->{_PARENT_TAG} ) {
640             $papa = $tag->{_PARENT_TAG};
641             } elsif ( (ref $tag) =~ /::TEMPLATE$/ ) {
642             $papa = $href;
643             } else {
644             die ("Internal error: Tag $tag->{NAME} has no parent");
645             }
646             # Look for the input value and parent result
647             my $val1 = $self->get_inval ($tag, $node->{NAME}, $inix);
648             my $val2 = $self->get_resval ($node, $node->{NAME}, $resix);
649             $self->trace (" inval=" . (defined $val1 ? $val1 : "UNDEF")
650             . ", resval=" . (defined $val2 ? $val2 : "UNDEF") . "\n");
651             if ( defined $val1 && length($val1) > 0 ) {
652             if ( defined $val2 && length($val2) > 0 ) {
653             if ( $val1 eq $val2 ) {
654             $val = $val1
655             } else {
656             die ("Internal error: $node->{NAME} column values don't "
657             . "match in parent result set and input ($val1,$val2)");
658             }
659             } else {
660             $val = $val1;
661             }
662             } else {
663             $val = $val2;
664             }
665             # print " val=$val\n";
666             # Also try the keys with matching EXPR|NAME
667             # as they might get pushed
668             # from the lower levels (not anymore ;^))
669             if ( !defined $val ) {
670             if ( $node->{EXPR} && $tag->{_KEYLIST}->{$node->{EXPR}} ) {
671             my $key = $tag->{_KEYLIST}->{$node->{EXPR}};
672             $val = $self->get_keyval ($key,$href,$inix,$resix);
673             } elsif ( $tag->{_KEYLIST}->{$node->{NAME}} ) {
674             my $key = $tag->{_KEYLIST}->{$node->{NAME}};
675             $val = $self->get_keyval ($key,$href,$inix,$resix);
676             } }
677              
678             if ( $val ) {
679             $val = $self->format_value ($node,$val);
680             } elsif ( $node->{GENERATE_PK} ) {
681             if ( $node->{GENERATE_PK} eq 'HASH' ) {
682             $val = &get_hashval ($href,"$tag->{TABLE}",$inix,$resix);
683             } else {
684             # Should contain a SQL that selects 1 value
685             if ( $dbh ) {
686             my $idtab = $tag->{TABLE} . "_ID";
687             my $sql = $node->{GENERATE_PK};
688             my $sth = $dbh->prepare ($sql) || die $DBI::errstr;
689             my $rc = $sth->execute() || die $DBI::errstr;
690             $rc = $sth->fetchall_arrayref();
691             $val = $rc->[0]->[0];
692             $rc = $sth->finish();
693             } elsif ( $self->{NODBH} eq 'OK' ) {
694             # No database handle: Try hash anyway
695             $self->trace ("Trying to get PK without database handle");
696             $val = &get_hashval ($href,"$tag->{TABLE}",$inix,$resix);
697             } else {
698             $self->error (
699             "Can not generate primary key for table $tag->{TABLE}");
700             } }
701             } elsif ( defined $node->{DEFAULT} ) {
702             $val = $node->{DEFAULT}; # This goes as-is
703             }
704             return $val;
705             } # -get_colval
706              
707             #__________________________________________________________________________
708             # Format element value according to its datatype
709             #
710             sub format_value {
711             my $self = shift;
712             my $node = shift;
713             my $val = shift;
714              
715             # DATATYPE is CHAR by default
716             if ( !$node->{DATATYPE} || $node->{DATATYPE} =~ /(CHAR|DATE|TIME)/ ) {
717             if ( $val !~ /^\'(.*)\'$/ && $val !~ /^\"(.*)\"$/ ) {
718             $val =~ s/\'/\'\'/g;
719             $val = "'$val'";
720             } }
721             return $val;
722             } # -format_value
723              
724             #__________________________________________________________________________
725             # Create the WHERE clause for SELECT/UPDATE
726             #
727             sub create_where {
728             my $self = shift; # XMLMessage
729             my $node = shift; # TEMPLATE|CHILD|REFERENCE
730             my $href = shift; # Global hash reference
731             my $inix = shift || 0; # Key set index
732             my $resix = shift || 0; # Parent result set index
733              
734             $self->trace (" create_where ($node->{NAME},$inix,$resix)\n");
735             my ($el, $where);
736             # Construct WHERE clause
737             foreach ( keys %{$node->{_KEYLIST}} ) {
738             $el = $node->{_KEYLIST}->{$_};
739             my $val = $self->get_keyval ($el,$href,$inix,$resix);
740             if ( !defined $val ) {
741             $self->error ("$el->{NAME}: Key value #($inix,$resix) not found");
742             }
743             $where .= " and ";
744             if ( defined $el->{EXPR} ) {
745             $where .= $el->{EXPR};
746             } else {
747             $where .= $el->{NAME};
748             }
749             if ( !$el->{DATATYPE} || $el->{DATATYPE} =~ /CHAR/ ) {
750             $where .= " like ";
751             } else {
752             $where .= " = ";
753             }
754             $val = $self->format_value($el,$val);
755             $where .= $val;
756             }
757             # Check if there is additional WHERE clause
758             if ( $node->{'WHERE_CLAUSE'} ) {
759             $where .= " and " if ( $where );
760             $where .= $node->{'WHERE_CLAUSE'};
761             }
762             # Cut off the initial 'and'
763             $where = substr ($where, 4) if ($where);
764             return $where;
765             } # -create_where
766              
767             # _________________________________________________________________________
768             # Construct SELECT statement
769             #
770             sub create_select {
771             my $self = shift; # XMLMessage
772             my $node = shift; # TEMPLATE|CHILD|REFERENCE
773             my $dbh = shift; # Database handle
774             my $href = shift; # Global hash reference
775             my $inix = shift || 0; # Input value set index
776             my $resix = shift || 0; # Parent result set index
777              
778             $self->trace (" create_select ($node->{NAME},$inix,$resix)\n");
779             my ($el, $colexpr, $sql);
780             # Construct column list, possibly with aliases
781             foreach ( keys %{$node->{_COLLIST}} ) {
782             # $self->trace (" create_select: found column $_\n");
783             $el = $node->{_COLLIST}->{$_};
784             # Include expression if present
785             if ( $el->{'EXPR'} ) {
786             $colexpr = $el->{EXPR};
787             } else {
788             $colexpr = $el->{NAME};
789             }
790             # Include name if not the same
791             if ( $el->{'NAME'} ne $colexpr ) {
792             $colexpr .= " " if ($colexpr);
793             $colexpr .= $el->{'NAME'};
794             }
795             # Add to the SQL if not empty
796             $sql .= "\n\t$colexpr," if ($colexpr);
797             }
798             if ( $sql ) {
799             chop ($sql); # Chop the last comma
800             $sql = "SELECT $sql";
801             }
802             if ( $sql && $node->{TABLE} ) {
803             $sql .= "\nFROM\n\t" . $node->{'TABLE'};
804             # WHERE clause doesn't make sence without FROM
805             my $where = $self->create_where ($node, $href, $inix, $resix);
806             $sql .= "\nWHERE $where";
807             }
808             return $sql;
809             } # -create_select
810              
811             # _________________________________________________________________________
812             # Construct INSERT statement
813             #
814             sub create_insert {
815             my $self = shift; # XMLMessage
816             my $node = shift; # TEMPLATE|CHILD|REFERENCE
817             my $dbh = shift; # Database handle
818             my $href = shift; # Global hash reference
819             my $inix = shift || 0; # Input value set index
820             my $resix = shift || 0; # Parent result set index
821              
822             my ($el, $colexpr, $colval, $sql, $sql1);
823             $self->error ("$node->{NAME}: Cannot INSERT without TABLE")
824             if(!$node->{TABLE});
825             # Construct the list of columns and list of values
826             foreach ( keys %{$node->{_COLLIST}} ) {
827             $el = $node->{_COLLIST}->{$_};
828             # Use EXPR if present
829             if ( $el->{'EXPR'} ) {
830             $colexpr = $el->{EXPR};
831             } else {
832             $colexpr = $el->{NAME};
833             }
834             $colval = $self->get_colval ($el, $dbh, $href, $inix, $resix);
835             if ( defined $colval && $colval ne '' ) {
836             # Add to the SQL if not empty
837             $sql .= "\n\t$colexpr," if ($colexpr);
838             $sql1 .= "\n\t$colval,";
839             } else {
840             my $er = "Value #($inix,$resix) for col $colexpr not found";
841             # For INSERT all column values are required
842             $self->trace ("* $er\n");
843             if ($node->{CARDINALITY} && $node->{CARDINALITY} eq 'OPTIONAL'){
844             return 1;
845             } else {
846             $self->error ("$er\n");
847             } } }
848             if ( $sql ) {
849             chop $sql;
850             chop $sql1;
851             $sql = "INSERT INTO $node->{TABLE} ($sql\n) VALUES ($sql1)";
852             }
853             return $sql;
854              
855             } # -create_insert
856              
857             # _________________________________________________________________________
858             # Construct UPDATE statement
859             #
860             sub create_update {
861             my $self = shift; # XMLMessage
862             my $node = shift; # TEMPLATE|CHILD|REFERENCE
863             my $dbh = shift; # Database handle
864             my $href = shift; # Global hash reference
865             my $inix = shift || 0; # Input value set index
866             my $resix = shift || 0; # Parent result set index
867              
868             $self->trace (" create_update ($node->{NAME},$inix,$resix)\n");
869             my ($el, $colexpr, $sql);
870             $self->error ("$node->{NAME}: Cannot UPDATE without TABLE")
871             if (!$node->{TABLE});
872             # Construct the list of columns with value assignments
873             undef $sql;
874             foreach ( keys %{$node->{_COLLIST}} ) {
875             $el = $node->{_COLLIST}->{$_};
876             # print " -el = $el->{NAME}\n";
877             $colexpr = $self->get_colval ($el, $href, $dbh, $inix, $resix);
878             # print " -colval = $colexpr\n";
879             if ( defined $colexpr && $colexpr ne "" ) {
880             if ( $el->{EXPR} ) {
881             $colexpr = "\n\t" . $el->{EXPR} . " = $colexpr,";
882             } else {
883             $colexpr = "\n\t" . $el->{NAME} . " = $colexpr,";
884             }
885             $sql .= $colexpr;
886             # print " -sql = $sql\n";
887             } }
888             # If anything was created
889             if ( $sql ) {
890             chop $sql;
891             my $where = $self->create_where ($node, $href, $inix, $resix);
892             $sql = "UPDATE $node->{TABLE} set $sql where $where";
893             }
894             return $sql;
895             } # -create_update
896              
897             # _________________________________________________________________________
898             # Construct EXEC statement (only works with Sybase/SQL Server I suspect)
899             #
900             sub create_exec {
901             my $self = shift; # XMLMessage
902             my $node = shift; # TEMPLATE|CHILD|REFERENCE
903             my $dbh = shift; # Database handle
904             my $href = shift; # Global hash reference
905             my $inix = shift || 0; # Input value set index
906             my $resix = shift || 0; # Parent result set index
907              
908             my ($el, $val, $sql, $dbdriver);
909             if ( !defined $node->{PROC} ) {
910             $self->error ("$node->{NAME}: PROC required where ACTION is EXEC");
911             }
912             # Retrieve the driver name
913             # $dbdriver = $dbh->{Driver}->{Name};
914              
915             # Collect the parameters
916             foreach my $pname ( keys %{$node->{_PARLIST}} ) {
917             my $el = $node->{_PARLIST}->{$pname};
918             my $val = $self->get_parval($el,$href,$inix,$resix);
919             if ( !defined $val ) {
920             if ($node->{CARDINALITY} && $node->{CARDINALITY} eq 'OPTIONAL'){
921             $self->trace ("Value #($inix,$resix) for $pname not found, "
922             ."but the tag is optional -- skipping");
923             return 1;
924             } else {
925             $self->error (
926             "$el->{NAME}: $pname value #($inix,$resix) not found");
927             }
928             } else {
929             $sql .= " \@$el->{NAME} = $val,"
930             } }
931             if ( $sql ) {
932             chop ($sql);
933             }
934             $sql = "EXEC $node->{PROC} $sql";
935             return $sql;
936             } # -create_exec
937              
938             #__________________________________________________________________________
939             # Execute the SQL for one index pair
940             #
941             sub execute_sql {
942             my $self = shift; # XMLMessage
943             my $node = shift; # TEMPLATE|CHILD|REFERENCE
944             my $dbh = shift; # Database handle
945             my $href = shift; # External hash reference for parameters
946             my $inix = shift || 0; # Input vector index
947             my $resix = shift || 0; # Parent result set index
948              
949             my ($sql, $sth, $rc, $row);
950             $self->trace (" execute_sql ($node->{NAME},$inix,$resix)\n");
951             # Verify that all key values are available
952             foreach my $el ( keys %{$node->{_KEYLIST}} ) {
953             my $val = $self->get_keyval ($node->{_KEYLIST}->{$el},$href,$inix,$resix);
954             if ( !defined $val ) {
955             if ($node->{CARDINALITY} && $node->{CARDINALITY} eq 'OPTIONAL'){
956             # Skipping the whole thing..
957             return 1;
958             } else {
959             $self->error ("$node->{NAME}: $el value #($inix,$resix) not found");
960             } } }
961             #
962             # Construct and execute SQL statement
963             #
964             # For different ACTIONs
965             my $action = $node->{ACTION} ? $node->{ACTION} : 'SELECT';
966             for ( $action ) {
967             if ( /INSERT/ ) {
968             $sql = $self->create_insert ($node,$href,$dbh,$inix,$resix);
969             $self->trace ("SQL = $sql\n");
970             $rc = $dbh->do ($sql) || croak ("$sql:\n" . $dbh->errstr);
971             my %rowh = ();
972             if ( $rc > 0 ) {
973             $self->process_result($node,$dbh,\%rowh,$href,$inix,$resix);
974             }
975             } elsif ( /UPDATE/ ) {
976             $sql = $self->create_update ($node,$href,$dbh,$inix,$resix);
977             $self->trace ("SQL = $sql\n");
978             &{$self->{_OnPreDoSQL}} ($dbh) if ($self->{_OnPreDoSQL});
979             $rc = $dbh->do ($sql) || $self->error ("$sql\n".$dbh->errstr);
980             &{$self->{_OnPostDoSQL}} ($dbh) if ($self->{_OnPostDoSQL});
981             my %rowh = ();
982             if ( $rc > 0 ) {
983             $self->process_result($node,$dbh,\%rowh,$href,$inix,$resix);
984             }
985             } elsif ( /SAVE/ ) {
986             # Logic of the SAVE operation: update if found, insert if not
987             $sql = $self->create_select ($node, $href, $dbh, $inix, $resix);
988             $self->trace ("SQL = $sql\n");
989             $sth = $dbh->prepare ($sql)
990             || $self->error ("$sql\n".$dbh->errstr);
991             $rc = $sth->execute() || croak ("$sql\n" . $dbh->errstr);
992             if ( $row = $sth->fetchrow_hashref() ) {
993             $sql = $self->create_update ($node,$href,$dbh,$inix,$resix);
994             $self->trace ("SQL = $sql\n");
995             $rc = $dbh->do ($sql)
996             || $self->error("$sql\n".$dbh->errstr);
997             } else {
998             $sql = $self->create_insert ($node,$href,$dbh,$inix,$resix);
999             $self->trace ("SQL = $sql\n");
1000             $rc = $dbh->do($sql) || $self->error("$sql\n".$dbh->errstr);
1001             }
1002             my %rowh = ();
1003             if ( $rc > 0 ) {
1004             $self->process_result($node,$dbh,\%rowh,$href,$inix,$resix);
1005             }
1006             } elsif ( /EXEC/ ) {
1007             $sql = $self->create_exec ($node, $href, $dbh, $inix, $resix);
1008             $self->trace ("SQL = $sql\n");
1009             $sth = $dbh->prepare ($sql)
1010             || $self->error ("$sql:\n" . $dbh->errstr);
1011             #
1012             # FIXME: we can analyze if the stored procedure does any selects
1013             # and fetch only for those. If there are no selects, we probably
1014             # should follow the INSERT/UPDATE schema and create one result
1015             # row..
1016             #
1017             $rc = $sth->execute() || $self->error ("$sql:\n".$dbh->errstr);
1018             while ( $row = $sth->fetchrow_hashref() ) {
1019             $self->process_result ($node,$dbh,$row,$href,$inix,$resix);
1020             }
1021             } elsif ( /SELECT/ || !defined $_ ) {
1022             $sql = $self->create_select ($node, $href, $dbh, $inix, $resix);
1023             $self->trace ("SQL = $sql\n");
1024             if ( !length $sql ) {
1025             $self->error ("ERROR: Unable to create a SQL statement");
1026             }
1027             $sth = $dbh->prepare ($sql)
1028             || $self->error ("$sql\n" . $dbh->errstr);
1029             $rc = $sth->execute()
1030             || $self->error ("$sql\n" . $dbh->errstr);
1031             while ( $row = $sth->fetchrow_hashref() ) {
1032             $self->process_result ($node,$dbh,$row,$href,$inix,$resix);
1033             }
1034             } else {
1035             $self->error ("$_: Unsupported action");
1036             }
1037             }
1038              
1039             } # -execute_sql
1040              
1041             #__________________________________________________________________________
1042             # Function to be inoked per retrieved row
1043             # Adds 2 pseudo-columns to the row:
1044             # ->{_INIX}
1045             # ->{_RESIX}
1046             #
1047             sub process_result {
1048             my $self = shift; # XMLMessage
1049             my $node = shift; # TEMPLATE|CHILD|REFERENCE
1050             my $dbh = shift; # DBI database handle
1051             my $results = shift; # Result row hash reference
1052             my $href = shift; # Global hash reference
1053             my $inix = shift || 0; # Input value set index
1054             my $resix = shift || 0; # Parent result set index
1055              
1056             my ($colname, $val, $el);
1057              
1058             # Collect the results on a per-colunm basis
1059             foreach $colname ( keys %{$node->{_COLLIST}} ) {
1060             $el = $node->{_COLLIST}->{$colname};
1061             if ( !defined $results->{$colname} ) {
1062             $val = $self->get_colval ($el, $dbh, $href, $inix, $resix);
1063             # De-format default values..
1064             if ( defined $val && $val =~ /^\'(.*)\'$/ ) {
1065             $val = $1;
1066             $val =~ s/\'\'/'/g;
1067             } elsif ( defined $val && $val =~ /^\"(.*)\"$/ ) {
1068             $val = $1;
1069             $val =~ s/\"\"/"/g;
1070             }
1071             if ( 'NULL' eq uc($val) ) {
1072             $val = undef;
1073             }
1074             $results->{$colname} = $val;
1075             } }
1076             # Now look from the results' perspective
1077             foreach $colname ( keys %$results ) {
1078             $results->{$colname} =~ s/\s*$// if (defined $results->{$colname});
1079             my $col = $node->{_COLLIST}->{$colname};
1080             if ( !$col ) { # Column does not exist
1081             # Should we tolerate undefined results?
1082             if ( $node->{TOLERANCE} && $node->{TOLERANCE} eq 'CREATE'
1083             && $colname !~ /^_/ ) {
1084             $col = new "$PACKAGE::Element::COLUMN";
1085             $col->{NAME} = $colname;
1086             $col->{_PARENT_TAG} = $node;
1087             push @{$node->{Kids}}, $col;
1088             $self->{_COLLIST}->{$colname} = $col;
1089             } elsif ( $node->{TOLERANCE} && $node->{TOLERANCE} eq 'REJECT' ) {
1090             $self->error (
1091             "ERROR: Unknown column $colname in the result set");
1092             # } elsif ( $self->{TOLERANCE} eq 'IGNORE' ) {
1093             } else { # IGNORE by default
1094             delete $$results{$colname};
1095             } }
1096             }
1097              
1098             # And push it into results array
1099             # ... BUT COPY FIRST ...
1100             my $rescopy;
1101             foreach $colname ( keys %$results ) {
1102             $rescopy->{$colname} = $results->{$colname};
1103             if ( $rescopy->{$colname} &&
1104             $node->{_COLLIST}->{$colname}->{BLTIN} ) { # Builtin
1105             my $bltin = $node->{_COLLIST}->{$colname}->{BLTIN};
1106             $self->trace ("BUILTIN func: $bltin\n");
1107             my $cmd = '$rescopy->{$colname} = &' . $bltin . ';';
1108             @_ = ($self,$node,$rescopy->{$colname});
1109             $self->trace ("BUILTIN: $cmd\n");
1110             eval $cmd;
1111             $self->error("Error in BUILT-IN $bltin of $colname: $@") if($@);
1112             }
1113             }
1114             $rescopy->{_INIX} = $inix;
1115             $rescopy->{_RESIX} = $resix;
1116             push @{$node->{_RESULTS}}, $rescopy;
1117              
1118             } # -process_result
1119              
1120             #__________________________________________________________________________
1121             # Execute the SQL for all parent results and input values
1122             #
1123             sub exec {
1124             my $self = shift; # XMLMessage
1125             my $node = shift; # TEMPLATE|CHILD|REFERENCE
1126             my $dbh = shift; # Database handle
1127             my $href = shift; # External hash reference for parameters
1128              
1129             $self->trace ("\n exec $node->{NAME}\n");
1130             my $success = 1;
1131             my $papa = $node->{_PARENT_TAG};
1132              
1133             my $nres;
1134             if ( $papa ) {
1135             $nres = $papa->{_RESULTS} ? scalar @{$papa->{_RESULTS}} : 0;
1136             } else {
1137             # No parent tag -- pick up the key #0 and count number of values.
1138             my @keynames = defined $node->{_KEYLIST}
1139             ? keys %{$node->{_KEYLIST}} : ();
1140             my $key0 = scalar @keynames
1141             ? $node->{_KEYLIST}->{$keynames[0]}->{PARENT_NAME}
1142             ? $node->{_KEYLIST}->{$keynames[0]}->{PARENT_NAME}
1143             : $keynames[0]
1144             : undef;
1145             $nres = defined $key0
1146             ? scalar @{$href->{$key0}} : 1; # No keys -- execute once
1147             }
1148              
1149             my $nval = $node->{_INVALUES} ? scalar @{$node->{_INVALUES}} : 0;
1150             my $inix = 0;
1151             $self->trace (" nval = $nval\n");
1152             do { # Execute once with no input values
1153             for ( my $resix=0; $resix<$nres; $resix++ ) {
1154             # But not without results
1155             $success &= $self->execute_sql($node,$dbh,$href,$inix,$resix);
1156             }
1157             } while ( ++$inix < $nval );
1158              
1159             $success;
1160             } # -exec
1161              
1162             #__________________________________________________________________________
1163             # Recursively execute SQL statements for all
1164             #
1165             sub rexec {
1166             my $self = shift; # XMLMessage
1167             my $dbh = shift; # database handle
1168             my $href = shift; # External hash reference for parameters
1169             my $node = shift; # TEMPLATE|CHILD|REFERENCE
1170              
1171             $node = $self->{_Template} if (!$node);
1172             $self->trace ("\nrexec $node->{NAME}\n");
1173             my ($el, $success);
1174             if ( !$dbh ) {
1175             #
1176             # FIXME: Allow for NODBH invocation
1177             #
1178             $self->error ("No database handle");
1179             }
1180             # Execute for yourself
1181             $success = $self->exec ($node, $dbh, $href);
1182             foreach $el ( @{$node->{'Kids'}} ) {
1183             if ( (ref $el) =~ /::REFERENCE$/ || (ref $el) =~ /::CHILD$/ ) {
1184             $success &= $self->rexec ($dbh, $href, $el);
1185             } }
1186              
1187             $success;
1188             } # -rexec
1189              
1190             #__________________________________________________________________________
1191             # Output the message
1192             #
1193             sub output_message {
1194             my $self = shift; # XMLMessage
1195              
1196             #if ( $self->{TYPE} eq 'XML' ) {
1197             return $self->output_xml();
1198             #} else {
1199             # print $self->{TYPE} . ": not implemented\n"
1200             #}
1201             }
1202              
1203             #__________________________________________________________________________
1204             # Should have executed prior to this
1205             #
1206             # FIXME: Prints multuple childs
1207             #
1208             #
1209             sub output_xml {
1210             my $self = shift; # XMLMessage
1211             my $level = shift || 0; # Level
1212             my $resix = shift || 0; # Parent result set index
1213             my $node = shift || $self->{_Template}; # TEMPLATE|CHILD|REFERENCE
1214              
1215             my ($r, $i, $j, $el, $el1, $res, $rref, $xml);
1216             $xml = ""; # Target string
1217              
1218             # see if there's anything to output
1219             my $found = 0;
1220             foreach (@{$node->{_RESULTS}}) {
1221             if ( $_->{_RESIX} == $resix ) {
1222             $found = 1;
1223             } }
1224             if ( !$found ) {
1225             if ( (ref $node) =~ /::TEMPLATE$/ ) { # Always print the template
1226             for ( $j=0;$j<$level;$j++ ) { $xml .= " "; }
1227             $xml .= "<$node->{NAME} />\n";
1228             return $xml;
1229             } else { # ... but nothing else!
1230             return $xml;
1231             } }
1232             $i = 0; # Initial input value. The loop will execute once always
1233             do {
1234             for ( $r=0; $node->{_RESULTS}->[$r]; $r++ ) { # $r is resix for kids
1235             # >>>>>>>>>>
1236             $rref = $node->{_RESULTS}->[$r];
1237             if ( $rref->{_INIX} == $i && $rref->{_RESIX} == $resix
1238             # FIXME: this is a hack...
1239             && !$rref->{_PRINTED} ) {
1240             # Output the tag
1241             for ( $j=0;$j<$level;$j++ ) { $xml .= " "; }
1242             $xml .= "<$node->{NAME}";
1243             # Output columns with the face of 'ATTRIBUTE' as attributes
1244             foreach my $elname ( keys %{$node->{_COLLIST}} ) {
1245             $el = $node->{_COLLIST}->{$elname};
1246             if ( $el->{FACE} && $el->{FACE} eq 'ATTRIBUTE' ) {
1247             if (defined $rref->{$el->{NAME}} && $rref->{$el->{NAME}} ne ''){
1248             $xml .= " $el->{'NAME'}=\"" .
1249             HTML::Entities::encode($rref->{$el->{NAME}},'&<>"').'"';
1250             } } }
1251             $xml .= ">\n";
1252             # Output the rest of the stuff
1253             foreach $el ( @{$node->{'Kids'}} ) {
1254             if ( (ref $el) =~ /::COLUMN$/ &&
1255             (!defined $el->{FACE} || $el->{FACE} eq 'TAG') ) {
1256             if ( !$el->{'HIDDEN'} ) {
1257             for ( $j=0;$j<$level+1;$j++ ) { $xml .= " "; }
1258             if ( defined $rref->{$el->{NAME}}
1259             && $rref->{$el->{NAME}} ne '' ) {
1260             $xml .= "<$el->{'NAME'}>"
1261             . HTML::Entities::encode($rref->{$el->{NAME}},"&<>")
1262             . "{'NAME'}>\n";
1263             } else {
1264             $xml .= "<$el->{'NAME'} />\n";
1265             }
1266             }
1267             } elsif ((ref $el)=~ /::REFERENCE$/ || (ref $el)=~ /::CHILD$/) {
1268             my $niter = (defined $el->{_INVALUES})
1269             ? scalar @{$el->{_INVALUES}}
1270             : 0;
1271             for ( $i=0; $i{_RESULTS}}; $i++ ) {
1272             $j = 0;
1273             do {
1274             $xml .= $self->output_xml ($level+1,$r,$el);
1275             } while ( $j++ < $niter );
1276             }
1277             }
1278             }
1279             for ( $j=0;$j<$level;$j++ ) { $xml .= " "; }
1280             $xml .= "{'NAME'}>\n";
1281             # FIXME: this is the second part of the hack.. See above..
1282             $rref->{_PRINTED} = 1;
1283             }
1284             # >>>>>>>>>>
1285             } ##for $r
1286             } while ( $node->{_INVALUES}->[$i++] );
1287              
1288             return $xml;
1289             } # -output_xml
1290              
1291             #__________________________________________________________________________
1292             # Test BUILT-IN
1293             #
1294             sub t_bltin {
1295             print "t_bltin:";
1296             foreach (@_) {
1297             print "\t$_\n";
1298             }
1299             return "returned by t_bltin";
1300             }
1301              
1302             #__________________________________________________________________________
1303             # Fix the GMTIME values
1304             #
1305             sub fix_gmdatetime {
1306             my $self = shift; # XMLMessage
1307             my $node = shift; # TEMPLATE | CHILD | REFERENCE
1308             my $val = shift || undef;
1309              
1310             if ( !defined $val ) {
1311             return undef;
1312             }
1313             my $direction = $node->{_PARENT_TAG}->{ACTION}
1314             ? $node->{_PARENT_TAG}->{ACTION} eq 'SELECT'
1315             ? 'TOGMT'
1316             : 'FROMGMT'
1317             : 'TOGMT';
1318             my $curfmt = '';
1319             my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
1320             my $hmon = { 'Jan' => 0, 'Feb' => 1, 'Mar' => 2, 'Apr' => 3,
1321             'May' => 4, 'Jun' => 5, 'Jul' => 6, 'Aug' => 7,
1322             'Sep' => 8, 'Oct' => 9, 'Nov' => 10, 'Dec' => 11
1323             };
1324             if ($val =~ /^\s*(\d{4})\/(\d{1,2})\/(\d{1,2})\s*(\d{1,2}):(\d{1,2})/ ||
1325             $val =~ /^\s*(\d{4})-(\d{1,2})-(\d{1,2})\s*(\d{1,2}):(\d{1,2})/
1326             ) {
1327             # E.g. 2000-3-21 12:05
1328             $curfmt = 'GMT'; # SES/SIS GMT
1329             } elsif ( $val =~ /^\s*(\d{8})\s*(\d{4})/ ) {
1330             # E.g. 20000321 1205
1331             $curfmt = 'GMTSHORT'; # Mark sends it like this..
1332             } elsif ( $val =~
1333             /^\s*(\D{3})\s*(\d{1,2})\s*(\d{4})\s*(\d{1,2}):(\d{2})(\D{2})/
1334             ) {
1335             # E.g. Mar 21 2000 12:05:46:350PM
1336             $curfmt = 'SYBASE'; # As delivered by the Sybase DB engine
1337             }
1338             if ( $direction eq 'TOGMT' && $curfmt eq 'SYBASE' ) {
1339             # - Transform from SYBASE to GMT
1340             # This time is received from database and it's local,
1341             # most probably according to the TZ environment variable
1342             # - Calculate the time difference to GMT
1343             my $ctime = time();
1344             ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
1345             = gmtime($ctime);
1346             my $time_t = POSIX::mktime ($sec,$min,$hour,$mday,$mon,$year);
1347             my $tdiff = $ctime - $time_t;
1348             ($year,$mon,$mday,$hour,$min) = ($3, $1, $2, $4, $5);
1349             $mon = $hmon->{$mon} ? $hmon->{$mon} : 0;
1350             $hour += 12 if ( $6 && $6 eq 'PM' && $hour != 12 );
1351             $year -= 1900;
1352             $time_t = POSIX::mktime (0,$min,$hour,$mday,$mon,$year);
1353             $val = POSIX::strftime "%Y/%m/%d %H:%M", gmtime($time_t-$tdiff);
1354             # print "Date = ", POSIX::ctime($time_t);
1355             } elsif ( $direction eq 'FROMGMT' && $curfmt eq 'GMT' ) {
1356             # - Transform from GMT to SYBASE
1357             ($year,$mon,$mday,$hour,$min) = ($1, $2, $3, $4, $5);
1358             $mon--;
1359             $year -= 1900;
1360             my $time_t = POSIX::mktime (0,$min,$hour,$mday,$mon,$year);
1361             if ( $node->{DATATYPE} eq 'DATE' ) {
1362             $val = POSIX::strftime "%b %d %Y", localtime($time_t);
1363             } elsif ( $node->{DATATYPE} eq 'TIME' ) {
1364             $val = POSIX::strftime "%I:%M", localtime($time_t);
1365             } else {
1366             $val = POSIX::strftime "%b %d %Y %I:%M:00:000%p",
1367             localtime($time_t);
1368             }
1369             } elsif ( $direction eq 'FROMGMT' && $curfmt eq 'GMTSHORT' ) {
1370             # - Transform from GMTSHORT to SYBASE
1371             my ($ymd,$hmi) = ($1,$2);
1372             $year = substr ($ymd,0,4);
1373             $mon = substr ($ymd,4,2);
1374             $mday = substr ($ymd,6,2);
1375             $hour = substr ($hmi,0,2);
1376             $min = substr ($hmi,2,2);
1377             $mon--;
1378             $year -= 1900;
1379             my $time_t = POSIX::mktime (0,$min,$hour,$mday,$mon,$year);
1380             if ( $node->{DATATYPE} eq 'DATE' ) {
1381             $val = POSIX::strftime "%b %d %Y", localtime($time_t);
1382             } elsif ( $node->{DATATYPE} eq 'TIME' ) {
1383             $val = POSIX::strftime "%I:%M:00:000%p", localtime($time_t);
1384             } else {
1385             $val = POSIX::strftime "%b %d %Y %I:%M:00:000%p",
1386             localtime($time_t);
1387             }
1388             } # Otherwise don't touch
1389             return $val;
1390             } ##fix_gmdatetime
1391              
1392              
1393              
1394             1;
1395             # -package DBIx::XMLMessage;
1396              
1397              
1398              
1399             # _________________________________________________________________________
1400             # Tag Prototype
1401             #
1402             package DBIx::XMLMessage::Element;
1403              
1404             use strict;
1405             use vars qw (@ISA %EXPORT_TAGS $VERSION @rattrs);
1406             $VERSION = '0.01';
1407             @ISA = qw ( Exporter );
1408             %EXPORT_TAGS = ('elements' => [ 'VERSION', '%TEMPLATE::',
1409             '%REFERENCE::', '%CHILD::', '%KEY::', '%COLUMN::', '%PARAMETER::']);
1410             Exporter::export_ok_tags ('elements');
1411             @rattrs = qw (NAME);
1412             1;
1413              
1414             #__________________________________________________________________________
1415             # Tag TEMPLATE
1416             #
1417             package DBIx::XMLMessage::TEMPLATE;
1418              
1419             use vars qw (@ISA %EXPORT_TAGS @rattrs @oattrs @rkids @okids);
1420             @ISA = qw (DBIx::XMLMessage::Element);
1421             @rattrs = qw (NAME VERSION TYPE);
1422             @oattrs = qw (
1423             ACTION
1424             DEBUG
1425             PROC
1426             RTRIMTEXT
1427             TABLE
1428             TOLERANCE
1429             _CHILIST
1430             _COLLIST
1431             _KEYLIST
1432             _PARENT_TAG
1433             _PARLIST
1434             _REFLIST
1435             );
1436             @okids = qw (COLUMN REFERENCE CHILD PARAMETER KEY);
1437              
1438             sub new {
1439             my ($class, %args) = @_;
1440             return bless \%args, $class;
1441             }
1442             1;
1443              
1444             #__________________________________________________________________________
1445             # Tag KEY
1446             #
1447             package DBIx::XMLMessage::KEY;
1448             use vars qw (@ISA %EXPORT_TAGS @rattrs @oattrs @rkids @okids);
1449             @ISA = qw (DBIx::XMLMessage::Element);
1450             @rattrs = qw (NAME);
1451             @oattrs = qw (_PARENT_TAG DATATYPE RTRIMTEXT DEFAULT PARENT_NAME);
1452              
1453             sub new {
1454             my ($class, %args) = @_;
1455             return bless \%args, $class;
1456             }
1457             1;
1458              
1459             #__________________________________________________________________________
1460             # Tag COLUMN
1461             #
1462             package DBIx::XMLMessage::COLUMN;
1463             use vars qw (@ISA %EXPORT_TAGS @rattrs @oattrs @rkids @okids);
1464             @ISA = qw (DBIx::XMLMessage::Element);
1465             @rattrs = qw (NAME);
1466             @oattrs = qw (
1467             ACTION
1468             BLTIN
1469             CARDINALITY
1470             DATATYPE
1471             DEBUG
1472             DEFAULT
1473             EXPR
1474             FACE
1475             GENERATE_PK
1476             HIDDEN
1477             RTRIMTEXT
1478             TOLERANCE
1479             _PARENT_TAG
1480             );
1481              
1482             sub new {
1483             my ($class, %args) = @_;
1484             return bless \%args, $class;
1485             }
1486             1;
1487              
1488             #__________________________________________________________________________
1489             # Tag REFERENCE
1490             #
1491             package DBIx::XMLMessage::REFERENCE;
1492             use vars qw (@ISA %EXPORT_TAGS @rattrs @oattrs @rkids @okids);
1493             @ISA = qw (DBIx::XMLMessage::Element);
1494             @rattrs = qw (NAME);
1495             @oattrs = qw (
1496             ACTION
1497             CARDINALITY
1498             DEBUG
1499             PROC
1500             RTRIMTEXT
1501             TABLE
1502             TOLERANCE
1503             WHERE_CLAUSE
1504             _CHILIST
1505             _COLLIST
1506             _KEYLIST
1507             _PARENT_TAG
1508             _PARLIST
1509             _REFLIST
1510             );
1511             @okids = qw (COLUMN REFERENCE CHILD PARAMETER KEY);
1512              
1513             sub new {
1514             my ($class, %args) = @_;
1515             return bless \%args, $class;
1516             }
1517             1;
1518              
1519             #__________________________________________________________________________
1520             # Tag CHILD
1521             #
1522             package DBIx::XMLMessage::CHILD;
1523             use vars qw (@ISA %EXPORT_TAGS @rattrs @oattrs @rkids @okids);
1524             @ISA = qw (DBIx::XMLMessage::Element);
1525             @rattrs = qw (NAME);
1526             @oattrs = qw (
1527             ACTION
1528             CARDINALITY
1529             DEBUG
1530             MAXROWS
1531             PROC
1532             RTRIMTEXT
1533             TABLE
1534             TOLERANCE
1535             WHERE_CLAUSE
1536             _CHILIST
1537             _COLLIST
1538             _KEYLIST
1539             _PARENT_TAG
1540             _PARLIST
1541             _REFLIST
1542             );
1543             @okids = qw (COLUMN REFERENCE CHILD PARAMETER KEY);
1544              
1545             sub new {
1546             my ($class, %args) = @_;
1547             return bless \%args, $class;
1548             }
1549             1;
1550              
1551             #__________________________________________________________________________
1552             # Tag PARAMETER
1553             #
1554             package DBIx::XMLMessage::PARAMETER;
1555             use vars qw (@ISA %EXPORT_TAGS @rattrs @oattrs @rkids @okids);
1556             @ISA = qw (DBIx::XMLMessage::Element);
1557             @rattrs = qw (NAME);
1558             @oattrs = qw (
1559             CARDINALITY
1560             DATATYPE
1561             DEFAULT
1562             EXPR
1563             RTRIMTEXT
1564             _PARENT_TAG
1565             );
1566              
1567             sub new {
1568             my ($class, %args) = @_;
1569             return bless \%args, $class;
1570             }
1571             1;
1572              
1573             __END__