From 07e85842f6876a23d807c8758189cc50cdebc128 Mon Sep 17 00:00:00 2001
From: Salvador Zaragoza Rubio <salvazm@masmedios.com>
Date: Fri, 4 Mar 2011 10:05:34 +0100
Subject: [PATCH] Bug 5478 - Automate MARC framework import/export
Content-Type: text/plain; charset="utf-8"

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 spreadsheet or importing
the spreadsheet to marc_tag_structure, marc_subfield_structure mysql tables, overwriting the data.

Bug 5478 - Automate MARC framework import/export

Add delete functionality.

Bug 5478 - Automate MARC framework import/export

Fix id on default form
---
 C4/ImportExportFramework.pm                        |  986 ++++++++++++++++++++
 admin/import_export_framework.pl                   |   68 ++
 .../intranet-tmpl/prog/en/css/staff-global.css     |   48 +
 .../prog/en/modules/admin/biblio_framework.tmpl    |  141 +++
 4 files changed, 1243 insertions(+), 0 deletions(-)
 create mode 100755 C4/ImportExportFramework.pm
 create mode 100755 admin/import_export_framework.pl
 mode change 100644 => 100755 koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css
 mode change 100644 => 100755 koha-tmpl/intranet-tmpl/prog/en/modules/admin/biblio_framework.tmpl

