Jonathan Druart
9d6d641d1f
On bug 17591 we discovered that there was something weird going on with the way we export and use subroutines/modules. This patch tries to standardize our EXPORT to use EXPORT_OK only. That way we will need to explicitely define the subroutine we want to use from a module. This patch is a squashed version of: Bug 17600: After export.pl Bug 17600: After perlimport Bug 17600: Manual changes Bug 17600: Other manual changes after second perlimports run Bug 17600: Fix tests And a lot of other manual changes. export.pl is a dirty script that can be found on bug 17600. "perlimport" is: git clone https://github.com/oalders/App-perlimports.git cd App-perlimports/ cpanm --installdeps . export PERL5LIB="$PERL5LIB:/kohadevbox/koha/App-perlimports/lib" find . \( -name "*.pl" -o -name "*.pm" \) -exec perl App-perlimports/script/perlimports --inplace-edit --no-preserve-unused --filename {} \; The ideas of this patch are to: * use EXPORT_OK instead of EXPORT * perltidy the EXPORT_OK list * remove '&' before the subroutine names * remove some uneeded use statements * explicitely import the subroutines we need within the controllers or modules Note that the private subroutines (starting with _) should not be exported (and not used from outside of the module except from tests). EXPORT vs EXPORT_OK (from https://www.thegeekstuff.com/2010/06/perl-exporter-examples/) """ Export allows to export the functions and variables of modules to user’s namespace using the standard import method. This way, we don’t need to create the objects for the modules to access it’s members. @EXPORT and @EXPORT_OK are the two main variables used during export operation. @EXPORT contains list of symbols (subroutines and variables) of the module to be exported into the caller namespace. @EXPORT_OK does export of symbols on demand basis. """ If this patch caused a conflict with a patch you wrote prior to its push: * Make sure you are not reintroducing a "use" statement that has been removed * "$subroutine" is not exported by the C4::$MODULE module means that you need to add the subroutine to the @EXPORT_OK list * Bareword "$subroutine" not allowed while "strict subs" means that you didn't imported the subroutine from the module: - use $MODULE qw( $subroutine list ); You can also use the fully qualified namespace: C4::$MODULE::$subroutine Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
1153 lines
46 KiB
Perl
1153 lines
46 KiB
Perl
package C4::ImportExportFramework;
|
|
|
|
# Copyright 2010-2011 MASmedios.com y Ministerio de Cultura
|
|
#
|
|
# This file is part of Koha.
|
|
#
|
|
# Koha is free software; you can redistribute it and/or modify it
|
|
# under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Koha is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Koha; if not, see <http://www.gnu.org/licenses>.
|
|
|
|
use strict;
|
|
use warnings;
|
|
use XML::LibXML;
|
|
use XML::LibXML::XPathContext;
|
|
use Digest::MD5;
|
|
use POSIX qw( strftime );
|
|
use Text::CSV_XS;
|
|
use List::MoreUtils qw( indexes );
|
|
|
|
use C4::Context;
|
|
use Koha::Logger;
|
|
|
|
our (@ISA, @EXPORT_OK);
|
|
BEGIN {
|
|
require Exporter;
|
|
@ISA = qw(Exporter);
|
|
@EXPORT_OK = qw(
|
|
ExportFramework
|
|
ImportFramework
|
|
createODS
|
|
);
|
|
}
|
|
|
|
|
|
use constant XMLSTR => '<?xml version="1.0" encoding="UTF-8"?>
|
|
<?mso-application progid="Excel.Sheet"?>
|
|
<Workbook
|
|
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
|
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
|
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
|
|
|
<Styles>
|
|
<Style ss:ID="Default" ss:Name="Normal">
|
|
<Alignment ss:Vertical="Bottom"/>
|
|
<Borders/>
|
|
<Font/>
|
|
<Interior/>
|
|
<NumberFormat/>
|
|
<Protection/>
|
|
</Style>
|
|
<Style ss:ID="s27">
|
|
<Font x:Family="Swiss" ss:Color="#0000FF" ss:Bold="1"/>
|
|
</Style>
|
|
<Style ss:ID="s21">
|
|
<NumberFormat ss:Format="yyyy\-mm\-dd"/>
|
|
</Style>
|
|
<Style ss:ID="s22">
|
|
<NumberFormat ss:Format="yyyy\-mm\-dd\ hh:mm:ss"/>
|
|
</Style>
|
|
<Style ss:ID="s23">
|
|
<NumberFormat ss:Format="hh:mm:ss"/>
|
|
</Style>
|
|
</Styles>
|
|
|
|
</Workbook>
|
|
';
|
|
|
|
|
|
use constant ODSSTR => '<?xml version="1.0" encoding="UTF-8"?>
|
|
<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">
|
|
<office:scripts/>
|
|
<office:font-face-decls/>
|
|
<office:automatic-styles/>
|
|
</office:document-content>';
|
|
|
|
|
|
use constant ODS_STYLES_STR => '<?xml version="1.0" encoding="UTF-8"?>
|
|
<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">
|
|
<office:font-face-decls></office:font-face-decls>
|
|
<office:styles></office:styles>
|
|
<office:automatic-styles></office:automatic-styles>
|
|
<office:master-styles></office:master-styles>
|
|
</office:document-styles>';
|
|
|
|
|
|
use constant ODS_SETTINGS_STR => '<?xml version="1.0" encoding="UTF-8"?>
|
|
<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>
|
|
<config:config-item-set config:name="ooo:view-settings">
|
|
<config:config-item config:name="VisibleAreaTop" config:type="int">0</config:config-item>
|
|
<config:config-item config:name="VisibleAreaLeft" config:type="int">0</config:config-item>
|
|
<config:config-item config:name="VisibleAreaWidth" config:type="int">2000</config:config-item>
|
|
<config:config-item config:name="VisibleAreaHeight" config:type="int">900</config:config-item>
|
|
<config:config-item-map-indexed config:name="Views"><config:config-item-map-entry>
|
|
<config:config-item config:name="ViewId" config:type="string">View1</config:config-item>
|
|
<config:config-item-map-named config:name="Tables">
|
|
<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>
|
|
</config:config-item-map-entry>
|
|
</config:config-item-map-named>
|
|
<config:config-item config:name="ActiveTable" config:type="string">Sheet1</config:config-item>
|
|
<config:config-item config:name="HorizontalScrollbarWidth" config:type="int">270</config:config-item>
|
|
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
|
|
<config:config-item config:name="ZoomValue" config:type="int">100</config:config-item>
|
|
<config:config-item config:name="PageViewZoomValue" config:type="int">50</config:config-item>
|
|
<config:config-item config:name="ShowPageBreakPreview" config:type="boolean">false</config:config-item>
|
|
<config:config-item config:name="ShowZeroValues" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="ShowNotes" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="ShowGrid" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="GridColor" config:type="long">12632256</config:config-item>
|
|
<config:config-item config:name="ShowPageBreaks" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="HasColumnRowHeaders" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="HasSheetTabs" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="IsOutlineSymbolsSet" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="IsSnapToRaster" config:type="boolean">false</config:config-item>
|
|
<config:config-item config:name="RasterIsVisible" config:type="boolean">false</config:config-item>
|
|
<config:config-item config:name="IsRasterAxisSynchronized" config:type="boolean">true</config:config-item></config:config-item-map-entry></config:config-item-map-indexed>
|
|
</config:config-item-set>
|
|
<config:config-item-set config:name="ooo:configuration-settings">
|
|
<config:config-item config:name="ShowZeroValues" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="ShowNotes" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="ShowGrid" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="GridColor" config:type="long">12632256</config:config-item>
|
|
<config:config-item config:name="ShowPageBreaks" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="LinkUpdateMode" config:type="short">3</config:config-item>
|
|
<config:config-item config:name="HasColumnRowHeaders" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="HasSheetTabs" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="IsOutlineSymbolsSet" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="IsSnapToRaster" config:type="boolean">false</config:config-item>
|
|
<config:config-item config:name="RasterIsVisible" config:type="boolean">false</config:config-item>
|
|
<config:config-item config:name="IsRasterAxisSynchronized" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="AutoCalculate" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="PrinterName" config:type="string">Generic Printer</config:config-item>
|
|
<config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
|
|
<config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
|
|
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">false</config:config-item>
|
|
<config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
|
|
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
|
|
</config:config-item-set>
|
|
</office:settings></office:document-settings>';
|
|
|
|
|
|
use constant ODS_MANIFEST_STR => '<?xml version="1.0" encoding="UTF-8"?>
|
|
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
|
|
<manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.spreadsheet" manifest:full-path="/"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/statusbar/"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/accelerator/"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/floater/"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/popupmenu/"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/progressbar/"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/menubar/"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/toolbar/"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/Bitmaps/"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/"/>
|
|
<manifest:file-entry manifest:media-type="application/vnd.sun.xml.ui.configuration" manifest:full-path="Configurations2/"/>
|
|
<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/>
|
|
<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/>
|
|
<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/>
|
|
<manifest:file-entry manifest:media-type="" manifest:full-path="Thumbnails/"/>
|
|
<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml"/>
|
|
</manifest:manifest>';
|
|
|
|
|
|
=head1 NAME
|
|
|
|
C4::ImportExportFramework - Import/Export Framework to Excel-xml/ODS Module Functions
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use C4::ImportExportFramework;
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Module to Import/Export Framework to Excel-xml/ODS on intranet administration - MARC Frameworks section
|
|
|
|
Module to Import/Export Framework to Excel-xml/ODS on intranet administration - MARC Frameworks section
|
|
exporting the tables marc_tag_structure, marc_subfield_structure to excel-xml/ods or viceversa
|
|
|
|
Functions for handling import/export.
|
|
|
|
|
|
=head1 SUBROUTINES
|
|
|
|
|
|
|
|
=head2 ExportFramework
|
|
|
|
Export all the information of a Framework to an excel "xml" file or OpenDocument SpreadSheet "ods" file.
|
|
|
|
return :
|
|
succes
|
|
|
|
=cut
|
|
|
|
sub ExportFramework
|
|
{
|
|
my ($frameworkcode, $xmlStrRef, $mode) = @_;
|
|
|
|
my $dbh = C4::Context->dbh;
|
|
if ($dbh) {
|
|
my $dom;
|
|
my $root;
|
|
my $elementSS;
|
|
if ($mode eq 'ods' || $mode eq 'excel') {
|
|
eval {
|
|
my $parser = XML::LibXML->new();
|
|
$dom = $parser->parse_string(($mode && $mode eq 'ods')?ODSSTR:XMLSTR);
|
|
if ($dom) {
|
|
$root = $dom->documentElement();
|
|
if ($mode && $mode eq 'ods') {
|
|
my $elementBody = $dom->createElement('office:body');
|
|
$root->appendChild($elementBody);
|
|
$elementSS = $dom->createElement('office:spreadsheet');
|
|
$elementBody->appendChild($elementSS);
|
|
}
|
|
}
|
|
};
|
|
if ($@) {
|
|
Koha::Logger->get->warn("Error ExportFramework $@");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (_export_table('marc_tag_structure', $dbh, ($mode eq 'csv')?$xmlStrRef:$dom, ($mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
|
|
if (_export_table('marc_subfield_structure', $dbh, ($mode eq 'csv')?$xmlStrRef:$dom, ($mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
|
|
$$xmlStrRef = $dom->toString(1) if ($mode eq 'ods' || $mode eq 'excel');
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}#ExportFramework
|
|
|
|
|
|
|
|
|
|
# Export all the data from a mysql table to an spreadsheet.
|
|
sub _export_table
|
|
{
|
|
my ($table, $dbh, $dom, $root, $frameworkcode, $mode) = @_;
|
|
if ($mode eq 'csv') {
|
|
_export_table_csv($table, $dbh, $dom, $root, $frameworkcode);
|
|
} elsif ($mode eq 'ods') {
|
|
_export_table_ods($table, $dbh, $dom, $root, $frameworkcode);
|
|
} else {
|
|
_export_table_excel($table, $dbh, $dom, $root, $frameworkcode);
|
|
}
|
|
}
|
|
|
|
# Export the mysql table to an csv file
|
|
sub _export_table_csv
|
|
{
|
|
my ($table, $dbh, $strCSV, $root, $frameworkcode) = @_;
|
|
|
|
eval {
|
|
# First row with the name of the columns
|
|
my $query = 'SHOW COLUMNS FROM ' . $table;
|
|
my $sth = $dbh->prepare($query);
|
|
$sth->execute();
|
|
my @fields = ();
|
|
while (my $hashRef = $sth->fetchrow_hashref) {
|
|
$$strCSV .= '"' . $hashRef->{Field} . '",';
|
|
push @fields, $hashRef->{Field};
|
|
}
|
|
chop $$strCSV;
|
|
$$strCSV .= chr(10);
|
|
# Populate rows with the data from mysql
|
|
$query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
|
|
$sth = $dbh->prepare($query);
|
|
$sth->execute($frameworkcode);
|
|
my $data;
|
|
while (my $hashRef = $sth->fetchrow_hashref) {
|
|
for my $field (@fields) {
|
|
my $value = $hashRef->{$field} // q||;
|
|
$value =~ s/[\r\n]//g;
|
|
$$strCSV .= '"' . $value . '",';
|
|
}
|
|
chop $$strCSV;
|
|
$$strCSV .= chr(10);
|
|
}
|
|
$$strCSV .= chr(10);
|
|
for (@fields) {
|
|
# Separator for change of table
|
|
$$strCSV .= '"#-#",';
|
|
}
|
|
chop $$strCSV;
|
|
$$strCSV .= chr(10);
|
|
$$strCSV .= chr(10);
|
|
};
|
|
if ($@) {
|
|
Koha::Logger->get->warn("Error _export_table_csv $@");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}#_export_table_csv
|
|
|
|
|
|
# Export the mysql table to an ods file
|
|
sub _export_table_ods
|
|
{
|
|
my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
|
|
|
|
eval {
|
|
my $elementTable = $dom->createElement('table:table');
|
|
$elementTable->setAttribute('table:name', $table);
|
|
$elementTable->setAttribute('table:print', 'false');
|
|
$root->appendChild($elementTable);
|
|
my $elementRow = $dom->createElement('table:table-row');
|
|
$elementTable->appendChild($elementRow);
|
|
|
|
my $elementCell;
|
|
my $elementData;
|
|
# First row with the name of the columns
|
|
my $query = 'SHOW COLUMNS FROM ' . $table;
|
|
my $sth = $dbh->prepare($query);
|
|
$sth->execute();
|
|
my @fields = ();
|
|
while (my $hashRef = $sth->fetchrow_hashref) {
|
|
$elementCell = $dom->createElement('table:table-cell');
|
|
$elementCell->setAttribute('office:value-type', 'string');
|
|
$elementCell->setAttribute('office:value', $hashRef->{Field});
|
|
$elementRow->appendChild($elementCell);
|
|
$elementData = $dom->createElement('text:p');
|
|
$elementCell->appendChild($elementData);
|
|
$elementData->appendTextNode($hashRef->{Field});
|
|
push @fields, {name => $hashRef->{Field}, type => ($hashRef->{Type} =~ /int/i)?'float':'string'};
|
|
}
|
|
# Populate rows with the data from mysql
|
|
$query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
|
|
$sth = $dbh->prepare($query);
|
|
$sth->execute($frameworkcode);
|
|
my $data;
|
|
while (my $hashRef = $sth->fetchrow_hashref) {
|
|
$elementRow = $dom->createElement('table:table-row');
|
|
$elementTable->appendChild($elementRow);
|
|
for (@fields) {
|
|
$data = $hashRef->{$_->{name}};
|
|
if ($_->{type} eq 'float' && !defined($data)) {
|
|
$data = '0';
|
|
} elsif ($_->{type} eq 'string' && !defined($data)) {
|
|
$data = q{};
|
|
} elsif ($_->{type} eq 'string' && (!$data && $data ne '0')) {
|
|
$data = '#';
|
|
}
|
|
$data = _parseContent2Xml($data) if ($_->{type} eq 'string');
|
|
$elementCell = $dom->createElement('table:table-cell');
|
|
$elementCell->setAttribute('office:value-type', $_->{type});
|
|
$elementCell->setAttribute('office:value', $data);
|
|
$elementRow->appendChild($elementCell);
|
|
$elementData = $dom->createElement('text:p');
|
|
$elementCell->appendChild($elementData);
|
|
$elementData->appendTextNode($data);
|
|
}
|
|
}
|
|
};
|
|
if ($@) {
|
|
Koha::Logger->get->warn("Error _export_table_ods $@");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}#_export_table_ods
|
|
|
|
|
|
# Export the mysql table to an excel-xml (openoffice/libreoffice compatible) file
|
|
sub _export_table_excel
|
|
{
|
|
my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
|
|
|
|
eval {
|
|
my $elementWS = $dom->createElement('Worksheet');
|
|
$elementWS->setAttribute('ss:Name', $table);
|
|
$root->appendChild($elementWS);
|
|
my $elementTable = $dom->createElement('ss:Table');
|
|
$elementWS->appendChild($elementTable);
|
|
my $elementRow = $dom->createElement('ss:Row');
|
|
$elementTable->appendChild($elementRow);
|
|
|
|
# First row with the name of the columns
|
|
my $elementCell;
|
|
my $elementData;
|
|
my $query = 'SHOW COLUMNS FROM ' . $table;
|
|
my $sth = $dbh->prepare($query);
|
|
$sth->execute();
|
|
my @fields = ();
|
|
while (my $hashRef = $sth->fetchrow_hashref) {
|
|
$elementCell = $dom->createElement('ss:Cell');
|
|
$elementCell->setAttribute('ss:StyleID', 's27');
|
|
$elementRow->appendChild($elementCell);
|
|
$elementData = $dom->createElement('ss:Data');
|
|
$elementData->setAttribute('ss:Type', 'String');
|
|
$elementCell->appendChild($elementData);
|
|
$elementData->appendTextNode($hashRef->{Field});
|
|
push @fields, {name => $hashRef->{Field}, type => ($hashRef->{Type} =~ /int/i)?'Number':'String'};
|
|
}
|
|
# Populate rows with the data from mysql
|
|
$query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
|
|
$sth = $dbh->prepare($query);
|
|
$sth->execute($frameworkcode);
|
|
my $data;
|
|
while (my $hashRef = $sth->fetchrow_hashref) {
|
|
$elementRow = $dom->createElement('ss:Row');
|
|
$elementTable->appendChild($elementRow);
|
|
for (@fields) {
|
|
$elementCell = $dom->createElement('ss:Cell');
|
|
$elementRow->appendChild($elementCell);
|
|
$elementData = $dom->createElement('ss:Data');
|
|
$elementData->setAttribute('ss:Type', $_->{type});
|
|
$elementCell->appendChild($elementData);
|
|
$data = $hashRef->{$_->{name}};
|
|
if ($_->{type} eq 'Number' && !defined($data)) {
|
|
$data = '0';
|
|
} elsif ($_->{type} eq 'String' && !defined($data)) {
|
|
$data = q{};
|
|
} elsif ($_->{type} eq 'String' && (!$data && $data ne '0')) {
|
|
$data = '#';
|
|
}
|
|
$elementData->appendTextNode(($_->{type} eq 'String')?_parseContent2Xml($data):$data);
|
|
}
|
|
}
|
|
};
|
|
if ($@) {
|
|
Koha::Logger->get->warn("Error _export_table_excel $@");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}#_export_table_excel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Format chars problematics to a correct format for xml.
|
|
sub _parseContent2Xml
|
|
{
|
|
my $content = shift;
|
|
|
|
$content =~ s/\&(?![a-zA-Z#0-9]{1,4};)/&/g;
|
|
$content =~ s/</</g;
|
|
$content =~ s/>/>/g;
|
|
return $content;
|
|
}#_parseContent2Xml
|
|
|
|
|
|
# Get the tmp directory on the system
|
|
sub _getTmp
|
|
{
|
|
my $tmp = '/tmp';
|
|
if ($ENV{'TMP'} && -d $ENV{'TMP'}) {
|
|
$tmp = $ENV{'TMP'};
|
|
} elsif ($ENV{'TMPDIR'} && -d $ENV{'TMPDIR'}) {
|
|
$tmp = $ENV{'TMPDIR'};
|
|
} elsif ($ENV{'TEMP'} && -d $ENV{'TEMP'}) {
|
|
$tmp = $ENV{'TEMP'};
|
|
}
|
|
return $tmp;
|
|
}#_getTmp
|
|
|
|
|
|
# Create our tempdir directory for the ods process
|
|
sub _createTmpDir
|
|
{
|
|
my $tmp = shift;
|
|
|
|
my $tempdir = (-d $tmp)?$tmp . '/':'./';
|
|
$tempdir .= 'tmp_ods_' . Digest::MD5::md5_hex(Digest::MD5::md5_hex(time().{}.rand().{}.$$));
|
|
eval {
|
|
mkdir $tempdir;
|
|
};
|
|
if ($@) {
|
|
return;
|
|
} else {
|
|
return $tempdir;
|
|
}
|
|
}#_createTmpDir
|
|
|
|
=head2 createODS
|
|
|
|
Creates a temporary directory to create the ods file and read it to store its content in a string.
|
|
|
|
return :
|
|
success
|
|
|
|
=cut
|
|
|
|
sub createODS
|
|
{
|
|
my ($strContent, $lang, $strODSRef) = @_;
|
|
|
|
my $tmp = _getTmp();
|
|
my $tempModule = 1;
|
|
my $tempdir;
|
|
eval {
|
|
require File::Temp;
|
|
import File::Temp qw/ tempfile tempdir /;
|
|
$tempdir = tempdir ( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR => (-d $tmp)?$tmp:'.', CLEANUP => 1);
|
|
};
|
|
if ($@) {
|
|
$tempModule = 0;
|
|
$tempdir = _createTmpDir($tmp);
|
|
}
|
|
if ($tempdir) {
|
|
my $fh;
|
|
# populate tempdir directory with the ods elements
|
|
eval {
|
|
if (open($fh, '>', "$tempdir/content.xml")) {
|
|
print {$fh} $strContent;
|
|
close($fh);
|
|
}
|
|
if (open($fh, '>', "$tempdir/mimetype")) {
|
|
print {$fh} 'application/vnd.oasis.opendocument.spreadsheet';
|
|
close($fh);
|
|
}
|
|
if (open($fh, '>', "$tempdir/meta.xml")) {
|
|
print {$fh} _getMeta($lang);
|
|
close($fh);
|
|
}
|
|
if (open($fh, '>', "$tempdir/styles.xml")) {
|
|
print {$fh} ODS_STYLES_STR;
|
|
close($fh);
|
|
}
|
|
if (open($fh, '>', "$tempdir/settings.xml")) {
|
|
print {$fh} ODS_SETTINGS_STR;
|
|
close($fh);
|
|
}
|
|
mkdir($tempdir.'/META-INF/');
|
|
mkdir($tempdir.'/Configurations2/');
|
|
mkdir($tempdir.'/Configurations2/acceleator/');
|
|
mkdir($tempdir.'/Configurations2/images/');
|
|
mkdir($tempdir.'/Configurations2/popupmenu/');
|
|
mkdir($tempdir.'/Configurations2/statusbar/');
|
|
mkdir($tempdir.'/Configurations2/floater/');
|
|
mkdir($tempdir.'/Configurations2/menubar/');
|
|
mkdir($tempdir.'/Configurations2/progressbar/');
|
|
mkdir($tempdir.'/Configurations2/toolbar/');
|
|
|
|
if (open($fh, '>', "$tempdir/META-INF/manifest.xml")) {
|
|
print {$fh} ODS_MANIFEST_STR;
|
|
close($fh);
|
|
}
|
|
};
|
|
if ($@) {
|
|
Koha::Logger->get->warn("Error createODS $@");
|
|
} else {
|
|
# create ods file from tempdir directory
|
|
eval {
|
|
require Archive::Zip;
|
|
import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
|
|
my $zip = Archive::Zip->new();
|
|
$zip->addTree( $tempdir, '' );
|
|
$zip->writeToFileNamed($tempdir . '/new.ods');
|
|
};
|
|
if ($@) {
|
|
my $cmd = qx(which zip 2>/dev/null || whereis zip);
|
|
chomp $cmd;
|
|
$cmd = 'zip' if (!$cmd || !-x $cmd);
|
|
system("cd $tempdir && $cmd -r new.ods ./");
|
|
}
|
|
my $ok = 0;
|
|
# read ods file and return as a string
|
|
if (-f "$tempdir/new.ods") {
|
|
if (open ($fh, '<', "$tempdir/new.ods")) {
|
|
binmode $fh;
|
|
my $buffer;
|
|
while (read ($fh, $buffer, 65536)) {
|
|
$$strODSRef .= $buffer;
|
|
}
|
|
close($fh);
|
|
$ok = 1;
|
|
}
|
|
}
|
|
# delete tempdir directory
|
|
if (!$tempModule && $tempdir) {
|
|
eval {
|
|
require File::Path;
|
|
import File::Temp qw/ rmtree /;
|
|
rmtree($tempdir);
|
|
};
|
|
if ($@) {
|
|
system("rm -rf $tempdir");
|
|
}
|
|
}
|
|
return 1 if ($ok);
|
|
}
|
|
}
|
|
return 0;
|
|
}#createODS
|
|
|
|
|
|
# return Meta content for ods file
|
|
sub _getMeta
|
|
{
|
|
my $lang = shift;
|
|
|
|
my $myDate = strftime ("%Y-%m-%dT%H:%M:%S", localtime(time()));
|
|
my $meta = '<?xml version="1.0" encoding="UTF-8"?>
|
|
<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">
|
|
<office:meta>
|
|
<meta:generator>ods-php</meta:generator>
|
|
<meta:creation-date>' . $myDate . '</meta:creation-date>
|
|
<dc:date>' . $myDate . '</dc:date>
|
|
<dc:language>' . $lang . '</dc:language>
|
|
<meta:editing-cycles>2</meta:editing-cycles>
|
|
<meta:editing-duration>PT15S</meta:editing-duration>
|
|
<meta:user-defined meta:name="Info 1"/>
|
|
<meta:user-defined meta:name="Info 2"/>
|
|
<meta:user-defined meta:name="Info 3"/>
|
|
<meta:user-defined meta:name="Info 4"/>
|
|
</office:meta>
|
|
</office:document-meta>';
|
|
return $meta;
|
|
}#_getMeta
|
|
|
|
|
|
=head2 ImportFramework
|
|
|
|
Import all the information of a Framework from a excel-xml/ods file.
|
|
|
|
return :
|
|
success
|
|
|
|
=cut
|
|
|
|
sub ImportFramework
|
|
{
|
|
my ($filename, $frameworkcode, $deleteFilename) = @_;
|
|
|
|
my $tempdir;
|
|
my $ok = -1;
|
|
my $dbh = C4::Context->dbh;
|
|
if (-r $filename && $dbh) {
|
|
my $extension = '';
|
|
if ($filename =~ /\.(csv|ods|xml)$/i) {
|
|
$extension = lc($1);
|
|
} else {
|
|
unlink ($filename) if ($deleteFilename); # remove temporary file
|
|
return -1;
|
|
}
|
|
if ($extension eq 'ods') {
|
|
($tempdir, $filename) = _openODS($filename, $deleteFilename);
|
|
}
|
|
if ($filename) {
|
|
my $dom;
|
|
eval {
|
|
if ($extension eq 'ods' || $extension eq 'xml') {
|
|
# They have xml structure, so read it on a dom object
|
|
my $parser = XML::LibXML->new();
|
|
$dom = $parser->parse_file($filename);
|
|
if ($dom) {
|
|
my $root = $dom->documentElement();
|
|
}
|
|
} else {
|
|
# They are text files, so open it to read
|
|
open($dom, '<', $filename);
|
|
}
|
|
if ($dom) {
|
|
# Process both tables
|
|
my $numDeleted = 0;
|
|
my $numDeletedAux = 0;
|
|
if (($numDeletedAux = _import_table($dbh, 'marc_tag_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield'], $extension)) >= 0) {
|
|
$numDeleted += $numDeletedAux if ($numDeletedAux > 0);
|
|
if (($numDeletedAux = _import_table($dbh, 'marc_subfield_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield', 'tagsubfield'], $extension)) >= 0) {
|
|
$numDeleted += $numDeletedAux if ($numDeletedAux > 0);
|
|
$ok = ($numDeleted > 0)?$numDeleted:0;
|
|
}
|
|
}
|
|
} else {
|
|
Koha::Logger->get->warn("Error ImportFramework couldn't create dom");
|
|
}
|
|
};
|
|
if ($@) {
|
|
Koha::Logger->get->warn("Error ImportFramework $@");
|
|
} else {
|
|
if ($extension eq 'csv') {
|
|
close($dom) if ($dom);
|
|
}
|
|
}
|
|
}
|
|
unlink ($filename) if ($deleteFilename); # remove temporary file
|
|
} else {
|
|
Koha::Logger->get->warn("Error ImportFramework no conex to database or not readeable $filename");
|
|
}
|
|
if ($deleteFilename && $tempdir && -d $tempdir && -w $tempdir) {
|
|
eval {
|
|
require File::Path;
|
|
import File::Temp qw/ rmtree /;
|
|
rmtree($tempdir);
|
|
};
|
|
if ($@) {
|
|
system("rm -rf $tempdir");
|
|
}
|
|
}
|
|
return $ok;
|
|
}#ImportFramework
|
|
|
|
# Open (uncompress) ods file and return the content.xml file
|
|
sub _openODS
|
|
{
|
|
my ($filename, $deleteFilename) = @_;
|
|
|
|
my $tmp = _getTmp();
|
|
my $tempModule = 1;
|
|
my $tempdir;
|
|
eval {
|
|
require File::Temp;
|
|
import File::Temp qw/ tempfile tempdir /;
|
|
$tempdir = tempdir ( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR => (-d $tmp)?$tmp:'.', CLEANUP => 1);
|
|
};
|
|
if ($@) {
|
|
$tempModule = 0;
|
|
$tempdir = _createTmpDir($tmp);
|
|
}
|
|
if ($tempdir) {
|
|
eval {
|
|
require Archive::Zip;
|
|
import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
|
|
my $zip = Archive::Zip->new($filename);
|
|
foreach my $file ($zip->members) {
|
|
next if ($file->isDirectory);
|
|
(my $extractName = $file->fileName) =~ s{.*/}{};
|
|
next unless ($extractName eq 'content.xml');
|
|
$file->extractToFileNamed("$tempdir/$extractName");
|
|
}
|
|
};
|
|
if ($@) {
|
|
my $cmd = qx(which unzip 2>/dev/null || whereis unzip);
|
|
chomp $cmd;
|
|
$cmd = 'unzip' if (!$cmd || !-x $cmd);
|
|
system("$cmd $filename -d $tempdir");
|
|
}
|
|
if (-f "$tempdir/content.xml") {
|
|
unlink ($filename) if ($deleteFilename);
|
|
return ($tempdir, "$tempdir/content.xml");
|
|
}
|
|
}
|
|
unlink ($filename) if ($deleteFilename);
|
|
return ($tempdir, undef);
|
|
}#_openODS
|
|
|
|
|
|
|
|
# Check the table and columns corresponds with worksheet
|
|
sub _check_validity_worksheet
|
|
{
|
|
my ($dbh, $table, $nodeFields, $fieldsA, $format) = @_;
|
|
|
|
my $ret = 0;
|
|
eval {
|
|
my $query = 'DESCRIBE ' . $table;
|
|
my $sth = $dbh->prepare($query);
|
|
$sth->execute();
|
|
$sth->finish;
|
|
$query = 'SHOW COLUMNS FROM ' . $table;
|
|
$sth = $dbh->prepare($query);
|
|
$sth->execute();
|
|
my $fields = {};
|
|
while (my $hashRef = $sth->fetchrow_hashref) {
|
|
$fields->{$hashRef->{Field}} = $hashRef->{Field};
|
|
}
|
|
my @fields;
|
|
my $fieldsR;
|
|
if ($fieldsA) {
|
|
$fieldsR = $fieldsA;
|
|
} else {
|
|
$fieldsR = \@fields;
|
|
_getFields($nodeFields, $fieldsR, $format);
|
|
}
|
|
$ret = 1;
|
|
for (@$fieldsR) {
|
|
unless (exists($fields->{$_})) {
|
|
$ret = 0;
|
|
last;
|
|
}
|
|
}
|
|
};
|
|
return $ret;
|
|
}#_check_validity_worksheet
|
|
|
|
|
|
# Import the data from an excel-xml/ods to mysql tables.
|
|
sub _import_table
|
|
{
|
|
my ($dbh, $table, $frameworkcode, $dom, $PKArray, $format) = @_;
|
|
my %fields2Delete;
|
|
my $query;
|
|
my @fields;
|
|
# Create hash with all elements defined by primary key to know which ones to delete after parsing the spreadsheet
|
|
eval {
|
|
@fields = @$PKArray;
|
|
shift @fields;
|
|
$query = 'SELECT ' . join(',', @fields) . ' FROM ' . $table . ' WHERE frameworkcode=?';
|
|
my $sth = $dbh->prepare($query);
|
|
$sth->execute($frameworkcode);
|
|
my $field;
|
|
while (my $hashRef = $sth->fetchrow_hashref) {
|
|
$field = '';
|
|
map { $field .= $hashRef->{$_} . '_'; } @fields;
|
|
chop $field;
|
|
$fields2Delete{$field} = 1;
|
|
}
|
|
$sth->finish;
|
|
};
|
|
my $ok = 0;
|
|
if ($format eq 'csv') {
|
|
my @fieldsName = ();
|
|
eval {
|
|
my $query = 'SHOW COLUMNS FROM ' . $table;
|
|
my $sth = $dbh->prepare($query);
|
|
$sth->execute();
|
|
while (my $hashRef = $sth->fetchrow_hashref) {
|
|
push @fieldsName, $hashRef->{Field};
|
|
}
|
|
};
|
|
$ok = _import_table_csv($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete, \@fieldsName);
|
|
} elsif ($format eq 'ods') {
|
|
$ok = _import_table_ods($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete);
|
|
} else {
|
|
$ok = _import_table_excel($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete);
|
|
}
|
|
if ($ok) {
|
|
if (($ok = scalar(keys %fields2Delete)) > 0) {
|
|
$query = 'DELETE FROM ' . $table . ' WHERE ';
|
|
map {$query .= $_ . '=? AND ';} @$PKArray;
|
|
$query = substr($query, 0, -4);
|
|
my $sth = $dbh->prepare($query);
|
|
for (keys %fields2Delete) {
|
|
eval {
|
|
$sth->execute(($frameworkcode, split('_', $_)));
|
|
};
|
|
}
|
|
}
|
|
} else {
|
|
$ok = -1;
|
|
}
|
|
return $ok;
|
|
}#_import_table
|
|
|
|
|
|
# Insert/Update the row from the spreadsheet in the database
|
|
sub _processRow_DB
|
|
{
|
|
my ($dbh, $table, $fields, $dataStr, $updateStr, $dataFields, $dataFieldsHash, $PKArray, $fieldsPK, $fields2Delete) = @_;
|
|
|
|
my $ok = 0;
|
|
my $query;
|
|
$query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ') ON DUPLICATE KEY UPDATE ' . $updateStr;
|
|
eval {
|
|
my $sth = $dbh->prepare($query);
|
|
$sth->execute((@$dataFields, @$dataFields));
|
|
};
|
|
if ($@) {
|
|
Koha::Logger->get->warn("Error _processRow_DB $@");
|
|
} else {
|
|
$ok = 1;
|
|
}
|
|
if ($ok) {
|
|
my $field = '';
|
|
map { $field .= $dataFieldsHash->{$_} . '_'; } @$fieldsPK;
|
|
chop $field;
|
|
delete $fields2Delete->{$field} if (exists($fields2Delete->{$field}));
|
|
}
|
|
return $ok;
|
|
}#_processRow_DB
|
|
|
|
|
|
# Process the rows of a worksheet and insert/update them in a mysql table.
|
|
sub _processRows_Table
|
|
{
|
|
my ($dbh, $frameworkcode, $nodeR, $table, $PKArray, $format, $fields2Delete) = @_;
|
|
|
|
my $query;
|
|
my @fields = ();
|
|
my $fields = '';
|
|
my $dataStr = '';
|
|
my $updateStr = '';
|
|
my $j = 0;
|
|
my $ok = 0;
|
|
my @fieldsPK = @$PKArray;
|
|
shift @fieldsPK;
|
|
while ($nodeR) {
|
|
if ($nodeR->nodeType == 1 && (($format && $format eq 'ods' && $nodeR->nodeName =~ /(?:table:)?table-row/) || ($nodeR->nodeName =~ /(?:ss:)?Row/)) && $nodeR->hasChildNodes()) {
|
|
if ($j == 0) {
|
|
# Get name columns
|
|
_getFields($nodeR, \@fields, $format);
|
|
return 0 unless _check_validity_worksheet($dbh, $table, $nodeR, \@fields, $format);
|
|
$fields = join(',', @fields);
|
|
$dataStr = '';
|
|
map { $dataStr .= '?,';} @fields;
|
|
chop($dataStr) if ($dataStr);
|
|
$updateStr = '';
|
|
map { $updateStr .= $_ . '=?,';} @fields;
|
|
chop($updateStr) if ($updateStr);
|
|
} else {
|
|
# Get data from row
|
|
my ($dataFields, $dataFieldsR) = _getDataFields($frameworkcode, $nodeR, \@fields, $format);
|
|
if (scalar(@fields) == scalar(@$dataFieldsR)) {
|
|
$ok = _processRow_DB($dbh, $table, $fields, $dataStr, $updateStr, $dataFieldsR, $dataFields, $PKArray, \@fieldsPK, $fields2Delete);
|
|
} else {
|
|
warn "$j don't match number of fields " . scalar(@fields) . ' vs ' . scalar(@$dataFieldsR) . "($dataStr)";
|
|
}
|
|
}
|
|
$j++;
|
|
}
|
|
$nodeR = $nodeR->nextSibling;
|
|
}
|
|
return 1;
|
|
}#_processRows_Table
|
|
|
|
|
|
|
|
|
|
# Import worksheet from the csv file to the mysql table
|
|
sub _import_table_csv
|
|
{
|
|
my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete, $fields) = @_;
|
|
|
|
my $row = '';
|
|
my $partialRow = '';
|
|
my $numFields = @$fields;
|
|
my $fieldsNameRead = 0;
|
|
my @arrData;
|
|
my ($fieldsStr, $dataStr, $updateStr, @empty_indexes);
|
|
my @fieldsPK = @$PKArray;
|
|
shift @fieldsPK;
|
|
my $ok = 0;
|
|
my $pos = 0;
|
|
my $csv = Text::CSV_XS->new ({ binary => 1 });
|
|
while ( my $row = $csv->getline($dom) ) {
|
|
my @fields = @$row;
|
|
@arrData = @fields;
|
|
next if scalar @arrData == grep { $_ eq '' } @arrData; # Emtpy lines
|
|
#$arrData[0] = substr($arrData[0], 1) if ($arrData[0] =~ /^"/);
|
|
#$arrData[$#arrData] =~ s/[\r\n]+$//;
|
|
#chop $arrData[$#arrData] if ($arrData[$#arrData] =~ /"$/);
|
|
if (@arrData) {
|
|
if ($arrData[0] eq '#-#' && $arrData[$#arrData] eq '#-#') {
|
|
# Change of table with separators #-#
|
|
return 1;
|
|
} elsif ($fieldsNameRead && $arrData[0] eq 'tagfield') {
|
|
# Change of table because we begin with field name with former field names read
|
|
seek($dom, $pos, 0);
|
|
return 1;
|
|
}
|
|
if (!$fieldsNameRead) {
|
|
# New table, we read the field names
|
|
$fieldsNameRead = 1;
|
|
$fields = [@arrData];
|
|
my $non_empty_fields = [ grep { $_ ne '' } @$fields ];
|
|
@empty_indexes = indexes { $_ eq '' } @$fields;
|
|
$fieldsStr = join(',', @$non_empty_fields);
|
|
$dataStr = '';
|
|
map { $dataStr .= '?,';} @$non_empty_fields;
|
|
chop($dataStr) if ($dataStr);
|
|
$updateStr = '';
|
|
map { $updateStr .= $_ . '=?,';} @$non_empty_fields;
|
|
chop($updateStr) if ($updateStr);
|
|
} else {
|
|
# Read data
|
|
my $j = 0;
|
|
my %dataFields = ();
|
|
my @values;
|
|
for my $value (@arrData) {
|
|
if ( grep { $_ == $j } @empty_indexes ) {
|
|
# empty field
|
|
} elsif ($fields->[$j] eq 'frameworkcode' && $value ne $frameworkcode) {
|
|
$dataFields{$fields->[$j]} = $frameworkcode;
|
|
push @values, $frameworkcode;
|
|
} elsif ($fields->[$j] eq 'isurl' && defined $value && $value eq q{}) {
|
|
$dataFields{$fields->[$j]} = undef;
|
|
push @values, undef;
|
|
} else {
|
|
$dataFields{$fields->[$j]} = $value;
|
|
push @values, $value;
|
|
}
|
|
$j++
|
|
}
|
|
$ok = _processRow_DB($dbh, $table, $fieldsStr, $dataStr, $updateStr, \@values, \%dataFields, $PKArray, \@fieldsPK, $fields2Delete);
|
|
}
|
|
$pos = tell($dom);
|
|
}
|
|
@arrData = ();
|
|
}
|
|
return $ok;
|
|
}#_import_table_csv
|
|
|
|
|
|
# Import worksheet from the ods content.xml file to the mysql table
|
|
sub _import_table_ods
|
|
{
|
|
my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
|
|
|
|
my $xc = XML::LibXML::XPathContext->new($dom);
|
|
$xc->registerNs('xmlns:office','urn:oasis:names:tc:opendocument:xmlns:office:1.0');
|
|
$xc->registerNs('xmlns:table','urn:oasis:names:tc:opendocument:xmlns:table:1.0');
|
|
$xc->registerNs('xmlns:text','urn:oasis:names:tc:opendocument:xmlns:text:1.0');
|
|
my @nodes;
|
|
@nodes = $xc->findnodes('//table:table[@table:name="' . $table . '"]');
|
|
if (@nodes == 1 && $nodes[0]->hasChildNodes()) {
|
|
my $nodeR = $nodes[0]->firstChild;
|
|
return _processRows_Table($dbh, $frameworkcode, $nodeR, $table, $PKArray, 'ods', $fields2Delete);
|
|
} else {
|
|
Koha::Logger->get->warn("Error _import_table_ods there's not worksheet for $table");
|
|
}
|
|
return 0;
|
|
}#_import_table_ods
|
|
|
|
|
|
# Import worksheet from the excel-xml file to the mysql table
|
|
sub _import_table_excel
|
|
{
|
|
my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
|
|
|
|
my $xc = XML::LibXML::XPathContext->new($dom);
|
|
$xc->registerNs('xmlns','urn:schemas-microsoft-com:office:spreadsheet');
|
|
$xc->registerNs('xmlns:ss','urn:schemas-microsoft-com:office:spreadsheet');
|
|
$xc->registerNs('xmlns:x','urn:schemas-microsoft-com:office:excel');
|
|
my @nodes;
|
|
@nodes = $xc->findnodes('//ss:Worksheet[@ss:Name="' . $table . '"]');
|
|
if (@nodes > 0) {
|
|
for (my $i=0; $i < @nodes; $i++) {
|
|
my @nodesT = $nodes[$i]->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Table');
|
|
if (@nodesT == 1 && $nodesT[0]->hasChildNodes()) {
|
|
my $nodeR = $nodesT[0]->firstChild;
|
|
return _processRows_Table($dbh, $frameworkcode, $nodeR, $table, $PKArray, undef, $fields2Delete);
|
|
}
|
|
}
|
|
} else {
|
|
Koha::Logger->get->warn("Error _import_table_excel there's not worksheet for $table");
|
|
}
|
|
return 0;
|
|
}#_import_table_excel
|
|
|
|
|
|
# Get the data from a cell on a ods file through the value attribute or the text node
|
|
sub _getDataNodeODS
|
|
{
|
|
my $node = shift;
|
|
|
|
my $data;
|
|
my $repeated = 0;
|
|
if ($node->nodeType == 1 && $node->nodeName =~ /(?:table:)?table-cell/) {
|
|
if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value')) {
|
|
$data = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value');
|
|
} elsif ($node->hasChildNodes()) {
|
|
my @nodes2 = $node->getElementsByTagNameNS('urn:oasis:names:tc:opendocument:xmlns:text:1.0', 'p');
|
|
if (@nodes2 == 1 && $nodes2[0]->hasChildNodes()) {
|
|
$data = $nodes2[0]->firstChild->nodeValue;
|
|
}
|
|
}
|
|
if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated')) {
|
|
$repeated = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated');
|
|
}
|
|
}
|
|
return ($data, $repeated);
|
|
}#_getDataNodeODS
|
|
|
|
|
|
# Get the data from a row of a spreadsheet
|
|
sub _getDataFields
|
|
{
|
|
my ($frameworkcode, $node, $fields, $format) = @_;
|
|
|
|
my $dataFields = {};
|
|
my @dataFieldsA = ();
|
|
if ($node && $node->hasChildNodes()) {
|
|
my $node2 = $node->firstChild;
|
|
my ($data, $repeated);
|
|
my $i = 0;
|
|
my $ok = 0;
|
|
$repeated = 0;
|
|
while ($node2) {
|
|
if ($format && $format eq 'ods') {
|
|
($data, $repeated) = _getDataNodeODS($node2) if ($repeated <= 0);
|
|
$repeated--;
|
|
$ok = 1;
|
|
} else {
|
|
if ($node2->nodeType == 1 && $node2->nodeName =~ /(?:ss:)?Cell/) {
|
|
my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
|
|
if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
|
|
$data = $nodes3[0]->firstChild->nodeValue;
|
|
$ok = 1;
|
|
}
|
|
}
|
|
}
|
|
if ($ok) {
|
|
$data //= '';
|
|
$data = '' if ($data eq '#');
|
|
if ( $fields->[$i] eq 'frameworkcode' ) {
|
|
$data = $frameworkcode;
|
|
}
|
|
elsif ( $fields->[$i] eq 'isurl' ) {
|
|
$data = undef if defined $data && $data eq q{};
|
|
}
|
|
$dataFields->{$fields->[$i]} = $data;
|
|
push @dataFieldsA, $data;
|
|
$i++;
|
|
}
|
|
$ok = 0;
|
|
$node2 = $node2->nextSibling if ($repeated <= 0);
|
|
}
|
|
}
|
|
return ($dataFields, \@dataFieldsA);
|
|
}#_getDataFields
|
|
|
|
|
|
# Get the data from the first row to know the column names
|
|
sub _getFields
|
|
{
|
|
my ($node, $fields, $format) = @_;
|
|
|
|
if ($node && $node->hasChildNodes()) {
|
|
my $node2 = $node->firstChild;
|
|
my ($data, $repeated);
|
|
while ($node2) {
|
|
if ($format && $format eq 'ods') {
|
|
($data, $repeated) = _getDataNodeODS($node2);
|
|
push @$fields, $data if (defined($data));
|
|
} else {
|
|
if ($node2->nodeType == 1 && $node2->nodeName =~ /(?:ss:)?Cell/) {
|
|
my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
|
|
if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
|
|
$data = $nodes3[0]->firstChild->nodeValue;
|
|
push @$fields, $data;
|
|
}
|
|
}
|
|
}
|
|
$node2 = $node2->nextSibling;
|
|
}
|
|
}
|
|
}#_getFields
|
|
|
|
|
|
|
|
|
|
1;
|
|
__END__
|
|
|
|
=head1 AUTHOR
|
|
|
|
Koha Development Team <http://koha-community.org/>
|
|
|
|
=cut
|
|
|
|
|