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