Bug 27569: Fix ODS export - take empty string into account
[koha.git] / C4 / ImportExportFramework.pm
1 package C4::ImportExportFramework;
2
3 # Copyright 2010-2011 MASmedios.com y Ministerio de Cultura
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use strict;
21 use warnings;
22 use XML::LibXML;
23 use XML::LibXML::XPathContext;
24 use Digest::MD5 qw();
25 use POSIX qw(strftime);
26 use Text::CSV_XS;
27 use List::MoreUtils qw(indexes);
28
29 use C4::Context;
30 use C4::Debug;
31
32
33 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
34
35 BEGIN {
36     require Exporter;
37     @ISA    = qw(Exporter);
38     @EXPORT = qw(
39         &ExportFramework
40         &ImportFramework
41         &createODS
42     );
43 }
44
45
46 use constant XMLSTR => '<?xml version="1.0" encoding="UTF-8"?>
47 <?mso-application progid="Excel.Sheet"?>
48 <Workbook
49   xmlns:x="urn:schemas-microsoft-com:office:excel"
50   xmlns="urn:schemas-microsoft-com:office:spreadsheet"
51   xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
52
53 <Styles>
54  <Style ss:ID="Default" ss:Name="Normal">
55   <Alignment ss:Vertical="Bottom"/>
56   <Borders/>
57   <Font/>
58   <Interior/>
59   <NumberFormat/>
60   <Protection/>
61  </Style>
62  <Style ss:ID="s27">
63   <Font x:Family="Swiss" ss:Color="#0000FF" ss:Bold="1"/>
64  </Style>
65  <Style ss:ID="s21">
66   <NumberFormat ss:Format="yyyy\-mm\-dd"/>
67  </Style>
68  <Style ss:ID="s22">
69   <NumberFormat ss:Format="yyyy\-mm\-dd\ hh:mm:ss"/>
70  </Style>
71  <Style ss:ID="s23">
72   <NumberFormat ss:Format="hh:mm:ss"/>
73  </Style>
74 </Styles>
75
76 </Workbook>
77 ';
78
79
80 use constant ODSSTR => '<?xml version="1.0" encoding="UTF-8"?>
81 <office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" office:version="1.0">
82 <office:scripts/>
83 <office:font-face-decls/>
84 <office:automatic-styles/>
85 </office:document-content>';
86
87
88 use constant ODS_STYLES_STR => '<?xml version="1.0" encoding="UTF-8"?>
89 <office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" office:version="1.0">
90 <office:font-face-decls></office:font-face-decls>
91 <office:styles></office:styles>
92 <office:automatic-styles></office:automatic-styles>
93 <office:master-styles></office:master-styles>
94 </office:document-styles>';
95
96
97 use constant ODS_SETTINGS_STR => '<?xml version="1.0" encoding="UTF-8"?>
98 <office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" office:version="1.0"><office:settings>
99 <config:config-item-set config:name="ooo:view-settings">
100 <config:config-item config:name="VisibleAreaTop" config:type="int">0</config:config-item>
101 <config:config-item config:name="VisibleAreaLeft" config:type="int">0</config:config-item>
102 <config:config-item config:name="VisibleAreaWidth" config:type="int">2000</config:config-item>
103 <config:config-item config:name="VisibleAreaHeight" config:type="int">900</config:config-item>
104 <config:config-item-map-indexed config:name="Views"><config:config-item-map-entry>
105 <config:config-item config:name="ViewId" config:type="string">View1</config:config-item>
106 <config:config-item-map-named config:name="Tables">
107 <config:config-item-map-entry config:name="Sheet1"><config:config-item config:name="CursorPositionX" config:type="int">0</config:config-item><config:config-item config:name="CursorPositionY" config:type="int">1</config:config-item><config:config-item config:name="HorizontalSplitMode" config:type="short">0</config:config-item><config:config-item config:name="VerticalSplitMode" config:type="short">0</config:config-item><config:config-item config:name="HorizontalSplitPosition" config:type="int">0</config:config-item><config:config-item config:name="VerticalSplitPosition" config:type="int">0</config:config-item><config:config-item config:name="ActiveSplitRange" config:type="short">2</config:config-item><config:config-item config:name="PositionLeft" config:type="int">0</config:config-item><config:config-item config:name="PositionRight" config:type="int">0</config:config-item><config:config-item config:name="PositionTop" config:type="int">0</config:config-item><config:config-item config:name="PositionBottom" config:type="int">0</config:config-item>
108 </config:config-item-map-entry>
109 </config:config-item-map-named>
110 <config:config-item config:name="ActiveTable" config:type="string">Sheet1</config:config-item>
111 <config:config-item config:name="HorizontalScrollbarWidth" config:type="int">270</config:config-item>
112 <config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
113 <config:config-item config:name="ZoomValue" config:type="int">100</config:config-item>
114 <config:config-item config:name="PageViewZoomValue" config:type="int">50</config:config-item>
115 <config:config-item config:name="ShowPageBreakPreview" config:type="boolean">false</config:config-item>
116 <config:config-item config:name="ShowZeroValues" config:type="boolean">true</config:config-item>
117 <config:config-item config:name="ShowNotes" config:type="boolean">true</config:config-item>
118 <config:config-item config:name="ShowGrid" config:type="boolean">true</config:config-item>
119 <config:config-item config:name="GridColor" config:type="long">12632256</config:config-item>
120 <config:config-item config:name="ShowPageBreaks" config:type="boolean">true</config:config-item>
121 <config:config-item config:name="HasColumnRowHeaders" config:type="boolean">true</config:config-item>
122 <config:config-item config:name="HasSheetTabs" config:type="boolean">true</config:config-item>
123 <config:config-item config:name="IsOutlineSymbolsSet" config:type="boolean">true</config:config-item>
124 <config:config-item config:name="IsSnapToRaster" config:type="boolean">false</config:config-item>
125 <config:config-item config:name="RasterIsVisible" config:type="boolean">false</config:config-item>
126 <config:config-item config:name="IsRasterAxisSynchronized" config:type="boolean">true</config:config-item></config:config-item-map-entry></config:config-item-map-indexed>
127 </config:config-item-set>
128 <config:config-item-set config:name="ooo:configuration-settings">
129 <config:config-item config:name="ShowZeroValues" config:type="boolean">true</config:config-item>
130 <config:config-item config:name="ShowNotes" config:type="boolean">true</config:config-item>
131 <config:config-item config:name="ShowGrid" config:type="boolean">true</config:config-item>
132 <config:config-item config:name="GridColor" config:type="long">12632256</config:config-item>
133 <config:config-item config:name="ShowPageBreaks" config:type="boolean">true</config:config-item>
134 <config:config-item config:name="LinkUpdateMode" config:type="short">3</config:config-item>
135 <config:config-item config:name="HasColumnRowHeaders" config:type="boolean">true</config:config-item>
136 <config:config-item config:name="HasSheetTabs" config:type="boolean">true</config:config-item>
137 <config:config-item config:name="IsOutlineSymbolsSet" config:type="boolean">true</config:config-item>
138 <config:config-item config:name="IsSnapToRaster" config:type="boolean">false</config:config-item>
139 <config:config-item config:name="RasterIsVisible" config:type="boolean">false</config:config-item>
140 <config:config-item config:name="IsRasterAxisSynchronized" config:type="boolean">true</config:config-item>
141 <config:config-item config:name="AutoCalculate" config:type="boolean">true</config:config-item>
142 <config:config-item config:name="PrinterName" config:type="string">Generic Printer</config:config-item>
143 <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
144 <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
145 <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
146 <config:config-item config:name="UpdateFromTemplate" config:type="boolean">false</config:config-item>
147 <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
148 <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
149 </config:config-item-set>
150 </office:settings></office:document-settings>';
151
152
153 use constant ODS_MANIFEST_STR => '<?xml version="1.0" encoding="UTF-8"?>
154 <manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
155  <manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.spreadsheet" manifest:full-path="/"/>
156  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/statusbar/"/>
157  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/accelerator/"/>
158  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/floater/"/>
159  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/popupmenu/"/>
160  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/progressbar/"/>
161  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/menubar/"/>
162  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/toolbar/"/>
163  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/Bitmaps/"/>
164  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/"/>
165  <manifest:file-entry manifest:media-type="application/vnd.sun.xml.ui.configuration" manifest:full-path="Configurations2/"/>
166  <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/>
167  <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/>
168  <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/>
169  <manifest:file-entry manifest:media-type="" manifest:full-path="Thumbnails/"/>
170  <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml"/>
171 </manifest:manifest>';
172
173
174 =head1 NAME
175
176 C4::ImportExportFramework - Import/Export Framework to Excel-xml/ODS Module Functions
177
178 =head1 SYNOPSIS
179
180   use C4::ImportExportFramework;
181
182 =head1 DESCRIPTION
183
184 Module to Import/Export Framework to Excel-xml/ODS on intranet administration - MARC Frameworks section
185
186 Module to Import/Export Framework to Excel-xml/ODS on intranet administration - MARC Frameworks section
187 exporting the tables marc_tag_structure, marc_subfield_structure to excel-xml/ods or viceversa
188
189 Functions for handling import/export.
190
191
192 =head1 SUBROUTINES
193
194
195
196 =head2 ExportFramework
197
198 Export all the information of a Framework to an excel "xml" file or OpenDocument SpreadSheet "ods" file.
199
200 return :
201 succes
202
203 =cut
204
205 sub ExportFramework
206 {
207     my ($frameworkcode, $xmlStrRef, $mode) = @_;
208
209     my $dbh = C4::Context->dbh;
210     if ($dbh) {
211         my $dom;
212         my $root;
213         my $elementSS;
214         if ($mode eq 'ods' || $mode eq 'excel') {
215             eval {
216                 my $parser = XML::LibXML->new();
217                 $dom = $parser->parse_string(($mode && $mode eq 'ods')?ODSSTR:XMLSTR);
218                 if ($dom) {
219                     $root = $dom->documentElement();
220                     if ($mode && $mode eq 'ods') {
221                         my $elementBody = $dom->createElement('office:body');
222                         $root->appendChild($elementBody);
223                         $elementSS = $dom->createElement('office:spreadsheet');
224                         $elementBody->appendChild($elementSS);
225                     }
226                 }
227             };
228             if ($@) {
229                 $debug and warn "Error ExportFramework $@\n";
230                 return 0;
231             }
232         }
233
234         if (_export_table('marc_tag_structure', $dbh, ($mode eq 'csv')?$xmlStrRef:$dom, ($mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
235             if (_export_table('marc_subfield_structure', $dbh, ($mode eq 'csv')?$xmlStrRef:$dom, ($mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
236                 $$xmlStrRef = $dom->toString(1) if ($mode eq 'ods' || $mode eq 'excel');
237                 return 1;
238             }
239         }
240     }
241     return 0;
242 }#ExportFramework
243
244
245
246
247 # Export all the data from a mysql table to an spreadsheet.
248 sub _export_table
249 {
250     my ($table, $dbh, $dom, $root, $frameworkcode, $mode) = @_;
251     if ($mode eq 'csv') {
252         _export_table_csv($table, $dbh, $dom, $root, $frameworkcode);
253     } elsif ($mode eq 'ods') {
254         _export_table_ods($table, $dbh, $dom, $root, $frameworkcode);
255     } else {
256         _export_table_excel($table, $dbh, $dom, $root, $frameworkcode);
257     }
258 }
259
260 # Export the mysql table to an csv file
261 sub _export_table_csv
262 {
263     my ($table, $dbh, $strCSV, $root, $frameworkcode) = @_;
264
265     eval {
266         # First row with the name of the columns
267         my $query = 'SHOW COLUMNS FROM ' . $table;
268         my $sth = $dbh->prepare($query);
269         $sth->execute();
270         my @fields = ();
271         while (my $hashRef = $sth->fetchrow_hashref) {
272             $$strCSV .= '"' . $hashRef->{Field} . '",';
273             push @fields, $hashRef->{Field};
274         }
275         chop $$strCSV;
276         $$strCSV .= chr(10);
277         # Populate rows with the data from mysql
278         $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
279         $sth = $dbh->prepare($query);
280         $sth->execute($frameworkcode);
281         my $data;
282         while (my $hashRef = $sth->fetchrow_hashref) {
283             for my $field (@fields) {
284                 my $value = $hashRef->{$field} // q||;
285                 $value =~ s/[\r\n]//g;
286                 $$strCSV .= '"' . $value . '",';
287             }
288             chop $$strCSV;
289             $$strCSV .= chr(10);
290         }
291         $$strCSV .= chr(10);
292         for (@fields) {
293             # Separator for change of table
294             $$strCSV .= '"#-#",';
295         }
296         chop $$strCSV;
297         $$strCSV .= chr(10);
298         $$strCSV .= chr(10);
299     };
300     if ($@) {
301         $debug and warn "Error _export_table_csv $@\n";
302         return 0;
303     }
304     return 1;
305 }#_export_table_csv
306
307
308 # Export the mysql table to an ods file
309 sub _export_table_ods
310 {
311     my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
312
313     eval {
314         my $elementTable = $dom->createElement('table:table');
315         $elementTable->setAttribute('table:name', $table);
316         $elementTable->setAttribute('table:print', 'false');
317         $root->appendChild($elementTable);
318         my $elementRow = $dom->createElement('table:table-row');
319         $elementTable->appendChild($elementRow);
320
321         my $elementCell;
322         my $elementData;
323         # First row with the name of the columns
324         my $query = 'SHOW COLUMNS FROM ' . $table;
325         my $sth = $dbh->prepare($query);
326         $sth->execute();
327         my @fields = ();
328         while (my $hashRef = $sth->fetchrow_hashref) {
329             $elementCell = $dom->createElement('table:table-cell');
330             $elementCell->setAttribute('office:value-type', 'string');
331             $elementCell->setAttribute('office:value', $hashRef->{Field});
332             $elementRow->appendChild($elementCell);
333             $elementData = $dom->createElement('text:p');
334             $elementCell->appendChild($elementData);
335             $elementData->appendTextNode($hashRef->{Field});
336             push @fields, {name => $hashRef->{Field}, type => ($hashRef->{Type} =~ /int/i)?'float':'string'};
337         }
338         # Populate rows with the data from mysql
339         $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
340         $sth = $dbh->prepare($query);
341         $sth->execute($frameworkcode);
342         my $data;
343         while (my $hashRef = $sth->fetchrow_hashref) {
344             $elementRow = $dom->createElement('table:table-row');
345             $elementTable->appendChild($elementRow);
346             for (@fields) {
347                 $data = $hashRef->{$_->{name}};
348                 if ($_->{type} eq 'float' && !defined($data)) {
349                     $data = '0';
350                 } elsif ($_->{type} eq 'string' && !defined($data)) {
351                     $data = q{};
352                 } elsif ($_->{type} eq 'string' && (!$data && $data ne '0')) {
353                     $data = '#';
354                 }
355                 $data = _parseContent2Xml($data) if ($_->{type} eq 'string');
356                 $elementCell = $dom->createElement('table:table-cell');
357                 $elementCell->setAttribute('office:value-type', $_->{type});
358                 $elementCell->setAttribute('office:value', $data);
359                 $elementRow->appendChild($elementCell);
360                 $elementData = $dom->createElement('text:p');
361                 $elementCell->appendChild($elementData);
362                 $elementData->appendTextNode($data);
363             }
364         }
365     };
366     if ($@) {
367         $debug and warn "Error _export_table_ods $@\n";
368         return 0;
369     }
370     return 1;
371 }#_export_table_ods
372
373
374 # Export the mysql table to an excel-xml (openoffice/libreoffice compatible) file
375 sub _export_table_excel
376 {
377     my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
378
379     eval {
380         my $elementWS = $dom->createElement('Worksheet');
381         $elementWS->setAttribute('ss:Name', $table);
382         $root->appendChild($elementWS);
383         my $elementTable = $dom->createElement('ss:Table');
384         $elementWS->appendChild($elementTable);
385         my $elementRow = $dom->createElement('ss:Row');
386         $elementTable->appendChild($elementRow);
387
388         # First row with the name of the columns
389         my $elementCell;
390         my $elementData;
391         my $query = 'SHOW COLUMNS FROM ' . $table;
392         my $sth = $dbh->prepare($query);
393         $sth->execute();
394         my @fields = ();
395         while (my $hashRef = $sth->fetchrow_hashref) {
396             $elementCell = $dom->createElement('ss:Cell');
397             $elementCell->setAttribute('ss:StyleID', 's27');
398             $elementRow->appendChild($elementCell);
399             $elementData = $dom->createElement('ss:Data');
400             $elementData->setAttribute('ss:Type', 'String');
401             $elementCell->appendChild($elementData);
402             $elementData->appendTextNode($hashRef->{Field});
403             push @fields, {name => $hashRef->{Field}, type => ($hashRef->{Type} =~ /int/i)?'Number':'String'};
404         }
405         # Populate rows with the data from mysql
406         $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
407         $sth = $dbh->prepare($query);
408         $sth->execute($frameworkcode);
409         my $data;
410         while (my $hashRef = $sth->fetchrow_hashref) {
411             $elementRow = $dom->createElement('ss:Row');
412             $elementTable->appendChild($elementRow);
413             for (@fields) {
414                 $elementCell = $dom->createElement('ss:Cell');
415                 $elementRow->appendChild($elementCell);
416                 $elementData = $dom->createElement('ss:Data');
417                 $elementData->setAttribute('ss:Type', $_->{type});
418                 $elementCell->appendChild($elementData);
419                 $data = $hashRef->{$_->{name}};
420                 if ($_->{type} eq 'Number' && !defined($data)) {
421                     $data = '0';
422                 } elsif ($_->{type} eq 'String' && !defined($data)) {
423                     $data = q{};
424                 } elsif ($_->{type} eq 'String' && (!$data && $data ne '0')) {
425                     $data = '#';
426                 }
427                 $elementData->appendTextNode(($_->{type} eq 'String')?_parseContent2Xml($data):$data);
428             }
429         }
430     };
431     if ($@) {
432         $debug and warn "Error _export_table_excel $@\n";
433         return 0;
434     }
435     return 1;
436 }#_export_table_excel
437
438
439
440
441
442
443
444 # Format chars problematics to a correct format for xml.
445 sub _parseContent2Xml
446 {
447     my $content = shift;
448
449     $content =~ s/\&(?![a-zA-Z#0-9]{1,4};)/&amp;/g;
450     $content =~ s/</&lt;/g;
451     $content =~ s/>/&gt;/g;
452     return $content;
453 }#_parseContent2Xml
454
455
456 # Get the tmp directory on the system
457 sub _getTmp
458 {
459     my $tmp = '/tmp';
460     if ($ENV{'TMP'} && -d $ENV{'TMP'}) {
461         $tmp = $ENV{'TMP'};
462     } elsif ($ENV{'TMPDIR'} && -d $ENV{'TMPDIR'}) {
463         $tmp = $ENV{'TMPDIR'};
464     } elsif ($ENV{'TEMP'} && -d $ENV{'TEMP'}) {
465         $tmp = $ENV{'TEMP'};
466     }
467     return $tmp;
468 }#_getTmp
469
470
471 # Create our tempdir directory for the ods process
472 sub _createTmpDir
473 {
474     my $tmp = shift;
475
476     my $tempdir = (-d $tmp)?$tmp . '/':'./';
477     $tempdir .= 'tmp_ods_' . Digest::MD5::md5_hex(Digest::MD5::md5_hex(time().{}.rand().{}.$$));
478     eval {
479         mkdir $tempdir;
480     };
481     if ($@) {
482         return;
483     } else {
484         return $tempdir;
485     }
486 }#_createTmpDir
487
488 =head2 createODS
489
490 Creates a temporary directory to create the ods file and read it to store its content in a string.
491
492 return :
493 success
494
495 =cut
496
497 sub createODS
498 {
499     my ($strContent, $lang, $strODSRef) = @_;
500
501     my $tmp = _getTmp();
502     my $tempModule = 1;
503     my $tempdir;
504     eval {
505         require File::Temp;
506         import File::Temp qw/ tempfile tempdir /;
507         $tempdir = tempdir ( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR => (-d $tmp)?$tmp:'.', CLEANUP => 1);
508     };
509     if ($@) {
510         $tempModule = 0;
511         $tempdir = _createTmpDir($tmp);
512     }
513     if ($tempdir) {
514         my $fh;
515         # populate tempdir directory with the ods elements
516         eval {
517             if (open($fh, '>',  "$tempdir/content.xml")) {
518                 print {$fh} $strContent;
519                 close($fh);
520             }
521             if (open($fh, '>', "$tempdir/mimetype")) {
522                 print {$fh} 'application/vnd.oasis.opendocument.spreadsheet';
523                 close($fh);
524             }
525             if (open($fh, '>', "$tempdir/meta.xml")) {
526                 print {$fh} _getMeta($lang);
527                 close($fh);
528             }
529             if (open($fh, '>', "$tempdir/styles.xml")) {
530                 print {$fh} ODS_STYLES_STR;
531                 close($fh);
532             }
533             if (open($fh, '>', "$tempdir/settings.xml")) {
534                 print {$fh} ODS_SETTINGS_STR;
535                 close($fh);
536             }
537             mkdir($tempdir.'/META-INF/');
538             mkdir($tempdir.'/Configurations2/');
539             mkdir($tempdir.'/Configurations2/acceleator/');
540             mkdir($tempdir.'/Configurations2/images/');
541             mkdir($tempdir.'/Configurations2/popupmenu/');
542             mkdir($tempdir.'/Configurations2/statusbar/');
543             mkdir($tempdir.'/Configurations2/floater/');
544             mkdir($tempdir.'/Configurations2/menubar/');
545             mkdir($tempdir.'/Configurations2/progressbar/');
546             mkdir($tempdir.'/Configurations2/toolbar/');
547
548             if (open($fh, '>', "$tempdir/META-INF/manifest.xml")) {
549                 print {$fh} ODS_MANIFEST_STR;
550                 close($fh);
551             }
552         };
553         if ($@) {
554             $debug and warn "Error createODS $@\n";
555         } else {
556             # create ods file from tempdir directory
557             eval {
558                 require Archive::Zip;
559                 import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
560                 my $zip = Archive::Zip->new();
561                 $zip->addTree( $tempdir, '' );
562                 $zip->writeToFileNamed($tempdir . '/new.ods');
563             };
564             if ($@) {
565                 my $cmd = qx(which zip 2>/dev/null || whereis zip);
566                 chomp $cmd;
567                 $cmd = 'zip' if (!$cmd || !-x $cmd);
568                 system("cd $tempdir && $cmd -r new.ods ./");
569             }
570             my $ok = 0;
571             # read ods file and return as a string
572             if (-f "$tempdir/new.ods") {
573                 if (open ($fh, '<', "$tempdir/new.ods")) {
574                     binmode $fh;
575                     my $buffer;
576                     while (read ($fh, $buffer, 65536)) {
577                         $$strODSRef .= $buffer;
578                     }
579                     close($fh);
580                     $ok = 1;
581                 }
582             }
583             # delete tempdir directory
584             if (!$tempModule && $tempdir) {
585                 eval {
586                     require File::Path;
587                     import File::Temp qw/ rmtree /;
588                     rmtree($tempdir);
589                 };
590                 if ($@) {
591                     system("rm -rf $tempdir");
592                 }
593             }
594             return 1 if ($ok);
595         }
596     }
597     return 0;
598 }#createODS
599
600
601 # return Meta content for ods file
602 sub _getMeta
603 {
604     my $lang = shift;
605
606     my $myDate = strftime ("%Y-%m-%dT%H:%M:%S", localtime(time()));
607     my $meta = '<?xml version="1.0" encoding="UTF-8"?>
608     <office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:ooo="http://openoffice.org/2004/office" office:version="1.0">
609         <office:meta>
610             <meta:generator>ods-php</meta:generator>
611             <meta:creation-date>' . $myDate . '</meta:creation-date>
612             <dc:date>' . $myDate . '</dc:date>
613             <dc:language>' . $lang . '</dc:language>
614             <meta:editing-cycles>2</meta:editing-cycles>
615             <meta:editing-duration>PT15S</meta:editing-duration>
616             <meta:user-defined meta:name="Info 1"/>
617             <meta:user-defined meta:name="Info 2"/>
618             <meta:user-defined meta:name="Info 3"/>
619             <meta:user-defined meta:name="Info 4"/>
620         </office:meta>
621     </office:document-meta>';
622     return $meta;
623 }#_getMeta
624
625
626 =head2 ImportFramework
627
628 Import all the information of a Framework from a excel-xml/ods file.
629
630 return :
631 success
632
633 =cut
634
635 sub ImportFramework
636 {
637     my ($filename, $frameworkcode, $deleteFilename) = @_;
638
639     my $tempdir;
640     my $ok = -1;
641     my $dbh = C4::Context->dbh;
642     if (-r $filename && $dbh) {
643         my $extension = '';
644         if ($filename =~ /\.(csv|ods|xml)$/i) {
645             $extension = lc($1);
646         } else {
647             unlink ($filename) if ($deleteFilename); # remove temporary file
648             return -1;
649         }
650         if ($extension eq 'ods') {
651             ($tempdir, $filename) = _openODS($filename, $deleteFilename);
652         }
653         if ($filename) {
654             my $dom;
655             eval {
656                 if ($extension eq 'ods' || $extension eq 'xml') {
657                     # They have xml structure, so read it on a dom object
658                     my $parser = XML::LibXML->new();
659                     $dom = $parser->parse_file($filename);
660                     if ($dom) {
661                         my $root = $dom->documentElement();
662                     }
663                 } else {
664                     # They are text files, so open it to read
665                     open($dom, '<', $filename);
666                 }
667                 if ($dom) {
668                     # Process both tables
669                     my $numDeleted = 0;
670                     my $numDeletedAux = 0;
671                     if (($numDeletedAux = _import_table($dbh, 'marc_tag_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield'], $extension)) >= 0) {
672                         $numDeleted += $numDeletedAux if ($numDeletedAux > 0);
673                         if (($numDeletedAux = _import_table($dbh, 'marc_subfield_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield', 'tagsubfield'], $extension)) >= 0) {
674                             $numDeleted += $numDeletedAux if ($numDeletedAux > 0);
675                             $ok = ($numDeleted > 0)?$numDeleted:0;
676                         }
677                     }
678                 } else {
679                     $debug and warn "Error ImportFramework couldn't create dom\n";
680                 }
681             };
682             if ($@) {
683                 $debug and warn "Error ImportFramework $@\n";
684             } else {
685                 if ($extension eq 'csv') {
686                     close($dom) if ($dom);
687                 }
688             }
689         }
690         unlink ($filename) if ($deleteFilename); # remove temporary file
691     } else {
692         $debug and warn "Error ImportFramework no conex to database or not readeable $filename\n";
693     }
694     if ($deleteFilename && $tempdir && -d $tempdir && -w $tempdir) {
695         eval {
696             require File::Path;
697             import File::Temp qw/ rmtree /;
698             rmtree($tempdir);
699         };
700         if ($@) {
701             system("rm -rf $tempdir");
702         }
703     }
704     return $ok;
705 }#ImportFramework
706
707 # Open (uncompress) ods file and return the content.xml file
708 sub _openODS
709 {
710     my ($filename, $deleteFilename) = @_;
711
712     my $tmp = _getTmp();
713     my $tempModule = 1;
714     my $tempdir;
715     eval {
716         require File::Temp;
717         import File::Temp qw/ tempfile tempdir /;
718         $tempdir = tempdir ( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR => (-d $tmp)?$tmp:'.', CLEANUP => 1);
719     };
720     if ($@) {
721         $tempModule = 0;
722         $tempdir = _createTmpDir($tmp);
723     }
724     if ($tempdir) {
725         eval {
726             require Archive::Zip;
727             import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
728             my $zip = Archive::Zip->new($filename);
729             foreach my $file ($zip->members) {
730                 next if ($file->isDirectory);
731                 (my $extractName = $file->fileName) =~ s{.*/}{};
732                 next unless ($extractName eq 'content.xml');
733                 $file->extractToFileNamed("$tempdir/$extractName");
734             }
735         };
736         if ($@) {
737             my $cmd = qx(which unzip 2>/dev/null || whereis unzip);
738             chomp $cmd;
739             $cmd = 'unzip' if (!$cmd || !-x $cmd);
740             system("$cmd $filename -d $tempdir");
741         }
742         if (-f "$tempdir/content.xml") {
743             unlink ($filename) if ($deleteFilename);
744             return ($tempdir, "$tempdir/content.xml");
745         }
746     }
747     unlink ($filename) if ($deleteFilename);
748     return ($tempdir, undef);
749 }#_openODS
750
751
752
753 # Check the table and columns corresponds with worksheet
754 sub _check_validity_worksheet
755 {
756     my ($dbh, $table, $nodeFields, $fieldsA, $format) = @_;
757
758     my $ret = 0;
759     eval {
760         my $query = 'DESCRIBE ' . $table;
761         my $sth = $dbh->prepare($query);
762         $sth->execute();
763         $sth->finish;
764         $query = 'SHOW COLUMNS FROM ' . $table;
765         $sth = $dbh->prepare($query);
766         $sth->execute();
767         my $fields = {};
768         while (my $hashRef = $sth->fetchrow_hashref) {
769             $fields->{$hashRef->{Field}} = $hashRef->{Field};
770         }
771         my @fields;
772         my $fieldsR;
773         if ($fieldsA) {
774             $fieldsR = $fieldsA;
775         } else {
776             $fieldsR = \@fields;
777             _getFields($nodeFields, $fieldsR, $format);
778         }
779         $ret = 1;
780         for (@$fieldsR) {
781             unless (exists($fields->{$_})) {
782                 $ret = 0;
783                 last;
784             }
785         }
786     };
787     return $ret;
788 }#_check_validity_worksheet
789
790
791 # Import the data from an excel-xml/ods to mysql tables.
792 sub _import_table
793 {
794     my ($dbh, $table, $frameworkcode, $dom, $PKArray, $format) = @_;
795     my %fields2Delete;
796     my $query;
797     my @fields;
798     # Create hash with all elements defined by primary key to know which ones to delete after parsing the spreadsheet
799     eval {
800         @fields = @$PKArray;
801         shift @fields;
802         $query = 'SELECT ' . join(',', @fields) . ' FROM ' . $table . ' WHERE frameworkcode=?';
803         my $sth = $dbh->prepare($query);
804         $sth->execute($frameworkcode);
805         my $field;
806         while (my $hashRef = $sth->fetchrow_hashref) {
807             $field = '';
808             map { $field .= $hashRef->{$_} . '_'; } @fields;
809             chop $field;
810             $fields2Delete{$field} = 1;
811         }
812         $sth->finish;
813     };
814     my $ok = 0;
815     if ($format eq 'csv') {
816         my @fieldsName = ();
817         eval {
818             my $query = 'SHOW COLUMNS FROM ' . $table;
819             my $sth = $dbh->prepare($query);
820             $sth->execute();
821             while (my $hashRef = $sth->fetchrow_hashref) {
822                 push @fieldsName, $hashRef->{Field};
823             }
824         };
825         $ok = _import_table_csv($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete, \@fieldsName);
826     } elsif ($format eq 'ods') {
827         $ok = _import_table_ods($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete);
828     } else {
829         $ok = _import_table_excel($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete);
830     }
831     if ($ok) {
832         if (($ok = scalar(keys %fields2Delete)) > 0) {
833             $query = 'DELETE FROM ' . $table . ' WHERE ';
834             map {$query .= $_ . '=? AND ';} @$PKArray;
835             $query = substr($query, 0, -4);
836             my $sth = $dbh->prepare($query);
837             for (keys %fields2Delete) {
838                 eval {
839                     $sth->execute(($frameworkcode, split('_', $_)));
840                 };
841             }
842         }
843     } else {
844         $ok = -1;
845     }
846     return $ok;
847 }#_import_table
848
849
850 # Insert/Update the row from the spreadsheet in the database
851 sub _processRow_DB
852 {
853     my ($dbh, $table, $fields, $dataStr, $updateStr, $dataFields, $dataFieldsHash, $PKArray, $fieldsPK, $fields2Delete) = @_;
854
855     my $ok = 0;
856     my $query;
857     $query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ') ON DUPLICATE KEY UPDATE ' . $updateStr;
858     eval {
859         my $sth = $dbh->prepare($query);
860         $sth->execute((@$dataFields, @$dataFields));
861     };
862     if ($@) {
863         warn $@;
864         $debug and warn "Error _processRow_DB $@\n";
865     } else {
866         $ok = 1;
867     }
868     if ($ok) {
869         my $field = '';
870         map { $field .= $dataFieldsHash->{$_} . '_'; } @$fieldsPK;
871         chop $field;
872         delete $fields2Delete->{$field} if (exists($fields2Delete->{$field}));
873     }
874     return $ok;
875 }#_processRow_DB
876
877
878 # Process the rows of a worksheet and insert/update them in a mysql table.
879 sub _processRows_Table
880 {
881     my ($dbh, $frameworkcode, $nodeR, $table, $PKArray, $format, $fields2Delete) = @_;
882
883     my $query;
884     my @fields = ();
885     my $fields = '';
886     my $dataStr = '';
887     my $updateStr = '';
888     my $j = 0;
889     my $ok = 0;
890     my @fieldsPK = @$PKArray;
891     shift @fieldsPK;
892     while ($nodeR) {
893         if ($nodeR->nodeType == 1 && (($format && $format eq 'ods' && $nodeR->nodeName =~ /(?:table:)?table-row/) || ($nodeR->nodeName =~ /(?:ss:)?Row/)) && $nodeR->hasChildNodes()) {
894             if ($j == 0) {
895                 # Get name columns
896                 _getFields($nodeR, \@fields, $format);
897                 return 0 unless _check_validity_worksheet($dbh, $table, $nodeR, \@fields, $format);
898                 $fields = join(',', @fields);
899                 $dataStr = '';
900                 map { $dataStr .= '?,';} @fields;
901                 chop($dataStr) if ($dataStr);
902                 $updateStr = '';
903                 map { $updateStr .= $_ . '=?,';} @fields;
904                 chop($updateStr) if ($updateStr);
905             } else {
906                 # Get data from row
907                 my ($dataFields, $dataFieldsR) = _getDataFields($frameworkcode, $nodeR, \@fields, $format);
908                 if (scalar(@fields) == scalar(@$dataFieldsR)) {
909                     $ok = _processRow_DB($dbh, $table, $fields, $dataStr, $updateStr, $dataFieldsR, $dataFields, $PKArray, \@fieldsPK, $fields2Delete);
910                 } else {
911                     warn "$j don't match number of fields " . scalar(@fields) . ' vs ' . scalar(@$dataFieldsR) . "($dataStr)";
912                 }
913             }
914             $j++;
915         }
916         $nodeR = $nodeR->nextSibling;
917     }
918     return 1;
919 }#_processRows_Table
920
921
922
923
924 # Import worksheet from the csv file to the mysql table
925 sub _import_table_csv
926 {
927     my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete, $fields) = @_;
928
929     my $row = '';
930     my $partialRow = '';
931     my $numFields = @$fields;
932     my $fieldsNameRead = 0;
933     my @arrData;
934     my ($fieldsStr, $dataStr, $updateStr, @empty_indexes);
935     my @fieldsPK = @$PKArray;
936     shift @fieldsPK;
937     my $ok = 0;
938     my $pos = 0;
939     my $csv = Text::CSV_XS->new ({ binary => 1 });
940     while ( my $row = $csv->getline($dom) ) {
941         my @fields = @$row;
942         @arrData = @fields;
943         next if scalar @arrData == grep { $_ eq '' } @arrData; # Emtpy lines
944         #$arrData[0] = substr($arrData[0], 1) if ($arrData[0] =~ /^"/);
945         #$arrData[$#arrData] =~ s/[\r\n]+$//;
946         #chop $arrData[$#arrData] if ($arrData[$#arrData] =~ /"$/);
947         if (@arrData) {
948             if ($arrData[0] eq '#-#' && $arrData[$#arrData] eq '#-#') {
949                 # Change of table with separators #-#
950                 return 1;
951             } elsif ($fieldsNameRead && $arrData[0] eq 'tagfield') {
952                 # Change of table because we begin with field name with former field names read
953                 seek($dom, $pos, 0);
954                 return 1;
955             }
956             if (!$fieldsNameRead) {
957                 # New table, we read the field names
958                 $fieldsNameRead = 1;
959                 $fields = [@arrData];
960                 my $non_empty_fields = [ grep { $_ ne '' } @$fields ];
961                 @empty_indexes = indexes { $_ eq '' } @$fields;
962                 $fieldsStr = join(',', @$non_empty_fields);
963                 $dataStr = '';
964                 map { $dataStr .= '?,';} @$non_empty_fields;
965                 chop($dataStr) if ($dataStr);
966                 $updateStr = '';
967                 map { $updateStr .= $_ . '=?,';} @$non_empty_fields;
968                 chop($updateStr) if ($updateStr);
969             } else {
970                 # Read data
971                 my $j = 0;
972                 my %dataFields = ();
973                 my @values;
974                 for my $value (@arrData) {
975                     if ( grep { $_ == $j } @empty_indexes ) {
976                         # empty field
977                     } elsif ($fields->[$j] eq 'frameworkcode' && $value ne $frameworkcode) {
978                         $dataFields{$fields->[$j]} = $frameworkcode;
979                         push @values, $frameworkcode;
980                     } else {
981                         $dataFields{$fields->[$j]} = $value;
982                         push @values, $value;
983                     }
984                     $j++
985                 }
986                 $ok = _processRow_DB($dbh, $table, $fieldsStr, $dataStr, $updateStr, \@values, \%dataFields, $PKArray, \@fieldsPK, $fields2Delete);
987             }
988             $pos = tell($dom);
989         }
990         @arrData = ();
991     }
992     return $ok;
993 }#_import_table_csv
994
995
996 # Import worksheet from the ods content.xml file to the mysql table
997 sub _import_table_ods
998 {
999     my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
1000
1001     my $xc = XML::LibXML::XPathContext->new($dom);
1002     $xc->registerNs('xmlns:office','urn:oasis:names:tc:opendocument:xmlns:office:1.0');
1003     $xc->registerNs('xmlns:table','urn:oasis:names:tc:opendocument:xmlns:table:1.0');
1004     $xc->registerNs('xmlns:text','urn:oasis:names:tc:opendocument:xmlns:text:1.0');
1005     my @nodes;
1006     @nodes = $xc->findnodes('//table:table[@table:name="' . $table . '"]');
1007     if (@nodes == 1 && $nodes[0]->hasChildNodes()) {
1008         my $nodeR = $nodes[0]->firstChild;
1009         return _processRows_Table($dbh, $frameworkcode, $nodeR, $table, $PKArray, 'ods', $fields2Delete);
1010     } else {
1011         $debug and warn "Error _import_table_ods there's not worksheet for $table\n";
1012     }
1013     return 0;
1014 }#_import_table_ods
1015
1016
1017 # Import worksheet from the excel-xml file to the mysql table
1018 sub _import_table_excel
1019 {
1020     my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
1021
1022     my $xc = XML::LibXML::XPathContext->new($dom);
1023     $xc->registerNs('xmlns','urn:schemas-microsoft-com:office:spreadsheet');
1024     $xc->registerNs('xmlns:ss','urn:schemas-microsoft-com:office:spreadsheet');
1025     $xc->registerNs('xmlns:x','urn:schemas-microsoft-com:office:excel');
1026     my @nodes;
1027     @nodes = $xc->findnodes('//ss:Worksheet[@ss:Name="' . $table . '"]');
1028     if (@nodes > 0) {
1029         for (my $i=0; $i < @nodes; $i++) {
1030             my @nodesT = $nodes[$i]->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Table');
1031             if (@nodesT == 1 && $nodesT[0]->hasChildNodes()) {
1032                 my $nodeR = $nodesT[0]->firstChild;
1033                 return _processRows_Table($dbh, $frameworkcode, $nodeR, $table, $PKArray, undef, $fields2Delete);
1034             }
1035         }
1036     } else {
1037         $debug and warn "Error _import_table_excel there's not worksheet for $table\n";
1038     }
1039     return 0;
1040 }#_import_table_excel
1041
1042
1043 # Get the data from a cell on a ods file through the value attribute or the text node
1044 sub _getDataNodeODS
1045 {
1046     my $node = shift;
1047
1048     my $data;
1049     my $repeated = 0;
1050     if ($node->nodeType == 1 && $node->nodeName =~ /(?:table:)?table-cell/) {
1051         if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value')) {
1052             $data = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value');
1053         } elsif ($node->hasChildNodes()) {
1054             my @nodes2 = $node->getElementsByTagNameNS('urn:oasis:names:tc:opendocument:xmlns:text:1.0', 'p');
1055             if (@nodes2 == 1 && $nodes2[0]->hasChildNodes()) {
1056                 $data = $nodes2[0]->firstChild->nodeValue;
1057             }
1058         }
1059         if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated')) {
1060             $repeated = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated');
1061         }
1062     }
1063     return ($data, $repeated);
1064 }#_getDataNodeODS
1065
1066
1067 # Get the data from a row of a spreadsheet
1068 sub _getDataFields
1069 {
1070     my ($frameworkcode, $node, $fields, $format) = @_;
1071
1072     my $dataFields = {};
1073     my @dataFieldsA = ();
1074     if ($node && $node->hasChildNodes()) {
1075         my $node2 = $node->firstChild;
1076         my ($data, $repeated);
1077         my $i = 0;
1078         my $ok = 0;
1079         $repeated = 0;
1080         while ($node2) {
1081             if ($format && $format eq 'ods') {
1082                 ($data, $repeated) = _getDataNodeODS($node2) if ($repeated <= 0);
1083                 $repeated--;
1084                 $ok = 1;
1085             } else {
1086                 if ($node2->nodeType == 1 && $node2->nodeName  =~ /(?:ss:)?Cell/) {
1087                     my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
1088                     if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
1089                         $data = $nodes3[0]->firstChild->nodeValue;
1090                         $ok = 1;
1091                     }
1092                 }
1093             }
1094             if ($ok) {
1095                 $data //= '';
1096                 $data = '' if ($data eq '#');
1097                 $data = $frameworkcode if ($fields->[$i] eq 'frameworkcode');
1098                 $dataFields->{$fields->[$i]} = $data;
1099                 push @dataFieldsA, $data;
1100                 $i++;
1101             }
1102             $ok = 0;
1103             $node2 = $node2->nextSibling if ($repeated <= 0);
1104         }
1105     }
1106     return ($dataFields, \@dataFieldsA);
1107 }#_getDataFields
1108
1109
1110 # Get the data from the first row to know the column names
1111 sub _getFields
1112 {
1113     my ($node, $fields, $format) = @_;
1114
1115     if ($node && $node->hasChildNodes()) {
1116         my $node2 = $node->firstChild;
1117         my ($data, $repeated);
1118         while ($node2) {
1119             if ($format && $format eq 'ods') {
1120                 ($data, $repeated) = _getDataNodeODS($node2);
1121                 push @$fields, $data if (defined($data));
1122             } else {
1123                 if ($node2->nodeType == 1 && $node2->nodeName =~ /(?:ss:)?Cell/) {
1124                     my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
1125                     if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
1126                         $data = $nodes3[0]->firstChild->nodeValue;
1127                         push @$fields, $data;
1128                     }
1129                 }
1130             }
1131             $node2 = $node2->nextSibling;
1132         }
1133     }
1134 }#_getFields
1135
1136
1137
1138
1139 1;
1140 __END__
1141
1142 =head1 AUTHOR
1143
1144 Koha Development Team <http://koha-community.org/>
1145
1146 =cut
1147
1148