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 <https://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 vice-versa

Functions for handling import/export.


=head1 SUBROUTINES



=head2 ExportFramework

Export all information of a bibliographic or authority MARC framework to an Excel "xml" file, comma separated values "csv" or OpenDocument SpreadSheet "ods".

return :
success

=cut

sub ExportFramework {
    my ( $frameworkcode, $xmlStrRef, $mode, $frameworktype ) = @_;

    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;
            }
        }

        my $table    = 'marc_tag_structure';
        my $subtable = 'marc_subfield_structure';
        if ( $frameworktype eq "authority" ) {
            $table    = 'auth_tag_structure';
            $subtable = 'auth_subfield_structure';
        }

        if (
            _export_table(
                $table, $dbh, ( $mode eq 'csv' ) ? $xmlStrRef : $dom, ( $mode eq 'ods' ) ? $elementSS : $root,
                $frameworkcode, $mode, $frameworktype
            )
            )
        {
            if (
                _export_table(
                    $subtable, $dbh, ( $mode eq 'csv' ) ? $xmlStrRef : $dom, ( $mode eq 'ods' ) ? $elementSS : $root,
                    $frameworkcode, $mode, $frameworktype
                )
                )
            {
                $$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, $frameworktype ) = @_;
    if ( $mode eq 'csv' ) {
        _export_table_csv( $table, $dbh, $dom, $root, $frameworkcode, $frameworktype );
    } elsif ( $mode eq 'ods' ) {
        _export_table_ods( $table, $dbh, $dom, $root, $frameworkcode, $frameworktype );
    } else {
        _export_table_excel( $table, $dbh, $dom, $root, $frameworkcode, $frameworktype );
    }
}

# Export the mysql table to an csv file
sub _export_table_csv {
    my ( $table, $dbh, $strCSV, $root, $frameworkcode, $frameworktype ) = @_;

    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=?';
        if ( $frameworktype eq "authority" ) {
            $query = 'SELECT * FROM ' . $table . ' WHERE authtypecode=?';
        }
        $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;
                $value =~ s/"/""/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, $frameworktype ) = @_;

    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=?';
        if ( $frameworktype eq "authority" ) {
            $query = 'SELECT * FROM ' . $table . ' WHERE authtypecode=?';
        }
        $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, $frameworktype ) = @_;

    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=?';
        if ( $frameworktype eq "authority" ) {
            $query = 'SELECT * FROM ' . $table . ' WHERE authtypecode=?';
        }
        $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};)/&amp;/g;
    $content =~ s/</&lt;/g;
    $content =~ s/>/&gt;/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 MARC or Authority Type Framework from a excel-xml/ods file.

return :
success

=cut

sub ImportFramework {
    my ( $filename, $frameworkcode, $deleteFilename, $frameworktype ) = @_;

    my $tempdir;
    my $ok  = -1;
    my $dbh = C4::Context->dbh;
    $frameworktype ||= '';
    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 );
                }

                my $table    = 'marc_tag_structure';
                my $subtable = 'marc_subfield_structure';
                if ( $frameworktype eq "authority" ) {
                    $table    = 'auth_tag_structure';
                    $subtable = 'auth_subfield_structure';
                }

                if ($dom) {

                    # Process both tables
                    my $numDeleted    = 0;
                    my $numDeletedAux = 0;
                    if ( $frameworktype eq "authority" ) {
                        if (
                            (
                                $numDeletedAux = _import_table(
                                    $dbh, $table, $frameworkcode, $dom, [ 'authtypecode', 'tagfield' ], $extension,
                                    $frameworktype
                                )
                            ) >= 0
                            )
                        {
                            $numDeleted += $numDeletedAux if ( $numDeletedAux > 0 );
                            if (
                                (
                                    $numDeletedAux = _import_table(
                                        $dbh, $subtable, $frameworkcode, $dom,
                                        [ 'authtypecode', 'tagfield', 'tagsubfield' ], $extension, $frameworktype
                                    )
                                ) >= 0
                                )
                            {
                                $numDeleted += $numDeletedAux if ( $numDeletedAux > 0 );
                                $ok = ( $numDeleted > 0 ) ? $numDeleted : 0;
                            }
                        }
                    } else {
                        if (
                            (
                                $numDeletedAux = _import_table(
                                    $dbh, $table, $frameworkcode, $dom, [ 'frameworkcode', 'tagfield' ], $extension,
                                    $frameworktype
                                )
                            ) >= 0
                            )
                        {
                            $numDeleted += $numDeletedAux if ( $numDeletedAux > 0 );
                            if (
                                (
                                    $numDeletedAux = _import_table(
                                        $dbh, $subtable, $frameworkcode, $dom,
                                        [ 'frameworkcode', 'tagfield', 'tagsubfield' ], $extension, $frameworktype
                                    )
                                ) >= 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 readable $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, $frameworktype ) = @_;
    my %fields2Delete;
    my $query;
    my @fields;
    $frameworktype ||= '';

    # 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=?';
        if ( $frameworktype eq "authority" ) {
            $query = 'SELECT ' . join( ',', @fields ) . ' FROM ' . $table . ' WHERE authtypecode=?';
        }
        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,
            $frameworktype
        );
    } elsif ( $format eq 'ods' ) {
        $ok = _import_table_ods( $dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete, $frameworktype );
    } else {
        $ok = _import_table_excel( $dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete, $frameworktype );
    }
    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, $frameworktype ) = @_;

    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, $frameworktype );
                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, $frameworktype ) = @_;
    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, formula => "empty" } );

    while ( my $row = $csv->getline($dom) ) {
        my @fields = @$row;
        @arrData = @fields;
        next if scalar @arrData == grep { $_ eq '' } @arrData;    # Empty 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 ( $frameworktype eq "authority" ) {
                        if ( $fields->[$j] eq 'authtypecode' && $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++;
                    } else {
                        if ( $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, $frameworktype ) = @_;

    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,
            $frameworktype
        );
    } 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, $frameworktype ) = @_;

    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,
                    $frameworktype
                );
            }
        }
    } 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, $frameworktype ) = @_;

    my $dataFields  = {};
    my @dataFieldsA = ();
    $frameworktype ||= '';
    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 ( $frameworktype eq "authority" ) {
                    if ( $fields->[$i] eq 'authtypecode' ) {
                        $data = $frameworkcode;
                    } elsif ( $fields->[$i] eq 'isurl' ) {
                        $data = undef if defined $data && $data eq q{};
                    }
                } else {
                    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 <https://koha-community.org/>

=cut


