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>.
23 use XML
::LibXML
::XPathContext
;
25 use POSIX
qw(strftime);
31 use vars
qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
34 $VERSION = 3.07.00.049; # set version for version checking
45 use constant XMLSTR
=> '<?xml version="1.0" encoding="UTF-8"?>
46 <?mso-application progid="Excel.Sheet"?>
48 xmlns:x="urn:schemas-microsoft-com:office:excel"
49 xmlns="urn:schemas-microsoft-com:office:spreadsheet"
50 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
53 <Style ss:ID="Default" ss:Name="Normal">
54 <Alignment ss:Vertical="Bottom"/>
62 <Font x:Family="Swiss" ss:Color="#0000FF" ss:Bold="1"/>
65 <NumberFormat ss:Format="yyyy\-mm\-dd"/>
68 <NumberFormat ss:Format="yyyy\-mm\-dd\ hh:mm:ss"/>
71 <NumberFormat ss:Format="hh:mm:ss"/>
79 use constant ODSSTR
=> '<?xml version="1.0" encoding="UTF-8"?>
80 <office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" office:version="1.0">
82 <office:font-face-decls/>
83 <office:automatic-styles/>
84 </office:document-content>';
87 use constant ODS_STYLES_STR
=> '<?xml version="1.0" encoding="UTF-8"?>
88 <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">
89 <office:font-face-decls></office:font-face-decls>
90 <office:styles></office:styles>
91 <office:automatic-styles></office:automatic-styles>
92 <office:master-styles></office:master-styles>
93 </office:document-styles>';
96 use constant ODS_SETTINGS_STR
=> '<?xml version="1.0" encoding="UTF-8"?>
97 <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>
98 <config:config-item-set config:name="ooo:view-settings">
99 <config:config-item config:name="VisibleAreaTop" config:type="int">0</config:config-item>
100 <config:config-item config:name="VisibleAreaLeft" config:type="int">0</config:config-item>
101 <config:config-item config:name="VisibleAreaWidth" config:type="int">2000</config:config-item>
102 <config:config-item config:name="VisibleAreaHeight" config:type="int">900</config:config-item>
103 <config:config-item-map-indexed config:name="Views"><config:config-item-map-entry>
104 <config:config-item config:name="ViewId" config:type="string">View1</config:config-item>
105 <config:config-item-map-named config:name="Tables">
106 <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>
107 </config:config-item-map-entry>
108 </config:config-item-map-named>
109 <config:config-item config:name="ActiveTable" config:type="string">Sheet1</config:config-item>
110 <config:config-item config:name="HorizontalScrollbarWidth" config:type="int">270</config:config-item>
111 <config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
112 <config:config-item config:name="ZoomValue" config:type="int">100</config:config-item>
113 <config:config-item config:name="PageViewZoomValue" config:type="int">50</config:config-item>
114 <config:config-item config:name="ShowPageBreakPreview" config:type="boolean">false</config:config-item>
115 <config:config-item config:name="ShowZeroValues" config:type="boolean">true</config:config-item>
116 <config:config-item config:name="ShowNotes" config:type="boolean">true</config:config-item>
117 <config:config-item config:name="ShowGrid" config:type="boolean">true</config:config-item>
118 <config:config-item config:name="GridColor" config:type="long">12632256</config:config-item>
119 <config:config-item config:name="ShowPageBreaks" config:type="boolean">true</config:config-item>
120 <config:config-item config:name="HasColumnRowHeaders" config:type="boolean">true</config:config-item>
121 <config:config-item config:name="HasSheetTabs" config:type="boolean">true</config:config-item>
122 <config:config-item config:name="IsOutlineSymbolsSet" config:type="boolean">true</config:config-item>
123 <config:config-item config:name="IsSnapToRaster" config:type="boolean">false</config:config-item>
124 <config:config-item config:name="RasterIsVisible" config:type="boolean">false</config:config-item>
125 <config:config-item config:name="IsRasterAxisSynchronized" config:type="boolean">true</config:config-item></config:config-item-map-entry></config:config-item-map-indexed>
126 </config:config-item-set>
127 <config:config-item-set config:name="ooo:configuration-settings">
128 <config:config-item config:name="ShowZeroValues" config:type="boolean">true</config:config-item>
129 <config:config-item config:name="ShowNotes" config:type="boolean">true</config:config-item>
130 <config:config-item config:name="ShowGrid" config:type="boolean">true</config:config-item>
131 <config:config-item config:name="GridColor" config:type="long">12632256</config:config-item>
132 <config:config-item config:name="ShowPageBreaks" config:type="boolean">true</config:config-item>
133 <config:config-item config:name="LinkUpdateMode" config:type="short">3</config:config-item>
134 <config:config-item config:name="HasColumnRowHeaders" config:type="boolean">true</config:config-item>
135 <config:config-item config:name="HasSheetTabs" config:type="boolean">true</config:config-item>
136 <config:config-item config:name="IsOutlineSymbolsSet" config:type="boolean">true</config:config-item>
137 <config:config-item config:name="IsSnapToRaster" config:type="boolean">false</config:config-item>
138 <config:config-item config:name="RasterIsVisible" config:type="boolean">false</config:config-item>
139 <config:config-item config:name="IsRasterAxisSynchronized" config:type="boolean">true</config:config-item>
140 <config:config-item config:name="AutoCalculate" config:type="boolean">true</config:config-item>
141 <config:config-item config:name="PrinterName" config:type="string">Generic Printer</config:config-item>
142 <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
143 <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
144 <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
145 <config:config-item config:name="UpdateFromTemplate" config:type="boolean">false</config:config-item>
146 <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
147 <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
148 </config:config-item-set>
149 </office:settings></office:document-settings>';
152 use constant ODS_MANIFEST_STR
=> '<?xml version="1.0" encoding="UTF-8"?>
153 <manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
154 <manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.spreadsheet" manifest:full-path="/"/>
155 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/statusbar/"/>
156 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/accelerator/"/>
157 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/floater/"/>
158 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/popupmenu/"/>
159 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/progressbar/"/>
160 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/menubar/"/>
161 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/toolbar/"/>
162 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/Bitmaps/"/>
163 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/"/>
164 <manifest:file-entry manifest:media-type="application/vnd.sun.xml.ui.configuration" manifest:full-path="Configurations2/"/>
165 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/>
166 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/>
167 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/>
168 <manifest:file-entry manifest:media-type="" manifest:full-path="Thumbnails/"/>
169 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml"/>
170 </manifest:manifest>';
175 C4::ImportExportFramework - Import/Export Framework to Excel-xml/ODS Module Functions
179 use C4::ImportExportFramework;
183 Module to Import/Export Framework to Excel-xml/ODS on intranet administration - MARC Frameworks section
185 Module to Import/Export Framework to Excel-xml/ODS on intranet administration - MARC Frameworks section
186 exporting the tables marc_tag_structure, marc_subfield_structure to excel-xml/ods or viceversa
188 Functions for handling import/export.
195 =head2 ExportFramework
197 Export all the information of a Framework to an excel "xml" file or OpenDocument SpreadSheet "ods" file.
206 my ($frameworkcode, $xmlStrRef, $mode) = @_;
208 my $dbh = C4
::Context
->dbh;
213 if ($mode eq 'ods' || $mode eq 'excel') {
215 my $parser = XML
::LibXML
->new();
216 $dom = $parser->parse_string(($mode && $mode eq 'ods')?ODSSTR
:XMLSTR
);
218 $root = $dom->documentElement();
219 if ($mode && $mode eq 'ods') {
220 my $elementBody = $dom->createElement('office:body');
221 $root->appendChild($elementBody);
222 $elementSS = $dom->createElement('office:spreadsheet');
223 $elementBody->appendChild($elementSS);
228 $debug and warn "Error ExportFramework $@\n";
233 if (_export_table
('marc_tag_structure', $dbh, ($mode eq 'csv')?
$xmlStrRef:$dom, ($mode eq 'ods')?
$elementSS:$root, $frameworkcode, $mode)) {
234 if (_export_table
('marc_subfield_structure', $dbh, ($mode eq 'csv')?
$xmlStrRef:$dom, ($mode eq 'ods')?
$elementSS:$root, $frameworkcode, $mode)) {
235 $$xmlStrRef = $dom->toString(1) if ($mode eq 'ods' || $mode eq 'excel');
246 # Export all the data from a mysql table to an spreadsheet.
249 my ($table, $dbh, $dom, $root, $frameworkcode, $mode) = @_;
250 if ($mode eq 'csv') {
251 _export_table_csv
($table, $dbh, $dom, $root, $frameworkcode);
252 } elsif ($mode eq 'ods') {
253 _export_table_ods
($table, $dbh, $dom, $root, $frameworkcode);
255 _export_table_excel
($table, $dbh, $dom, $root, $frameworkcode);
259 # Export the mysql table to an csv file
260 sub _export_table_csv
262 my ($table, $dbh, $strCSV, $root, $frameworkcode) = @_;
265 # First row with the name of the columns
266 my $query = 'SHOW COLUMNS FROM ' . $table;
267 my $sth = $dbh->prepare($query);
270 while (my $hashRef = $sth->fetchrow_hashref) {
271 $$strCSV .= '"' . $hashRef->{Field
} . '",';
272 push @fields, $hashRef->{Field
};
276 # Populate rows with the data from mysql
277 $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
278 $sth = $dbh->prepare($query);
279 $sth->execute($frameworkcode);
281 while (my $hashRef = $sth->fetchrow_hashref) {
283 $hashRef->{$_} =~ s/[\r\n]//g;
284 $$strCSV .= '"' . $hashRef->{$_} . '",';
291 # Separator for change of table
292 $$strCSV .= '"#-#",';
299 $debug and warn "Error _export_table_csv $@\n";
306 # Export the mysql table to an ods file
307 sub _export_table_ods
309 my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
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);
321 # First row with the name of the columns
322 my $query = 'SHOW COLUMNS FROM ' . $table;
323 my $sth = $dbh->prepare($query);
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);
341 while (my $hashRef = $sth->fetchrow_hashref) {
342 $elementRow = $dom->createElement('table:table-row');
343 $elementTable->appendChild($elementRow);
345 $data = $hashRef->{$_->{name
}};
346 if ($_->{type
} eq 'float' && !defined($data)) {
348 } elsif ($_->{type
} eq 'string' && (!$data && $data ne '0')) {
351 $data = _parseContent2Xml
($data) if ($_->{type
} eq 'string');
352 $elementCell = $dom->createElement('table:table-cell');
353 $elementCell->setAttribute('office:value-type', $_->{type
});
354 $elementCell->setAttribute('office:value', $data);
355 $elementRow->appendChild($elementCell);
356 $elementData = $dom->createElement('text:p');
357 $elementCell->appendChild($elementData);
358 $elementData->appendTextNode($data);
363 $debug and warn "Error _export_table_ods $@\n";
370 # Export the mysql table to an excel-xml (openoffice/libreoffice compatible) file
371 sub _export_table_excel
373 my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
376 my $elementWS = $dom->createElement('Worksheet');
377 $elementWS->setAttribute('ss:Name', $table);
378 $root->appendChild($elementWS);
379 my $elementTable = $dom->createElement('ss:Table');
380 $elementWS->appendChild($elementTable);
381 my $elementRow = $dom->createElement('ss:Row');
382 $elementTable->appendChild($elementRow);
384 # First row with the name of the columns
387 my $query = 'SHOW COLUMNS FROM ' . $table;
388 my $sth = $dbh->prepare($query);
391 while (my $hashRef = $sth->fetchrow_hashref) {
392 $elementCell = $dom->createElement('ss:Cell');
393 $elementCell->setAttribute('ss:StyleID', 's27');
394 $elementRow->appendChild($elementCell);
395 $elementData = $dom->createElement('ss:Data');
396 $elementData->setAttribute('ss:Type', 'String');
397 $elementCell->appendChild($elementData);
398 $elementData->appendTextNode($hashRef->{Field
});
399 push @fields, {name
=> $hashRef->{Field
}, type
=> ($hashRef->{Type
} =~ /int/i)?
'Number':'String'};
401 # Populate rows with the data from mysql
402 $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
403 $sth = $dbh->prepare($query);
404 $sth->execute($frameworkcode);
406 while (my $hashRef = $sth->fetchrow_hashref) {
407 $elementRow = $dom->createElement('ss:Row');
408 $elementTable->appendChild($elementRow);
410 $elementCell = $dom->createElement('ss:Cell');
411 $elementRow->appendChild($elementCell);
412 $elementData = $dom->createElement('ss:Data');
413 $elementData->setAttribute('ss:Type', $_->{type
});
414 $elementCell->appendChild($elementData);
415 $data = $hashRef->{$_->{name
}};
416 if ($_->{type
} eq 'Number' && !defined($data)) {
418 } elsif ($_->{type
} eq 'String' && (!$data && $data ne '0')) {
421 $elementData->appendTextNode(($_->{type
} eq 'String')?_parseContent2Xml
($data):$data);
426 $debug and warn "Error _export_table_excel $@\n";
430 }#_export_table_excel
438 # Format chars problematics to a correct format for xml.
439 sub _parseContent2Xml
443 $content =~ s/\&(?![a-zA-Z#0-9]{1,4};)/&/g;
444 $content =~ s/</</g;
445 $content =~ s/>/>/g;
450 # Get the tmp directory on the system
454 if ($ENV{'TMP'} && -d
$ENV{'TMP'}) {
456 } elsif ($ENV{'TMPDIR'} && -d
$ENV{'TMPDIR'}) {
457 $tmp = $ENV{'TMPDIR'};
458 } elsif ($ENV{'TEMP'} && -d
$ENV{'TEMP'}) {
465 # Create our tempdir directory for the ods process
470 my $tempdir = (-d
$tmp)?
$tmp . '/':'./';
471 $tempdir .= 'tmp_ods_' . Digest
::MD5
::md5_hex
(Digest
::MD5
::md5_hex
(time().{}.rand().{}.$$));
484 Creates a temporary directory to create the ods file and read it to store its content in a string.
493 my ($strContent, $lang, $strODSRef) = @_;
500 import File
::Temp qw
/ tempfile tempdir /;
501 $tempdir = tempdir
( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR
=> (-d
$tmp)?
$tmp:'.', CLEANUP
=> 1);
505 $tempdir = _createTmpDir
($tmp);
509 # populate tempdir directory with the ods elements
511 if (open($fh, '>', "$tempdir/content.xml")) {
512 print {$fh} $strContent;
515 if (open($fh, '>', "$tempdir/mimetype")) {
516 print {$fh} 'application/vnd.oasis.opendocument.spreadsheet';
519 if (open($fh, '>', "$tempdir/meta.xml")) {
520 print {$fh} _getMeta
($lang);
523 if (open($fh, '>', "$tempdir/styles.xml")) {
524 print {$fh} ODS_STYLES_STR
;
527 if (open($fh, '>', "$tempdir/settings.xml")) {
528 print {$fh} ODS_SETTINGS_STR
;
531 mkdir($tempdir.'/META-INF/');
532 mkdir($tempdir.'/Configurations2/');
533 mkdir($tempdir.'/Configurations2/acceleator/');
534 mkdir($tempdir.'/Configurations2/images/');
535 mkdir($tempdir.'/Configurations2/popupmenu/');
536 mkdir($tempdir.'/Configurations2/statusbar/');
537 mkdir($tempdir.'/Configurations2/floater/');
538 mkdir($tempdir.'/Configurations2/menubar/');
539 mkdir($tempdir.'/Configurations2/progressbar/');
540 mkdir($tempdir.'/Configurations2/toolbar/');
542 if (open($fh, '>', "$tempdir/META-INF/manifest.xml")) {
543 print {$fh} ODS_MANIFEST_STR
;
548 $debug and warn "Error createODS $@\n";
550 # create ods file from tempdir directory
552 require Archive
::Zip
;
553 import Archive
::Zip
qw( :ERROR_CODES :CONSTANTS );
554 my $zip = Archive
::Zip
->new();
555 $zip->addTree( $tempdir, '' );
556 $zip->writeToFileNamed($tempdir . '/new.ods');
559 my $cmd = qx(which zip
2>/dev/null
|| whereis zip
);
561 $cmd = 'zip' if (!$cmd || !-x
$cmd);
562 system("cd $tempdir && $cmd -r new.ods ./");
565 # read ods file and return as a string
566 if (-f
"$tempdir/new.ods") {
567 if (open ($fh, '<', "$tempdir/new.ods")) {
570 while (read ($fh, $buffer, 65536)) {
571 $$strODSRef .= $buffer;
577 # delete tempdir directory
578 if (!$tempModule && $tempdir) {
581 import File
::Temp qw
/ rmtree /;
585 system("rm -rf $tempdir");
595 # return Meta content for ods file
600 my $myDate = strftime
("%Y-%m-%dT%H:%M:%S", localtime(time()));
601 my $meta = '<?xml version="1.0" encoding="UTF-8"?>
602 <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">
604 <meta:generator>ods-php</meta:generator>
605 <meta:creation-date>' . $myDate . '</meta:creation-date>
606 <dc:date>' . $myDate . '</dc:date>
607 <dc:language>' . $lang . '</dc:language>
608 <meta:editing-cycles>2</meta:editing-cycles>
609 <meta:editing-duration>PT15S</meta:editing-duration>
610 <meta:user-defined meta:name="Info 1"/>
611 <meta:user-defined meta:name="Info 2"/>
612 <meta:user-defined meta:name="Info 3"/>
613 <meta:user-defined meta:name="Info 4"/>
615 </office:document-meta>';
620 =head2 ImportFramework
622 Import all the information of a Framework from a excel-xml/ods file.
631 my ($filename, $frameworkcode, $deleteFilename) = @_;
635 my $dbh = C4
::Context
->dbh;
636 if (-r
$filename && $dbh) {
638 if ($filename =~ /\.(csv|ods|xml)$/i) {
641 unlink ($filename) if ($deleteFilename); # remove temporary file
644 if ($extension eq 'ods') {
645 ($tempdir, $filename) = _openODS
($filename, $deleteFilename);
650 if ($extension eq 'ods' || $extension eq 'xml') {
651 # They have xml structure, so read it on a dom object
652 my $parser = XML
::LibXML
->new();
653 $dom = $parser->parse_file($filename);
655 my $root = $dom->documentElement();
658 # They are text files, so open it to read
659 open($dom, '<', $filename);
662 # Process both tables
664 my $numDeletedAux = 0;
665 if (($numDeletedAux = _import_table
($dbh, 'marc_tag_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield'], $extension)) >= 0) {
666 $numDeleted += $numDeletedAux if ($numDeletedAux > 0);
667 if (($numDeletedAux = _import_table
($dbh, 'marc_subfield_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield', 'tagsubfield'], $extension)) >= 0) {
668 $numDeleted += $numDeletedAux if ($numDeletedAux > 0);
669 $ok = ($numDeleted > 0)?
$numDeleted:0;
673 $debug and warn "Error ImportFramework couldn't create dom\n";
677 $debug and warn "Error ImportFramework $@\n";
679 if ($extension eq 'csv') {
680 close($dom) if ($dom);
684 unlink ($filename) if ($deleteFilename); # remove temporary file
686 $debug and warn "Error ImportFramework no conex to database or not readeable $filename\n";
688 if ($deleteFilename && $tempdir && -d
$tempdir && -w
$tempdir) {
691 import File
::Temp qw
/ rmtree /;
695 system("rm -rf $tempdir");
701 # Open (uncompress) ods file and return the content.xml file
704 my ($filename, $deleteFilename) = @_;
711 import File
::Temp qw
/ tempfile tempdir /;
712 $tempdir = tempdir
( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR
=> (-d
$tmp)?
$tmp:'.', CLEANUP
=> 1);
716 $tempdir = _createTmpDir
($tmp);
720 require Archive
::Zip
;
721 import Archive
::Zip
qw( :ERROR_CODES :CONSTANTS );
722 my $zip = Archive
::Zip
->new($filename);
723 foreach my $file ($zip->members) {
724 next if ($file->isDirectory);
725 (my $extractName = $file->fileName) =~ s{.*/}{};
726 next unless ($extractName eq 'content.xml');
727 $file->extractToFileNamed("$tempdir/$extractName");
731 my $cmd = qx(which unzip
2>/dev/null
|| whereis unzip
);
733 $cmd = 'unzip' if (!$cmd || !-x
$cmd);
734 system("$cmd $filename -d $tempdir");
736 if (-f
"$tempdir/content.xml") {
737 unlink ($filename) if ($deleteFilename);
738 return ($tempdir, "$tempdir/content.xml");
741 unlink ($filename) if ($deleteFilename);
742 return ($tempdir, undef);
747 # Check the table and columns corresponds with worksheet
748 sub _check_validity_worksheet
750 my ($dbh, $table, $nodeFields, $fieldsA, $format) = @_;
754 my $query = 'DESCRIBE ' . $table;
755 my $sth = $dbh->prepare($query);
758 $query = 'SHOW COLUMNS FROM ' . $table;
759 $sth = $dbh->prepare($query);
762 while (my $hashRef = $sth->fetchrow_hashref) {
763 $fields->{$hashRef->{Field
}} = $hashRef->{Field
};
771 _getFields
($nodeFields, $fieldsR, $format);
775 unless (exists($fields->{$_})) {
782 }#_check_validity_worksheet
785 # Import the data from an excel-xml/ods to mysql tables.
788 my ($dbh, $table, $frameworkcode, $dom, $PKArray, $format) = @_;
792 # Create hash with all elements defined by primary key to know which ones to delete after parsing the spreadsheet
796 $query = 'SELECT ' . join(',', @fields) . ' FROM ' . $table . ' WHERE frameworkcode=?';
797 my $sth = $dbh->prepare($query);
798 $sth->execute($frameworkcode);
800 while (my $hashRef = $sth->fetchrow_hashref) {
802 map { $field .= $hashRef->{$_} . '_'; } @fields;
804 $fields2Delete{$field} = 1;
809 if ($format eq 'csv') {
812 my $query = 'SHOW COLUMNS FROM ' . $table;
813 my $sth = $dbh->prepare($query);
815 while (my $hashRef = $sth->fetchrow_hashref) {
816 push @fieldsName, $hashRef->{Field
};
819 $ok = _import_table_csv
($dbh, $table, $frameworkcode, $dom, $PKArray, \
%fields2Delete, \
@fieldsName);
820 } elsif ($format eq 'ods') {
821 $ok = _import_table_ods
($dbh, $table, $frameworkcode, $dom, $PKArray, \
%fields2Delete);
823 $ok = _import_table_excel
($dbh, $table, $frameworkcode, $dom, $PKArray, \
%fields2Delete);
826 if (($ok = scalar(keys %fields2Delete)) > 0) {
827 $query = 'DELETE FROM ' . $table . ' WHERE ';
828 map {$query .= $_ . '=? AND ';} @
$PKArray;
829 $query = substr($query, 0, -4);
830 my $sth = $dbh->prepare($query);
831 for (keys %fields2Delete) {
833 $sth->execute(($frameworkcode, split('_', $_)));
844 # Insert/Update the row from the spreadsheet in the database
847 my ($dbh, $db_scheme, $table, $fields, $dataStr, $updateStr, $dataFields, $dataFieldsHash, $PKArray, $fieldsPK, $fields2Delete) = @_;
851 if ($db_scheme eq 'mysql') {
852 $query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ') ON DUPLICATE KEY UPDATE ' . $updateStr;
854 $query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ')';
857 my $sth = $dbh->prepare($query);
858 if ($db_scheme eq 'mysql') {
859 $sth->execute((@
$dataFields, @
$dataFields));
861 $sth->execute(@
$dataFields);
865 unless ($db_scheme eq 'mysql') {
866 $query = 'UPDATE ' . $table . ' SET ' . $updateStr . ' WHERE ';
867 map {$query .= $_ . '=? AND ';} @
$PKArray;
868 $query = substr($query, 0, -4);
870 my $sth2 = $dbh->prepare($query);
872 map {push @dataPK, $dataFieldsHash->{$_};} @
$PKArray;
873 $sth2->execute((@
$dataFields, @dataPK));
877 $debug and warn "Error _processRows_Table $@\n";
883 map { $field .= $dataFieldsHash->{$_} . '_'; } @
$fieldsPK;
885 delete $fields2Delete->{$field} if (exists($fields2Delete->{$field}));
891 # Process the rows of a worksheet and insert/update them in a mysql table.
892 sub _processRows_Table
894 my ($dbh, $frameworkcode, $nodeR, $table, $PKArray, $format, $fields2Delete) = @_;
902 my $db_scheme = C4
::Context
->config("db_scheme");
904 my @fieldsPK = @
$PKArray;
907 if ($nodeR->nodeType == 1 && (($format && $format eq 'ods' && $nodeR->nodeName =~ /(?:table:)?table-row/) || ($nodeR->nodeName =~ /(?:ss:)?Row/)) && $nodeR->hasChildNodes()) {
910 _getFields
($nodeR, \
@fields, $format);
911 return 0 unless _check_validity_worksheet
($dbh, $table, $nodeR, \
@fields, $format);
912 $fields = join(',', @fields);
914 map { $dataStr .= '?,';} @fields;
915 chop($dataStr) if ($dataStr);
917 map { $updateStr .= $_ . '=?,';} @fields;
918 chop($updateStr) if ($updateStr);
921 my ($dataFields, $dataFieldsR) = _getDataFields
($frameworkcode, $nodeR, \
@fields, $format);
922 if (scalar(@fields) == scalar(@
$dataFieldsR)) {
923 $ok = _processRow_DB
($dbh, $db_scheme, $table, $fields, $dataStr, $updateStr, $dataFieldsR, $dataFields, $PKArray, \
@fieldsPK, $fields2Delete);
928 $nodeR = $nodeR->nextSibling;
936 # Import worksheet from the csv file to the mysql table
937 sub _import_table_csv
939 my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete, $fields) = @_;
943 my $numFields = @
$fields;
944 my $fieldsNameRead = 0;
946 my ($fieldsStr, $dataStr, $updateStr);
947 my $db_scheme = C4
::Context
->config("db_scheme");
948 my @fieldsPK = @
$PKArray;
955 # Check whether the line has an unfinished field, i.e., a field with CR/LF in its data
956 if ($row =~ /,"[^"]*[\r\n]+$/ || $row =~ /^[^"]+[\r\n]+$/) {
957 $row =~ s/[\r\n]+$//;
962 $row = $partialRow . $row;
965 # Line OK, process it
966 if ($row =~ /(?:".*?",?)+/) {
967 @arrData = split('","', $row);
968 $arrData[0] = substr($arrData[0], 1) if ($arrData[0] =~ /^"/);
969 $arrData[$#arrData] =~ s/[\r\n]+$//;
970 chop $arrData[$#arrData] if ($arrData[$#arrData] =~ /"$/);
972 if ($arrData[0] eq '#-#' && $arrData[$#arrData] eq '#-#') {
973 # Change of table with separators #-#
975 } elsif ($fieldsNameRead && $arrData[0] eq 'tagfield') {
976 # Change of table because we begin with field name with former field names read
980 if (scalar(@
$fields) == scalar(@arrData)) {
981 if (!$fieldsNameRead) {
982 # New table, we read the field names
984 for (my $i=0; $i < @arrData; $i++) {
985 if ($arrData[$i] ne $fields->[$i]) {
990 if ($fieldsNameRead) {
991 $fieldsStr = join(',', @
$fields);
993 map { $dataStr .= '?,';} @
$fields;
994 chop($dataStr) if ($dataStr);
996 map { $updateStr .= $_ . '=?,';} @
$fields;
997 chop($updateStr) if ($updateStr);
1002 my %dataFields = ();
1004 if ($fields->[$j] eq 'frameworkcode' && $_ ne $frameworkcode) {
1005 $dataFields{$fields->[$j]} = $frameworkcode;
1006 $arrData[$j] = $frameworkcode;
1008 $dataFields{$fields->[$j]} = $_;
1012 $ok = _processRow_DB
($dbh, $db_scheme, $table, $fieldsStr, $dataStr, $updateStr, \
@arrData, \
%dataFields, $PKArray, \
@fieldsPK, $fields2Delete);
1025 # Import worksheet from the ods content.xml file to the mysql table
1026 sub _import_table_ods
1028 my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
1030 my $xc = XML
::LibXML
::XPathContext
->new($dom);
1031 $xc->registerNs('xmlns:office','urn:oasis:names:tc:opendocument:xmlns:office:1.0');
1032 $xc->registerNs('xmlns:table','urn:oasis:names:tc:opendocument:xmlns:table:1.0');
1033 $xc->registerNs('xmlns:text','urn:oasis:names:tc:opendocument:xmlns:text:1.0');
1035 @nodes = $xc->findnodes('//table:table[@table:name="' . $table . '"]');
1036 if (@nodes == 1 && $nodes[0]->hasChildNodes()) {
1037 my $nodeR = $nodes[0]->firstChild;
1038 return _processRows_Table
($dbh, $frameworkcode, $nodeR, $table, $PKArray, 'ods', $fields2Delete);
1040 $debug and warn "Error _import_table_ods there's not worksheet for $table\n";
1046 # Import worksheet from the excel-xml file to the mysql table
1047 sub _import_table_excel
1049 my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
1051 my $xc = XML
::LibXML
::XPathContext
->new($dom);
1052 $xc->registerNs('xmlns','urn:schemas-microsoft-com:office:spreadsheet');
1053 $xc->registerNs('xmlns:ss','urn:schemas-microsoft-com:office:spreadsheet');
1054 $xc->registerNs('xmlns:x','urn:schemas-microsoft-com:office:excel');
1056 @nodes = $xc->findnodes('//ss:Worksheet[@ss:Name="' . $table . '"]');
1058 for (my $i=0; $i < @nodes; $i++) {
1059 my @nodesT = $nodes[$i]->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Table');
1060 if (@nodesT == 1 && $nodesT[0]->hasChildNodes()) {
1061 my $nodeR = $nodesT[0]->firstChild;
1062 return _processRows_Table
($dbh, $frameworkcode, $nodeR, $table, $PKArray, undef, $fields2Delete);
1066 $debug and warn "Error _import_table_excel there's not worksheet for $table\n";
1069 }#_import_table_excel
1072 # Get the data from a cell on a ods file through the value attribute or the text node
1079 if ($node->nodeType == 1 && $node->nodeName =~ /(?:table:)?table-cell/) {
1080 if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value')) {
1081 $data = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value');
1082 } elsif ($node->hasChildNodes()) {
1083 my @nodes2 = $node->getElementsByTagNameNS('urn:oasis:names:tc:opendocument:xmlns:text:1.0', 'p');
1084 if (@nodes2 == 1 && $nodes2[0]->hasChildNodes()) {
1085 $data = $nodes2[0]->firstChild->nodeValue;
1088 if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated')) {
1089 $repeated = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated');
1092 return ($data, $repeated);
1096 # Get the data from a row of a spreadsheet
1099 my ($frameworkcode, $node, $fields, $format) = @_;
1101 my $dataFields = {};
1102 my @dataFieldsA = ();
1103 if ($node && $node->hasChildNodes()) {
1104 my $node2 = $node->firstChild;
1105 my ($data, $repeated);
1110 if ($format && $format eq 'ods') {
1111 ($data, $repeated) = _getDataNodeODS
($node2) if ($repeated <= 0);
1113 $ok = 1 if (defined($data));
1115 if ($node2->nodeType == 1 && $node2->nodeName =~ /(?:ss:)?Cell/) {
1116 my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
1117 if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
1118 $data = $nodes3[0]->firstChild->nodeValue;
1124 $data = '' if ($data eq '#');
1125 $data = $frameworkcode if ($fields->[$i] eq 'frameworkcode');
1126 $dataFields->{$fields->[$i]} = $data;
1127 push @dataFieldsA, $data;
1131 $node2 = $node2->nextSibling if ($repeated <= 0);
1134 return ($dataFields, \
@dataFieldsA);
1138 # Get the data from the first row to know the column names
1141 my ($node, $fields, $format) = @_;
1143 if ($node && $node->hasChildNodes()) {
1144 my $node2 = $node->firstChild;
1145 my ($data, $repeated);
1147 if ($format && $format eq 'ods') {
1148 ($data, $repeated) = _getDataNodeODS
($node2);
1149 push @
$fields, $data if (defined($data));
1151 if ($node2->nodeType == 1 && $node2->nodeName =~ /(?:ss:)?Cell/) {
1152 my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
1153 if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
1154 $data = $nodes3[0]->firstChild->nodeValue;
1155 push @
$fields, $data;
1159 $node2 = $node2->nextSibling;
1172 Koha Development Team <http://koha-community.org/>