File Coverage

blib/lib/Gwybodaeth/Write.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2              
3 2     2   24509 use warnings;
  2         5  
  2         100  
4 2     2   12 use strict;
  2         3  
  2         95  
5              
6             package Gwybodaeth::Write;
7              
8 2     2   1053 use Gwybodaeth::Escape;
  2         6  
  2         63  
9              
10             =head1 NAME
11              
12             Write::Write - Main class for applying maps to data.
13              
14             =head1 SYNOPSIS
15              
16             use base qw(Write);
17              
18             =head1 DESCRIPTION
19              
20             This class is intended to be subclassed thus has no public methods bar new().
21              
22             =over
23             =cut
24              
25 2     2   11 use Carp qw(croak);
  2         4  
  2         121  
26 2     2   1021 use XML::Twig;
  0            
  0            
27              
28             # Allow output to be in utf8
29             binmode( STDOUT, ':utf8' );
30             binmode( STDERR, ':utf8' );
31              
32             =item new()
33              
34             Returns an instance of whichever class has subclassed Gwybodaeth::Write.;
35              
36             =cut
37            
38             sub new {
39             my $class = shift;
40             my $self = { ids => {}, Data => ""};
41             $self->{XML} = XML::Twig->new(pretty_print => 'nice');
42             bless $self, $class;
43             return $self;
44             }
45              
46             # Check cleanliness of input data
47             sub _check_data {
48             my $self = shift;
49             my $triple_data = shift;
50             my $data = shift;
51             my $data_type = shift; # data type of $data;
52            
53             # Check $triple_data is the correct data type.
54             unless (ref($triple_data) eq 'ARRAY') {
55             croak "expected array ref as first argument";
56             }
57              
58             my $triples = ${ $triple_data }[0];
59             my $functions = ${ $triple_data }[1];
60              
61             # Check that both array elements are the correct data types in
62             # $triple_data.
63             unless (eval{ $triples->isa('Gwybodaeth::Triples') }) {
64             croak 'expected a Gwybodaeth::Triples object as first argument of array';
65             }
66             unless (ref($functions) eq 'HASH') {
67             croak 'expected a hash ref as second argument of array';
68             }
69            
70             # Check $data is in the correct data type.
71             unless (ref($data) eq $data_type) {
72             croak "expected $data_type in the second array ref";
73             }
74             return 1;
75             }
76              
77             sub _print2str {
78             my $self = shift;
79             my $str = shift;
80              
81             $self->{Data} .= $str;
82              
83             return 1;
84             }
85              
86             sub _extract_field {
87             my $self = shift;
88             my $data = shift;
89             my $field = shift;
90              
91              
92             # The object is a specific field
93             if ($field =~ m/^\" # string's first char is a double quote
94             Ex:
95             \$ # $ sign
96             ( # start variable scope
97             [\:\w]+ # one or more word or colon chars
98             \/? # possible forward slash
99             [\:\w]* # zero or more word or colon chars
100             ) # end variable scope
101             ( # start option scope
102             (\^\^|\@) # ^^ or @
103             .* # zero or more of any non \n chars
104             )? # end option scope and make the scope
105             # non essential
106             \"$ # string's last cha is a double quote
107             /x) {
108             # Remeber that _get_field() is often subclassed
109             # so we can't assume what form of data it returns.
110             return $self->_get_field($data,$1,$2);
111             }
112             # The object is a concatination of fields
113             if ($field =~ m/^[\"\<] # string's first char is a double quote
114             # or an opening angle bracket
115             Ex:
116             .*\+ # zero or more non \n char followed by a plus
117             /x) {
118             return $self->_cat_field($data, $field);
119             }
120             if ($field =~ m/^\$ # string's first char is a doller sign
121             ( # start scope
122             [\:\w]+ # one or more word or colon chars
123             \/? # possible forward slash
124             [\:\w]* # zero or more word or colon chars
125             ) # end scope
126             $/x) {
127             return $self->_get_field($data,$1);
128             }
129             if ($field =~ m/^\< # string's first char is an opening angle bracket
130             Ex:
131             \$ # $ sign
132             ( # start scope
133             [\:\w]+ # one or more word or colon chars
134             \/? # possible forward slash
135             [\:\w]* # zero or more word or colon chars
136             ) # close scope
137             \>$ # string's last char is a closing angle bracket
138             /x) {
139             return $self->_get_field($data,$1);
140             }
141             if ( $field =~ m/\@Split/x) {
142             return $self->_split_field($data, $field);
143             }
144            
145             # If it doesn't match any of the above, allow it to be a bareword field
146             return "$field";
147             }
148              
149             # Concatinate fields
150             sub _cat_field {
151             my $self = shift;
152             my $data = shift;
153             (my $field = shift) =~ s/
154             # any char followed by Ex:
155             .Ex://x;
156              
157             my $string = qq{};
158              
159             my @values = split /\+/x, $field;
160              
161             for my $val (@values) {
162             # Extract ${num} variables from data
163             if ($val =~ m/\$ # $ sign
164             ( # start variable scope
165             [\:\w]+ # one or more word or colon characters
166             ) # end variable scope
167             /x) {
168             $string .= $self->_get_field($data,$1);
169             }
170             # Put a space;
171             elsif ($val =~ m/\'\s*\' # single quoted zero or more whitespace char
172             /x) {
173             $string .= " ";
174             }
175             # Print a literal
176             else {
177             $string .= $val;
178             }
179             }
180             return $string;
181             }
182              
183             # How to interpret the @Split grammar
184             sub _split_field {
185             my($self, $data, $field) = @_;
186              
187             my @strings;
188            
189             if ($field =~ m/\@Split # Split grammar
190             \( # open bracket
191             Ex:
192             \$ # $ sign
193             ( # start variable scope
194             \d+ # one or more numeric character
195             ) # end variable scope
196             ,
197             "(.)" # doublpe quoted any non \n char - delimeter
198             \) # close bracket
199             /x) {
200             my $delimeter = $2;
201              
202             @strings = split /$delimeter/x, $self->_get_field($data,$1);
203             return \@strings;
204             }
205              
206             return $field;
207             }
208              
209             sub _write_meta_data {
210             my $self = shift;
211              
212             my $namespace = Gwybodaeth::NamespaceManager->new();
213             my $name_hash = $namespace->get_namespace_hash();
214             my $base = $namespace->get_base();
215              
216             $self->_print2str("\n
217             for my $keys (keys %{ $name_hash }) {
218             (my $key = $keys) =~ s/
219             # string ends in a colon
220             :$//x;
221             next if ($key eq "");
222             $self->_print2str("xmlns:$key=\"" . $name_hash->{$keys} . "\"\n");
223             }
224             if (${ $base }) {
225             $self->_print2str("xml:base=\"${ $base }\"\n");
226             }
227             $self->_print2str(">\n");
228            
229             return 1;
230             }
231              
232             sub _write_triples {
233             my ($self,@vars) = @_;
234             return $self->_really_write_triples(@vars);
235             }
236              
237             sub _really_write_triples {
238             my ($self, $row, $triples, $id) = @_;
239              
240             for my $triple_key ( keys %{ $triples } ) {
241              
242             my $subject = $self->_if_parse($triple_key,$row);
243             $self->_print2str("<".$subject);
244             if ($id) {
245             chomp(my $id_text = $self->_extract_field($row,$id));
246             if (ref($id_text) eq 'ARRAY') {
247             for my $obj (@{ $id_text }) {
248             $self->_print2str($self->_about_or_id($obj));
249             }
250             } else {
251             $self->_print2str($self->_about_or_id($id_text));
252             }
253             $self->_print2str('"');
254             }
255             $self->_print2str(">\n");
256              
257             my @verbs = @{ $triples->{$triple_key}{'predicate'} };
258             for my $indx (0..$#verbs ) {
259             $self->_get_verb_and_object(
260             $verbs[$indx],
261             $triples->{$triple_key}{'obj'}[$indx],
262             $row);
263             }
264             $self->_print2str("\n");
265             }
266             return;
267             }
268              
269             sub _get_verb_and_object {
270             my($self, $verb, $object, $row) = @_;
271              
272             my $obj_text = "";
273             unless ( eval{ $object->isa('Gwybodaeth::Triples') } ) {
274             $obj_text = $self->_get_object($row, $object);
275             }
276              
277             if (ref($obj_text) eq 'ARRAY') {
278             for my $obj (@{ $obj_text }) {
279             $self->_print_verb_and_object($verb, $obj, $row, $object);
280             }
281             } else {
282             $self->_print_verb_and_object($verb, $obj_text, $row, $object);
283             }
284             return 1;
285             }
286              
287             sub _print_verb_and_object {
288             my ($self, $verb, $object, $row, $unparsed_obj) = @_;
289             my $esc = Gwybodaeth::Escape->new();
290              
291             my $predicate = $self->_if_parse($verb,$row);
292             my $obj="";
293             $self->_print2str("<" . $predicate );
294              
295             if ( $unparsed_obj =~ m/\< # opening angle bracket
296             Ex:
297             \$ # $ sign
298             \w+ # one or more word chars
299             \/? # a possible forward slash
300             \w* # zero or more word chars
301             \>$ # string ends with a closing angle brackt
302             /x ) {
303             # We have a reference
304             $self->_print2str(' rdf:resource="#');
305             my $parsed_obj = $self->_get_object($row,$unparsed_obj);
306             if (ref($parsed_obj) eq 'ARRAY') {
307             for my $obj (@{ $parsed_obj }) {
308             $self->_print2str($esc->escape($obj));
309             }
310             } else {
311             $obj = $esc->escape($parsed_obj);
312             $self->_print2str($obj);
313             }
314             $self->_print2str("\"/>\n");
315             } else {
316             $self->_print2str(">");
317             if (eval{$unparsed_obj->isa('Gwybodaeth::Triples')}) {
318             $obj = $esc->escape($self->_get_object($row,$unparsed_obj));
319             $self->_print2str($obj);
320             } else {
321             $obj = $esc->escape($self->_get_object($row,$object));
322             $self->_print2str($obj);
323             }
324             $self->_print2str("\n");
325             }
326             return 1;
327             }
328              
329             sub _get_object {
330             my($self, $row, $object) = @_;
331              
332             if (eval {$object->isa('Gwybodaeth::Triples')}) {
333             $self->_write_triples($row, $object);
334             } else {
335             return $self->_extract_field($row, $object);
336             }
337             return "";
338             }
339              
340             sub _about_or_id {
341             my($self, $text) = @_;
342              
343             if ($text =~ /\s/x or $text =~ /[^A-Z]+ # one or more non capital letters/x)
344             {
345             $self->_print2str(' rdf:about="#');
346             } else {
347             $self->_print2str(' rdf:ID="');
348             }
349             return $text;
350             }
351             sub _if_parse {
352             my($self, $token, $row) = @_;
353              
354             if ($token =~ m/\@If
355             \( # open bracket
356             ( # start question scope
357             .+ # one or more non \n char
358             ) # end question scope
359             \;
360             ( # start 'true' scope
361             .+ # one or more non \n char
362             ) # end 'true' scope
363             \;
364             ( # start 'false' scope
365             .+
366             ) # end 'false scope
367             \) # close bracket
368             /ix) {
369             my($question,$true,$false) = ($1, $2, $3);
370              
371             $true =~ s/\'//gx;
372             $false =~ s/\'//gx;
373              
374             my @q_split = split q{=}, $question;
375              
376             $q_split[0] =~ s/\'//gx;
377             $q_split[1] =~ s/\'//gx;
378              
379             my $ans = qq{};
380             if ($token =~ m/\< # opening angle bracket
381             Ex
382             \: # a colon
383             ( # start scope
384             .+ # one or more non \n chars
385             \+ # a plus sign
386             ) # end scope
387             \@If/ix ) {
388             ($ans .= $1) =~ s/\+//gx;
389             $ans .= qq{:};
390             }
391              
392             if ($q_split[0] =~ m/^\$ # first char of the string is a $
393             (\w+) # one or more word characters scoped
394             # as the field
395             /x) {
396             $q_split[0] = $self->_get_field($row,$1);
397             }
398              
399             # If the returned field is an ARRAY join the elements
400             # into one scalar string.
401             if (ref($q_split[0]) eq 'ARRAY') {
402             $q_split[0] = join ' ', @{ $q_split[0] };
403             }
404              
405             if ($q_split[0] eq $q_split[1]) {
406             $ans .= $true;
407             } else {
408             $ans .= $false;
409             }
410             $token = $ans;
411             }
412             return $token;
413             }
414              
415             # Structure the serialized data string into an XML::Twig object.
416             sub _structurize {
417             my $self = shift;
418              
419             my $twig = $self->{XML};
420              
421             my $xml = $self->{Data};
422              
423             $twig->safe_parse($xml);
424              
425             return $self->_set_datatype($twig);
426             }
427              
428             sub _set_datatype {
429             my($self, $twig) = @_;
430              
431             my $elt = $twig->root;
432             while( $elt = $elt->next_elt($twig->root) ) {
433             if ($elt->text_only =~ m/( # begin text scope
434             .+ # one or more of any non \n character
435             ) # end text scope
436             \^\^# matches ^^
437             ( # begin datatype scope
438             \w+ # one ore more word character
439             ) # end datatype scope
440             $ # end of string/x ) {
441             $elt->set_text($1);
442             $elt->set_att(
443             'rdf:datatype' => "http://www.w3.org/TR/xmlschema-2/#".$2
444             );
445             }
446             elsif ($elt->text_only =~ m/
447             ( # begin text scope
448             .+ # one or more of any non \n character
449             ) # end text scope
450             \@ # 'at' symbol
451             ( # begin lang scope
452             \w+ # one or more word characters
453             ) # end of lang scope
454             $ # end of string/x ) {
455             $elt->set_text($1);
456             $elt->set_att(
457             'xml:lang' => $2
458             );
459             }
460             }
461              
462             return $twig;
463             }
464             1;
465             __END__