Bug 18501: (follow-up) Use t::lib::Dates::compare in tests
[koha.git] / C4 / ImportExportFramework.pm
blobd4a11cabc51e8c475ace1785bfc28acd0e514b20
1 package C4::ImportExportFramework;
3 # Copyright 2010-2011 MASmedios.com y Ministerio de Cultura
5 # This file is part of Koha.
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.
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.
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>.
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);
27 use C4::Context;
28 use C4::Debug;
31 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
33 BEGIN {
34 require Exporter;
35 @ISA = qw(Exporter);
36 @EXPORT = qw(
37 &ExportFramework
38 &ImportFramework
39 &createODS
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">
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>
74 </Workbook>
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>';
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>';
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>';
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>';
172 =head1 NAME
174 C4::ImportExportFramework - Import/Export Framework to Excel-xml/ODS Module Functions
176 =head1 SYNOPSIS
178 use C4::ImportExportFramework;
180 =head1 DESCRIPTION
182 Module to Import/Export Framework to Excel-xml/ODS on intranet administration - MARC Frameworks section
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
187 Functions for handling import/export.
190 =head1 SUBROUTINES
194 =head2 ExportFramework
196 Export all the information of a Framework to an excel "xml" file or OpenDocument SpreadSheet "ods" file.
198 return :
199 succes
201 =cut
203 sub ExportFramework
205 my ($frameworkcode, $xmlStrRef, $mode) = @_;
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);
226 if ($@) {
227 $debug and warn "Error ExportFramework $@\n";
228 return 0;
232 if (_export_table('marc_tag_structure', $dbh, ($mode eq 'csv')?$xmlStrRef:$dom, ($mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
233 if (_export_table('marc_subfield_structure', $dbh, ($mode eq 'csv')?$xmlStrRef:$dom, ($mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
234 $$xmlStrRef = $dom->toString(1) if ($mode eq 'ods' || $mode eq 'excel');
235 return 1;
239 return 0;
240 }#ExportFramework
245 # Export all the data from a mysql table to an spreadsheet.
246 sub _export_table
248 my ($table, $dbh, $dom, $root, $frameworkcode, $mode) = @_;
249 if ($mode eq 'csv') {
250 _export_table_csv($table, $dbh, $dom, $root, $frameworkcode);
251 } elsif ($mode eq 'ods') {
252 _export_table_ods($table, $dbh, $dom, $root, $frameworkcode);
253 } else {
254 _export_table_excel($table, $dbh, $dom, $root, $frameworkcode);
258 # Export the mysql table to an csv file
259 sub _export_table_csv
261 my ($table, $dbh, $strCSV, $root, $frameworkcode) = @_;
263 eval {
264 # First row with the name of the columns
265 my $query = 'SHOW COLUMNS FROM ' . $table;
266 my $sth = $dbh->prepare($query);
267 $sth->execute();
268 my @fields = ();
269 while (my $hashRef = $sth->fetchrow_hashref) {
270 $$strCSV .= '"' . $hashRef->{Field} . '",';
271 push @fields, $hashRef->{Field};
273 chop $$strCSV;
274 $$strCSV .= chr(10);
275 # Populate rows with the data from mysql
276 $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
277 $sth = $dbh->prepare($query);
278 $sth->execute($frameworkcode);
279 my $data;
280 while (my $hashRef = $sth->fetchrow_hashref) {
281 for my $field (@fields) {
282 my $value = $hashRef->{$field} // q||;
283 $value =~ s/[\r\n]//g;
284 $$strCSV .= '"' . $value . '",';
286 chop $$strCSV;
287 $$strCSV .= chr(10);
289 $$strCSV .= chr(10);
290 for (@fields) {
291 # Separator for change of table
292 $$strCSV .= '"#-#",';
294 chop $$strCSV;
295 $$strCSV .= chr(10);
296 $$strCSV .= chr(10);
298 if ($@) {
299 $debug and warn "Error _export_table_csv $@\n";
300 return 0;
302 return 1;
303 }#_export_table_csv
306 # Export the mysql table to an ods file
307 sub _export_table_ods
309 my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
311 eval {
312 my $elementTable = $dom->createElement('table:table');
313 $elementTable->setAttribute('table:name', $table);
314 $elementTable->setAttribute('table:print', 'false');
315 $root->appendChild($elementTable);
316 my $elementRow = $dom->createElement('table:table-row');
317 $elementTable->appendChild($elementRow);
319 my $elementCell;
320 my $elementData;
321 # First row with the name of the columns
322 my $query = 'SHOW COLUMNS FROM ' . $table;
323 my $sth = $dbh->prepare($query);
324 $sth->execute();
325 my @fields = ();
326 while (my $hashRef = $sth->fetchrow_hashref) {
327 $elementCell = $dom->createElement('table:table-cell');
328 $elementCell->setAttribute('office:value-type', 'string');
329 $elementCell->setAttribute('office:value', $hashRef->{Field});
330 $elementRow->appendChild($elementCell);
331 $elementData = $dom->createElement('text:p');
332 $elementCell->appendChild($elementData);
333 $elementData->appendTextNode($hashRef->{Field});
334 push @fields, {name => $hashRef->{Field}, type => ($hashRef->{Type} =~ /int/i)?'float':'string'};
336 # Populate rows with the data from mysql
337 $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
338 $sth = $dbh->prepare($query);
339 $sth->execute($frameworkcode);
340 my $data;
341 while (my $hashRef = $sth->fetchrow_hashref) {
342 $elementRow = $dom->createElement('table:table-row');
343 $elementTable->appendChild($elementRow);
344 for (@fields) {
345 $data = $hashRef->{$_->{name}};
346 if ($_->{type} eq 'float' && !defined($data)) {
347 $data = '0';
348 } elsif ($_->{type} eq 'string' && !defined($data)) {
349 $data = q{};
350 } elsif ($_->{type} eq 'string' && (!$data && $data ne '0')) {
351 $data = '#';
353 $data = _parseContent2Xml($data) if ($_->{type} eq 'string');
354 $elementCell = $dom->createElement('table:table-cell');
355 $elementCell->setAttribute('office:value-type', $_->{type});
356 $elementCell->setAttribute('office:value', $data);
357 $elementRow->appendChild($elementCell);
358 $elementData = $dom->createElement('text:p');
359 $elementCell->appendChild($elementData);
360 $elementData->appendTextNode($data);
364 if ($@) {
365 $debug and warn "Error _export_table_ods $@\n";
366 return 0;
368 return 1;
369 }#_export_table_ods
372 # Export the mysql table to an excel-xml (openoffice/libreoffice compatible) file
373 sub _export_table_excel
375 my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
377 eval {
378 my $elementWS = $dom->createElement('Worksheet');
379 $elementWS->setAttribute('ss:Name', $table);
380 $root->appendChild($elementWS);
381 my $elementTable = $dom->createElement('ss:Table');
382 $elementWS->appendChild($elementTable);
383 my $elementRow = $dom->createElement('ss:Row');
384 $elementTable->appendChild($elementRow);
386 # First row with the name of the columns
387 my $elementCell;
388 my $elementData;
389 my $query = 'SHOW COLUMNS FROM ' . $table;
390 my $sth = $dbh->prepare($query);
391 $sth->execute();
392 my @fields = ();
393 while (my $hashRef = $sth->fetchrow_hashref) {
394 $elementCell = $dom->createElement('ss:Cell');
395 $elementCell->setAttribute('ss:StyleID', 's27');
396 $elementRow->appendChild($elementCell);
397 $elementData = $dom->createElement('ss:Data');
398 $elementData->setAttribute('ss:Type', 'String');
399 $elementCell->appendChild($elementData);
400 $elementData->appendTextNode($hashRef->{Field});
401 push @fields, {name => $hashRef->{Field}, type => ($hashRef->{Type} =~ /int/i)?'Number':'String'};
403 # Populate rows with the data from mysql
404 $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
405 $sth = $dbh->prepare($query);
406 $sth->execute($frameworkcode);
407 my $data;
408 while (my $hashRef = $sth->fetchrow_hashref) {
409 $elementRow = $dom->createElement('ss:Row');
410 $elementTable->appendChild($elementRow);
411 for (@fields) {
412 $elementCell = $dom->createElement('ss:Cell');
413 $elementRow->appendChild($elementCell);
414 $elementData = $dom->createElement('ss:Data');
415 $elementData->setAttribute('ss:Type', $_->{type});
416 $elementCell->appendChild($elementData);
417 $data = $hashRef->{$_->{name}};
418 if ($_->{type} eq 'Number' && !defined($data)) {
419 $data = '0';
420 } elsif ($_->{type} eq 'String' && !defined($data)) {
421 $data = q{};
422 } elsif ($_->{type} eq 'String' && (!$data && $data ne '0')) {
423 $data = '#';
425 $elementData->appendTextNode(($_->{type} eq 'String')?_parseContent2Xml($data):$data);
429 if ($@) {
430 $debug and warn "Error _export_table_excel $@\n";
431 return 0;
433 return 1;
434 }#_export_table_excel
442 # Format chars problematics to a correct format for xml.
443 sub _parseContent2Xml
445 my $content = shift;
447 $content =~ s/\&(?![a-zA-Z#0-9]{1,4};)/&amp;/g;
448 $content =~ s/</&lt;/g;
449 $content =~ s/>/&gt;/g;
450 return $content;
451 }#_parseContent2Xml
454 # Get the tmp directory on the system
455 sub _getTmp
457 my $tmp = '/tmp';
458 if ($ENV{'TMP'} && -d $ENV{'TMP'}) {
459 $tmp = $ENV{'TMP'};
460 } elsif ($ENV{'TMPDIR'} && -d $ENV{'TMPDIR'}) {
461 $tmp = $ENV{'TMPDIR'};
462 } elsif ($ENV{'TEMP'} && -d $ENV{'TEMP'}) {
463 $tmp = $ENV{'TEMP'};
465 return $tmp;
466 }#_getTmp
469 # Create our tempdir directory for the ods process
470 sub _createTmpDir
472 my $tmp = shift;
474 my $tempdir = (-d $tmp)?$tmp . '/':'./';
475 $tempdir .= 'tmp_ods_' . Digest::MD5::md5_hex(Digest::MD5::md5_hex(time().{}.rand().{}.$$));
476 eval {
477 mkdir $tempdir;
479 if ($@) {
480 return;
481 } else {
482 return $tempdir;
484 }#_createTmpDir
486 =head2 createODS
488 Creates a temporary directory to create the ods file and read it to store its content in a string.
490 return :
491 success
493 =cut
495 sub createODS
497 my ($strContent, $lang, $strODSRef) = @_;
499 my $tmp = _getTmp();
500 my $tempModule = 1;
501 my $tempdir;
502 eval {
503 require File::Temp;
504 import File::Temp qw/ tempfile tempdir /;
505 $tempdir = tempdir ( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR => (-d $tmp)?$tmp:'.', CLEANUP => 1);
507 if ($@) {
508 $tempModule = 0;
509 $tempdir = _createTmpDir($tmp);
511 if ($tempdir) {
512 my $fh;
513 # populate tempdir directory with the ods elements
514 eval {
515 if (open($fh, '>', "$tempdir/content.xml")) {
516 print {$fh} $strContent;
517 close($fh);
519 if (open($fh, '>', "$tempdir/mimetype")) {
520 print {$fh} 'application/vnd.oasis.opendocument.spreadsheet';
521 close($fh);
523 if (open($fh, '>', "$tempdir/meta.xml")) {
524 print {$fh} _getMeta($lang);
525 close($fh);
527 if (open($fh, '>', "$tempdir/styles.xml")) {
528 print {$fh} ODS_STYLES_STR;
529 close($fh);
531 if (open($fh, '>', "$tempdir/settings.xml")) {
532 print {$fh} ODS_SETTINGS_STR;
533 close($fh);
535 mkdir($tempdir.'/META-INF/');
536 mkdir($tempdir.'/Configurations2/');
537 mkdir($tempdir.'/Configurations2/acceleator/');
538 mkdir($tempdir.'/Configurations2/images/');
539 mkdir($tempdir.'/Configurations2/popupmenu/');
540 mkdir($tempdir.'/Configurations2/statusbar/');
541 mkdir($tempdir.'/Configurations2/floater/');
542 mkdir($tempdir.'/Configurations2/menubar/');
543 mkdir($tempdir.'/Configurations2/progressbar/');
544 mkdir($tempdir.'/Configurations2/toolbar/');
546 if (open($fh, '>', "$tempdir/META-INF/manifest.xml")) {
547 print {$fh} ODS_MANIFEST_STR;
548 close($fh);
551 if ($@) {
552 $debug and warn "Error createODS $@\n";
553 } else {
554 # create ods file from tempdir directory
555 eval {
556 require Archive::Zip;
557 import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
558 my $zip = Archive::Zip->new();
559 $zip->addTree( $tempdir, '' );
560 $zip->writeToFileNamed($tempdir . '/new.ods');
562 if ($@) {
563 my $cmd = qx(which zip 2>/dev/null || whereis zip);
564 chomp $cmd;
565 $cmd = 'zip' if (!$cmd || !-x $cmd);
566 system("cd $tempdir && $cmd -r new.ods ./");
568 my $ok = 0;
569 # read ods file and return as a string
570 if (-f "$tempdir/new.ods") {
571 if (open ($fh, '<', "$tempdir/new.ods")) {
572 binmode $fh;
573 my $buffer;
574 while (read ($fh, $buffer, 65536)) {
575 $$strODSRef .= $buffer;
577 close($fh);
578 $ok = 1;
581 # delete tempdir directory
582 if (!$tempModule && $tempdir) {
583 eval {
584 require File::Path;
585 import File::Temp qw/ rmtree /;
586 rmtree($tempdir);
588 if ($@) {
589 system("rm -rf $tempdir");
592 return 1 if ($ok);
595 return 0;
596 }#createODS
599 # return Meta content for ods file
600 sub _getMeta
602 my $lang = shift;
604 my $myDate = strftime ("%Y-%m-%dT%H:%M:%S", localtime(time()));
605 my $meta = '<?xml version="1.0" encoding="UTF-8"?>
606 <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">
607 <office:meta>
608 <meta:generator>ods-php</meta:generator>
609 <meta:creation-date>' . $myDate . '</meta:creation-date>
610 <dc:date>' . $myDate . '</dc:date>
611 <dc:language>' . $lang . '</dc:language>
612 <meta:editing-cycles>2</meta:editing-cycles>
613 <meta:editing-duration>PT15S</meta:editing-duration>
614 <meta:user-defined meta:name="Info 1"/>
615 <meta:user-defined meta:name="Info 2"/>
616 <meta:user-defined meta:name="Info 3"/>
617 <meta:user-defined meta:name="Info 4"/>
618 </office:meta>
619 </office:document-meta>';
620 return $meta;
621 }#_getMeta
624 =head2 ImportFramework
626 Import all the information of a Framework from a excel-xml/ods file.
628 return :
629 success
631 =cut
633 sub ImportFramework
635 my ($filename, $frameworkcode, $deleteFilename) = @_;
637 my $tempdir;
638 my $ok = -1;
639 my $dbh = C4::Context->dbh;
640 if (-r $filename && $dbh) {
641 my $extension = '';
642 if ($filename =~ /\.(csv|ods|xml)$/i) {
643 $extension = lc($1);
644 } else {
645 unlink ($filename) if ($deleteFilename); # remove temporary file
646 return -1;
648 if ($extension eq 'ods') {
649 ($tempdir, $filename) = _openODS($filename, $deleteFilename);
651 if ($filename) {
652 my $dom;
653 eval {
654 if ($extension eq 'ods' || $extension eq 'xml') {
655 # They have xml structure, so read it on a dom object
656 my $parser = XML::LibXML->new();
657 $dom = $parser->parse_file($filename);
658 if ($dom) {
659 my $root = $dom->documentElement();
661 } else {
662 # They are text files, so open it to read
663 open($dom, '<', $filename);
665 if ($dom) {
666 # Process both tables
667 my $numDeleted = 0;
668 my $numDeletedAux = 0;
669 if (($numDeletedAux = _import_table($dbh, 'marc_tag_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield'], $extension)) >= 0) {
670 $numDeleted += $numDeletedAux if ($numDeletedAux > 0);
671 if (($numDeletedAux = _import_table($dbh, 'marc_subfield_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield', 'tagsubfield'], $extension)) >= 0) {
672 $numDeleted += $numDeletedAux if ($numDeletedAux > 0);
673 $ok = ($numDeleted > 0)?$numDeleted:0;
676 } else {
677 $debug and warn "Error ImportFramework couldn't create dom\n";
680 if ($@) {
681 $debug and warn "Error ImportFramework $@\n";
682 } else {
683 if ($extension eq 'csv') {
684 close($dom) if ($dom);
688 unlink ($filename) if ($deleteFilename); # remove temporary file
689 } else {
690 $debug and warn "Error ImportFramework no conex to database or not readeable $filename\n";
692 if ($deleteFilename && $tempdir && -d $tempdir && -w $tempdir) {
693 eval {
694 require File::Path;
695 import File::Temp qw/ rmtree /;
696 rmtree($tempdir);
698 if ($@) {
699 system("rm -rf $tempdir");
702 return $ok;
703 }#ImportFramework
705 # Open (uncompress) ods file and return the content.xml file
706 sub _openODS
708 my ($filename, $deleteFilename) = @_;
710 my $tmp = _getTmp();
711 my $tempModule = 1;
712 my $tempdir;
713 eval {
714 require File::Temp;
715 import File::Temp qw/ tempfile tempdir /;
716 $tempdir = tempdir ( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR => (-d $tmp)?$tmp:'.', CLEANUP => 1);
718 if ($@) {
719 $tempModule = 0;
720 $tempdir = _createTmpDir($tmp);
722 if ($tempdir) {
723 eval {
724 require Archive::Zip;
725 import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
726 my $zip = Archive::Zip->new($filename);
727 foreach my $file ($zip->members) {
728 next if ($file->isDirectory);
729 (my $extractName = $file->fileName) =~ s{.*/}{};
730 next unless ($extractName eq 'content.xml');
731 $file->extractToFileNamed("$tempdir/$extractName");
734 if ($@) {
735 my $cmd = qx(which unzip 2>/dev/null || whereis unzip);
736 chomp $cmd;
737 $cmd = 'unzip' if (!$cmd || !-x $cmd);
738 system("$cmd $filename -d $tempdir");
740 if (-f "$tempdir/content.xml") {
741 unlink ($filename) if ($deleteFilename);
742 return ($tempdir, "$tempdir/content.xml");
745 unlink ($filename) if ($deleteFilename);
746 return ($tempdir, undef);
747 }#_openODS
751 # Check the table and columns corresponds with worksheet
752 sub _check_validity_worksheet
754 my ($dbh, $table, $nodeFields, $fieldsA, $format) = @_;
756 my $ret = 0;
757 eval {
758 my $query = 'DESCRIBE ' . $table;
759 my $sth = $dbh->prepare($query);
760 $sth->execute();
761 $sth->finish;
762 $query = 'SHOW COLUMNS FROM ' . $table;
763 $sth = $dbh->prepare($query);
764 $sth->execute();
765 my $fields = {};
766 while (my $hashRef = $sth->fetchrow_hashref) {
767 $fields->{$hashRef->{Field}} = $hashRef->{Field};
769 my @fields;
770 my $fieldsR;
771 if ($fieldsA) {
772 $fieldsR = $fieldsA;
773 } else {
774 $fieldsR = \@fields;
775 _getFields($nodeFields, $fieldsR, $format);
777 $ret = 1;
778 for (@$fieldsR) {
779 unless (exists($fields->{$_})) {
780 $ret = 0;
781 last;
785 return $ret;
786 }#_check_validity_worksheet
789 # Import the data from an excel-xml/ods to mysql tables.
790 sub _import_table
792 my ($dbh, $table, $frameworkcode, $dom, $PKArray, $format) = @_;
793 my %fields2Delete;
794 my $query;
795 my @fields;
796 # Create hash with all elements defined by primary key to know which ones to delete after parsing the spreadsheet
797 eval {
798 @fields = @$PKArray;
799 shift @fields;
800 $query = 'SELECT ' . join(',', @fields) . ' FROM ' . $table . ' WHERE frameworkcode=?';
801 my $sth = $dbh->prepare($query);
802 $sth->execute($frameworkcode);
803 my $field;
804 while (my $hashRef = $sth->fetchrow_hashref) {
805 $field = '';
806 map { $field .= $hashRef->{$_} . '_'; } @fields;
807 chop $field;
808 $fields2Delete{$field} = 1;
810 $sth->finish;
812 my $ok = 0;
813 if ($format eq 'csv') {
814 my @fieldsName = ();
815 eval {
816 my $query = 'SHOW COLUMNS FROM ' . $table;
817 my $sth = $dbh->prepare($query);
818 $sth->execute();
819 while (my $hashRef = $sth->fetchrow_hashref) {
820 push @fieldsName, $hashRef->{Field};
823 $ok = _import_table_csv($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete, \@fieldsName);
824 } elsif ($format eq 'ods') {
825 $ok = _import_table_ods($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete);
826 } else {
827 $ok = _import_table_excel($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete);
829 if ($ok) {
830 if (($ok = scalar(keys %fields2Delete)) > 0) {
831 $query = 'DELETE FROM ' . $table . ' WHERE ';
832 map {$query .= $_ . '=? AND ';} @$PKArray;
833 $query = substr($query, 0, -4);
834 my $sth = $dbh->prepare($query);
835 for (keys %fields2Delete) {
836 eval {
837 $sth->execute(($frameworkcode, split('_', $_)));
841 } else {
842 $ok = -1;
844 return $ok;
845 }#_import_table
848 # Insert/Update the row from the spreadsheet in the database
849 sub _processRow_DB
851 my ($dbh, $db_scheme, $table, $fields, $dataStr, $updateStr, $dataFields, $dataFieldsHash, $PKArray, $fieldsPK, $fields2Delete) = @_;
853 my $ok = 0;
854 my $query;
855 if ($db_scheme eq 'mysql') {
856 $query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ') ON DUPLICATE KEY UPDATE ' . $updateStr;
857 } else {
858 $query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ')';
860 eval {
861 my $sth = $dbh->prepare($query);
862 if ($db_scheme eq 'mysql') {
863 $sth->execute((@$dataFields, @$dataFields));
864 } else {
865 $sth->execute(@$dataFields);
868 if ($@) {
869 unless ($db_scheme eq 'mysql') {
870 $query = 'UPDATE ' . $table . ' SET ' . $updateStr . ' WHERE ';
871 map {$query .= $_ . '=? AND ';} @$PKArray;
872 $query = substr($query, 0, -4);
873 eval {
874 my $sth2 = $dbh->prepare($query);
875 my @dataPK = ();
876 map {push @dataPK, $dataFieldsHash->{$_};} @$PKArray;
877 $sth2->execute((@$dataFields, @dataPK));
879 $ok = 1 unless ($@);
881 $debug and warn "Error _processRows_Table $@\n";
882 } else {
883 $ok = 1;
885 if ($ok) {
886 my $field = '';
887 map { $field .= $dataFieldsHash->{$_} . '_'; } @$fieldsPK;
888 chop $field;
889 delete $fields2Delete->{$field} if (exists($fields2Delete->{$field}));
891 return $ok;
892 }#_processRow_DB
895 # Process the rows of a worksheet and insert/update them in a mysql table.
896 sub _processRows_Table
898 my ($dbh, $frameworkcode, $nodeR, $table, $PKArray, $format, $fields2Delete) = @_;
900 my $query;
901 my @fields = ();
902 my $fields = '';
903 my $dataStr = '';
904 my $updateStr = '';
905 my $j = 0;
906 my $db_scheme = C4::Context->config("db_scheme");
907 my $ok = 0;
908 my @fieldsPK = @$PKArray;
909 shift @fieldsPK;
910 while ($nodeR) {
911 if ($nodeR->nodeType == 1 && (($format && $format eq 'ods' && $nodeR->nodeName =~ /(?:table:)?table-row/) || ($nodeR->nodeName =~ /(?:ss:)?Row/)) && $nodeR->hasChildNodes()) {
912 if ($j == 0) {
913 # Get name columns
914 _getFields($nodeR, \@fields, $format);
915 return 0 unless _check_validity_worksheet($dbh, $table, $nodeR, \@fields, $format);
916 $fields = join(',', @fields);
917 $dataStr = '';
918 map { $dataStr .= '?,';} @fields;
919 chop($dataStr) if ($dataStr);
920 $updateStr = '';
921 map { $updateStr .= $_ . '=?,';} @fields;
922 chop($updateStr) if ($updateStr);
923 } else {
924 # Get data from row
925 my ($dataFields, $dataFieldsR) = _getDataFields($frameworkcode, $nodeR, \@fields, $format);
926 if (scalar(@fields) == scalar(@$dataFieldsR)) {
927 $ok = _processRow_DB($dbh, $db_scheme, $table, $fields, $dataStr, $updateStr, $dataFieldsR, $dataFields, $PKArray, \@fieldsPK, $fields2Delete);
930 $j++;
932 $nodeR = $nodeR->nextSibling;
934 return 1;
935 }#_processRows_Table
940 # Import worksheet from the csv file to the mysql table
941 sub _import_table_csv
943 my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete, $fields) = @_;
945 my $row = '';
946 my $partialRow = '';
947 my $numFields = @$fields;
948 my $fieldsNameRead = 0;
949 my @arrData;
950 my ($fieldsStr, $dataStr, $updateStr);
951 my $db_scheme = C4::Context->config("db_scheme");
952 my @fieldsPK = @$PKArray;
953 shift @fieldsPK;
954 my $ok = 0;
955 my $numRow = 0;
956 my $pos = 0;
957 while (<$dom>) {
958 $row = $_;
959 # Check whether the line has an unfinished field, i.e., a field with CR/LF in its data
960 if ($row =~ /,"[^"]*[\r\n]+$/ || $row =~ /^[^"]+[\r\n]+$/) {
961 $row =~ s/[\r\n]+$//;
962 $partialRow .= $row;
963 next;
965 if ($partialRow) {
966 $row = $partialRow . $row;
967 $partialRow = '';
969 # Line OK, process it
970 if ($row =~ /(?:".*?",?)+/) {
971 @arrData = split('","', $row);
972 $arrData[0] = substr($arrData[0], 1) if ($arrData[0] =~ /^"/);
973 $arrData[$#arrData] =~ s/[\r\n]+$//;
974 chop $arrData[$#arrData] if ($arrData[$#arrData] =~ /"$/);
975 if (@arrData) {
976 if ($arrData[0] eq '#-#' && $arrData[$#arrData] eq '#-#') {
977 # Change of table with separators #-#
978 return 1;
979 } elsif ($fieldsNameRead && $arrData[0] eq 'tagfield') {
980 # Change of table because we begin with field name with former field names read
981 seek($dom, $pos, 0);
982 return 1;
984 if (!$fieldsNameRead) {
985 # New table, we read the field names
986 $fieldsNameRead = 1;
987 $fields = [@arrData];
988 $fieldsStr = join(',', @$fields);
989 $dataStr = '';
990 map { $dataStr .= '?,';} @$fields;
991 chop($dataStr) if ($dataStr);
992 $updateStr = '';
993 map { $updateStr .= $_ . '=?,';} @$fields;
994 chop($updateStr) if ($updateStr);
995 } else {
996 # Read data
997 my $j = 0;
998 my %dataFields = ();
999 for (@arrData) {
1000 if ($fields->[$j] eq 'frameworkcode' && $_ ne $frameworkcode) {
1001 $dataFields{$fields->[$j]} = $frameworkcode;
1002 $arrData[$j] = $frameworkcode;
1003 } else {
1004 $dataFields{$fields->[$j]} = $_;
1006 $j++
1008 $ok = _processRow_DB($dbh, $db_scheme, $table, $fieldsStr, $dataStr, $updateStr, \@arrData, \%dataFields, $PKArray, \@fieldsPK, $fields2Delete);
1010 $pos = tell($dom);
1012 @arrData = ();
1014 $numRow++;
1016 return $ok;
1017 }#_import_table_csv
1020 # Import worksheet from the ods content.xml file to the mysql table
1021 sub _import_table_ods
1023 my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
1025 my $xc = XML::LibXML::XPathContext->new($dom);
1026 $xc->registerNs('xmlns:office','urn:oasis:names:tc:opendocument:xmlns:office:1.0');
1027 $xc->registerNs('xmlns:table','urn:oasis:names:tc:opendocument:xmlns:table:1.0');
1028 $xc->registerNs('xmlns:text','urn:oasis:names:tc:opendocument:xmlns:text:1.0');
1029 my @nodes;
1030 @nodes = $xc->findnodes('//table:table[@table:name="' . $table . '"]');
1031 if (@nodes == 1 && $nodes[0]->hasChildNodes()) {
1032 my $nodeR = $nodes[0]->firstChild;
1033 return _processRows_Table($dbh, $frameworkcode, $nodeR, $table, $PKArray, 'ods', $fields2Delete);
1034 } else {
1035 $debug and warn "Error _import_table_ods there's not worksheet for $table\n";
1037 return 0;
1038 }#_import_table_ods
1041 # Import worksheet from the excel-xml file to the mysql table
1042 sub _import_table_excel
1044 my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
1046 my $xc = XML::LibXML::XPathContext->new($dom);
1047 $xc->registerNs('xmlns','urn:schemas-microsoft-com:office:spreadsheet');
1048 $xc->registerNs('xmlns:ss','urn:schemas-microsoft-com:office:spreadsheet');
1049 $xc->registerNs('xmlns:x','urn:schemas-microsoft-com:office:excel');
1050 my @nodes;
1051 @nodes = $xc->findnodes('//ss:Worksheet[@ss:Name="' . $table . '"]');
1052 if (@nodes > 0) {
1053 for (my $i=0; $i < @nodes; $i++) {
1054 my @nodesT = $nodes[$i]->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Table');
1055 if (@nodesT == 1 && $nodesT[0]->hasChildNodes()) {
1056 my $nodeR = $nodesT[0]->firstChild;
1057 return _processRows_Table($dbh, $frameworkcode, $nodeR, $table, $PKArray, undef, $fields2Delete);
1060 } else {
1061 $debug and warn "Error _import_table_excel there's not worksheet for $table\n";
1063 return 0;
1064 }#_import_table_excel
1067 # Get the data from a cell on a ods file through the value attribute or the text node
1068 sub _getDataNodeODS
1070 my $node = shift;
1072 my $data;
1073 my $repeated = 0;
1074 if ($node->nodeType == 1 && $node->nodeName =~ /(?:table:)?table-cell/) {
1075 if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value')) {
1076 $data = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value');
1077 } elsif ($node->hasChildNodes()) {
1078 my @nodes2 = $node->getElementsByTagNameNS('urn:oasis:names:tc:opendocument:xmlns:text:1.0', 'p');
1079 if (@nodes2 == 1 && $nodes2[0]->hasChildNodes()) {
1080 $data = $nodes2[0]->firstChild->nodeValue;
1083 if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated')) {
1084 $repeated = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated');
1087 return ($data, $repeated);
1088 }#_getDataNodeODS
1091 # Get the data from a row of a spreadsheet
1092 sub _getDataFields
1094 my ($frameworkcode, $node, $fields, $format) = @_;
1096 my $dataFields = {};
1097 my @dataFieldsA = ();
1098 if ($node && $node->hasChildNodes()) {
1099 my $node2 = $node->firstChild;
1100 my ($data, $repeated);
1101 my $i = 0;
1102 my $ok = 0;
1103 $repeated = 0;
1104 while ($node2) {
1105 if ($format && $format eq 'ods') {
1106 ($data, $repeated) = _getDataNodeODS($node2) if ($repeated <= 0);
1107 $repeated--;
1108 $ok = 1 if (defined($data));
1109 } else {
1110 if ($node2->nodeType == 1 && $node2->nodeName =~ /(?:ss:)?Cell/) {
1111 my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
1112 if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
1113 $data = $nodes3[0]->firstChild->nodeValue;
1114 $ok = 1;
1118 if ($ok) {
1119 $data = '' if ($data eq '#');
1120 $data = $frameworkcode if ($fields->[$i] eq 'frameworkcode');
1121 $dataFields->{$fields->[$i]} = $data;
1122 push @dataFieldsA, $data;
1123 $i++;
1125 $ok = 0;
1126 $node2 = $node2->nextSibling if ($repeated <= 0);
1129 return ($dataFields, \@dataFieldsA);
1130 }#_getDataFields
1133 # Get the data from the first row to know the column names
1134 sub _getFields
1136 my ($node, $fields, $format) = @_;
1138 if ($node && $node->hasChildNodes()) {
1139 my $node2 = $node->firstChild;
1140 my ($data, $repeated);
1141 while ($node2) {
1142 if ($format && $format eq 'ods') {
1143 ($data, $repeated) = _getDataNodeODS($node2);
1144 push @$fields, $data if (defined($data));
1145 } else {
1146 if ($node2->nodeType == 1 && $node2->nodeName =~ /(?:ss:)?Cell/) {
1147 my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
1148 if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
1149 $data = $nodes3[0]->firstChild->nodeValue;
1150 push @$fields, $data;
1154 $node2 = $node2->nextSibling;
1157 }#_getFields
1163 __END__
1165 =head1 AUTHOR
1167 Koha Development Team <http://koha-community.org/>
1169 =cut