File Coverage

blib/lib/XML/CSV.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package XML::CSV;
2            
3 1     1   2022 use Text::CSV_XS;
  0            
  0            
4             use Carp;
5            
6             #use strict;
7             BEGIN
8             {
9             use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
10            
11             require 5.004;
12             require Exporter;
13             #require DynaLoader;
14             require AutoLoader;
15            
16             @ISA = qw(Exporter);# DynaLoader);
17            
18             @EXPORT = qw(
19            
20             );
21             $VERSION = '0.14';
22             };
23             #bootstrap XML::CSV $VERSION;
24            
25             my $xml_xs_obj; #Declared for global usage
26             my $csvxml_error; #Error container
27            
28            
29             sub new($;$)
30             {
31            
32             my $class = shift;
33             my $attr = shift || {};
34            
35             my $self = { 'error_out' => 0,
36             'column_headings' => 0,
37             'column_data' => 0,
38             'csv_xs' => 0,
39             %$attr
40             };
41            
42             bless $self, $class;
43            
44             if ($attr->{csv_xs}) ### if custom Text::CSV_XS object is provided use it
45             {
46             $xml_xs_obj = $attr->{csv_xs};
47             $attr->{csv_xs} = undef;
48             } else { ### else create our own Text::CSV_XS object with it's defaults
49             $xml_xs_obj = Text::CSV_XS->new();
50             }
51            
52            
53            
54             return $self;
55            
56             }
57            
58             sub parse_doc
59             {
60             my $class = shift;
61             my $file_name = shift || croak "Usage: parse_doc(file_name, [\%attr])";
62             my $attr = shift; # %attr (headings, sub_char)
63            
64             eval {open FILE_CSV, "$file_name";};
65            
66             $csvxml_error = "Couldn't open file: $file_name. $@" if $@;
67            
68             croak "$csvxml_error" if ($class->{'error_out'} == 1 && $@);
69            
70             my @col_headings;
71            
72             $attr->{headings} = 0 unless (exists($attr->{headings})); ### default headings to 0
73            
74             if ($attr->{headings} == 0) ### No headings to be used from file
75             {
76             if ($class->{column_headings})
77             {
78             @col_headings = @{$class->{column_headings}}; ### if column_heading are provided
79             } ### by user, use them
80            
81             }
82            
83             my $line; ### declare $line outside of scope to be use later
84            
85             if ($attr->{headings} != 0)
86             {
87             $line = ;
88             my $cols_returned = $get_header->($line, \@col_headings, defined($attr->{sub_char})? $attr->{sub_char}:undef );
89             $csvxml_error = "There were no columns returned for headers, please check your CSV file" if (!$cols_returned);
90            
91             croak "$csvxml_error" if ($class->{'error_out'} == 1);
92            
93             return 0 if (!$cols_returned);
94             }
95            
96             my @arr_cols_data; ### declare @arr_cols_data to be used for stacking data
97            
98             while ($line = )
99             {
100            
101             my @cols_data;
102             my $status = $xml_xs_obj->parse($line); ### parse line by line
103             @cols_data = $xml_xs_obj->fields(); ### CSV_XS method returns data array for line passed
104             $escape_char->(\@cols_data);
105             push @arr_cols_data, \@cols_data; ### stack the returned data
106            
107             }
108            
109             $class->{'column_headings'} = \@col_headings; ### assign reference of @col_headings (xml headers) to object
110             $class->{'column_data'} = \@arr_cols_data; ### assign reference of @arr_cols_data (xml data) to object
111            
112             close FILE_CSV;
113            
114             return 1;
115             }
116            
117             sub print_xml
118             {
119             my $class = shift;
120             my $file_out = shift || 0;
121             my $args = shift || {}; # %attr (file_tag, parent_tag, format)
122            
123             $args->{file_tag} = "records" unless $args->{file_tag}; #default {parent_tag} to record if not supplied
124             $args->{parent_tag} = "record" unless $args->{parent_tag};
125             $args->{format} = "\t" unless $args->{format}; #default {format} to tab if not supplied
126            
127             $class->{'document_element'} = $args->{file_tag}; ### Used later for declare_doctype() method
128            
129             if ($class->{'column_data'} == 0 || ($class->{'column_headings'} == 0 && $class->{'headings'}))
130             {
131             croak "There is no data to print, make sure that you parsed the document before printing";
132             }
133            
134             ###Open file $file_out for output or output to STDOUT
135             if ($file_out)
136             {
137             open FILE_OUT, ">$file_out";
138             } else {
139             *FILE_OUT = *STDOUT;
140             }
141            
142             print FILE_OUT $class->{'declare_xml'}."\n" if $class->{'declare_xml'};
143             ###This will replace the non-interpolated $class->{'document_element'} inside the $class->{'declare_doctype'} to get the real value
144             ###Should be replace with something more practical in the future...
145             $class->{'declare_doctype'} =~ s/\$class\-\>\{\'document_element\'\}/$class->{'document_element'}/ if $class->{'declare_doctype'};
146            
147             print FILE_OUT $class->{'declare_doctype'}."\n" if $class->{'declare_doctype'};
148             print FILE_OUT "<$args->{file_tag}>", "\n"; ### print initial document tag
149            
150             ### declare the $tag for <$tag> and $loop_num for headers and data index tracking
151             my $tag;
152             my $loop_num;
153            
154             if ($#{$class->{'column_headings'}} > 0) ### if column headings are provided
155             {
156            
157             foreach $loop_num (0..$#{$class->{'column_data'}})
158             {
159             print FILE_OUT $args->{format}, "<$args->{parent_tag}>", "\n";
160             foreach $tag (0..$#{$class->{'column_headings'}})
161             {
162             print FILE_OUT $args->{format}, $args->{format}, "<$class->{'column_headings'}[$tag]>$class->{'column_data'}[$loop_num][$tag]{'column_headings'}[$tag]>\n";
163             }
164             print FILE_OUT $args->{format}, "{parent_tag}>", "\n";
165             }
166            
167             } else { ### if column headings are not provided we default to
168            
169             foreach $loop_num (0..$#{$class->{'column_data'}})
170             {
171             print FILE_OUT $args->{format}, "<$args->{parent_tag}>", "\n";
172             foreach $tag (0..$#{$class->{'column_data'}->[$loop_num]})
173             {
174             print FILE_OUT $args->{format}, $args->{format}, "$class->{'column_data'}[$loop_num][$tag]\n";
175             }
176             print FILE_OUT $args->{format}, "{parent_tag}>", "\n";
177             }
178             }
179            
180             print FILE_OUT "{file_tag}>", "\n"; ### print the final document tag
181            
182             close FILE_OUT;
183            
184             }
185            
186            
187             sub declare_xml
188             {
189            
190             my $class = shift;
191             my $attr = shift || {};
192            
193             ### Attributes: version, encoding, standalone
194            
195             if (exists $attr->{'version'})
196             {
197             $class->{'declare_xml'} = "{'version'}\""
198             }
199             else
200             {
201             $csvxml_error = "The version attribute must be specified for declare_xml()\n
202             Usage: declare_xml\({version=>1.0, [encoding=>..., standalone=>yes/no]}\)";
203             croak "$csvxml_error" if ($class->{'error_out'} == 1);
204             }
205            
206             $class->{'declare_xml'} .= " encoding=\"$attr->{'encoding'}\"" if exists $attr->{'encoding'};
207             if (exists $attr->{'standalone'} && ($attr->{'standalone'} =~ /[yes|no]/))
208             {
209             $class->{'declare_xml'} .= " standalone=\"$attr->{'standalone'}\"";
210             }
211             elsif (!($attr->{'standalone'} =~ /[yes|no]/))
212             {
213             $csvxml_error = "The standalone attribute must be yes|no for declare_xml()\n
214             Usage: declare_xml\({version=>1.0, [encoding=>..., standalone=>yes/no]}\)";
215             croak "$csvxml_error" if ($class->{'error_out'} == 1);
216             }
217            
218             $class->{'declare_xml'} .= "?>";
219            
220             return $class->{'declare_xml'};
221            
222             }
223            
224             sub declare_doctype
225             {
226            
227             my $class = shift;
228             my $attr = shift || {};
229            
230             ### Attributes: source, location1, location2, subset
231            
232             $class->{'declare_doctype'} = '{\'document_element\'}';
233             if ($attr->{source} eq "SYSTEM" || $attr->{source} eq "PUBLIC")
234             {
235             $class->{'declare_doctype'} .= " $attr->{'source'}";
236             }
237             else
238             {
239             $csvxml_error = "The source attribute is not set correctly";
240             croak "$csvxml_error" if ($class->{'error_out'} == 1);
241             }
242            
243             if (exists $attr->{location1} && !(exists $attr->{subset}))
244             {
245             $class->{'declare_doctype'} .= " \"$attr->{'location1'}\"";
246             }
247             else
248             {
249             $csvxml_error = "$attr->{'source'} location1 must be specified";
250             croak "$csvxml_error" if ($class->{'error_out'} == 1);
251             }
252            
253             $class->{'declare_doctype'} .= " \"$attr->{'location2'}\"" if exists $attr->{'location2'};
254             $class->{'declare_doctype'} .= " [$attr->{'subset'}]" if exists $attr->{'subset'};
255            
256            
257             $class->{'declare_doctype'} .= ">";
258            
259             return $class->{'declare_doctype'};
260            
261             }
262            
263             $get_header = sub()
264             {
265             my $line = shift;
266             my $ref_col = shift;
267             my $sub_char = shift;
268            
269             my $status = $xml_xs_obj->parse($line);
270             @$ref_col = $xml_xs_obj->fields();
271            
272             if (defined($sub_char))
273             {
274             map {s/^([^a-zA-Z|_|:]|((x|X)(m|M)(l|L)))/$sub_char/g;} @$ref_col; #convert all beginning \n or \t or \s to '_'
275             map {s/[^a-zA-Z|^-|^.|^0-9|^:]/$sub_char/g;} @$ref_col;
276             }
277            
278             #print __LINE__.": $ref_col->[0]\n";
279            
280             if ($ref_col) {return $#$ref_col;}else{return 0;}
281             };
282            
283             $escape_char = sub() ### Escape char per XML 1.0 specifications
284             { ### Needs to be optimized for faster processing
285            
286             my $arg = shift;
287             if (ref($arg) eq 'ARRAY')
288             {
289             my $arr_index;
290             foreach $arr_index (0..$#{$arg})
291             {
292             @{$arg}[$arr_index] =~ s/\&/\&\;/g;
293             @{$arg}[$arr_index] =~ s/\
294             @{$arg}[$arr_index] =~ s/\>/\>\;/g;
295             @{$arg}[$arr_index] =~ s/\'/\&apos\;/g;
296             @{$arg}[$arr_index] =~ s/\"/\"\;/g;
297             }
298             }
299             elsif (ref($arg) eq 'SCALAR')
300             {
301             ${$arg} =~ s/\&/\&\;/g;
302             ${$arg} =~ s/\
303             ${$arg} =~ s/\>/\>\;/g;
304             ${$arg} =~ s/\'/\&apos\;/g;
305             ${$arg} =~ s/\"/\"\;/g;
306             ${$arg} =~ s/([\x80-\xFF])/$XmlUtf8Encode->(ord($1))/ge;
307             }
308             else
309             {
310             croak "Usage: $escape_char->(\@cols_data) or $escape_char->(\$foo)\n";
311             }
312            
313             };
314            
315             $XmlUtf8Encode = sub() {
316            
317             my $n = shift;
318             if ($n < 0x80) {
319             return chr ($n);
320             } elsif ($n < 0x800) {
321             return pack ("CC", (($n >> 6) | 0xc0), (($n & 0x3f) | 0x80));
322             } elsif ($n < 0x10000) {
323             return pack ("CCC", (($n >> 12) | 0xe0), ((($n >> 6) & 0x3f) | 0x80),
324             (($n & 0x3f) | 0x80));
325             } elsif ($n < 0x110000) {
326             return pack ("CCCC", (($n >> 18) | 0xf0), ((($n >> 12) & 0x3f) | 0x80),
327             ((($n >> 6) & 0x3f) | 0x80), (($n & 0x3f) | 0x80));
328             }
329             return $n;
330             };
331            
332            
333             # Preloaded methods go here.
334            
335             # Autoload methods go after =cut, and are processed by the autosplit program.
336            
337             1;
338             __END__