File Coverage

blib/lib/Data/Transformator.pm
Criterion Covered Total %
statement 256 357 71.7
branch 84 140 60.0
condition 8 16 50.0
subroutine 29 35 82.8
pod 0 3 0.0
total 377 551 68.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             #!/usr/bin/perl -d:ptkdb -w
3             #
4             # This is module is based on a module with the same name, implemented
5             # when working for Newtec Cy, located in Belgium,
6             # http://www.newtec.be/.
7             #
8              
9             =head1 NAME
10              
11             Data::Transformator - transform nested Perl data structures.
12              
13             =head1 SYNOPSIS
14              
15             From one of the test cases:
16              
17             use Data::Transformator;
18              
19             my $tree
20             = {
21             e => [
22             {
23             e1 => {
24             },
25             },
26             {
27             e2 => {
28             },
29             },
30             {
31             e3 => {
32             },
33             },
34             ],
35             };
36              
37             my $expected_data
38             = {
39             e => [
40             {
41             e1 => {
42             },
43             },
44             ],
45             };
46              
47             my $transformation
48             = Data::Transformator->new
49             (
50             name => 'test_transform3',
51             contents => $tree,
52             apply_identity_transformation => {
53             e => [
54             1,
55             ],
56             },
57             );
58              
59             my $transformed_data = $transformation->transform();
60              
61             use Data::Comparator qw(data_comparator);
62              
63             my $differences = data_comparator($transformed_data, $expected_data);
64              
65             if ($differences->is_empty())
66             {
67             print "$0: 3: success\n";
68              
69             ok(1, '3: success');
70             }
71             else
72             {
73             print "$0: 3: failed\n";
74              
75             ok(0, '3: failed');
76             }
77              
78             =head1 DESCRIPTION
79              
80             Data::Transformator allows to transform a nested perl data structure
81             -- the source data -- in a new nested perl data structure -- the
82             result data. The source nested data structure can contain perl hashes,
83             arrays and scalars, but should not be self-referential (if I remember
84             well, the existing protection against self-referential data structures
85             in the transformator engine is currently broken).
86              
87             =head1 USE
88              
89             Running a transformation can be done in two essentially different
90             ways:
91              
92             =over 2
93              
94             =item
95              
96             extract data from an existing source : the transformation applies an
97             identity transformation (meaning copy the source structure to the
98             result), and during the process it uses selectors to select what data
99             from the source to copy to the result. The result automatically
100             inherits the structure from the source. This is called a selective
101             transformation. Use the key 'apply_identity_transformation' to enable
102             this mode.
103              
104             =item
105              
106             copy data from source to result : the transformation must be told what
107             the exact structure of the result is, before the result data can be
108             inserted into the result. This is called a constructive
109             transformation.
110              
111             =back
112              
113             It is possible to combine the two above in a single run, but that is
114             currently not tested enough to be sure it works alright, so be very
115             careful with that.
116              
117             To use Data::Transformator, you have to
118              
119             =over 2
120              
121             =item 1
122              
123             Construct the transformation with appropriate options:
124              
125             =over 2
126              
127             =item
128              
129             Give the transformation a name, the name describes the purpose and/or
130             activities for the transformation.
131              
132             =item
133              
134             Tell the transformation if you want it to run as a selective
135             transformation or not (option name
136             'apply_identity_transformation'). Transformations can always be used
137             as constructive transformations.
138              
139             =item
140              
141             Tell the transformation how to find the data source.
142              
143             =over 2
144              
145             =item
146              
147             Or the data source is literal content (option name 'contents').
148              
149             =item
150              
151             Or the data source is an object that implements a '->generate()'
152             method (option name 'source'). Transformations implement themselves a
153             '->generate()' method such that transformations can be cascaded
154             easily.
155              
156             =back
157              
158             =item
159              
160             Tell the transformation what to transform (selection) and how to
161             transform (generate result). Data::Transformator uses code references
162             to generate results, of alternatively simpler things as explained
163             below. Whenever a code reference is used, it is called with the
164             arguments (self, context, current_content). Here, self is the
165             Data::Transformator object, context is an object that describes the
166             current context in the source data structure, current_content is the
167             generated result so far. $context->{path} contains a string with the
168             path to the current element. Compononents of the path are '/'
169             separated (unless overwritten with the constructor key 'separator').
170             This can be used for regular matching, and is especially handy using
171             'simple_transformators', see below.
172              
173             Following keys are available to the constructor of
174             Data::Transformator:
175              
176             =over 2
177              
178             =item ->{simple_transformators}
179              
180             Is an array of simple_transformators. Each simple_transformator is a
181             hash with a 'matcher' key that contains a regular expression that is
182             matched with the path of the currently selected element. If there is
183             a match, the selected subtree is put in the end result, under the
184             value of the 'key' element of the simple_transformator (creating a
185             hash in the result if necessary). If there is no 'key' element in the
186             simple_transformator, a 'code' element is looked for, which is a code
187             reference. The code is called to insert an appropriate result.
188              
189             =item ->{transformators}
190              
191             Is an array of transformators. Each transformator is code reference
192             that gets called as usual.
193              
194             =item ->{apply_identity_transformation}
195              
196             Contains a nested perl data structure that reflects the structure of
197             the source data. All data of the source that is selected by scalars
198             that evaluate to true in the content of apply_identity_transformation,
199             is inserted in the result. This key is very handy for selecting a set
200             of small portions of data, if the structure of the source is known
201             beforehand.
202              
203             =back
204              
205             Take a look at existing examples, e.g. in the unit tests of the
206             transformation engine.
207              
208             =back
209              
210             =item 2
211              
212             Call the '->transform()' method on the transformator.
213              
214             =item 3
215              
216             Use the result data that is returned by the '->transform()' method.
217              
218             =back
219              
220              
221             =head1 The transformation library
222              
223             There is a small transformation library embedded in the
224             Data::Transformator. This library currently allows to
225              
226             =over 2
227              
228             =item
229              
230             transform an array to be found somewhere in the data source to a hash
231             in the result set.
232              
233             =item
234              
235             transform a hash to be found somewhere in the data source to an array
236             in the result set.
237              
238             =back
239              
240             The library generates closures that work on the source data.
241              
242              
243             =head1 BACKGROUND
244              
245             For the interested reader, please follow these reasoning steps:
246              
247             =over 2
248              
249             =item 1
250              
251             A database query of a relational database (using tables and nested
252             tabled) can always be written out in one of the XML query dialects.
253              
254             =item 2
255              
256             Following from point 1: a database query can be expressed as a
257             structured query.
258              
259             =item 3
260              
261             A structured query can be summarized as
262              
263             =over 2
264              
265             =item 1
266              
267             a selection of data from a preexisting data source.
268              
269             =item 2
270              
271             a structural simplification of the selection.
272              
273             =back
274              
275             =item 4
276              
277             Combining the above: a query can be defined as applying (1) a
278             selective transformation and (2) a constructive transformation in
279             sequence to a preexisting data source. This is called cascaded
280             transformations.
281              
282             =back
283              
284             =head1 BUGS
285              
286             Does only work with scalars, hashes and arrays. Support for
287             self-referential structures seems broken at the moment.
288              
289             =head1 AUTHOR
290              
291             Hugo Cornelis, hugo.cornelis@gmail.com
292              
293             Copyright 2007 Hugo Cornelis.
294              
295             This module is free software; you can redistribute it and/or
296             modify it under the same terms as Perl itself.
297              
298             =head1 SEE ALSO
299              
300             Data::Merger(3), Data::Comparator(3), Clone(3)
301              
302             =cut
303              
304              
305             package Data::Transformator;
306              
307              
308 5     5   3199 use strict;
  5         11  
  5         176  