diff --git a/C4/ImportExportFramework.pm b/C4/ImportExportFramework.pm
new file mode 100755
index 0000000..43900ac
--- /dev/null
+++ b/C4/ImportExportFramework.pm
@@ -0,0 +1,986 @@
+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 2 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use strict;
+use warnings;
+use XML::LibXML;
+use XML::LibXML::XPathContext;
+use Digest::MD5 qw(md5_base64);
+use POSIX qw(strftime);
+
+use C4::Context;
+use C4::Debug;
+
+
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+BEGIN {
+    $VERSION = 3.03;    # set version for version checking
+    require Exporter;
+    @ISA    = qw(Exporter);
+    @EXPORT = 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;
+        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 ($@) {
+            $debug and warn "Error ExportFramework $@\n";
+            return 0;
+        }
+        if (_export_table('marc_tag_structure', $dbh, $dom, ($mode && $mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
+            if (_export_table('marc_subfield_structure', $dbh, $dom, ($mode && $mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
+                $$xmlStrRef = $dom->toString(1);
+                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 && $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 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' && (!$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 ($@) {
+        $debug and warn "Error _export_table_ods $@\n";
+        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' && (!$data && $data ne '0')) {
+                    $data = '#';
+                }
+                $elementData->appendTextNode(($_->{type} eq 'String')?_parseContent2Xml($data):$data);
+            }
+        }
+    };
+    if ($@) {
+        $debug and warn "Error _export_table_excel $@\n";
+        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 undef;
+    } 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) {
+        # populate tempdir directory with the ods elements
+        eval {
+            if (open(OUT, "> $tempdir/content.xml")) {
+                print OUT $strContent;
+                close(OUT);
+            }
+            if (open(OUT, "> $tempdir/mimetype")) {
+                print OUT 'application/vnd.oasis.opendocument.spreadsheet';
+                close(OUT);
+            }
+            if (open(OUT, "> $tempdir/meta.xml")) {
+                print OUT _getMeta($lang);
+                close(OUT);
+            }
+            if (open(OUT, "> $tempdir/styles.xml")) {
+                print OUT ODS_STYLES_STR;
+                close(OUT);
+            }
+            if (open(OUT, "> $tempdir/settings.xml")) {
+                print OUT ODS_SETTINGS_STR;
+                close(OUT);
+            }
+            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(OUT, "> $tempdir/META-INF/manifest.xml")) {
+                print OUT ODS_MANIFEST_STR;
+                close(OUT);
+            }
+        };
+        if ($@) {
+            $debug and warn "Error createODS $@\n";
+        } 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('new.ods');
+            };
+            if ($@) {
+                system("cd $tempdir && zip -r new.ods ./");
+            }
+            my $ok = 0;
+            # read ods file and return as a string
+            if (-f "$tempdir/new.ods") {
+                if (open (MYFILE, "$tempdir/new.ods")) {
+                    binmode MYFILE;
+                    my $buffer;
+                    while (read (MYFILE, $buffer, 65536)) {
+                        $$strODSRef .= $buffer;
+                    }
+                    close(MYFILE);
+                    $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 =~ /\.(ods|xml)$/i) {
+            $extension = lc($1);
+        } else {
+            unlink ($filename) if ($deleteFilename);
+            return -1;
+        }
+        if ($extension eq 'ods') {
+            ($tempdir, $filename) = _openODS($filename, $deleteFilename);
+        }
+        if ($filename) {
+            eval {
+                my $parser = XML::LibXML->new();
+                my $dom = $parser->parse_file($filename);
+                if ($dom) {
+                    my $element;
+                    my $root = $dom->documentElement();
+                    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 {
+                    $debug and warn "Error ImportFramework couldn't create dom\n";
+                }
+            };
+            if ($@) {
+                $debug and warn "Error ImportFramework $@\n";
+            }
+        }
+        unlink ($filename) if ($deleteFilename);
+    } else {
+        $debug and warn "Error ImportFramework no conex to database or not readeable $filename\n";
+    }
+    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 => 0);
+    };
+    if ($@) {
+        $tempModule = 0;
+        $tempdir = _createTmpDir($tmp);
+    }
+    if ($tempdir) {
+        eval {
+            require Archive::Zip;
+            import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
+            my $zip = Archive::Zip->new();
+            $zip->extractTree( $filename, $tempdir );
+        };
+        if ($@) {
+            system("unzip $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;
+    eval {
+        my @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 && $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
+
+
+# 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 $db_scheme = C4::Context->config("db_scheme");
+    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)) {
+                    if ($db_scheme eq 'mysql') {
+                        $query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ') ON DUPLICATE KEY UPDATE ' . $updateStr;
+                    } else {
+                        $query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ')';
+                    }
+                    eval {
+                        my $sth = $dbh->prepare($query);
+                        if ($db_scheme eq 'mysql') {
+                            $sth->execute((@$dataFieldsR, @$dataFieldsR));
+                        } else {
+                            $sth->execute(@$dataFieldsR);
+                        }
+                    };
+                    if ($@) {
+                        unless ($db_scheme eq 'mysql') {
+                            $query = 'UPDATE ' . $table . ' SET ' . $updateStr . ' WHERE ';
+                            map {$query .= $_ . '=? AND ';} @$PKArray;
+                            $query = substr($query, 0, -4);
+                            eval {
+                                my $sth2 = $dbh->prepare($query);
+                                my @dataPK = ();
+                                map {push @dataPK, $dataFields->{$_};} @$PKArray;
+                                $sth2->execute((@$dataFieldsR, @dataPK));
+                            };
+                            $ok = 1 unless ($@);
+                        }
+                        $debug and warn "Error _processRows_Table $@\n";
+                    } else {
+                        $ok = 1;
+                    }
+                    if ($ok) {
+                        my $field = '';
+                        map { $field .= $dataFields->{$_} . '_'; } @fieldsPK;
+                        chop $field;
+                        delete $fields2Delete->{$field} if (exists($fields2Delete->{$field}));
+                    }
+                    $ok = 0;
+                }
+            }
+            $j++;
+        }
+        $nodeR = $nodeR->nextSibling;
+    }
+    return 1;
+}#_processRows_Table
+
+
+# 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 {
+        $debug and warn "Error _import_table_ods there's not worksheet for $table\n";
+    }
+    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 {
+        $debug and warn "Error _import_table_excel there's not worksheet for $table\n";
+    }
+    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 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;
+                        $ok = 1;
+                    }
+                }
+            }
+            if ($ok) {
+                $data = '' if ($data eq '#');
+                $data = $frameworkcode if ($fields->[$i] eq 'frameworkcode');
+                $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
+
+
diff --git a/admin/import_export_framework.pl b/admin/import_export_framework.pl
new file mode 100755
index 0000000..756b1a6
--- /dev/null
+++ b/admin/import_export_framework.pl
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+
+# 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 2 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+use strict;
+use warnings;
+use CGI;
+use C4::Context;
+use C4::ImportExportFramework;
+
+my $input = new CGI;
+
+my $frameworkcode = $input->param('frameworkcode') || '';
+my $action = $input->param('action') || 'export';
+
+## Exporting
+if ($action eq 'export' && $input->request_method() eq 'GET') {
+    my $strXml = '';
+    ExportFramework($frameworkcode, \$strXml, $input->param('type_export_' . $frameworkcode));
+    if ($input->param('type_export_' . $frameworkcode) eq 'excel') {
+        # Excel-xml file
+        print $input->header(-type => 'application/excel', -attachment => 'export_' . $frameworkcode . '.xml');
+        print $strXml;
+    } else {
+        # ODS file
+        my $strODS = '';
+        createODS($strXml, 'en', \$strODS);
+        print $input->header(-type => 'application/vnd.oasis.opendocument.spreadsheet', -attachment => 'export_' . $frameworkcode . '.ods');
+        print $strODS;
+    }
+## Importing
+} elsif ($input->request_method() eq 'POST') {
+    my $ok = -1;
+    my $fieldname = 'file_import_' . $frameworkcode;
+    my $filename = $input->param($fieldname);
+    # upload the input file
+    if ($filename && $filename =~ /\.(ods|xml)$/i) {
+        my $extension = $1;
+        my $uploadFd = $input->upload($fieldname);
+        if ($uploadFd && !$input->cgi_error) {
+            my $tmpfilename = $input->tmpFileName($input->param($fieldname));
+            $filename = $tmpfilename . '.' . $extension; # rename the tmp file with the extension
+            $ok = ImportFramework($filename, $frameworkcode, 1) if (rename($tmpfilename, $filename));
+        }
+    }
+    if ($ok >= 0) { # If everything went ok go to the framework marc structure
+        print $input->redirect( -location => '/cgi-bin/koha/admin/marctagstructure.pl?frameworkcode=' . $frameworkcode);
+    } else {
+        # If something failed go to the list of frameworks and show message
+        print $input->redirect( -location => '/cgi-bin/koha/admin/biblio_framework.pl?error_import_export=' . $frameworkcode);
+    }
+}
diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css
old mode 100644
new mode 100755
index 66b092c..dd3700a
--- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css
+++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css
@@ -1924,3 +1924,51 @@ ul.budget_hierarchy li:first-child:after {
 	margin : 1em 0;
 }
 fieldset.rows+h3 {clear:both;padding-top:.5em;}
+
+.import_export{
+    position:relative;
+}
+.import_export .import_export_options{
+    background: white;
+    border: 1px solid #CDCDCD;
+    left: 60px;
+    padding: 10px;
+    position: absolute;
+    top: 0;
+    z-index: 1;
+    width: 300px;
+}
+.import_export_options li{
+    display: block;
+    list-style: none;
+    padding-top: 10px;
+}
+.import_export_options .import_export_close {
+    cursor: pointer;
+    text-decoration: underline;
+}
+.import_export_options .export_ok {
+    padding: 10;
+    background: #E3E3E3 none;
+    cursor: pointer;
+    margin-left: 20px;
+    border: none;
+}
+.import_export_options .import_ok {
+    padding: 10;
+    background: #E3E3E3 none;
+    cursor: pointer;
+    margin-left: 20px;
+    border: none;
+}
+.form_import .input_import {
+    border: 1px solid #bcbcbc;
+}
+.import_export_options .importing {
+    padding: inherit;
+    background: none;
+}
+.li_close_import_export {
+    text-align: right;
+}
+ 
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/biblio_framework.tmpl b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/biblio_framework.tmpl
old mode 100644
new mode 100755
index ba5dde8..87cc082
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/biblio_framework.tmpl
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/biblio_framework.tmpl
@@ -48,6 +48,88 @@ $(document).ready(function() {
 });
 //]]>
 </script>
+<script type="text/javascript">
+/* Import/Export from/to spreadsheet */
+
+    var importing = false;
+
+    $(document).ready(function() {
+        $("body").css("cursor", "auto");
+        $('.import_export_options').hide();
+        $('a.import_export_fw').click(function() {
+            if (!importing) {
+                $('.import_export_options').hide();
+                $(this).next().show('slide');
+            }
+            return false;
+        });
+        $('.import_export_close').click(function() {
+            if (!importing) $('.import_export_options').fadeOut('fast');
+        });
+        $('.input_import').val("");
+
+        var matches = new RegExp("\\?error_import_export=(.+)$").exec(window.location.search);
+        if (matches && matches.length > 1) {
+            alert("Error importing the framework " + decodeURIComponent(matches[1]));
+        }
+    });
+    
+    $(function() {
+        $('input.input_import').change( function() {
+            var filename = $(this).val();
+            if ( ! /(?:\.ods|\.xml)$/.test(filename)) {
+                $(this).css("background-color","yellow");
+                try {
+                    var pos = filename.lastIndexOf("/");
+                    if (pos < 0) pos = filename.lastIndexOf("\\");
+                    if (pos < 0) pos = filename.length;
+                    var id = $(this).attr('id');
+                    var obj = document.getElementById(id);
+                    if (obj.setSelectionRange) {
+                        obj.focus();
+                        obj.setSelectionRange(pos * 2, filename.length * 2);
+                    } else if (obj.createTextRange) {
+                        var range = obj.createTextRange();
+                        range.collapse(true);
+                        range.moveEnd('character', filename.length * 2);
+                        range.moveStart('character', pos);
+                        range.select();
+                    }
+                } catch (e) {}
+                alert('Please select an ods or xml file');
+                $(this).val("");
+                $(this).css("background-color","white");
+            }
+        });
+        $('form.form_export').submit(function() {
+            $('.import_export_options').hide();
+            return true;
+        });
+        $('form.form_import').submit(function() {
+            var id = $(this).attr('id');
+            var obj = $('#' + id + ' input:file');
+            if (/(?:\.ods|\.xml)$/.test(obj.val())) {
+                if (confirm('Do you really want to import the framework fields/subfields (will overwrite current configuration, for safety reasons please make before an export to have a backup file)?')) {
+                    var frameworkcode = $('#' + id + ' input:hidden[name=frameworkcode]').val();
+                    $('#importing_' + frameworkcode).css('display', 'block');
+                    if (navigator.userAgent.toLowerCase().indexOf('firefox') == -1) $("body").css("cursor", "progress");
+                    importing = true;
+                    return true;
+                } else
+                    return false;
+            }
+            obj.css("background-color","yellow");
+            alert('Please select an ods or xml file');
+            obj.val("");
+            obj.css("background-color","white");
+            return false;
+        });
+    });
+    
+    
+
+</script>
+
 </head>
 <body>
 <!-- TMPL_INCLUDE NAME="header.inc" -->
@@ -115,6 +197,8 @@ $(document).ready(function() {
         <th>&nbsp;</th>
         <th>Edit</th>
         <th>Delete</th>
+        <th title="Export framework structure (fields, subfields) to a spreadsheet file (.xml, .ods)">Export</th>
+        <th title="Import framework structure (fields, subfields) from a spreadsheet file (.xml, .ods)">Import</th>
     </tr>
     <tr>
         <td>&nbsp;</td>
@@ -122,6 +206,34 @@ $(document).ready(function() {
         <td><a href="marctagstructure.pl?frameworkcode=<!-- TMPL_VAR NAME="frameworkcode" -->">MARC structure</a></td>
         <td>&nbsp;</td>
         <td>&nbsp;</td>
+        <td><div class="import_export"><a class="import_export_fw" href="#" title="Export <!-- TMPL_VAR NAME="frameworkcode" --> framework structure (fields, subfields) to a spreadsheet file (.xml, .ods)">Export</a>
+            <div class="import_export_options">
+                <form action="import_export_framework.pl" name="form_<!-- TMPL_VAR NAME="frameworkcode" -->" method="get" target="_blank"  class="form_export">
+                    <input type="hidden" name="frameworkcode" value="<!-- TMPL_VAR NAME="frameworkcode" -->" />
+                    <ul>
+                        <li class="li_close_import_export"><span class="import_export_close" title="Close popup">Close</span></li>
+                        <li><input type="radio" name="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" value="excel" id="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" checked="checked" /><label for="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" title="Export to Excel as xml format, compatible with OpenOffice/LibreOffice as well">Export to Excel with xml format</label></li>
+                        <li><input type="radio" name="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" value="ods" id="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" /><label for="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->">Export to OpenDocument Spreadsheet format</label></li>
+                        <li><input type="submit" class="export_ok" href="#" value="Export" title="Export to a spreadsheet" /></li>
+                    </ul>
+                </form>
+            </div>
+            </div>
+        </td>
+        <td><div class="import_export"><a href="#" class="import_export_fw" title="Import <!-- TMPL_VAR NAME="frameworkcode" --> framework structure (fields, subfields) from a spreadsheet file (.xml, .ods)">Import</a>
+            <div class="import_export_options">
+                <form action="/cgi-bin/koha/admin/import_export_framework.pl" name="form_i_<!-- TMPL_VAR NAME="frameworkcode" -->" id="form_i_<!-- TMPL_VAR NAME="frameworkcode" -->" method="post" enctype="multipart/form-data" class="form_import">
+                    <input type="hidden" name="frameworkcode" value="<!-- TMPL_VAR NAME="frameworkcode" -->" />
+                    <input type="hidden" name="action" value="import" />
+                    <ul>
+                        <li class="li_close_import_export"><span class="import_export_close" title="Close popup">Close</span></li>
+                        <li><label for="file_import_<!-- TMPL_VAR NAME="frameworkcode" -->" title="Import from a spreadsheet, formats available: ods, xml (formatted from excel)">Spreadsheet file</label><input type="file" name="file_import_<!-- TMPL_VAR NAME="frameworkcode" -->" id="file_import_<!-- TMPL_VAR NAME="frameworkcode" -->" class="input_import" value="" autocomplete="off" /></li>
+                        <li><input type="submit" class="import_ok" value="Import" title="Import from a spreadsheet" /><div id="importing_<!-- TMPL_VAR NAME="frameworkcode" -->" style="display:none" class="importing"><img src="/intranet-tmpl/prog/img/loading.gif" />Importing...</div></li>
+                    </ul>
+                </form>
+            </div>
+            </div>
+        </td>
     </tr>
     <!-- note highlight assignment appears backwards because we already have a normal row for Default -->
     <!-- TMPL_LOOP NAME="loop" -->
@@ -133,6 +245,35 @@ $(document).ready(function() {
             <td><a href="marctagstructure.pl?frameworkcode=<!-- TMPL_VAR name="frameworkcode" -->" >MARC structure</a></td>
             <td><a href="<!-- TMPL_VAR name="script_name" -->?op=add_form&amp;frameworkcode=<!-- TMPL_VAR name="frameworkcode" escape="HTML" -->">Edit</a></td>
             <td><a href="<!-- TMPL_VAR name="script_name" -->?op=delete_confirm&amp;frameworkcode=<!-- TMPL_VAR name="frameworkcode" escape="HTML" -->">Delete</a></td>
+            <td>
+            <div class="import_export"><a class="import_export_fw" href="#" title="Export <!-- TMPL_VAR NAME="frameworkcode" --> framework structure (fields, subfields) to a spreadsheet file (.xml, .ods)">Export</a>
+                <div class="import_export_options">
+                    <form action="import_export_framework.pl" name="form_<!-- TMPL_VAR NAME="frameworkcode" -->" method="get" target="_blank" class="form_export">
+                        <input type="hidden" name="frameworkcode" value="<!-- TMPL_VAR NAME="frameworkcode" -->" />
+                        <ul>
+                            <li class="li_close_import_export"><span class="import_export_close" title="Close popup">Close</span></li>
+                            <li><input type="radio" name="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" value="excel" id="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" checked="checked" /><label for="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" title="Export to Excel as xml format, compatible with OpenOffice/LibreOffice as well">Export to Excel as xml format</label></li>
+                            <li><input type="radio" name="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" value="ods" id="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->" /><label for="type_export_<!-- TMPL_VAR NAME="frameworkcode" -->">Export to OpenDocument Spreadsheet format</label></li>
+                            <li><input type="submit" class="export_ok" href="#" value="Export" title="Export to a spreadsheet" /></li>
+                        </ul>
+                    </form>
+                </div>
+                </div>
+            </td>
+            <td><div class="import_export"><a class="import_export_fw" href="#" title="Import <!-- TMPL_VAR NAME="frameworkcode" --> framework structure (fields, subfields) from a spreadsheet file (.xml, .ods)">Import</a>
+            <div class="import_export_options">
+                <form action="/cgi-bin/koha/admin/import_export_framework.pl" name="form_i_<!-- TMPL_VAR NAME="frameworkcode" -->" id="form_i_<!-- TMPL_VAR NAME="frameworkcode" -->" method="post" enctype="multipart/form-data" class="form_import">
+                    <input type="hidden" name="frameworkcode" value="<!-- TMPL_VAR NAME="frameworkcode" -->" />
+                    <input type="hidden" name="action" value="import" />
+                    <ul>
+                        <li class="li_close_import_export"><span class="import_export_close" title="Close popup">Close</span></li>
+                        <li><label for="file_import_<!-- TMPL_VAR NAME="frameworkcode" -->" title="Import from a spreadsheet, formats available: ods, xml (formatted from excel)">Spreadsheet file</label><input type="file" name="file_import_<!-- TMPL_VAR NAME="frameworkcode" -->" id="file_import_<!-- TMPL_VAR NAME="frameworkcode" -->" class="input_import" value="" autocomplete="off" /></li>
+                        <li><input type="submit" class="import_ok" value="Import" title="Import from a spreadsheet" /><div id="importing_<!-- TMPL_VAR NAME="frameworkcode" -->" style="display:none" class="importing"><img src="/intranet-tmpl/prog/img/loading.gif" />Importing...</div></li>
+                    </ul>
+                </form>
+            </div>
+            </div>
+        </td>
         </tr>
     <!-- /TMPL_LOOP -->
 </table>
-- 
1.5.6.5