309 5     5   5420 use Data::Dumper;
  5         58434  
  5         26685  
310              
311              
312             my $debug_enabled = '0';
313              
314             my $debug_context = '0';
315              
316             my $debug_identity_transform = '0';
317              
318             my $debug_identity_transform2 = '0';
319              
320             my $debug_identity_transform3 = '0';
321              
322             my $separator = "/";
323              
324              
325             sub _apply_identity_transformation
326             {
327 264     264   240 my $self = shift;
328              
329 264         232 my $context = shift;
330              
331 264         335 my $current = _context_get_current($context);
332              
333 264         365 my $result_current = _context_get_current_result($context);
334              
335 264         369 my $previous = _context_get_previous($context);
336              
337 264         355 my $result_previous = _context_get_previous_result($context);
338              
339 264         359 my $previous_type = $previous->{type};
340              
341             #! Do not change the order of the tests, it does harm, though it should
342             #! not. I do not know what is happening.
343              
344             # fill in constants
345              
346 264 100       529 if ($current->{type} eq 'SCALAR')
347             {
348 100         147 my $content = _context_get_current_content($context);
349              
350             # fill in the constant
351              
352 100         137 $result_current->{content} = $content;
353              
354 100 50       188 if ($debug_identity_transform)
355             {
356 0         0 print STDERR "_apply_identity_transformation() for constant $content\n";
357              
358 0 0       0 if ($debug_identity_transform2)
359             {
360 0         0 print STDERR Data::Dumper::Dumper($result_current);
361             }
362             }
363              
364             }
365              
366             # link items on stack by looking at the type of one level up
367              
368             # if the previous result is the root
369              
370 264 100       559 if ($previous_type eq 'ROOT')
    100          
    50          
371             {
372             # we do not have a result yet, get one by assigning (initialization)
373              
374 21         36 $result_previous->{content} = $result_current->{content};
375             }
376             elsif ($previous_type eq 'HASH')
377             {
378 190         213 my $previous_component_key = $previous->{component_key};
379              
380 190         351 $result_previous->{content}->{$previous_component_key}
381             = $result_current->{content};
382              
383 190 50       398 if ($debug_identity_transform2)
384             {
385 0         0 my $component_key = $previous->{component_key};
386              
387 0         0 my $content = $result_current->{content};
388              
389 0         0 print STDERR "Default Transform : added to previous hash for key $component_key\n";
390 0         0 print STDERR Data::Dumper::Dumper($content);
391 0         0 print STDERR "Default Transform : results in\n";
392 0         0 print STDERR Data::Dumper::Dumper($result_previous);
393 0         0 print STDERR "Default Transform : end\n";
394             }
395             }
396             elsif ($previous_type eq 'ARRAY')
397             {
398 53         42 push @{$result_previous->{content}}, $result_current->{content};
  53         115  
399              
400 53 50       110 if ($debug_identity_transform2)
401             {
402 0         0 my $component_key = $previous->{component_key};
403              
404 0         0 my $content = $result_current->{content};
405              
406 0         0 print STDERR "Default Transform : added to previous array, item $component_key\n";
407 0         0 print STDERR Data::Dumper::Dumper($content);
408 0         0 print STDERR "Default Transform : results in\n";
409 0         0 print STDERR Data::Dumper::Dumper($result_previous);
410 0         0 print STDERR "Default Transform : end\n";
411             }
412             }
413             else
414             {
415 0 0       0 if ($debug_identity_transform)
416             {
417 0         0 print STDERR "_apply_identity_transformation(): Illegal context type $previous_type\n";
418              
419 0 0       0 if ($debug_identity_transform2)
420             {
421 0         0 print STDERR Data::Dumper::Dumper($context, $previous, $current);
422             }
423             }
424             }
425              
426 264 50       585 if ($debug_identity_transform3)
427             {
428 0         0 print STDERR "_apply_identity_transformation() main result is now\n", Dumper($context->{result});
429             }
430             }
431              
432              
433             sub _apply_transformations
434             {
435 555     555   516 my $self = shift;
436              
437 555         472 my $context = shift;
438              
439 555         516 my $component_key = shift;
440              
441 555         481 my $component = shift;
442              
443             # # if the default transformation is enabled (i.e. straight copy)
444              
445             # if ($self->{apply_identity_transformation})
446             # {
447             # $self->_apply_identity_transformation($context);
448             # }
449              
450             # if the component_key is defined
451              
452             #! note that component_keys are always defined for hashes and arrays, but
453             #! not for scalars. This is currently a bug.
454              
455 555 100       942 if (defined $component_key)
456             {
457             # apply user transformations
458              
459 533         802 $self->_apply_user_transformations($context, $component_key, $component, );
460             }
461             }
462              
463              
464             sub _apply_user_transformations
465             {
466 533     533   484 my $self = shift;
467              
468 533         449 my $context = shift;
469              
470 533         466 my $component_key = shift;
471              
472 533         459 my $component = shift;
473              
474 533         600 my $array = $context->{array};
475              
476             # simple regex transforms (based on sems_system module)
477              
478 533 50       1021 if (exists $self->{simple_transformators})
479             {
480              
481 0         0 SIMPLE_TRANSFORM:
482 0         0 foreach my $transformator (@{$self->{simple_transformators}})
483             {
484 0         0 my $matcher = $transformator->{matcher};
485              
486 0 0       0 if ($context->{path} =~ m|$matcher|)
487             {
488             # print STDERR "Calling transformator : $transformator->{name}\n";
489              
490             # a simple one-to-one mapping
491              
492 0 0       0 if (exists $transformator->{key})
    0          
493             {
494 0         0 my $result_key = $transformator->{key};
495              
496             #t why do I fetch the main result here ?
497             #t I should be fetching the current result ?
498              
499             #t Perhaps I could use $result_key in an eval string
500             #t to allow nested results for simple transformators.
501              
502 0         0 my $result = _context_get_main_result($context);
503              
504 0         0 $result->{content}->{$result_key}
505             = _context_get_current_content($context);
506             }
507              
508             # possibly a one-to-many mapping
509              
510             elsif (exists $transformator->{code})
511             {
512 0         0 my $code = $transformator->{code};
513              
514 0         0 my $result = _context_get_main_result($context);
515              
516 0         0 my $current_content
517             = _context_get_current_content($context);
518              
519 0         0 my $transformation_result
520             = &$code($self, $context, $current_content, );
521              
522 0 0       0 if ($transformation_result)
523             {
524             #! expensive copy, can be replaced with a loop
525              
526 0 0 0     0 if (exists $result->{content}
527             && defined $result->{content})
528             {
529 0         0 $result->{content}
530             = {
531 0         0 %{$result->{content}},
532             %$transformation_result,
533             };
534             }
535             else
536             {
537 0         0 $result->{content}
538             = {
539             %$transformation_result,
540             };
541             }
542             }
543             }
544              
545 0         0 last SIMPLE_TRANSFORM;
546             }
547             else
548             {
549             # print STDERR "$context->{path} does not match with $matcher\n";
550             }
551             }
552             }
553              
554             # general transformators
555              
556 533 100       1267 if (exists $self->{transformators})
557             {
558 193         186 my $count = 0;
559              
560 193         166 foreach my $transformator (@{$self->{transformators}})
  193         315  
561             {
562 273         245 $count++;
563              
564             # print STDERR "Calling transformator $count\n"; #$transformator->{name}\n";
565              
566 273         467 &$transformator($array->[$#$array], $context, $component);
567             }
568             }
569             }
570              
571              
572             sub _result_create
573             {
574 434     434   395 my $default_result = shift;
575              
576             return {
577 434         1137 content => $default_result,
578             };
579             }
580              
581              
582             sub _context_create
583             {
584 30     30   35 my $base_path = shift;
585              
586 30         47 my $base_result = shift;
587              
588 30   50     100 my $separator = shift || "/";
589              
590             #t the root contains the complete result, the other entries in the
591             #t array are sub results, which are to be covered by the root
592             #t result.
593              
594 30         57 my $context
595             = {
596             array => [
597             {
598             result => _result_create($base_result),
599             type => 'ROOT',
600             }
601             ],
602             path => $base_path,
603             separator => $separator,
604             };
605              
606 30         68 $context->{result} = $context->{array}->[0]->{result};
607              
608 30         77 return $context;
609             }
610              
611              
612             #
613             # Obtain a ref. containing info of the top element of the current context.
614             #
615              
616             sub _context_get_current
617             {
618 1075     1075   1097 my $context = shift;
619              
620 1075         1059 my $array = $context->{array};
621              
622 1075         1193 my $current = $array->[$#$array];
623              
624 1075         1710 return $current;
625             }
626              
627              
628             #
629             # Obtain the original content of the top element of the current context. The
630             # original content is taken from the original element. Must be considered
631             # read-only, and for informational purposes only. Do not embed it into the
632             # result, since if so, you are mixing the content of the original data
633             # structure with the resulting data structure, which is not the intent of this
634             # module. Perhaps in the future this can be changed, to allow creation of
635             # simplified views on a complicated data structure.
636             #
637              
638             sub _context_get_current_content
639             {
640 110     110   163 return _context_get_current($_[0])->{content};
641             }
642              
643              
644             #
645             # Obtain information of the resulting content of the top element of the
646             # current context. The actual content of the result can be found ->{content}.
647             #
648              
649             sub _context_get_current_result
650             {
651 671     671   943 return _context_get_current($_[0])->{result};
652             }
653              
654              
655             #
656             # Obtain information of the main content of the current context. The actual
657             # content of the result can be found ->{content}.
658             #
659              
660             sub _context_get_main_result
661             {
662 52     52   95 return $_[0]->{result};
663             }
664              
665              
666             #
667             # Obtain a ref. containing info of the next-to-top element of the current
668             # context.
669             #
670              
671             sub _context_get_previous
672             {
673 528     528   460 my $context = shift;
674              
675 528         511 my $array = $context->{array};
676              
677 528         500 my $top_index = $#$array;
678              
679 528 50       820 if ($top_index > 0)
680             {
681 528         603 my $current = $array->[$#$array - 1];
682              
683 528         735 return $current;
684             }
685             else
686             {
687 0         0 return undef;
688             }
689             }
690              
691              
692             #
693             # Obtain information of the next-to-top result of the context. The actual
694             # content of the result can be found ->{content}.
695             #
696             # returns undef if there is no such item.
697             #
698              
699             sub _context_get_previous_result
700             {
701 264     264   372 my $previous = _context_get_previous($_[0]);
702              
703 264 50       382 if ($previous)
704             {
705 264         381 return $previous->{result};
706             }
707             else
708             {
709 0         0 return undef;
710             }
711             }
712              
713              
714             sub _context_get_seen_info
715             {
716 0     0   0 return($_[0]->{seen}->{$_[1]});
717             }
718              
719              
720             sub _context_has_seen
721             {
722 407     407   1463 return(exists $_[0]->{seen}->{$_[1]});
723             }
724              
725              
726             sub _context_matches
727             {
728             #t we could do fancy things, e.g. if $_[1] is a 'context
729             #t describing' hash, convert hash to string before comparing.
730              
731 0     0   0 return $_[0]->{path} =~ /$_[1]/;
732             }
733              
734              
735             sub _context_pop
736             {
737 404     404   370 my $context = shift;
738              
739 404         444 my $separator = $context->{separator};
740              
741 404         325 pop @{$context->{array}};
  404         498  
742              
743 404         2711 $context->{path} =~ s/^(.*[^\\])$separator.*/$1/;
744              
745 404 50       997 if ($debug_context)
746             {
747 0         0 print STDERR "($context->{path}) _context_pop()\n";
748             }
749             }
750              
751              
752             sub _context_push
753             {
754 404     404   415 my $context = shift;
755              
756 404         371 my $new = shift;
757              
758 404         461 my $separator = $context->{separator};
759              
760 404         621 $new->{result} = _result_create($new->{default_result});
761              
762 404         408 push @{$context->{array}}, $new;
  404         578  
763              
764 404         704 $context->{path} .= "${separator}__NONE__";
765              
766 404 50       819 if ($debug_context)
767             {
768 0         0 print STDERR "($context->{path}) _context_push(), default_result $new->{default_result}\n";
769             }
770             }
771              
772              
773             sub _context_register_current
774             {
775 574     574   526 my $context = shift;
776              
777 574         532 my $transform = shift;
778              
779 574         604 my $component_key = shift;
780              
781 574         505 my $component = shift;
782              
783 574         519 my $count = shift;
784              
785 574         630 my $separator = $context->{separator};
786              
787 574         558 my $array = $context->{array};
788              
789             #t actually I think I need an _context_unregister_current() sub
790             #t that resets ->{string}, ->{display}, and possibly others.
791              
792             # print STDERR "($separator), $component, $component_key\n";
793              
794 574 100       879 if ($component_key)
795             {
796 558         903 $component_key =~ s|$separator|\\${separator}|g;
797             }
798              
799 574         966 $array->[$#$array]->{current} = $count;
800 574 100       1147 $array->[$#$array]->{component_key}
801             = defined $component_key ? $component_key : '__UNDEF__';
802             # $array->[$#$array]->{string} = undef;
803             # $array->[$#$array]->{type} = ref $component || 'SCALAR';
804 574         824 $array->[$#$array]->{content} = $component;
805              
806 574 50       1037 if ($debug_enabled)
807             {
808 0         0 print
809             STDERR
810             " " x (2 * $#$array)
811             . "$array->[$#$array]->{type} : $array->[$#$array]->{component_key}\n";
812             }
813              
814 574         3500 $context->{path}
815             =~ s|(.*[^\\]$separator).*|$1$array->[$#$array]->{component_key}|;
816              
817 574 50       1637 if ($debug_context)
818             {
819 0         0 print STDERR "($context->{path}) [_context_register_current($array->[$#$array]->{component_key})]\n";
820             }
821             }
822              
823              
824             sub _context_set_seen_info
825             {
826 814     814   1845 $_[0]->{seen}->{$_[1]} = $_[2];
827             }
828              
829              
830             sub _transform_any
831             {
832 407     407   403 my $self = shift;
833              
834 407         352 my $context = shift;
835              
836 407         397 my $contents = shift;
837              
838 407         339 my $result;
839              
840 407 100       582 my $contents_output
841             = defined $contents ? $contents : '__UNDEF__';
842              
843             #t The problem is as follows : the current is not pushed on the context,
844             #t so we consult the wrong entry to compute the result.
845             #t Second this result may only be computed if the default transform
846             #t is enabled.
847              
848 407 50 100     546 if (_context_has_seen($context, $contents_output) && 0)
849             {
850 0         0 my $seen_info = _context_get_seen_info($context, $contents_output);
851              
852 0 0       0 if ($debug_context)
853             {
854 0         0 print STDERR "For context $context->{path}, we have seen $contents_output, $seen_info\n";
855             }
856              
857 0         0 $result = $seen_info;
858              
859 0         0 return $result;
860             }
861              
862 407         666 _context_set_seen_info($context, $contents_output, $contents_output);
863              
864 407         565 local $_ = ref($contents);
865              
866 407 100       651 if (!$_)
867             {
868             # a constant.
869              
870 162 50       266 if ($debug_enabled)
871             {
872 0         0 print STDERR "$self->{name} : Constant ($contents_output)\n";
873             }
874              
875 162         275 $result = $self->_transform_constant($context, $contents);
876             }
877             else
878             {
879             BASE_TYPE:
880             {
881             # an array.
882              
883 245         205 /^ARRAY$/ and do
884 245 100       493 {
885 26 50       56 if ($debug_enabled)
886             {
887 0         0 print STDERR "$self->{name} : Array ($contents_output)\n";
888             }
889              
890 26         67 $result = $self->_transform_array($context, $contents);
891              
892 26         37 last BASE_TYPE;
893             };
894              
895             # a hash
896              
897             /^HASH$/ and do
898 219 100       599 {
899 208 50       344 if ($debug_enabled)
900             {
901 0         0 print STDERR "$self->{name} : Hash ($contents_output)\n";
902             }
903              
904 208         358 $result = $self->_transform_hash($context, $contents);
905              
906 208         276 last BASE_TYPE;
907             };
908              
909             # an object.
910              
911 11 50       19 if ($debug_enabled)
912             {
913 0         0 print STDERR "$self->{name} : Object ($_)\n";
914             }
915              
916 11         12 local $_ = $contents_output;
917              
918             OBJECT_TYPE:
919             {
920 11         11 /=HASH/ and do
921 11 100       29 {
922 8 50       14 if ($debug_enabled)
923             {
924 0         0 print STDERR "$self->{name} : Object hash ($contents_output)\n";
925             }
926              
927 8         16 $result = $self->_transform_hash($context, $contents);
928              
929 8         14 last OBJECT_TYPE;
930             };
931              
932             /=ARRAY/ and do
933 3 50       11 {
934 0 0       0 if ($debug_enabled)
935             {
936 0         0 print STDERR "$self->{name} : Object Array ($contents_output)\n";
937             }
938              
939 0         0 $result = $self->_transform_array($context, $contents);
940              
941 0         0 last OBJECT_TYPE;
942             };
943              
944             #t implement.
945             #t
946             #t This is meant for easy extensibility.
947             #t Probably it is the most convenient if we force the object to
948             #t implement an agreed upon interface.
949             #t
950             #t use UNIVERSAL::isa() and perhaps UNIVERSAL::can() to check if
951             #t the interface is implemented by the object.
952             #t
953             #t perform a default action for hashes and arrays if the object
954             #t does not implement a suitable interface, allow the user to
955             #t configure or change the default action.
956             #t
957              
958             #$str .= $self->_formalize_object($context, $contents);
959             }
960             }
961             }
962              
963 407         562 my $current_result = _context_get_current_result($context);
964              
965 407         780 _context_set_seen_info($context, $contents_output, $current_result->{content});
966              
967 407         1195 return($result);
968             }
969              
970              
971             sub _transform_array
972             {
973 26     26   27 my $self = shift;
974              
975 26         24 my $context = shift;
976              
977 26         23 my $contents = shift;
978              
979 26         33 my $array = $context->{array};
980              
981 26         26 my $count = 0;
982              
983 26         68 my $result = [];
984              
985 26         121 _context_push(
986             $context,
987             {
988             # contents => $contents,
989             current => 0,
990             default_result => [], #$result,
991             component_key => '__NONE__',
992             size => scalar @$contents,
993             # recurse => 1,
994             # string => undef,
995             type => 'ARRAY',
996             },
997             );
998              
999             # if the default transformation is enabled (i.e. straight copy)
1000              
1001 26 100       56 if ($self->{apply_identity_transformation})
1002             {
1003 20         36 $self->_apply_identity_transformation($context);
1004             }
1005              
1006             # the sort is quite useless in this form : has no clue of nesting
1007             # of $contents.
1008              
1009             #t given an array of (sort(), ) tuples, match with
1010             #t current component, if matches, apply associated sorting
1011             #t function.
1012             #t
1013             #t this is quite close to transformations too.
1014              
1015             #! note that this assumes that perl sort is order preserving. The man page
1016             #! of perl sort says that it is 'stable' i.e. order preserving.
1017              
1018 26         67 foreach my $component (sort
  0         0  
1019             {
1020             defined $self->{sort}
1021 64 50       126 ? &{$self->{sort}}
1022             (
1023             $a,
1024             $b,
1025             $contents->{$a},
1026             $contents->{$b},
1027             $context,
1028             )
1029             : 0;
1030             }
1031             @$contents)
1032             {
1033             # compute the component key
1034              
1035 69         110 my $component_key = '[' . $count . ']';
1036              
1037             # register the name and count of current column
1038              
1039 69         110 _context_register_current
1040             ($context,
1041             $self,
1042             $component_key,
1043             $component,
1044             $count);
1045              
1046             # increment count (before applying filters)
1047              
1048 69         65 $count++;
1049              
1050             #
1051             # array_filter return values :
1052             #
1053             # 0 : do not recurse.
1054             # 1 : do recurse.
1055             #
1056              
1057 69         62 my $filter_data = 1;
1058              
1059 69 100       153 if (exists $self->{array_filter})
1060             {
1061 6         6 $filter_data = &{$self->{array_filter}}($context, $component);
  6         15  
1062             }
1063              
1064 69 50       919 next if $filter_data eq 0;
1065              
1066             # apply transformations
1067              
1068 69         177 $self->_apply_transformations($context, $component_key, $component);
1069              
1070             # transform content of array
1071              
1072 69         145 push @$result, $self->_transform_any($context, $component);
1073             }
1074              
1075 26 100       62 if (scalar @$contents eq 0)
1076             {
1077             # apply transformations
1078              
1079 2         5 $self->_apply_transformations($context, undef, undef,);
1080             }
1081              
1082             # remove this column
1083              
1084 26         42 _context_pop($context);
1085              
1086 26         43 return($result);
1087             }
1088              
1089              
1090             sub _transform_constant
1091             {
1092 162     162   164 my $self = shift;
1093              
1094 162         140 my $context = shift;
1095              
1096 162         136 my $contents = shift;
1097              
1098 162         153 my $result = '';
1099              
1100 162         550 _context_push(
1101             $context,
1102             {
1103             # contents => $contents,
1104             current => 0,
1105             default_result => '', #$result,
1106             component_key => '__NONE__',
1107             size => 1,
1108             # recurse => 1,
1109             # string => undef,
1110             type => 'SCALAR',
1111             },
1112             );
1113              
1114 162 50       259 if ($debug_enabled)
1115             {
1116 0         0 print STDERR " # register the name and count of current column\n";
1117             }
1118              
1119             # register the name and count of current column
1120              
1121 162         250 _context_register_current($context, $self, $contents, $contents, 0);
1122              
1123             # if the default transformation is enabled (i.e. straight copy)
1124              
1125 162 100       317 if ($self->{apply_identity_transformation})
1126             {
1127 100         155 $self->_apply_identity_transformation($context);
1128             }
1129              
1130 162 100       224 if (defined $contents)
1131             {
1132 156         197 $result .= $contents;
1133             }
1134             else
1135             {
1136 6         7 $result .= '__UNDEF__';
1137             }
1138              
1139             # apply transformations
1140              
1141 162         294 $self->_apply_transformations($context, $contents, $contents, );
1142              
1143             # remove this column
1144              
1145 162         396 _context_pop($context);
1146              
1147 162         280 return($result);
1148             }
1149              
1150              
1151             sub _transform_hash
1152             {
1153 216     216   197 my $self = shift;
1154              
1155 216         179 my $context = shift;
1156              
1157 216         211 my $contents = shift;
1158              
1159 216         240 my $count = 0;
1160              
1161 216         262 my $result = {};
1162              
1163 216         965 _context_push(
1164             $context,
1165             {
1166             # contents => $contents,
1167             current => 0,
1168             default_result => {}, #$result,
1169             component_key => '__NONE__',
1170             size => scalar keys %$contents,
1171             # recurse => 1,
1172             # string => undef,
1173             type => 'HASH',
1174             },
1175             );
1176              
1177             # if the default transformation is enabled (i.e. straight copy)
1178              
1179 216 100       445 if ($self->{apply_identity_transformation})
1180             {
1181 144         234 $self->_apply_identity_transformation($context);
1182             }
1183              
1184             # the sort is quite useless in this form : has no clue of nesting
1185             # of $contents.
1186              
1187             #t given an array of (sort(), ) tuples, match with
1188             #t current component, if matches, apply associated sorting
1189             #t function.
1190             #t
1191             #t this is quite close to transformations too.
1192              
1193 216         476 foreach my $component_key (sort
  15         25  
1194             {
1195             defined $self->{sort}
1196 214 100       451 ? &{$self->{sort}}
1197             (
1198             $a,
1199             $b,
1200             $contents->{$a},
1201             $contents->{$b},
1202             $context,
1203             )
1204             : $a cmp $b;
1205             }
1206             keys %$contents)
1207             {
1208 343         454 my $component = $contents->{$component_key};
1209              
1210             # register name and count of current column
1211              
1212 343         556 _context_register_current($context, $self, $component_key, $component, $count);
1213              
1214             # increment count (before applying filters)
1215              
1216 343         315 $count++;
1217              
1218             #
1219             # hash_filter return values :
1220             #
1221             # 0 : do not recurse.
1222             # 1 : do recurse.
1223             #
1224              
1225 343         321 my $filter_data = 1;
1226              
1227 343 100       658 if (exists $self->{hash_filter})
1228             {
1229 103         233 $filter_data
1230 103         100 = &{$self->{hash_filter}}($context, $component_key, $component);
1231             }
1232              
1233 343 100       1444 next if $filter_data eq 0;
1234              
1235             # apply transformations
1236              
1237 308         536 $self->_apply_transformations($context, $component_key, $component);
1238              
1239             # transform component
1240              
1241 308         713 $result->{$component_key} = $self->_transform_any($context, $component);
1242             }
1243              
1244 216 100       501 if (scalar keys %$contents eq 0)
1245             {
1246             # apply transformations
1247              
1248 14         27 $self->_apply_transformations($context, undef, undef, );
1249             }
1250              
1251             # remove this column
1252              
1253 216         288 _context_pop($context);
1254              
1255 216         348 return($result);
1256             }
1257              
1258              
1259             #
1260             # generate()
1261             #
1262             # Generate data resulting from this transformation. This is particularly
1263             # useful when cascading transformations.
1264             #
1265              
1266             sub generate
1267             {
1268 2     2 0 3 my $self = shift;
1269              
1270             # we are being used in a transformation cascade of some sort
1271              
1272 2         6 return $self->transform();
1273             }
1274              
1275              
1276             sub new
1277             {
1278 28     28 0 5993 my $proto = shift;
1279 28   33     102 my $class = ref($proto) || $proto;
1280              
1281 28         102 my $self = { @_ };
1282              
1283 28         60 bless ($self, $class);
1284              
1285 28         57 return $self;
1286             }
1287              
1288              
1289             sub transform
1290             {
1291 30     30 0 94 my $self = shift;
1292              
1293 30         31 my $contents = shift;
1294              
1295             # construct data to be transformed
1296              
1297 30   66     125 my $data = $contents || $self->{contents};
1298              
1299             # if there is a cascade from a related object
1300              
1301 30 100 66     90 if (!$data
1302             && $self->{source})
1303             {
1304             # construct the data from the source
1305              
1306 2         3 my $source = $self->{source};
1307              
1308 2         4 $data = $source->generate();
1309             }
1310              
1311             # initialize the working context of the transformation
1312              
1313 30         103 my $context = _context_create($self->{name}, $self->{context_separator}, );
1314              
1315             # $self->{result} is a literal copy of the original, the
1316             # construction of this copy will probably be removed later on for
1317             # reasons of efficiency.
1318              
1319 30         86 $self->{result} = $self->_transform_any($context, $data);
1320              
1321 30         217 return $context->{result}->{content};
1322             }
1323              
1324              
1325             # small convenience library : common transforms of hashes and arrays.
1326             #
1327             # note that transformators simply copy the root of a sub-tree (with
1328             # the sub-tree beneath), such that the filters applied to the sub-tree
1329             # do not interfere anymore with the result. This is currently a
1330             # deliberate choice for (1) ease of implementation, (2) performance..
1331              
1332             # transform an array with given name to a hash with hash_keys as the array
1333             # indices. Use an additional prefix to nest the result.
1334              
1335             sub _lib_transform_array_to_hash
1336             {
1337 3     3   718 my $array_name = shift;
1338              
1339 3         6 my $prefix = shift;
1340              
1341 3 50       11 if (!$prefix)
1342             {
1343 0         0 $prefix = '';
1344             }
1345              
1346 3         9 my $array_name_quoted = quotemeta $array_name;
1347              
1348             return
1349             sub
1350             {
1351 87     87   107 my ($transform_data, $context, $contents) = @_;
1352              
1353             # print STDERR $context->{path}, "\n";
1354              
1355             # initialize library sub private variables
1356              
1357 87 100       324 if ($context->{path} =~ m|[^/]/$array_name_quoted$|)
1358             {
1359 4         12 my $result = _context_get_main_result($context);
1360              
1361 4         14 return;
1362             }
1363              
1364 83 100       302 if ($context->{path} =~ m|[^/]/$array_name_quoted/\[([0-9])*\]$|)
1365             {
1366             # print STDERR "Setting result for $context->{path}\n";
1367              
1368 8         15 my $component = $1;
1369              
1370 8         25 my $result = _context_get_main_result($context);
1371              
1372 8         35 my $eval = 1;
1373              
1374 8 50       20 if ($eval)
1375             {
1376             # $prefix is interpolated during compilation if this code,
1377             # the other variables are interpolated during the eval.
1378             #
1379             # note the use of single and double quotes.
1380              
1381 8         18 my $command
1382             # = '$result->{content}${prefix}->{$component} = undef;';
1383             = '#print STDERR Dumper($result, $component);
1384             $result->{content}' . "$prefix" . '->{$component} = undef;';
1385              
1386 8         550 eval $command;
1387             }
1388             else
1389             {
1390             # print STDERR Dumper($result); print STDERR Dumper($component);
1391 0         0 $result->{content}->{$component} = undef;
1392             }
1393              
1394 8         30 return;
1395             }
1396              
1397 75 100       346 if ($context->{path} =~ m|[^/]/$array_name_quoted/\[([0-9])*\]/[^/]+$|)
1398             {
1399 9         16 my $component = $1;
1400              
1401 9         24 my $result = _context_get_main_result($context);
1402              
1403 9         13 my $eval = 1;
1404              
1405 9 50       19 if ($eval)
1406             {
1407             # $prefix is interpolated during execution if this code,
1408             # the other variables are interpolated during the eval.
1409             #
1410             # note the use of single and double quotes.
1411              
1412 9         17 my $command
1413             # = '$result->{content}${prefix}->{$component} = _context_get_current_content($context);';
1414             = '#print STDERR Dumper($result, $component);
1415             $result->{content}' . "$prefix" . '->{$component} = _context_get_current_content($context);';
1416              
1417 9         546 eval $command;
1418             }
1419             else
1420             {
1421             # print STDERR Dumper($result); print STDERR Dumper($component);
1422 0         0 $result->{content}->{$component} = _context_get_current_content($context);
1423             }
1424              
1425 9         41 return;
1426             }
1427 3         29 };
1428             }
1429              
1430              
1431             # transform the entries of a hash with given name to an array in the
1432             # result. The result is nested with a given prefix.
1433              
1434             sub _lib_transform_hash_to_array
1435             {
1436 6     6   608 my $hash_name = shift;
1437              
1438 6         8 my $prefix = shift;
1439              
1440 6 100       15 if (!$prefix)
1441             {
1442 4         6 $prefix = '';
1443             }
1444              
1445 6         12 my $hash_name_quoted = quotemeta $hash_name;
1446              
1447             return
1448             sub
1449             {
1450 144     144   165 my ($transform_data, $context, $contents) = @_;
1451              
1452             # print STDERR $context->{path}, "\n";
1453              
1454             # initialize library sub private variables
1455              
1456 144 100       486 if ($context->{path} =~ m|[^/]/$hash_name_quoted$|)
1457             {
1458 6         12 my $result = _context_get_main_result($context);
1459              
1460 6         17 $result->{library}->{"_lib_transform_hash_to_array"} = 0;
1461              
1462 6         26 return;
1463             }
1464              
1465 138 100       809 if ($context->{path} =~ m|[^/]/$hash_name_quoted/([^/])*$|)
1466             {
1467             # print STDERR "Setting result for $context->{path}\n";
1468              
1469 24         41 my $component_key = $1;
1470              
1471 24         43 my $result = _context_get_main_result($context);
1472              
1473 24         29 my $eval = 1;
1474              
1475 24 50       63 if ($eval)
1476             {
1477             # $prefix is interpolated during execution of this code,
1478             # the other variables are interpolated during the eval.
1479             #
1480             # note the use of single and double quotes.
1481              
1482 24         97 my $command
1483             # = '$result->{content}${prefix}->{$component_key} = _context_get_current_content($context);';
1484             = '#print STDERR Dumper($result, $component_key);
1485             $result->{content}' . "${prefix}->[$result->{library}->{_lib_transform_hash_to_array}]" . ' = $contents;';
1486              
1487 24         1256 eval $command;
1488              
1489             # print STDERR Dumper($result, $component_key);
1490             }
1491             else
1492             {
1493             # print STDERR Dumper($result);
1494 0         0 $result->{content}->[$component_key] = $contents;
1495             }
1496              
1497 24         67 $result->{library}->{"_lib_transform_hash_to_array"} += 1;
1498              
1499 24         90 return;
1500             }
1501 6         46 };
1502             }
1503              
1504              
1505             # test sub : test the functionality of Data::Transformator.
1506              
1507             sub _main
1508             {
1509 0     0     my $tree;
1510             my $tree1;
1511              
1512 0           $Data::Dumper::Sortkeys = 1;
1513              
1514 0           $tree
1515             = {
1516             a => {
1517             a1 => '-a1',
1518             a2 => '-a2',
1519             },
1520             b => [
1521             '-b1',
1522             '-b2',
1523             '-b3',
1524             ],
1525             c => {
1526             c1 => {
1527             c11 => '-c11',
1528             },
1529             c2 => {
1530             c21 => '-c21',
1531             },
1532             },
1533             d => {
1534             d1 => {
1535             d11 => {
1536             d111 => '-d111',
1537             },
1538             },
1539             },
1540             e => [
1541             {
1542             e1 => {
1543             e11 => {
1544             e111 => '-e111',
1545             },
1546             },
1547             },
1548             {
1549             e2 => {
1550             e21 => {
1551             e211 => '-e211',
1552             },
1553             },
1554             },
1555             {
1556             e3 => {
1557             e31 => {
1558             e311 => '-e311',
1559             },
1560             },
1561             },
1562             ],
1563             };
1564              
1565             # my $config = do '/var/sems/sems.config';
1566              
1567 0           my $config = {};
1568              
1569 0           my $devices;
1570              
1571 0           $devices->{ANT_CTRL} =
1572             {
1573             type => 'UserDefined',
1574             bus => 'dummy',
1575             addr => 0,
1576             equipm_url => 'USS_MON+main',
1577             ok_function => { "USS_MON.ant_ctrl.ntcSeEqSxSwitchControl" => 0, },
1578             };
1579              
1580             # $devices = $config->{devices};
1581              
1582             my $transformation1
1583             = new Data::Transformator
1584             (
1585             name => 'tree-tester',
1586             contents => $tree->{devices} ? $tree->{devices} : $tree,
1587             array_filter =>
1588             sub
1589             {
1590             # my ($context, $component) = @_;
1591              
1592 0 0   0     $_[0]->{path} =~ m|/b2$| ? 0 : 1;
1593             },
1594             hash_filter1 =>
1595             sub
1596             {
1597             # my ($context, $hash_key, $hash) = @_;
1598              
1599 0 0   0     $_[0]->{path} =~ m|/c2| ? 0 : 1;
1600             },
1601 0 0         transformators =>
1602             [
1603             _lib_transform_array_to_hash('b', '->{hash_from_array}'),
1604             _lib_transform_hash_to_array('c', '->{array_from_hash}'),
1605             ],
1606             );
1607              
1608 0           my $result1 = $transformation1->transform();
1609              
1610 0           print Dumper($result1);
1611              
1612 0           my $b_entries_source = scalar @{$tree->{b}};
  0            
1613 0           my $b_entries_result = scalar keys %{$result1->{hash_from_array}};
  0            
1614              
1615 0           my $c_entries_source = scalar keys %{$tree->{c}};
  0            
1616 0           my $c_entries_result = scalar @{$result1->{array_from_hash}};
  0            
1617              
1618             # print "b entries source $b_entries_source =? b entries result $b_entries_result\n";
1619             # print "c entries source $c_entries_source =? c entries result $c_entries_result\n";
1620              
1621             my $transformation2
1622             = new Data::Transformator
1623             (
1624             name => 'devices',
1625             contents => $devices,
1626             transformators =>
1627             [
1628             # _lib_transform_array_to_hash('b', '->{hash_from_array}'),
1629             # _lib_transform_hash_to_array('c', '->{array_from_hash}'),
1630             sub
1631             {
1632 0     0     my ($transform_data, $context, $contents) = @_;
1633              
1634             # print STDERR $context->{path}, "\n";
1635              
1636             # retain functions and ok_function.
1637              
1638 0 0         if ($context->{path} =~ m|^[^/]*/([^/]*)/([^/]*?function[^/]*?)$|)
1639             {
1640 0           my $device = $1;
1641 0           my $function = $2;
1642              
1643 0           my $result = _context_get_main_result($context);
1644              
1645 0           $result->{content}->{$device}->{$function}
1646             = _context_get_current_content($context);
1647              
1648 0           return;
1649             }
1650              
1651 0 0         if ($context->{path} =~ m|^[^/]*/([^/]*)/([^/]*led[^/]*)$|)
1652             {
1653 0           my $device = $1;
1654 0           my $led = $2;
1655              
1656 0           my $result = _context_get_main_result($context);
1657              
1658 0           $result->{content}->{$device}->{$led}
1659             = _context_get_current_content($context);
1660              
1661 0           return;
1662             }
1663              
1664             },
1665 0           ],
1666             );
1667              
1668             # my $result2 = $transformation2->transform();
1669              
1670             # print Dumper($result2);
1671              
1672             }
1673              
1674              
1675             1;
1676              
1677